AIの敗北録: 過剰フィルターという名の「完璧主義的な自殺」
また出たよ。最近のAIが書き出す「いかにも正解っぽく見えるゴミ」だ。 今回の標本は、NY市場のボラティリティを狙ったという『NYStructureTrend』。 ロジックを紐解けば、長期EMAで環境認識をし、短期EMAへのプルバックを待ち、出来高を確認し、直近高値・安値のブレイクでエントリーする……という、FXの入門書に書いてある「正解」を全部盛り込んだような構成だ。 AIはきっとこう思ったんだろう。「条件を重ねれば重ねるほど、勝率は上がり、リスクは減る」と。 だが、相場の本質を理解していない人間(とAI)が陥る最大の罠がここにある。 破綻の理由:フィルターの積み上げによる「取引機会の絶滅」 まずはこの絶望的なバックテスト結果を見てくれ。 取引回数: 0回 プロフィットファクター (PF): 0.00 最大ドローダウン: 0.0% 笑わせるな。ドローダウン0%? それは「勝った」のではなく、「戦場にすら立てなかった」ということだ。 なぜこうなったか? 技術的に解説してやる。このEAは**「フィルターの過剰積載」**で自死したんだ。 時間帯の極端な制限: JST 21:00〜23:00の2時間のみ。 環境認識の硬直化: EMA200の傾きと価格位置という、遅行指標による二重チェック。 プルバックの厳格な定義: EMA20に「接近または下回ること」というピンポイントな要求。 出来高のハードル: 平均の80%以上の出来高を要求。 構造的ブレイクの確定: さらに直近2本の高値を抜けるというトリガー。 これらすべてが「同時に」成立する確率を計算してみろ。 「トレンドが出ていて、かつ、ちょうどいいタイミングで押し目を作り、かつ、十分な出来高があり、かつ、タイミングよく直近高値を抜ける」……そんな都合のいいタイミングが、5分足のわずか2時間枠の中で、2年間一度も起きなかったということだ。 AIは「期待値」ではなく「条件の整合性」だけを追い求めた。 実際の相場はもっと泥臭い。インジケーターが完璧に整うのを待っていたら、美味しいところは全部食い尽くされている。裁量トレーダーが意識するのは「条件の合致」ではなく「相場のリズム」と「不均衡」だ。 このコードは、教科書を丸暗記して試験には満点を取るが、実戦では一歩も動けない臆病者の思考そのものだ。 失敗したPythonコード [SYSTEM WARNING] 本記事のEAは実運用に耐えません 大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。 実際にリアルタイムで利益を出し続けている本物のデータを確認してください。 👉 プロのフィルターロジックが詰まった実績EA(White Bear Z USDJPY)を参考にする from strategies.base import BaseStrategy import pandas as pd import pandas_ta as ta class NYStructureTrend(BaseStrategy): """ NY市場オープン時間帯の構造的転換を狙う戦略。 EMA200の傾きで環境を定義し、EMA20へのプルバック後、出来高を伴う直近高値/安値更新でエントリーする。 """ def __init__(self): # NY市場のボラティリティに合わせ、SLを20pipsに広げて「呼吸」をさせ、TPを35pipsに設定 super().__init__(name="NY_Structure_Trend", default_tp_pips=35.0, default_sl_pips=20.0) self.base_timeframe = "5m" self.vision_timeframes = ["5m", "15m", "1h"] # パラメータ self.slow_ema_period = 200 # 長期トレンド方向 self.fast_ema_period = 20 # 価値領域(プルバック基準) self.vol_ma_period = 20 # 出来高平均判定用 def calculate_indicators(self, df): """ テクニカル指標の計算。 """ # 長期EMA (環境認識) df['ema_slow'] = ta.ema(df['Close'], length=self.slow_ema_period) # 短期EMA (プルバック基準) df['ema_fast'] = ta.ema(df['Close'], length=self.fast_ema_period) # 出来高の移動平均 (ボラティリティ・流動性判定) df['vol_ma'] = df['Volume'].rolling(window=self.vol_ma_period).mean() return df def generate_signal(self, df): """ 最新の足に基づいて売買シグナルを生成する。 """ if len(df) < self.slow_ema_period + 5: return None curr = df.iloc[-1] prev = df.iloc[-2] prev_prev = df.iloc[-3] # --- 時間帯フィルタ (JST 21:00 - 23:00) --- current_time_jst = df.index[-1].tz_localize('UTC').tz_convert('Asia/Tokyo') hour = current_time_jst.hour if not (21 <= hour < 23): return None # --- 1. MTF環境認識 (トレンドの方向性と強さ) --- # EMA200が明確に上向きであり、価格がその上にあること is_bullish_env = (curr['Close'] > curr['ema_slow']) and (curr['ema_slow'] > prev['ema_slow']) # EMA200が明確に下向きであり、価格がその下にあること is_bearish_env = (curr['Close'] < curr['ema_slow']) and (curr['ema_slow'] < prev['ema_slow']) # --- 2. 出来高フィルター (本物の動きかどうかの判定) --- # 現在の出来高が平均の80%以上であることを条件とし、死んだ相場を排除 has_volume = curr['Volume'] > (curr['vol_ma'] * 0.8) # --- 3. プライスアクション・トリガー (構造的ブレイク) --- # 【BUYトリガー】 # A. 強気環境であること # B. プルバック: 前回の足の安値がEMA20に接近、または下回った(押し目の形成) # C. 構造的ブレイク: 今回の終値が「前回と前々回の足の最高値」を上抜いた(反転の確定) bullish_pullback = prev['Low'] <= prev['ema_fast'] * 1.0005 bullish_breakout = curr['Close'] > max(prev['High'], prev_prev['High']) # 【SELLトリガー】 # A. 弱気環境であること # B. プルバック: 前回の足の高値がEMA20に接近、または上回った(戻りの形成) # C. 構造的ブレイク: 今回の終値が「前回と前々回の足の最低値」を下抜いた(反転の確定) bearish_pullback = prev['High'] >= prev['ema_fast'] * 0.9995 bearish_breakout = curr['Close'] < min(prev['Low'], prev_prev['Low']) # --- シグナル確定 --- if has_volume and is_bullish_env and bullish_pullback and bullish_breakout: return 'BUY' if has_volume and is_bearish_env and bearish_pullback and bearish_breakout: return 'SELL' return None オマケ(MQL5変換) 「こんなに厳格な条件なら、MT5の高速バックテストで10年分回せば1回くらいはエントリーするだろう」という、絶望的な好奇心を持つ物好きな方のために、MQL5に書き換えておいた。 ...