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

これが過酷な全期間10年の市場変動を破綻せず生き抜いた資産推移グラフ(長期検証証拠)です。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.38 |
| 勝率 | 45.7% |
| 総取引数 | 35 回 |
| 純利益 (0.1ロット/1万通貨時) | $92.83 |
| 最大ドローダウン | 1.35% |
今回の検証結果の「限界」と「ダメ出し」
このロジックの最大の問題は、取引回数の少なさです。 10年間で35回という数値は、機会損失が極めて大きいことを意味します。 PF1.38という数値は安定していますが、収益力は不足しています。
原因は、フィルターを重ねすぎたことです。 上位足のトレンド判定とZ-Scoreの乖離率を同時に求めています。 さらに時間帯制限まで加えたため、エントリー条件が厳しくなりすぎました。 結果として、相場の大きな波を何度も逃しています。
ロジックの技術的詳細
本ロジックは、均衡点からの統計的乖離を狙う逆張り的なアプローチに、トレンドフィルターを掛け合わせた構成です。
| 項目 | 設定・条件 | 内容 |
|---|---|---|
| ベース時間足 | 5分足 | GBPUSD専用の短期スキャルピング |
| トレンド判定 | 1時間足基準線 | 価格が基準線より上なら買い、下なら売り |
| エントリー指標 | Z-Score | 基準線から$\pm$2.0$\sigma$以上の乖離を検知 |
| ボラティリティ | ATR / ATR_MA | ATRが平均の1.2倍未満の時のみエントリー |
| 反転トリガー | RSI $&$ 直近高安値 | RSIの方向転換と直近足のブレイクで確定 |
| 取引時間帯 | JST 16:00 - 24:00 | 流動性の高い欧州・NY市場序盤に限定 |
| 決済戦略 | トレーリングストップ | 10pips利益到達後にストップを切り上げ |
どう改善すべきか(次なる展望)
このロジックを実用的なレベルへ引き上げるには、フィルターの緩和が必要です。 例えば、Z-Scoreの閾値を2.0から1.5へ下げること。 これにより、取引回数を増やして収益機会を広げられます。
また、ADXを用いたトレンド強度判定の導入を推奨します。 強いトレンド発生時はZ-Scoreの逆張りを禁止する制御を加えること。 プロのEAは、こうした「環境認識による切り替え」でエッジを強めています。
公開コード(Python)
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
class ElasticEquilibriumBreakout(BaseStrategy):
def __init__(self):
# 【ルール4】トレーリングストップ有効化、利確損切の伸長防衛
super().__init__(
name="ElasticEquilibriumBreakout",
default_tp_pips=50.0,
default_sl_pips=20.0,
enable_trailing_stop=True,
trail_start_pips=10.0
)
# 【ルール5】ベース時間足の定義
self.base_timeframe = "5m"
# 【ルール6】画像認識AI用時間足の定義
self.vision_timeframes = ["5m", "15m", "1h"]
def calculate_indicators(self, df):
"""
高速ベクトル演算によるテクニカル指標の計算。ループ処理は一切禁止。
"""
# --- 1. 5分足の基本指標 (ベクトル演算) ---
# 基準線 (Kijun-sen): (期間内最高値 + 期間内最安値) / 2
df['Kijun'] = (df['High'].rolling(window=26).max() + df['Low'].rolling(window=26).min()) / 2
# ATRおよびボラティリティ・フィルター
df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)
df['ATR_MA'] = df['ATR'].rolling(window=100).mean()
# RSI
df['RSI'] = ta.rsi(df['Close'], length=14)
# ADX (pandas_taのADXはDataFrameを返すため、特定のカラムを抽出)
adx_df = ta.adx(df['High'], df['Low'], df['Close'], length=14)
df['ADX'] = adx_df['ADX_14']
# Z-Score (均衡点からの統計的乖離率)
df['Z_Score'] = (df['Close'] - df['Kijun']) / df['ATR']
# --- 2. 上位足 (1h) MTFフィルター ---
# 【ルール7】ルックアヘッドバイアスの完全排除: resample -> shift(1) -> reindex
# 1時間足の基準線を計算
h1_high = df['High'].resample('1h').max().shift(1)
h1_low = df['Low'].resample('1h').min().shift(1)
h1_close = df['Close'].resample('1h').last().shift(1)
h1_kijun = (h1_high + h1_low) / 2
# 5分足のインデックスに再マッピング(前方の値を維持)
df['H1_Kijun'] = h1_kijun.reindex(df.index, method='ffill')
df['H1_Close'] = h1_close.reindex(df.index, method='ffill')
# 上位足トレンド判定 (価格が基準線の上か下か)
df['MTF_Trend'] = 'bull'
df.loc[df['H1_Close'] < df['H1_Kijun'], 'MTF_Trend'] = 'bear'
return df
def generate_signal(self, df):
"""
最新の行に基づいたシグナル生成。
"""
if len(df) < 100:
return None
# 最新行と1本前の行を取得
curr = df.iloc[-1]
prev = df.iloc[-2]
# 【戦略仕様】時間帯フィルター (JST 16:00 - 24:00)
# サーバー時間(UTC)を想定し、必要に応じて調整されるが、ここではインデックスのhourを使用
current_hour = curr.name.hour
is_trading_window = 16 <= current_hour <= 24
# --- 買いシグナル条件 ---
# 1. 上位足が強気
# 2. 取引時間帯内
# 3. 均衡点から下方に過伸展 (Z-Score < -2.0)
# 4. ボラティリティが暴走していない (ATR < ATR_MA * 1.2)
# 5. RSIの切り上がり (ダイバージェンスの萌芽)
# 6. 直近高値の更新 (反転のトリガー)
long_condition = (
(curr['MTF_Trend'] == 'bull') and
(is_trading_window) and
(curr['Z_Score'] < -2.0) and
(curr['ATR'] < curr['ATR_MA'] * 1.2) and
(curr['RSI'] > prev['RSI']) and
(curr['Close'] > prev['High'])
)
# --- 売りシグナル条件 ---
# 1. 上位足が弱気
# 2. 取引時間帯内
# 3. 均衡点から上方に過伸展 (Z-Score > 2.0)
# 4. ボラティリティが暴走していない (ATR < ATR_MA * 1.2)
# 5. RSIの切り下がり (ダイバージェンスの萌芽)
# 6. 直近安値の更新 (反転のトリガー)
short_condition = (
(curr['MTF_Trend'] == 'bear') and
(is_trading_window) and
(curr['Z_Score'] > 2.0) and
(curr['ATR'] < curr['ATR_MA'] * 1.2) and
(curr['RSI'] < prev['RSI']) and
(curr['Close'] < prev['Low'])
)
if long_condition:
return 'BUY'
if short_condition:
return 'SELL'
return None
MT5用コード(MQL5)
//+------------------------------------------------------------------+
//| ElasticEquilibriumBreakout.mq5|
//| Copyright 2026, Quant Engineer |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Quant Engineer"
#property link "https://your-blog-url.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
// 入力パラメータ
input int InpKijunPeriod = 26; // Kijun-sen Period
input int InpATRPeriod = 14; // ATR Period
input int InpATRMAPeriod = 100; // ATR MA Period
input int InpRSIPeriod = 14; // RSI Period
input double InpZScoreLevel = 2.0; // Z-Score Threshold
input double InpTP_Pips = 50.0; // Take Profit (Pips)
input double InpSL_Pips = 20.0; // Stop Loss (Pips)
input double InpTrailStart = 10.0; // Trailing Start (Pips)
input double InpLotSize = 0.1; // Lot Size
// グローバル変数
CTrade trade;
int handleRSI, handleATR;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handleRSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
handleATR = iATR(_Symbol, _Period, InpATRPeriod);
if(handleRSI == INVALID_HANDLE || handleATR == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(!IsNewBar()) return;
// 指標値の取得
double rsi[], atr[], close[], high[], low[];
ArraySetAsSeries(rsi, true);
ArraySetAsSeries(atr, true);
ArraySetAsSeries(close, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
if(CopyBuffer(handleRSI, 0, 0, 3, rsi) < 3) return;
if(CopyBuffer(handleATR, 0, 0, 101, atr) < 101) return;
if(CopyClose(_Symbol, _Period, 0, 3, close) < 3) return;
if(CopyHigh(_Symbol, _Period, 0, 3, high) < 3) return;
if(CopyLow(_Symbol, _Period, 0, 3, low) < 3) return;
// 基準線の計算 (5分足)
double highest = high[ArrayMaximum(high, 1, InpKijunPeriod)];
double lowest = low[ArrayMinimum(low, 1, InpKijunPeriod)];
double kijun = (highest + lowest) / 2.0;
// ATR MAの計算
double atr_sum = 0;
for(int i=1; i<=InpATRMAPeriod; i++) atr_sum += atr[i];
double atr_ma = atr_sum / InpATRMAPeriod;
// Z-Score計算
double z_score = (close[1] - kijun) / atr[1];
// 上位足(1時間足)基準線判定
double h1_high = iHigh(_Symbol, PERIOD_H1, 1);
double h1_low = iLow(_Symbol, PERIOD_H1, 1);
double h1_close = iClose(_Symbol, PERIOD_H1, 1);
double h1_kijun = (iHigh(_Symbol, PERIOD_H1, iHighest(_Symbol, PERIOD_H1, MODE_HIGH, InpKijunPeriod, 1)) +
iLow(_Symbol, PERIOD_H1, iLowest(_Symbol, PERIOD_H1, MODE_LOW, InpKijunPeriod, 1))) / 2.0;
string mtf_trend = (h1_close > h1_kijun) ? "bull" : "bear";
// 時間帯フィルター (UTC 16:00 - 24:00)
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
bool is_window = (dt.hour >= 16 && dt.hour <= 23);
// エントリー判定
if(PositionsTotal() == 0 && is_window)
{
// Long: 上位足強気 & 過剰売られ & ボラ安定 & RSI切り上がり & 直近高値更新
if(mtf_trend == "bull" && z_score < -InpZScoreLevel && atr[1] < atr_ma * 1.2
&& rsi[1] > rsi[2] && close[1] > high[2])
{
double sl = close[1] - InpSL_Pips * _Point * 10;
double tp = close[1] + InpTP_Pips * _Point * 10;
trade.Buy(InpLotSize, _Symbol, close[1], sl, tp);
}
// Short: 上位足弱気 & 過剰買われ & ボラ安定 & RSI切り下がり & 直近安値更新
else if(mtf_trend == "bear" && z_score > InpZScoreLevel && atr[1] < atr_ma * 1.2
&& rsi[1] < rsi[2] && close[1] < low[2])
{
double sl = close[1] + InpSL_Pips * _Point * 10;
double tp = close[1] - InpTP_Pips * _Point * 10;
trade.Sell(InpLotSize, _Symbol, close[1], sl, tp);
}
}
ManageTrailingStop();
}
//+------------------------------------------------------------------+
//| トレーリングストップ管理 |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i=PositionsTotal()-1; i>=0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
double stop_loss = PositionGetDouble(POSITION_SL);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
if(current_price - open_price > InpTrailStart * _Point * 10)
{
double new_sl = current_price - InpTrailStart * _Point * 10;
if(new_sl > stop_loss) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
if(open_price - current_price > InpTrailStart * _Point * 10)
{
double new_sl = current_price + InpTrailStart * _Point * 10;
if(new_sl < stop_loss || stop_loss == 0) trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
}
}
}
}
}
bool IsNewBar()
{
static datetime last_time = 0;
datetime lastbar_time = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
if(last_time != lastbar_time)
{
last_time = lastbar_time;
return true;
}
return false;
}