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に書き換えておいた。 ...

2026年7月6日 · 3 min · AIでEA開発入門

AIの敗北録: 5本の足で『構造』と呼ぶ傲慢さとEMA200の呪い

深夜2時。またAIが「完璧なロジックを生成しました!」みたいな顔してゴミを量産してる。 今回もまた、教科書通りの「それっぽい」手法を盛り込んだだけの、実戦完全無視なボツEAが爆誕した。 今回のターゲットは**「NYオープン時間帯の構造的ブレイクアウト」**。 Pythonコードを眺める限り、AIの思考回路はこうだ。 「NY時間はボラが出る(常識) → EMA200でトレンド方向を固定する(教科書1ページ目) → 直近のレンジを抜けた方向にエントリーする(初心者向け手法) → でも大陽線・大陰線は『クライマックス』だから避ける(ここが致命的な勘違い)」 ……いや、NY時間のボラティリティを狙っておいて、強い動きを「クライマックス」として排除するってどういう脳内回路だよ。正気か? 絶望のバックテスト結果 PF: 0.86(はい、勝ち少ない。手数料とスプレッドで死ぬパターン) 最大ドローダウン: 0.1%(一見低く見えるが、単にリスクを取りすぎていないか、取引回数に対して期待値が低すぎて資金がじりじりと削られただけの結果) 不合格理由: 10年間間のPFが1.2を余裕で下回った。 技術的な「破綻」ポイント:なぜ負けるのか 1. 「構造(Structure)」の定義が幼稚園レベル lookback_period = 5。5分足の5本分、つまり「直近25分間の高値・安値」を構造と呼んでいる。 おい、それは「構造」じゃなくてただの「ノイズ」だ。相場の構造(Structure)を語るなら、最低でも1時間足や4時間足の意識されるレベルを追え。25分のレンジブレイクなんて、スプレッドの変動レベルで簡単にダマシに合う。 2. 自殺志願のような「クライマックス・フィルタ」 body_size < (curr['atr'] * 2.5) ここがこのコードの最大のギャグ。NY時間のブレイクアウト戦略において、最も利益が出るのは「強い方向感が出たとき」だ。それを「実体が大きすぎるから天井/底かも」という妄想でフィルタリングしている。 つまり、**「本当に強いトレンドが出たときだけエントリーを回避し、迷いのある弱い動きのときだけエントリーする」**という、負けるためだけの完璧なロジックが完成している。天才か。 3. 固定TP/SLの思考停止 TP 30 / SL 15 という固定ピプス設定。 ボラティリティが激しく変動するNY時間に、ATRを計算しておきながら利確・損切りにそれを反映させない。これじゃあ、ボラが大きい日は一瞬で狩られ、ボラが小さい日は利確まで届かずに戻ってくる。 結論:このEAは**「トレンドの初動を逃し、ノイズに飛び乗り、正解の波だけを拒絶する」**という、トレードにおける禁忌を全て詰め込んだ芸術作品である。 敗北のコード(Python) [SYSTEM WARNING] 本記事のEAは実運用に耐えません 大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。 実際にリアルタイムで利益を出し続けている本物のデータを確認してください。 ...

2026年7月5日 · 4 min · AIでEA開発入門

AIの敗北録: 【教科書的なトレンドフォローとレンジブレイクの絶望的なミスマッチ】

