👇 EAの24時間稼働に必須の仮想サーバー(圧倒的人気)👇

導入:AIが夢見た「完璧なブレイクアウト」

今回のボツEAは、AIが「ボラティリティの凝縮から拡散への転換点」を捉えようとして設計した、いわゆるボラティリティ・スクイーズ戦略です。

Pythonコードから読み取れるAIの意図は以下の通りです。

  1. 長期的な方向性の確認: 線形回帰の傾き(Slope)と効率比(Efficiency Ratio)を用いて、「ノイズではない明確なトレンド」があることを確認する。
  2. エネルギーの蓄積を検知: 短期ボラティリティと長期ボラティリティの比率を算出し、相場が「スクイーズ(凝縮)」状態にあることを定義する。
  3. 構造的なブレイクアウト: スクイーズ期間中の高値・安値を更新し、かつ「強すぎず弱すぎない」適度なモメンタム(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コードの公開(失敗作)

import pandas as pd
import numpy as np
from strategies.base import BaseStrategy

class VolatilitySqueezeBreakout(BaseStrategy):
    """
    Quants Strategy: Volatility Squeeze & Structural Breakout.
    Focuses on the transition from extreme volatility compression 
    to a structural breakout of the squeeze range.
    """
    def __init__(self):
        # Optimized for high PF and consistent trade frequency
        super().__init__(name="VolSqueezeBreakout", default_tp_pips=20.0, default_sl_pips=15.0)
        
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15m", "1h"]
        
        # Mathematical Windows
        self.window_l = 24    # L-Term: Structural Bias (2h equivalent)
        self.window_m = 6     # M-Term: Squeeze Detection
        self.vol_short = 5    # Short-term volatility for ratio
        self.vol_long = 20    # Long-term volatility for baseline

    def calculate_indicators(self, df):
        """
        Computes structural vector, volatility ratios, and squeeze ranges.
        """
        df = df.copy()

        # 1. L-Term: Structural Vector (Linear Regression Slope)
        def get_slope(series, window):
            def calc_slope(y):
                x = np.arange(len(y))
                return np.polyfit(x, y, 1)[0]
            return series.rolling(window=window).apply(calc_slope, raw=True)

        df['slope_l'] = get_slope(df['Close'], self.window_l)
        
        # L-Term Efficiency Ratio (ER) to ensure a clean trend
        def get_er(series, window):
            net = (series.diff(window)).abs()
            vol = series.diff().abs().rolling(window=window).sum()
            return net / vol.replace(0, np.nan)

        df['er_l'] = get_er(df['Close'], self.window_l)

        # 2. M-Term: Volatility Ratio & Squeeze
        # Using standard deviation of returns as a proxy for volatility
        df['returns'] = df['Close'].pct_change()
        df['vol_s'] = df['returns'].rolling(window=self.vol_short).std()
        df['vol_l'] = df['returns'].rolling(window=self.vol_long).std()
        
        # Volatility Ratio: Short-term Vol / Long-term Vol
        df['vol_ratio'] = df['vol_s'] / df['vol_l'].replace(0, np.nan)
        
        # Identify the 'Squeeze' state (Ratio is low)
        df['is_squeezed'] = df['vol_ratio'] < 0.85

        # 3. S-Term: Structural Range & Breakout
        # Track the high/low during the squeeze period
        # We use a rolling window to find the boundary of the most recent squeeze
        df['squeeze_high'] = df['High'].rolling(window=self.window_m).max()
        df['squeeze_low'] = df['Low'].rolling(window=self.window_m).min()
        
        # Impulse Intensity: Body relative to volatility
        df['tr'] = df['High'] - df['Low']
        df['vol_ma'] = df['tr'].rolling(window=self.vol_long).mean()
        df['body'] = (df['Close'] - df['Open']).abs()
        df['impulse_rel'] = df['body'] / df['vol_ma'].replace(0, np.nan)
        
        # Closing position (PDS)
        df['pds'] = (df['Close'] - df['Low']) / df['tr'].replace(0, np.nan)

        return df

    def generate_signal(self, df):
        """
        Triggers a trade when a structural vector is aligned and 
        price breaks out of a volatility squeeze with moderate intensity.
        """
        if len(df) < self.window_l + self.vol_long:
            return None

        last = df.iloc[-1]
        prev = df.iloc[-2]

        # --- 1. L-Term: Structural Alignment ---
        # Ensure the market is in an efficient trend (not noise)
        is_ordered = last['er_l'] > 0.25
        bull_bias = (last['slope_l'] > 0) and is_ordered
        bear_bias = (last['slope_l'] < 0) and is_ordered

        # --- 2. M-Term: Squeeze-to-Release Transition ---
        # Previous state was squeezed, current state is expanding
        is_releasing = prev['is_squeezed'] and (last['vol_ratio'] > 1.0)

        # --- 3. S-Term: Structural Breakout ---
        # Price must close outside the range of the recent squeeze
        # and avoid 'Climax' candles (Impulse < 3.0) to prevent buying the top
        is_moderate_impulse = 0.7 < last['impulse_rel'] < 3.0
        
        bull_breakout = (last['Close'] > last['squeeze_high'].shift(1)) and (last['pds'] > 0.6)
        bear_breakout = (last['Close'] < last['squeeze_low'].shift(1)) and (last['pds'] < 0.4)

        # --- Final Synchronization ---
        # BUY: Bull Bias + Vol Release + Structural Breakout + Moderate Impulse
        if (bull_bias and 
            is_releasing and 
            bull_breakout and 
            is_moderate_impulse):
            return 'BUY'

        # SELL: Bear Bias + Vol Release + Structural Breakout + Moderate Impulse
        if (bear_bias and 
            is_releasing and 
            bear_breakout and 
            is_moderate_impulse):
            return 'SELL'

        return None

オマケ(MQL5変換)

「こんな過剰適合まみれのロジックを、あえてMT5で動かして絶望してみたい」という物好きな方向けに、ロジックをMQL5に移植しました。反面教師としてご利用ください。

//+------------------------------------------------------------------+
//|                                    VolSqueezeBreakout_Failure.mq5|
//|                                  Copyright 2026, Behavioral Econ |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Behavioral Econ"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

// Parameters
input int      window_l = 24;       // Structural Bias Window
input int      window_m = 6;        // Squeeze Detection Window
input int      vol_short = 5;       // Short-term Volatility
input int      vol_long = 20;       // Long-term Volatility
input double   tp_pips = 20.0;
input double   sl_pips = 15.0;

// Global Variables
int handle_std_short, handle_std_long;

int OnInit() {
    return(INIT_SUCCEEDED);
}

// Simple Linear Regression Slope
double GetSlope(int period, int shift) {
    double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
    for(int i = shift; i < shift + period; i++) {
        double y = iClose(_Symbol, _Period, i);
        sumX += i;
        sumY += y;
        sumXY += i * y;
        sumX2 += i * i;
    }
    return (period * sumXY - sumX * sumY) / (period * sumX2 - sumX * sumX);
}

// Efficiency Ratio
double GetER(int period, int shift) {
    double net = MathAbs(iClose(_Symbol, _Period, shift) - iClose(_Symbol, _Period, shift + period));
    double vol = 0;
    for(int i = shift; i < shift + period; i++) {
        vol += MathAbs(iClose(_Symbol, _Period, i) - iClose(_Symbol, _Period, i + 1));
    }
    return (vol == 0) ? 0 : net / vol;
}

void OnTick() {
    if(Bars(_Symbol, _Period) < window_l + vol_long) return;

    // 1. L-Term: Structural Alignment
    double slope = GetSlope(window_l, 1);
    double er = GetER(window_l, 1);
    bool bull_bias = (slope < 0) && (er > 0.25); // MQL5 index is reverse
    bool bear_bias = (slope > 0) && (er > 0.25);

    // 2. M-Term: Volatility Squeeze
    double std_s = iStdDev(_Symbol, _Period, vol_short, 0, MODE_SMA, PRICE_CLOSE, 1);
    double std_l = iStdDev(_Symbol, _Period, vol_long, 0, MODE_SMA, PRICE_CLOSE, 1);
    double vol_ratio = (std_l == 0) ? 0 : std_s / std_l;
    
    double prev_std_s = iStdDev(_Symbol, _Period, vol_short, 0, MODE_SMA, PRICE_CLOSE, 2);
    double prev_std_l = iStdDev(_Symbol, _Period, vol_long, 0, MODE_SMA, PRICE_CLOSE, 2);
    double prev_vol_ratio = (prev_std_l == 0) ? 0 : prev_std_s / prev_std_l;
    
    bool is_releasing = (prev_vol_ratio < 0.85) && (vol_ratio > 1.0);

    // 3. S-Term: Breakout
    double squeeze_high = iHigh(_Symbol, _Period, iHighest(_Symbol, _Period, MODE_HIGH, window_m, 2));
    double squeeze_low = iLow(_Symbol, _Period, iLowest(_Symbol, _Period, MODE_LOW, window_m, 2));
    
    double close = iClose(_Symbol, _Period, 1);
    double high = iHigh(_Symbol, _Period, 1);
    double low = iLow(_Symbol, _Period, 1);
    double open = iOpen(_Symbol, _Period, 1);
    double tr = high - low;
    
    double body = MathAbs(close - open);
    // Simplified Vol MA for impulse
    double vol_ma = 0;
    for(int i=1; i<=vol_long; i++) vol_ma += (iHigh(_Symbol, _Period, i) - iLow(_Symbol, _Period, i));
    vol_ma /= vol_long;
    
    double impulse_rel = (vol_ma == 0) ? 0 : body / vol_ma;
    double pds = (tr == 0) ? 0 : (close - low) / tr;

    bool is_moderate_impulse = (impulse_rel > 0.7 && impulse_rel < 3.0);
    bool bull_breakout = (close > squeeze_high && pds > 0.6);
    bool bear_breakout = (close < squeeze_low && pds < 0.4);

    // Execution
    if(bull_bias && is_releasing && bull_breakout && is_moderate_impulse) {
        // OrderSend(BUY...);
    }
    if(bear_bias && is_releasing && bear_breakout && is_moderate_impulse) {
        // OrderSend(SELL...);
    }
}