対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用

これが過酷な全期間8年半の市場変動を破綻せず生き抜いた資産推移グラフ(長期検証証拠)です。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.21 |
| 勝率 | 46.5% |
| 総取引数 | 527 回 |
| 純利益 (0.1ロット/1万通貨時) | $96888.54 |
| 最大ドローダウン | 3.93% |
今回の検証結果の「限界」と「ダメ出し」
PF1.21という数値は、実運用では物足りない成績です。勝率が50%を切っているため、連敗時の心理的負荷は避けられません。
この要因は、フィルターを厳格にしすぎたことにあります。ボラティリティ圧縮(ATRパーセンタイル)を重視したため、絶好のトレンド初動を逃した局面が散見されました。また、5分足という短い時間軸ゆえに、ノイズによる損切り回数が増えています。
結果として、最大ドローダウンを3.93%に抑えることには成功しました。しかし、攻めの姿勢が欠けており、機会損失を許容しすぎた設計と言わざるを得ません。
ロジックの技術的詳細
本ロジックは、ボラティリティの「圧縮」と「爆発」を数値化して狙う戦略です。
| 指標 | 設定値 | 役割 |
|---|---|---|
| KAMA | 期間 21 | 価格の適応的平滑化とトレンド方向の判定 |
| ADX | 期間 14 / 閾値 25 | トレンドの強度を測定し、弱い相場を除外 |
| ER (効率比) | 期間 14 / 閾値 0.4 | 価格変動の効率性を測定し、急騰・急落を検知 |
| ATR Percentile | 期間 100 / 閾値 0.3 | ボラティリティが極めて低い「スクイーズ」を判定 |
| SuperTrend | 15分足 / 12 / 3.0 | 上位足のトレンド方向へのみエントリーを制限 |
| 時間フィルター | 16:00 - 23:00 JST | 流動性の高い欧州・NY市場のみに限定 |
どう改善すべきか(次なる展望)
このロジックをさらに進化させるには、価格以外の視点が必要です。
例えば、出来高(Volume)の急増を条件に加えることが考えられます。真のブレイクアウトは、出来高を伴うものです。現在のATRベースの判定に、出来高フィルターを組み合わせれば、ダマシをさらに減らせる可能性があります。
また、固定のTP/SLではなく、直近の高値・安値に基づいた動的な決済ロジックへの変更も有効です。プロのEAは、相場のボラティリティに応じて利確幅を変動させます。
Pythonコード
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas as pd
import pandas_ta as ta
class AEISStrategy(BaseStrategy):
def __init__(self):
# 【クオンツ調整】リカバリーファクターと最大DDの改善のため、
# リスクリワード比を最適化(3.33 -> 2.25)し、勝率の底上げを図る
super().__init__(
name="Adaptive Efficiency Impulse Strategy",
default_tp_pips=45.0,
default_sl_pips=20.0,
enable_trailing_stop=True,
trail_start_pips=15.0
)
# 時間足の定義
self.base_timeframe = "5m"
self.vision_timeframes = ["5m", "15min", "1h"]
def calculate_indicators(self, df):
"""
【クオンツ調整】
- KAMA期間を21に延長し、ノイズによるダマシを軽減 (PF向上)
- ER閾値とADX閾値を引き上げ、より高精度なインパルスのみを抽出 (勝率向上)
- ATRパーセンタイル閾値を下げ、ボラティリティ圧縮の状態を厳格化 (最大DD抑制)
"""
# --- 1. 下位足 (5m) 指標計算 ---
# KAMA: 期間を10 -> 21へ変更して平滑化
df['KAMA'] = ta.kama(df['Close'], length=21)
# ADX: トレンド強度判定
adx_df = ta.adx(df['High'], df['Low'], df['Close'], length=14)
df['ADX'] = adx_df['ADX_14'] if adx_df is not None else 0
# ATR: ボラティリティ計算 (KeyError回避のため明示的に代入)
df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)
# Efficiency Ratio (ER): 期間を10 -> 14へ変更し、相関性を向上
change = df['Close'].diff(14).abs()
volatility = df['Close'].diff().abs().rolling(14).sum()
df['ER'] = change / volatility
# ATR Percentile: 閾値を0.5 -> 0.3へ。より強い「圧縮」を条件とする
df['ATR_pct'] = df['ATR'].rolling(100).rank(pct=True)
# --- 2. 上位足 (15min) トレンド判定 ---
# ルックアヘッドバイアス排除のため shift(1) を徹底
df_15 = df[['High', 'Low', 'Close']].resample('15min').last().shift(1)
# SuperTrend: 期間12, 倍率3.0に調整してトレンド追随性を最適化
st_15 = ta.supertrend(df_15['High'], df_15['Low'], df_15['Close'], length=12, multiplier=3.0)
if st_15 is not None:
# SuperTrendの方向性カラム(通常はSUPERTd_12_3.0)を抽出
# カラム名に依存せず、方向性を示す2番目のカラム(index 1)を取得
trend_col = st_15.columns[1]
df['trend_15'] = st_15[trend_col].reindex(df.index, method='ffill')
else:
df['trend_15'] = 0
return df
def generate_signal(self, df):
"""
【クオンツ調整】
- ADX > 25: トレンドの確信度を高める
- ER > 0.4: 効率性の爆発条件を厳格化
- ATR_pct < 0.3: 真のスクイーズからのブレイクアウトを狙う
"""
if len(df) < 100:
return None
last_row = df.iloc[-1]
# 必須データの存在確認
required_cols = ['KAMA', 'ADX', 'ATR', 'ER', 'ATR_pct', 'trend_15']
if any(col not in last_row for col in required_cols):
return None
# 時間帯フィルター (16:00 - 24:00 JST)
hour = df.index[-1].hour
time_filter = (16 <= hour <= 23)
# --- エントリー条件の再構築 ---
# 1. MTF同期: 上位足トレンド方向
upper_bull = last_row['trend_15'] == 1
upper_bear = last_row['trend_15'] == -1
# 2. 効率性の爆発: ER閾値を 0.3 -> 0.4 に引き上げ
er_explosion = last_row['ER'] > 0.4
# 3. 適応的価格突破: 価格 vs KAMA
price_above_kama = last_row['Close'] > last_row['KAMA']
price_below_kama = last_row['Close'] < last_row['KAMA']
# 4. ボラティリティ圧縮: ATRパーセンタイルを 0.5 -> 0.3 に引き下げ
vol_squeeze = last_row['ATR_pct'] < 0.3
# 5. 勢い判定: ADX閾値を 20 -> 25 に引き上げ
trend_strength = last_row['ADX'] > 25
if time_filter:
# ロング条件: 全てのクオンツフィルターを通過
if upper_bull and er_explosion and price_above_kama and vol_squeeze and trend_strength:
return 'BUY'
# ショート条件: 全てのクオンツフィルターを通過
if upper_bear and er_explosion and price_below_kama and vol_squeeze and trend_strength:
return 'SELL'
return None
MQL5コード
//+------------------------------------------------------------------+
//| AEIS_Strategy.mq5 |
//| Copyright 2026, Quant Engineer |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Quant Engineer"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
//--- 入力パラメータ
input int InpKAMA_Period = 21; // KAMA Period
input int InpADX_Period = 14; // ADX Period
input double InpADX_Level = 25.0; // ADX Threshold
input int InpER_Period = 14; // Efficiency Ratio Period
input double InpER_Level = 0.4; // ER Threshold
input int InpATR_Period = 14; // ATR Period
input double InpATR_Pct = 0.3; // ATR Percentile Threshold
input int InpST_Period = 12; // SuperTrend Period
input double InpST_Mult = 3.0; // SuperTrend Multiplier
input double InpTP_Pips = 45.0; // Take Profit (Pips)
input double InpSL_Pips = 20.0; // Stop Loss (Pips)
input double InpTrailStart = 15.0; // Trailing Start (Pips)
input double InpLotSize = 0.1; // Lot Size
//--- グローバル変数
CTrade trade;
int handleADX, handleATR;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handleADX = iADX(_Symbol, _Period, InpADX_Period);
handleATR = iATR(_Symbol, _Period, InpATR_Period);
if(handleADX == INVALID_HANDLE || handleATR == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| KAMA calculation |
//+------------------------------------------------------------------+
double CalculateKAMA(int period)
{
double close[];
ArraySetAsSeries(close, true);
CopyClose(_Symbol, _Period, 0, period * 5, close);
double change = MathAbs(close[0] - close[period]);
double volatility = 0;
for(int i=0; i<period; i++) volatility += MathAbs(close[i] - close[i+1]);
double er = (volatility != 0) ? change / volatility : 0;
double sc = MathPow(er * (2.0 / (3.0) - 1.0) + 1.0, 2);
static double lastKAMA = 0;
if(lastKAMA == 0) lastKAMA = close[0];
double currentKAMA = lastKAMA + sc * (close[0] - lastKAMA);
lastKAMA = currentKAMA;
return currentKAMA;
}
//+------------------------------------------------------------------+
//| Efficiency Ratio (ER) calculation |
//+------------------------------------------------------------------+
double CalculateER(int period)
{
double close[];
ArraySetAsSeries(close, true);
CopyClose(_Symbol, _Period, 0, period + 1, close);
double change = MathAbs(close[0] - close[period]);
double volatility = 0;
for(int i=0; i<period; i++) volatility += MathAbs(close[i] - close[i+1]);
return (volatility != 0) ? change / volatility : 0;
}
//+------------------------------------------------------------------+
//| SuperTrend for Higher Timeframe (15m) |
//+------------------------------------------------------------------+
int GetSuperTrend(ENUM_TIMEFRAMES tf)
{
double high[], low[], close[];
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
CopyHigh(_Symbol, tf, 0, 2, high);
CopyLow(_Symbol, tf, 0, 2, low);
CopyClose(_Symbol, tf, 0, 2, close);
double atr[];
int hATR = iATR(_Symbol, tf, InpATR_Period);
CopyBuffer(hATR, 0, 0, 1, atr);
double upperBand = (high[0] + low[0]) / 2 + InpST_Mult * atr[0];
double lowerBand = (high[0] + low[0]) / 2 - InpST_Mult * atr[0];
if(close[0] > upperBand) return 1;
if(close[0] < lowerBand) return -1;
return 0;
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.hour < 16 || dt.hour > 23) return;
if(PositionsTotal() > 0)
{
ManageTrailingStop();
return;
}
double adx[], atr[];
CopyBuffer(handleADX, 0, 0, 1, adx);
CopyBuffer(handleATR, 0, 0, 1, atr);
double kama = CalculateKAMA(InpKAMA_Period);
double er = CalculateER(InpER_Period);
int trend15 = GetSuperTrend(PERIOD_M15);
// ATR Percentile (Simplified: check if current ATR is below average of last 100)
double atrSum = 0;
double atrBuffer[];
CopyBuffer(handleATR, 0, 0, 100, atrBuffer);
for(int i=0; i<100; i++) atrSum += atrBuffer[i];
double atrAvg = atrSum / 100;
bool volSqueeze = (atr[0] < atrAvg * 0.7); // Approximate 0.3 percentile
double close = iClose(_Symbol, _Period, 0);
// Long Entry
if(trend15 == 1 && er > InpER_Level && close > kama && volSqueeze && adx[0] > InpADX_Level)
{
double sl = close - InpSL_Pips * _Point * 10;
double tp = close + InpTP_Pips * _Point * 10;
trade.Buy(InpLotSize, _Symbol, close, sl, tp);
}
// Short Entry
else if(trend15 == -1 && er > InpER_Level && close < kama && volSqueeze && adx[0] > InpADX_Level)
{
double sl = close + InpSL_Pips * _Point * 10;
double tp = close - InpTP_Pips * _Point * 10;
trade.Sell(InpLotSize, _Symbol, close, sl, tp);
}
}
//+------------------------------------------------------------------+
//| Trailing Stop Logic |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i=PositionsTotal()-1; i>=0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
double price = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
double sl = PositionGetDouble(POSITION_SL);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
if(currentPrice - price > InpTrailStart * _Point * 10)
{
double newSL = currentPrice - InpTrailStart * _Point * 10;
if(newSL > sl) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
if(price - currentPrice > InpTrailStart * _Point * 10)
{
double newSL = currentPrice + InpTrailStart * _Point * 10;
if(sl == 0 || newSL < sl) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
}
}
}
}