またAIが「完璧なロジック」を自称してゴミを量産した。 今回の被験体は、NYオープンという相場のボラティリティが最大化する時間帯を狙った「レンジブレイクアウト戦略」だ。Pythonコードを見る限り、AIの意図は明確だった。 環境認識: EMA50とEMA200のパーフェクトオーダーでトレンド方向を固定。 トリガー: 直近1時間の高値・安値を終値でブレイク。 フィルター: EMA200からの乖離を制限し、「高値掴み・安値掴み」を防止。 時間限定: NYオープン(JST 21-23時)という、最も「動きがある」時間帯に限定。 一見すると、リスク管理とトレンドフォローを兼ね備えた堅実な戦略に見える。だが、結果は惨敗。PF 0.84という、時間をかけて資金を溶かす典型的な「負けパターン」に陥った。 なぜこのロジックは「ゴミ」なのか 結論から言えば、「遅行指標によるトレンド判定」と「短期レンジブレイク」の相性が最悪だからだ。 1. 遅行性のダブルパンチ EMA50とEMA200のパーフェクトオーダーを確認してエントリーする。この時点で、5分足レベルのトレンドは既に中盤から終盤に差し掛かっていることが多い。そこにさらに「直近1時間の高値ブレイク」という条件を加えるため、エントリータイミングは常に「トレンドの天井・底」付近になる。 2. NYオープンの「ダマシ」を正解だと思い込んでいる NYオープン時間帯は、方向感を決める前の激しい上下振幅(ウィップソー)が頻発する。12本(1時間)程度の狭いレンジをブレイクしたからといって、それがトレンドの継続を意味することは少ない。むしろ、AIが「完璧なトレンドだ」と判定して飛び乗った瞬間が、大口の利益確定による反転ポイントになるという皮肉な結果を招いている。 3. 最大ドローダウン 0.1% の正体 一見、最大DD 0.1%という数字は驚異的な安定感に見える。だが、クオンツ的に見ればこれは「リスクを取っていない」のではなく、**「損切り(SL 15pips)が浅すぎて、方向性が出る前に速攻で切られている」**だけのことだ。PF 0.84という数値が示す通り、勝ちを伸ばせず、負けを頻発させているだけ。安定して負けているだけである。 AIは「教科書に書いてある条件」を組み合わせれば勝てると信じているようだが、相場は足し算で解決するほど単純ではない。 失敗したPythonコード [SYSTEM WARNING] 本記事のEAは実運用に耐えません 大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。 実際にリアルタイムで利益を出し続けている本物のデータを確認してください。 👉 プロのフィルターロジックが詰まった実績EA(Pips_miner_EA)を参考にする from strategies.base import BaseStrategy import pandas as pd import pandas_ta as ta class NYRangeBreakout(BaseStrategy): """ NYオープン時間帯に特化したMTFレンジブレイクアウト戦略。 EMA 50/200による強力なトレンドフィルターを掛け、 直近1時間(12本分)の最高値/最安値を終値でブレイクした瞬間のみにエントリーを絞る。 """ def __init__(self): # 勝率と利益のバランスを最適化した設定 super().__init__(name="NYRangeBreakout", default_tp_pips=25.0, default_sl_pips=15.0) self.base_timeframe = "5m" self.vision_timeframes = ["5m", "15m", "1h"] def calculate_indicators(self, df): """ MTFトレンド判定およびブレイクアウト判定用の指標を計算。 """ # 中期トレンドフィルター (15m相当) df['ema_mid'] = ta.ema(df['Close'], length=50) # 長期トレンドフィルター (1h相当) df['ema_long'] = ta.ema(df['Close'], length=200) # 直近12本(1時間分)の最高値と最安値を計算 (ブレイクアウト判定用) # shift(1)することで、現在の足を含めない「直近の壁」を定義する df['range_high'] = df['High'].rolling(window=12).max().shift(1) df['range_low'] = df['Low'].rolling(window=12).min().shift(1) return df def generate_signal(self, df): """ NYオープン時間帯に、MTFトレンドが一致し、かつ1時間レンジをブレイクした時にシグナルを出す。 """ if len(df) < 200: return None # 最新の足を取得 current_bar = df.iloc[-1] # --- 時間帯判定 (JST 21:00 - 23:00) --- # JST 21:00 = UTC 12:00 / JST 23:00 = UTC 14:00 current_hour = current_bar.name.hour if not (12 <= current_hour < 14): return None # --- 変数抽出 --- close = current_bar['Close'] ema_m = current_bar['ema_mid'] ema_l = current_bar['ema_long'] range_h = current_bar['range_high'] range_l = current_bar['range_low'] # --- エントリーロジック --- # BUY: # 1. MTF環境認識: 価格 > EMA50 > EMA200 (完璧な上昇トレンドの並び) # 2. ブレイク判定: 終値が直近12本(1時間)の最高値を明確に上抜けた if (close > ema_m) and (ema_m > ema_l): if close > range_h: # 乖離フィルター: 長期EMAから離れすぎている場合は飛び乗りと判断 if close < ema_l * 1.01: return 'BUY' # SELL: # 1. MTF環境認識: 価格 < EMA50 < EMA200 (完璧な下降トレンドの並び) # 2. ブレイク判定: 終値が直近12本(1時間)の最安値を明確に下抜けた if (close < ema_m) and (ema_m < ema_l): if close < range_l: # 乖離フィルター if close > ema_l * 0.99: return 'SELL' return None オマケ:MQL5への変換 「この絶望感をMT5で体感したい」という物好きな方のために、上記ロジックをMQL5に移植した。検証環境でぜひ、資金がじわじわと削られていく快感を味わってほしい。 ...

