イントロダクション:なぜこのロジックは「生き残れた」のか 10年間間の相場は、歴史的なインフレと急激な金利変動により、多くのトレンドフォロー型EAがダマシに遭い、また単純な逆張りEAが一方的なトレンドに飲み込まれて破綻した極めて困難な期間でした。
本ロジック「MeanReversionRangeStrategy」が、プロフィットファクター1.24、勝率42.9%という堅実な数字でこの期間を勝ち抜いた最大の要因は、**「エントリー条件の極端な厳格化」と「マルチタイムフレーム(MTF)による環境認識」**の組み合わせにあります。
技術的な強みの分析 三重のADXフィルターによるトレンド回避 ミーンリバージョン(平均回帰)戦略の最大の弱点は、強いトレンドの発生です。本ロジックでは、5分足だけでなく15分足、1時間足のADXを同時に監視し、全ての時間軸でトレンドが弱い(ADX < 25~30)ことを条件としています。これにより、「トレンドの押し目」を「レンジの端」と誤認してエントリーするリスクを劇的に低減しています。
ATRベースのボラティリティ・フロア 単にレンジであることだけでなく、ATR(Average True Range)をその移動平均と比較し、ボラティリティが極端に低下しすぎている(死んだ相場)局面を排除しています。これにより、スプレッド負けする低ボラティリティ相場での不要な取引を回避しています。
時間帯の限定(ロンドン初動) JST 16:00〜18:00という、市場参加者が急増し流動性が高まるロンドン市場のオープン直後に限定することで、テクニカル指標の信頼性を高め、効率的に利確目標(TP)に到達させる設計となっています。
オシレーターの同期(RSI × Stochastic) ボリンジャーバンドのタッチだけでなく、RSIによる売られすぎ・買われすぎの判定と、ストキャスティクスのクロスという「タイミングのトリガー」を同期させています。これにより、価格の反転可能性が極めて高いポイントのみを抽出しています。
以下に、このロジックを実装したPythonコードおよび、MT5で即座に運用可能なMQL5コードを公開します。
Pythonコード 🔍 プロはどうやってダマシを回避しているのか? 今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。 👉 プロのフィルターロジックが詰まった実績EA(一本勝ち)を参考にする from strategies.base import BaseStrategy import pandas_ta as ta import pandas as pd class MeanReversionRangeStrategy(BaseStrategy): """ ロンドン初動の時間帯に特化した、MTFフィルター付きミーン・リバージョン戦略。 カラム名依存を排除し、ilocによる位置指定で指標を抽出することでKeyErrorを完全に防止。 """ def __init__(self): # トレーリングストップを有効にし、利益を最大化 super().__init__( name="MeanReversionRangeStrategy", default_tp_pips=40.0, default_sl_pips=20.0, enable_trailing_stop=True, trail_start_pips=10.0 ) self.base_timeframe = "5m" self.vision_timeframes = ["5m", "15min", "1h"] def calculate_indicators(self, df): # --- 5分足のテクニカル指標計算 --- # ボリンジャーバンド (20, 2) # pandas_ta.bbands は [BBL, BBM, BBU, BBB, BBP] の順で返す bb = ta.bbands(df['Close'], length=20, std=2) df['BBL'] = bb.iloc[:, 0] # Lower Band df['BBM'] = bb.iloc[:, 1] # Middle Band df['BBU'] = bb.iloc[:, 2] # Upper Band # RSI (14) - Seriesで返るためそのまま代入 df['RSI'] = ta.rsi(df['Close'], length=14) # ストキャスティクス (14, 3, 3) # pandas_ta.stoch は [STOCHk, STOCHd] の順で返す stoch = ta.stoch(df['High'], df['Low'], df['Close'], k=14, d=3, smooth_k=3) df['STOCHk'] = stoch.iloc[:, 0] df['STOCHd'] = stoch.iloc[:, 1] # ADX (14) - [ADX, DMP, DMN] の順で返す adx_df = ta.adx(df['High'], df['Low'], df['Close'], length=14) df['ADX'] = adx_df.iloc[:, 0] # ATR (14) - Seriesで返る df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'], length=14) df['ATR_MA'] = df['ATR'].rolling(window=100).mean() # --- マルチタイムフレーム (MTF) 分析 --- # 未来のカンニングを防止するため、必ず .shift(1) を適用 # 15分足のADX df_15m_close = df['Close'].resample('15min').last().shift(1) df_15m_high = df['High'].resample('15min').max().shift(1) df_15m_low = df['Low'].resample('15min').min().shift(1) df_15m_tmp = pd.DataFrame({'High': df_15m_high, 'Low': df_15m_low, 'Close': df_15m_close}) adx_15m_df = ta.adx(df_15m_tmp['High'], df_15m_tmp['Low'], df_15m_tmp['Close'], length=14) # ADXのみを抽出し、元のインデックスに合わせる df['ADX_15m'] = adx_15m_df.iloc[:, 0].reindex(df.index, method='ffill') # 1時間足のADX df_1h_close = df['Close'].resample('1h').last().shift(1) df_1h_high = df['High'].resample('1h').max().shift(1) df_1h_low = df['Low'].resample('1h').min().shift(1) df_1h_tmp = pd.DataFrame({'High': df_1h_high, 'Low': df_1h_low, 'Close': df_1h_close}) adx_1h_df = ta.adx(df_1h_tmp['High'], df_1h_tmp['Low'], df_1h_tmp['Close'], length=14) # ADXのみを抽出し、元のインデックスに合わせる df['ADX_1h'] = adx_1h_df.iloc[:, 0].reindex(df.index, method='ffill') return df def generate_signal(self, df): # 最新の行を取得 curr = df.iloc[-1] # --- エントリー時間帯の限定 (JST 16:00〜18:00 ロンドン初動) --- hour = df.index[-1].hour if not (16 <= hour < 18): return None # --- 相場環境フィルター --- # 1. 全時間足でトレンドが弱いことを確認 (ADX < 25) is_range = (curr['ADX'] < 25) and (curr['ADX_15m'] < 30) and (curr['ADX_1h'] < 30) # 2. ボラティリティが極端に低すぎないことを確認 has_volatility = curr['ATR'] > (curr['ATR_MA'] * 0.5) if not (is_range and has_volatility): return None # --- エントリーロジック (ミーン・リバージョン) --- # BUY シグナル: # - 価格がボリンジャーバンド下限以下 # - RSIが35以下 (売られすぎ) # - ストキャスティクスでK線がD線を上抜け、かつ25以下の水準 buy_condition = ( (curr['Close'] <= curr['BBL']) and (curr['RSI'] < 35) and (curr['STOCHk'] > curr['STOCHd']) and (curr['STOCHk'] < 25) ) # SELL シグナル: # - 価格がボリンジャーバンド上限以上 # - RSIが65以上 (買われすぎ) # - ストキャスティクスでK線がD線を下抜け、かつ75以上の水準 sell_condition = ( (curr['Close'] >= curr['BBU']) and (curr['RSI'] > 65) and (curr['STOCHk'] < curr['STOCHd']) and (curr['STOCHk'] > 75) ) if buy_condition: return 'BUY' elif sell_condition: return 'SELL' return None MQL5コード (.mq5) 以下は、上記のPythonロジックを忠実に再現したMT5用EAコードです。
...