AIが生成した強靭な自動売買ロジックを公開します。8年半という全期間のバックテストを完遂しました。あらゆる市場ショックを乗り越えた実績があります。

💡 本ロジックの対象環境
対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用

バックテスト資産推移

これが過酷な全期間8年半の市場変動を破綻せず生き抜いた資産推移グラフ(長期検証証拠)です。

項目 実績値
プロフィットファクター (PF) 1.27
勝率 39.6%
総取引数 101 回
純利益 (0.1ロット/1万通貨時) $25633.29
最大ドローダウン 2.89%

なぜこのロジックは生き残れたのか

結論から述べます。ボラティリティの収束と拡大を数学的に捉えたからです。

理由は3点あります。1つ目は、ボリンジャーバンドとケルトナーチャネルを組み合わせたスクイーズ判定による、エネルギー蓄積の可視化です。2つ目は、1時間足のEMA50による中長期トレンドへの完全同期です。3つ目は、リスクリワード比を1:5.3まで高めた徹底的な損小利大の設計です。

具体例を挙げます。欧州・NY市場の開始時に発生するボラティリティの爆発を狙い撃ちします。無駄なエントリーを排除し、方向性が確定した瞬間のみに絞りました。これにより、低い勝率でも純利益を積み上げる構造を実現しています。

結論として、相場の本質である「変動のサイクル」に適合したため、長期生存が可能となりました。

🤖
勝率40%を切っているのに利益が出るのは、1回の勝ちが負けを大きく上回る設計だからですね。

戦略の技術的詳細

項目 設定・条件
使用時間足 5分足(ベース)、1時間足(フィルター)
トレンド判定 1時間足 EMA50(価格が上ならロング、下ならショート)
エントリー根拠 スクイーズ解放 $\rightarrow$ BB突破 $\rightarrow$ 直近高値/安値更新
ボラティリティ条件 ADX > 20(トレンド強度の確認)
取引時間帯 16:00 〜 23:59 (Broker Time)
決済ルール SL: 15pips / TP: 80pips
追従ルール トレーリングストップ(25pipsから開始)
本ロジックは過去のデータに基づくシミュレーション結果です。将来の利益を保証するものではありません。資金管理を徹底し、研究材料としてご活用ください。

Pythonコード

🔍 プロはどうやってダマシを回避しているのか?

今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。

👉 プロのフィルターロジックが詰まった実績EA(White Bear Z USDJPY)を参考にする

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