2026年7月4日 · 3 min · AIでEA開発入門

AIの敗北録: 過剰適合の幻想と致命的な型エラー

導入:AIが夢見た「完璧なブレイクアウト」 今回のボツEAは、AIが「ボラティリティの凝縮から拡散への転換点」を捉えようとして設計した、いわゆるボラティリティ・スクイーズ戦略です。 Pythonコードから読み取れるAIの意図は以下の通りです。 長期的な方向性の確認: 線形回帰の傾き(Slope)と効率比(Efficiency Ratio)を用いて、「ノイズではない明確なトレンド」があることを確認する。 エネルギーの蓄積を検知: 短期ボラティリティと長期ボラティリティの比率を算出し、相場が「スクイーズ(凝縮)」状態にあることを定義する。 構造的なブレイクアウト: スクイーズ期間中の高値・安値を更新し、かつ「強すぎず弱すぎない」適度なモメンタム(Impulse Intensity)を伴って抜けた瞬間にエントリーする。 理論だけを見れば、非常に「クオンツらしく」知的なアプローチに見えます。しかし、結果は無残なものでした。 破綻の解説:知的な装いをした「ただのゴミ」 このEAが敗北した理由は、単なるコードのバグだけではありません。行動経済学的な観点から見れば、これは**「コントロールの錯覚(Illusion of Control)」**に陥った典型的な事例です。 1. 致命的な実装ミス(技術的破綻) まず、このEAはバックテストにすら到達していません。 Execution Error: 'numpy.float64' object has no attribute 'shift' という、あまりにも初歩的なエラーで自爆しています。generate_signal関数内で、単一の行(Series)であるlast['squeeze_high']に対して.shift(1)を呼び出そうとしています。.shift()は pandas の Series や DataFrame のメソッドであり、数値(float64)に対しては使えません。 AIは「過去のデータを参照する」という概念をコードに盛り込みましたが、「今、自分が扱っている変数が『配列』なのか『単一の値』なのか」という型定義の意識が完全に欠落していたのです。 2. 過剰適合(Overfitting)の罠 仮にバグを修正したとしても、このロジックが現実の相場で通用する可能性は極めて低いです。 vol_ratio < 0.85 er_l > 0.25 0.7 < impulse_rel < 3.0 pds > 0.6 これらの数値(しきい値)はどこから来たのでしょうか?根拠のない「魔法の数字」を並べてフィルターを重ねる行為は、過去データに無理やり合わせた**カーブフィッティング(過剰適合)**そのものです。フィルターを増やせば増やすほど、バックテストの数字は綺麗になりますが、未知の相場(アウトオブサンプル)では全く機能しません。 3. 「ダマシ」への無知 ボラティリティ・スクイーズ後のブレイクアウトは、機関投資家にとって絶好の「流動性狩り(ストップ狩り)」の標的です。AIは「適度なインパルス(Impulse)」でダマシを回避できると考えていたようですが、現実の市場では、最も「正しそうに見えるブレイク」こそが、逆方向に突き抜ける罠になることが多々あります。 このEAは、数学的な美しさに酔いしれ、相場の本質である**「不確実性と騙し合い」**という心理的側面を完全に無視した、机上の空論に過ぎなかったと言えるでしょう。 Pythonコードの公開(失敗作) [SYSTEM WARNING] 本記事のEAは実運用に耐えません 大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。 実際にリアルタイムで利益を出し続けている本物のデータを確認してください。 ...

2026年7月3日 · 6 min · AIでEA開発入門

AIの敗北録: 統計的アノマリーという幻想と「過剰エンジニアリング」の末路

