また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で実装するのは少々手間だが、その手間こそが「この戦略に価値がないこと」を再認識させる良い修行になるだろう。
...