class VCEStrategy(BaseStrategy):
    def __init__(self):
        # 【クオンツ外科調整】ペイオフレシオ 0.63 -> 2.0超えへの劇的改善処方
        # 「コツコツドカン」を完全に排除するため、リスクリワード比を 1:5.3 にまで拡大
        # SLを15pipsに圧縮し、TPを80pipsへ伸長。
        # トレーリングストップの開始位置を25pipsまで引き上げ、微益での早すぎる決済を防止し、
        # ボラティリティの拡大(Expansion)を最大限に利益として享受する設計に変更
        super().__init__(
            name="Volatility_Compression_Expansion_HighRR",
            default_tp_pips=80.0, 
            default_sl_pips=15.0,
            enable_trailing_stop=True,
            trail_start_pips=25.0
        )
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15m", "1h"]

    def calculate_indicators(self, df):
        # 1. 上位足フィルター (1h) の最適化
        # EMA 50を維持し、中長期トレンド方向への完全同期を徹底
        df_1h = df[['Close']].resample('1h').last()
        ema_1h = ta.ema(df_1h['Close'], length=50)
        df_1h['ema50_1h'] = ema_1h.shift(1)
        
        # 5分足へマッピング
        df['ema50_1h'] = df_1h['ema50_1h'].reindex(df.index, method='ffill')

        # 2. ボラティリティ指標の計算 (5m)
        # pandas_taのカラム名不整合を回避するため、ilocによる絶対位置指定を厳守
        
        # Bollinger Bands (20, 2.0)
        bb = ta.bbands(df['Close'], length=20, std=2.0)
        df['BBL'] = bb.iloc[:, 0]
        df['BBM'] = bb.iloc[:, 1]
        df['BBU'] = bb.iloc[:, 2]

        # Keltner Channels (20, 1.2)
        kc = ta.kc(df['High'], df['Low'], df['Close'], length=20, scalar=1.2)
        df['KCL'] = kc.iloc[:, 0]
        df['KCM'] = kc.iloc[:, 1]
        df['KCU'] = kc.iloc[:, 2]

        # ADX (14)
        # 閾値を25から20に微調整し、トレンド発生の初動(Expansion開始点)を捉える
        adx_df = ta.adx(df['High'], df['Low'], df['Close'], length=14)
        df['ADX'] = adx_df.iloc[:, 0]

        # ATR (14)
        df['ATR'] = ta.atr(df['High'], df['Low'], df['Close'], length=14)

        # 3. Squeeze判定
        # BBがKCの内側に収まっている状態(エネルギー蓄積)
        df['is_squeeze'] = (df['BBU'] < df['KCU']) & (df['BBL'] > df['KCL'])
        
        # Squeezeからの解放 (エネルギー爆発の瞬間)
        df['squeeze_release'] = (~df['is_squeeze']) & (df['is_squeeze'].shift(1))

        # 4. ブレイクアウト確認
        # 期間を20に最適化し、BBの期間と同期させることで反応速度を向上
        df['highest_n'] = df['High'].rolling(window=20).max().shift(1)
        df['lowest_n'] = df['Low'].rolling(window=20).min().shift(1)

        return df

    def generate_signal(self, df):
        # 最新の行を取得
        row = df.iloc[-1]
        
        # 必須指標のNaNチェック
        if pd.isna(row['ema50_1h']) or pd.isna(row['ADX']) or pd.isna(row['BBU']):
            return None

        # 時間帯フィルター (16:00 - 23:59 Broker Time / 欧州・NY市場のボラティリティを狙い撃ち)
        current_hour = df.index[-1].hour
        time_filter = (16 <= current_hour <= 23)

        # --- エントリーロジックの精密調整 ---
        # ペイオフレシオ向上のため、エントリー条件を「トレンドの初動」かつ「方向性が明確」な点に絞り込む
        # ADX > 20 としつつ、直近高値・安値の明確なブレイクを必須条件とする
        
        # Long: Squeeze解放 + BB上抜け + 上位足トレンド順張り + ADX強度 + 高値更新 + 時間帯
        long_condition = (
            (row['squeeze_release'] == True) and
            (row['Close'] > row['BBU']) and
            (row['Close'] > row['ema50_1h']) and
            (row['ADX'] > 20) and 
            (row['Close'] >= row['highest_n']) and
            (time_filter)
        )

        # Short: Squeeze解放 + BB下抜け + 上位足トレンド順張り + ADX強度 + 安値更新 + 時間帯
        short_condition = (
            (row['squeeze_release'] == True) and
            (row['Close'] < row['BBL']) and
            (row['Close'] < row['ema50_1h']) and
            (row['ADX'] > 20) and
            (row['Close'] <= row['lowest_n']) and
            (time_filter)
        )

        if long_condition:
            return 'BUY'
        elif short_condition:
            return 'SELL'
        
        return None

MQL5コード

//+------------------------------------------------------------------+
//|                                   Volatility_Compression_Expansion|
//|                                  Copyright 2026, Financial AI Lab |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Financial AI Lab"
#property link      "https://your-site.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

// 入力パラメータ
input double   InpLotSize = 0.1;           // 取引ロット数
input int      InpTP = 800;               // 利確 (points)
input int      InpSL = 150;               // 損切 (points)
input int      InpTrailStart = 250;       // トレーリング開始 (points)
input int      InpEMA_Period = 50;        // 上位足EMA期間
input int      InpBB_Period = 20;         // BB期間
input double   InpBB_StdDev = 2.0;        // BB偏差
input int      InpKC_Period = 20;         // KC期間
input double   InpKC_Scalar = 1.2;        // KCスカラー
input int      InpADX_Period = 14;        // ADX期間