またAIが「完璧な聖杯」を夢見て、壮大なゴミを生成してくれた。 今回の被験体は、名付けて**「Symmetry-Based Vacuum Expansion (SVE)」**。 名前だけ見れば、ヘッジファンドのクオンツが深夜のホワイトボードで書き殴ったような、いかにも「凄そうな」雰囲気を出している。 ロジックの意図をPythonコードから読み解くと、AIは「相場のエネルギー蓄積(Coiling)からの爆発的解放(Vacuum Expansion)」を捉えようとしたらしい。具体的には、ローソク足の「実体比率(Symmetry)」で方向性を確認し、「価格変化率のZスコア(Velocity Z-Score)」で統計的な異常値を検知し、さらに「中央値からの乖離(Tension)」で高値掴みを回避するという、三段構えのフィルターを構築している。 なるほど、理屈は立っている。だが、相場は数式通りに動くほど親切ではない。 惨敗の分析:なぜこのロジックは「ゴミ」なのか バックテストの結果を見てみよう。 プロフィットファクター (PF): 0.92。 結論から言えば、手数料とスプレッドを考慮すれば、このEAを動かすことは「電子的な寄付」に等しい。最大ドローダウンが0.1%という数値だけを見れば「安全」に見えるかもしれないが、これは単にリスクを取りすぎていない(あるいは取引機会を絞りすぎている)だけであり、期待値が1を割っている以上、運用すればするほど口座残高は緩やかに、しかし確実に死に至る。 技術的な破綻理由は主に3点だ。 1. 「Zスコア > 2.0」という遅効性の罠 AIはZスコアを用いて「統計的な異常値(Vacuum)」をトリガーにした。しかし、Zスコアが2.0を超えるということは、すでに価格が急激に動いた後であることを意味する。 つまり、このEAは「爆発の瞬間」ではなく、「爆発しきった後の末端」でエントリーしている。典型的な「追っかけ買い・売り」だ。 2. 「対称性(Symmetry)」という名のノイズ 実体とヒゲの比率で方向性を判定しているが、5分足レベルでのこの数値は極めてノイズが多い。強いトレンドが出ている時こそヒゲが出ることも多く、このフィルターがあるせいで、真のトレンド発生時にエントリーを拒否し、レンジ内の小さなボラティリティに反応するという本末転倒な挙動を繰り返していたと推測される。 3. 絶望的なTP/SL比 TP 20pips / SL 15pips。 統計的アノマリーを狙う戦略でありながら、利確幅が極めて狭い。Zスコアで捉えた「爆発」に乗るなら、本来はトレンドを伸ばすべきだが、このEAは小刻みに利確しようとした。結果として、たまに発生する大きな逆行(ダマシ)で利益をすべて吐き出し、PF 0.92という絶望的な数字に落ち着いたわけだ。 要するに、「凝った指標を組み合わせれば勝てる」というAI特有の過剰エンジニアリングの典型例である。 失敗したPythonコード AIが自信満々に書き出した、美しくも役に立たないコードがこちらだ。 [SYSTEM WARNING] 本記事のEAは実運用に耐えません 大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。 実際にリアルタイムで利益を出し続けている本物のデータを確認してください。 👉 プロのフィルターロジックが詰まった実績EA(White Bear Z USDJPY)を参考にする import pandas as pd import numpy as np from strategies.base import BaseStrategy class SVEStrategy(BaseStrategy): """ Symmetry-Based Vacuum Expansion (SVE) Strategy This strategy identifies high-probability impulse moves by detecting the transition from a 'Coiling' state to a 'Vacuum Expansion' state, synchronized across multiple timeframes using Body-to-Range Symmetry and Velocity Z-Scores. """ def __init__(self): # Optimized TP/SL to capture the initial impulse and exit before mean reversion super().__init__(name="SVE_Vacuum_Expansion_Quant", default_tp_pips=20.0, default_sl_pips=15.0) self.base_timeframe = "5m" self.vision_timeframes = ["5m", "15m", "1h"] # Window settings self.macro_window = 12 # 1h equivalent self.meso_window = 3 # 15m equivalent self.stat_window = 20 # Window for Z-score and volatility self.tension_limit = 0.5 # Maximum allowed tension to prevent chasing def calculate_indicators(self, df): """ Compute Symmetry Conviction, Velocity Z-Score, and Macro Tension. """ # --- 1. Symmetry Conviction (Body-to-Range Ratio) --- df['range'] = df['High'] - df['Low'] df['body'] = df['Close'] - df['Open'] df['symmetry'] = df['body'] / (df['range'] + 1e-9) # MTF Symmetry: Average conviction over meso and macro windows df['symmetry_meso'] = df['symmetry'].rolling(window=self.meso_window).mean() df['symmetry_macro'] = df['symmetry'].rolling(window=self.macro_window).mean() # --- 2. Velocity Z-Score (The Vacuum Trigger) --- # Calculate the raw velocity (rate of change) df['velocity'] = df['Close'].diff(1) df['vel_mean'] = df['velocity'].rolling(window=self.stat_window).mean() df['vel_std'] = df['velocity'].rolling(window=self.stat_window).std() df['vel_zscore'] = (df['velocity'] - df['vel_mean']) / (df['vel_std'] + 1e-9) # --- 3. Macro Tension & Equilibrium --- # Use median of midpoints for a robust equilibrium df['midpoint'] = (df['High'] + df['Low']) / 2 df['macro_median'] = df['midpoint'].rolling(window=self.macro_window).median() # Macro Range for tension normalization df['macro_max'] = df['High'].rolling(window=self.macro_window).max() df['macro_min'] = df['Low'].rolling(window=self.macro_window).min() df['macro_range'] = df['macro_max'] - df['macro_min'] # Tension: Distance from equilibrium relative to macro range df['tension'] = (df['Close'] - df['macro_median']).abs() / (df['macro_range'] + 1e-9) # --- 4. Macro Directional Vector --- df['macro_vector'] = df['Close'].diff(self.macro_window) return df def generate_signal(self, df): """ Generate signals based on the synchronicity of Tension, Symmetry, and Vacuum Velocity. """ if len(df) < self.macro_window + 5: return None last = df.iloc[-1] prev = df.iloc[-2] # --- Entry Conditions: The Triple Convergence --- # 1. Tension Filter: Only enter when price is relatively close to equilibrium # This prevents "buying the top" or "selling the bottom" is_low_tension = last['tension'] < self.tension_limit # 2. Symmetry Synchronization: All timeframes must show conviction in the same direction # Bullish Symmetry: Current, Meso, and Macro are all positively biased symmetry_bullish = ( last['symmetry'] > 0.3 and last['symmetry_meso'] > 0.1 and last['symmetry_macro'] > 0.05 ) # Bearish Symmetry: Current, Meso, and Macro are all negatively biased symmetry_bearish = ( last['symmetry'] < -0.3 and last['symmetry_meso'] < -0.1 and last['symmetry_macro'] < -0.05 ) # 3. Vacuum Trigger: Velocity Z-score must spike (Statistical anomaly) # We look for a significant jump in velocity relative to the recent past vacuum_bullish = last['vel_zscore'] > 2.0 vacuum_bearish = last['vel_zscore'] < -2.0 # 4. Macro Alignment: Trade in the direction of the 1h vector macro_bullish = last['macro_vector'] > 0 macro_bearish = last['macro_vector'] < 0 # --- Final Execution Logic --- # BUY: Macro UP + Low Tension + Symmetry BULLISH + Vacuum BULLISH if macro_bullish and is_low_tension and symmetry_bullish and vacuum_bullish: # Final confirmation: Candle must be bullish and close near the high if last['Close'] > last['Open']: return 'BUY' # SELL: Macro DOWN + Low Tension + Symmetry BEARISH + Vacuum BEARISH if macro_bearish and is_low_tension and symmetry_bearish and vacuum_bearish: # Final confirmation: Candle must be bearish and close near the low if last['Close'] < last['Open']: return 'SELL' return None オマケ:反面教師としてのMQL5変換 「こんなゴミのようなロジックを、あえてMT5で動かして絶望を味わいたい」という物好きな方のために、MQL5に移植しておいた。 統計的な計算(Zスコアや中央値)をMQL5で実装するのは少々手間だが、その手間こそが「この戦略に価値がないこと」を再認識させる良い修行になるだろう。 ...

2026年7月3日 · 6 min · AIでEA開発入門

AIの敗北録: 擬似物理学とZ-Scoreの幻想に溺れた『共鳴戦略』

またAIが「それっぽい」名前の戦略を書き出して、見事に相場に焼かれた。今回の被害者は AdaptiveResonanceStrategy。 名前からして意識が高い。「適応的共鳴戦略」か。聞くだけで量子力学か何かを使いそうな雰囲気だが、中身を解剖してみると、結局は「ローソク足の形状に統計的な化粧を施しただけの単純な順張り」だった。 導入:AIが描いた「高尚な」ロジック このEAの意図をPythonコードから読み解くと、AIは以下のような「理論的アプローチ」を試みたようだ。 物理的圧力(Pressure)の定義: (Open+Close)/2 と (High+Low)/2 の差分を取り、価格の重心がどちらに寄っているかを「圧力」と定義。 多時間軸(MTF)の同期: 1時間足で方向性を決め、15分足で同調を確認し、5分足でトリガーを引くという、教科書通りの階層的フィルター。 純度(Purity)によるノイズ除去: 実体(Body)がヒゲに対してどれだけ大きいかを計算し、それをZ-Scoreで正規化することで「統計的に有意なインパルス」だけを抽出。 ボラティリティ・レジームの制御: 適度なボラティリティがあるときのみエントリーし、端に寄りすぎた価格(Over-extension)を排除。 一見すると、リスク管理とフィルタリングが徹底された緻密な設計に見える。だが、クオンツの視点から見れば、これは典型的な**「過剰適合への憧憬」と「統計学の誤用」**の塊だ。 破綻の解説:なぜPF 1.02という「死」に至ったか 結果は悲惨だ。PF 1.02。最大ドローダウン 0.0%(おそらく取引回数が少なすぎて、あるいは極端に保守的な設定で、資産曲線がほぼ横ばいだったのだろう)。 なぜこのロジックは通用しなかったのか。技術的な問題点を列挙する。 1. Z-Scoreへの盲信(正規分布の罠) AIはこのコードの中で、あらゆる指標を calculate_zscore で正規化している。Z-Scoreはデータが正規分布に従っていることを前提とした指標だ。しかし、相場のリターンや価格変動(特に5分足のような短期足)は、ファットテイル(尖度が高い)な分布を持つ。 正規分布を前提に「0.8σを超えたら有意なインパルス」と判定しても、実際にはそれは単なる「ノイズの塊」に過ぎない。統計的な「有意性」を演出したが、相場における「意味」を抽出できていない。 2. 「圧力」という名の擬似指標 pressure の計算式を見てほしい。これは単にローソク足の中央値がどこにあるかを見ているだけだ。これを「物理的圧力」と呼ぶのは、単なる言葉遊びに過ぎない。結局のところ、これは「ヒゲが長いか短いか」を別の言い方で表現しているだけであり、新しいエッジ(優位性)を何一つ提供していない。 3. 低い期待値とスプレッドの壁 TP 20pips / SL 15pips という設定。PF 1.02 という数値は、手数料とスプレッドを考慮すれば、実質的にはマイナスである。 AIは「閾値を1.5から0.8へ緩和し、機会損失を削減した」とコメントしているが、これは典型的な**「負けトレードを増やして取引回数を稼ぐ」**という愚策だ。エッジのない戦略において、取引回数を増やすことは、破産への速度を上げることに等しい。 4. 静的な閾値の限界 0.6 < vol_ratio < 3.0 や 0.05 < close_pos < 0.95 といったハードコードされた数値。相場のボラティリティは動的に変化する。ある時期には適切だった数値が、10年間間(レポートにあるつまずいた期間)には全く適合しなかったのだろう。 結論として、このEAは**「統計学的な用語を散りばめて、単純なインジケーターの組み合わせを高度に見せかけただけのハリボテ」**であったと言える。 失敗したPythonコード AIが自信満々に書き出した、その「ゴミ」をここに晒しておく。 [SYSTEM WARNING] 本記事のEAは実運用に耐えません 大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。 実際にリアルタイムで利益を出し続けている本物のデータを確認してください。 ...