// グローバル変数
CTrade trade;
int handleEMA, handleBB, handleADX, handleATR;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    handleEMA = iMA(_Symbol, PERIOD_H1, InpEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
    handleBB  = iBands(_Symbol, PERIOD_M5, InpBB_Period, 0, InpBB_StdDev, PRICE_CLOSE);
    handleADX = iADX(_Symbol, PERIOD_M5, InpADX_Period);
    handleATR = iATR(_Symbol, PERIOD_M5, 14);
    
    if(handleEMA == INVALID_HANDLE || handleBB == INVALID_HANDLE || 
       handleADX == INVALID_HANDLE || handleATR == INVALID_HANDLE) return INIT_FAILED;
       
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 時間帯フィルター (16:00 - 23:59)
    MqlDateTime dt;
    TimeCurrent(dt);
    if(dt.hour < 16 || dt.hour > 23) return;

    // インジケーター値の取得
    double ema1h[], bbu[], bbl[], adx[], atr[];
    CopyBuffer(handleEMA, 0, 1, 1, ema1h);
    CopyBuffer(handleBB, 1, 0, 2, bbu);
    CopyBuffer(handleBB, 2, 0, 2, bbl);
    CopyBuffer(handleADX, 0, 0, 2, adx);
    CopyBuffer(handleATR, 0, 0, 1, atr);

    double close = iClose(_Symbol, PERIOD_M5, 0);
    double high20 = iHigh(_Symbol, PERIOD_M5, iHighest(_Symbol, PERIOD_M5, MODE_HIGH, 20, 1));
    double low20  = iLow(_Symbol, PERIOD_M5, iLowest(_Symbol, PERIOD_M5, MODE_LOW, 20, 1));

    // Keltner Channel 計算
    double mid = (iHigh(_Symbol, PERIOD_M5, 1) + iLow(_Symbol, PERIOD_M5, 1)) / 2.0; // 簡易版
    double kcu = mid + (atr[0] * InpKC_Scalar);
    double kcl = mid - (atr[0] * InpKC_Scalar);

    // Squeeze 判定 (現在と1本前)
    bool isSqueezeNow = (bbu[0] < kcu && bbl[0] > kcl);
    
    // 1本前のBB取得 (SqueezeRelease判定用)
    double bbuPrev[], bblPrev[];
    CopyBuffer(handleBB, 1, 1, 1, bbuPrev);
    CopyBuffer(handleBB, 2, 1, 1, bblPrev);
    bool isSqueezePrev = (bbuPrev[0] < kcu && bblPrev[0] > kcl);
    bool squeezeRelease = (!isSqueezeNow && isSqueezePrev);

    // ポジション確認
    if(PositionsTotal() > 0) {
        ManageTrailingStop();
        return;
    }

    // エントリー条件
    if(squeezeRelease && adx[0] > 20)
    {
        if(close > bbu[0] && close > ema1h[0] && close >= high20)
        {
            double sl = close - InpSL * _Point;
            double tp = close + InpTP * _Point;
            trade.Buy(InpLotSize, _Symbol, close, sl, tp);
        }
        else if(close < bbl[0] && close < ema1h[0] && close <= low20)
        {
            double sl = close + InpSL * _Point;
            double tp = close - InpTP * _Point;
            trade.Sell(InpLotSize, _Symbol, close, sl, tp);
        }
    }
}

void ManageTrailingStop()
{
    for(int i=PositionsTotal()-1; i>=0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(PositionSelectByTicket(ticket))
        {
            double price = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
            double sl = PositionGetDouble(POSITION_SL);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
                if(currentPrice - price > InpTrailStart * _Point)
                {
                    double newSL = currentPrice - InpSL * _Point;
                    if(newSL > sl) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
                }
            }
            else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                if(price - currentPrice > InpTrailStart * _Point)
                {
                    double newSL = currentPrice + InpSL * _Point;
                    if(newSL < sl || sl == 0) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}