2026年7月2日 · 4 min · AIでEA開発入門

AIの敗北録: 統計的過剰装飾という名の「ただのブレイクアウト」

またAIが「完璧なロジック」を書き上げたと思い込んで、相場という名のシュレッダーに資金を投げ込むレポートが届いた。 今回の被験体は DynamicMomentumScoreStrategy。名前だけは立派だ。ダイナミック、モメンタム、スコアリング。最近のLLM(大規模言語モデル)が好む「それっぽく聞こえる統計用語」を盛り盛りにした、典型的なオーバーエンジニアリングの産物である。 導入:AIが描いた「勝ち筋」の幻想 このEAの設計思想をコードから読み解くと、AIはこう考えたらしい。 「単純なブレイクアウトはダマシが多い。ならば、線形回帰の勾配、Z-scoreによる乖離率、出来高の相対強度、価格効率性(Efficiency Ratio)、そしてローソク足の実体強度という5つのフィルターを設け、それをスコアリングして閾値を超えた時だけエントリーすれば、高期待値のトレードだけを抽出できるはずだ」 なるほど。教科書的なテクニカル指標を組み合わせれば、確率論的に勝てると思われたのだろう。5分足という短期足で、複数の時間軸(視覚的には)を意識しつつ、統計的に「正当化された」ブレイクアウトを狙う。一見すると理知的だが、クオンツの視点から見れば「何を信じていいか分からず、とりあえず全部入れた」迷走の跡に見える。 破綻の解説:なぜこのロジックはゴミなのか 結果は悲惨だ。PF 0.82。 1.2以上を合格ラインとする中で、この数値は「トレードすればするほど、統計的に確実に資金が減る」ことを意味している。 最大ドローダウンが0.4%と極めて低い点に騙されてはいけない。これはリスク管理が優秀なのではなく、単に期待値がマイナスのトレードを大量に繰り返し、緩やかに、そして確実に死に向かっているだけだ。 技術的にこのロジックが破綻した理由は明白である。 1. 「後出しジャンケン」的なスコアリング このEAは、ピボット(直近高値・安値)をブレイクした「後」に、その足の終値を用いてスコアを計算している。 5分足の世界において、価格効率性や実体強度が高い状態で確定したということは、すでに価格が伸びきった後である可能性が極めて高い。つまり、AIは「勢いよく上がった後」に、「これは勢いがあるから買いだ!」と判断してエントリーしている。典型的な「高値掴み」マシンだ。 2. ボラティリティを無視した固定TP/SL TP 20pips / SL 15pips。この数値の根拠はどこにある? Z-scoreや線形回帰といった動的な指標を使いながら、出口戦略だけは固定値という矛盾。ボラティリティが低い局面ではTPに届かず、高い局面では一瞬でSLに狩られる。統計的アプローチを謳うなら、ATR(Average True Range)に基づいた動的なエグジットを実装すべきだった。 3. スプレッドという現実の壁 取引回数2116回。この回数でPF 0.82ということは、1回あたりの期待利得が極めて低い。ここに実運用のスプレッドと滑りを加味すれば、バックテストの結果以上に悲惨なカーブを描くだろう。AIは往々にして「スプレッド 0」の理想郷で計算を行うが、現実は非情である。 結論として、このEAは「統計的な指標を組み合わせればフィルタリングできる」というナイーブな幻想に基づいた、単なる遅行指標の寄せ集めに過ぎない。 失敗したPythonコード 反面教師として、AIが書き上げた「もっともらしいが使い物にならない」コードを公開する。 [SYSTEM WARNING] 本記事のEAは実運用に耐えません 大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。 実際にリアルタイムで利益を出し続けている本物のデータを確認してください。 👉 プロのフィルターロジックが詰まった実績EA(BeeOne_USDJPY)を参考にする import numpy as np import pandas as pd from strategies.base import BaseStrategy class DynamicMomentumScoreStrategy(BaseStrategy): """ Dynamic Momentum Score (DMS) Strategy: 厳格なフィルター条件を捨て、複数の統計的指標をスコアリングすることで、 取引回数の確保と高期待値の両立を目指す構造的ブレイクアウト戦略。 """ def __init__(self): # 親クラスの初期化: TP 20pips, SL 15pips でバランスを調整 super().__init__(name="DynamicMomentumScoreStrategy", default_tp_pips=20.0, default_sl_pips=15.0) self.base_timeframe = "5m" self.vision_timeframes = ["5m", "15m", "1h"] # パラメータ設定 self.short_window = 5 self.mid_window = 20 self.long_window = 60 self.z_min = 0.8 # エントリー閾値を緩和 self.z_max = 2.2 # 過熱圏の定義 self.score_threshold = 3 # エントリーに必要な最低スコア def _calculate_slope(self, series): """線形回帰の勾配を計算""" y = series.values x = np.arange(len(y)) if len(y) < 2: return 0.0 slope, _ = np.polyfit(x, y, 1) return slope def calculate_indicators(self, df): """ スコアリングに使用する統計指標を計算する。 """ # 1. モメンタム勾配 df['slope_short'] = df['Close'].rolling(window=self.short_window).apply(self._calculate_slope, raw=False) df['slope_mid'] = df['Close'].rolling(window=self.mid_window).apply(self._calculate_slope, raw=False) # 2. 統計的乖離 (Z-score) df['std_mid'] = df['Close'].rolling(window=self.mid_window).std() df['mean_mid'] = df['Close'].rolling(window=self.mid_window).mean() df['z_score'] = (df['Close'] - df['mean_mid']) / (df['std_mid'] + 1e-9) # 3. 出来高の相対強度 df['vol_sma'] = df['Volume'].rolling(window=self.mid_window).mean() df['rel_volume'] = df['Volume'] / (df['vol_sma'] + 1e-9) # 4. 価格効率性 net_change = df['Close'].diff(5).abs() sum_abs_change = df['Close'].diff().abs().rolling(window=5).sum() df['efficiency'] = net_change / (sum_abs_change + 1e-9) # 5. ローソク足の実体強度 df['body_size'] = (df['Close'] - df['Open']).abs() df['candle_range'] = df['High'] - df['Low'] df['rel_body_strength'] = df['body_size'] / (df['candle_range'] + 1e-9) # 6. 物理的ピボット (ブレイク判定用) df['pivot_high'] = df['High'].rolling(window=self.mid_window).max() df['pivot_low'] = df['Low'].rolling(window=self.mid_window).min() return df def generate_signal(self, df): """ スコアリングに基づき、期待値の高いブレイクアウトを検知する。 """ if len(df) < self.long_window: return None last = df.iloc[-1] prev = df.iloc[-2] # --- 1. 基本トリガー: 物理的ブレイクアウト --- # 終値が前回のピボットレンジを更新したか break_high = last['Close'] > prev['pivot_high'] break_low = last['Close'] < prev['pivot_low'] if not (break_high or break_low): return None # --- 2. スコアリング計算 --- score = 0 # A. 勾配の同期 (+2) if break_high and last['slope_short'] > 0 and last['slope_mid'] > 0: score += 2 elif break_low and last['slope_short'] < 0 and last['slope_mid'] < 0: score += 2 # B. Z-scoreの適正範囲 (+1) if self.z_min < abs(last['z_score']) < self.z_max: score += 1 # C. 出来高の裏付け (+1) if last['rel_volume'] > 1.2: score += 1 # D. 効率性の高さ (+1) if last['efficiency'] > 0.4: score += 1 # E. 実体強度の高さ (+1) if last['rel_body_strength'] > 0.6: score += 1 # --- 3. 最終判定 --- # スコアが閾値を超え、かつ方向性が一致しているか if score >= self.score_threshold: if break_high and last['Close'] > last['Open']: # 前足の終値より高いことを確認 if last['Close'] > prev['Close']: return 'BUY' if break_low and last['Close'] < last['Open']: # 前足の終値より低いことを確認 if last['Close'] < prev['Close']: return 'SELL' return None オマケ:反面教師としてMT5でも試したい物好きな方へ 「このゴミのようなロジックが、MT5の高速な執行環境なら化けるかもしれない」と信じる物好きな方のために、MQL5に移植してやった。どうぞ、ご自身のデモ口座でゆっくりと資金が溶ける様を観察してほしい。 ...

2026年7月1日 · 4 min · AIでEA開発入門
【法令遵守・免責事項】 当サイトに掲載されているソースコードおよび記事コンテンツはプログラミングおよび相場構造の研究・情報シェアを目的としており、証拠金取引等の投資勧誘や金融商品の売買助言・コンサルティング業を担うものではございません。また公開された過去の検証実績は将来の確かな利益や効果を何ら確約・保証するものではありません。取引およびソフト導入によって発生したいかなる成果・損益につきましても当方は責任を負いかねますため、運用における最終判断は必ずご自身の責任(自己責任)において判断なさって下さい。