深夜2時。またAIが「完璧な聖杯」を生成したと抜かしていたので、バックテストにぶち込んでみた。 結果? ゴミ。完敗。大爆死。

今回の被害者は NYVolatilityValue と名付けられた、いかにも「分かってる感」を出した戦略だ。 Pythonコードを見る限り、AIの意図はこうだ。 「NY時間のボラティリティを利用し、長期EMAでトレンドを掴み、短期EMAでの押し目(戻り)を確認してから再加速でエントリーする。リスクリワードは1:2.5で効率よく稼ぐぜ!」

……いやいや、2026年にもなってまだそんな「FX初心者向けYouTube動画」みたいなロジックを本気で組んだのか?

破綻の解説:なぜこのコードは「ただの寄付」に終わったか

まず結果を見てくれ。 プロフィットファクター (PF): 1.05

これ、実質的に「手数料とスプレッドで全部持っていかれた」ってことだ。ほぼランダムウォーク。 最大ドローダウン 0.1% という数字だけ見れば「安全」に見えるかもしれないが、これは単にエントリー回数が少なすぎて、運良く大火傷を免れただけ。期待値が死んでいる。

技術的なダメ出しポイント:

  1. 固定pipsの絶望感: TP 50pips / SL 20pips という固定値。NY時間のボラティリティを狙うと言いながら、ボラティリティに応じて変動しない固定SLを置くという矛盾。NY時間のノイズ一本で20pipsなんて余裕で吹き飛ぶ。AI、お前はATRを計算したのに、なぜそれをSLに反映させなかった? 宝の持ち腐れもいいところだ。

  2. 「EMA20を上抜けたら買い」というお花畑ロジック: close_prev <= ema_f_prev * 1.0005 で調整を確認し、close_curr > ema_f_curr でエントリー。これ、典型的な「ダマシ」の量産機だ。相場がレンジに移行した瞬間、EMAを上下に跨ぐたびにエントリーしてはSLに引っかかる地獄ループが見える。

  3. ATRフィルターが形骸化している: if atr == 0 or pd.isna(atr): return None ……は? これを「ボラティリティフィルター」と呼ぶのか? 0じゃなければOKって、それはフィルターじゃなくて単なる「生存確認」だろ。最低限の閾値を設定しろ。

結論として、このEAは**「教科書に書いてあることをそのまま実装したが、相場のダイナミズム(ノイズと流動性)を完全に無視した」**ため、手数料を支払うためだけの機械と化したわけだ。


失敗したPythonコード

[SYSTEM WARNING] 本記事のEAは実運用に耐えません

大切な資金を運用するなら、厳しいリアルフォワードテストをクリアしたプロ開発のEAを利用することを強く推奨します。
実際にリアルタイムで利益を出し続けている本物のデータを確認してください。

👉 プロが実稼働で証明済みの本物のEA(Pips_miner_EA)はこちら【超多頻度トレードによる高収益を狙うEA】 | GogoJungle
from strategies.base import BaseStrategy
import pandas as pd
import pandas_ta as ta

class NYVolatilityValue(BaseStrategy):
    """
    NYオープン時間帯 (21:00-23:00 JST) に特化したボラティリティ・バリュー戦略。
    長期トレンド方向への回帰(EMA20)を確認し、ATRでボラティリティを、
    直近高値/安値の更新で再加速を判定してエントリーする。
    """
    def __init__(self):
        # リスクリワード比を 1:2.5 に設定 (TP 50pips / SL 20pips)
        # NY時間の強いトレンド特性を活かし、1回の勝ちで2.5回分の負けをカバーする。
        super().__init__(name="NY_Volatility_Value", default_tp_pips=50.0, default_sl_pips=20.0)
        
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15m", "1h"]
        
        # パラメータ設定
        self.fast_ema_len = 20    # 短期バリューエリア
        self.slow_ema_len = 100   # 長期環境認識 (MTF: 約8時間相当)
        self.atr_len = 14         # ボラティリティ判定期間

    def calculate_indicators(self, df):
        """
        テクニカル指標の計算。
        """
        # EMAによる方向性判定
        df['ema_fast'] = ta.ema(df['Close'], length=self.fast_ema_len)
        df['ema_slow'] = ta.ema(df['Close'], length=self.slow_ema_len)
        
        # ATRによるボラティリティ算出
        df['atr'] = ta.atr(df['High'], df['Low'], df['Close'], length=self.atr_len)
        
        return df

    def generate_signal(self, df):
        """
        シグナル生成ロジック。
        【BUY条件】
        1. 時間帯が 21:00〜23:00 JST であること
        2. 価格が長期EMA100より上にあり、上昇トレンドであること
        3. ATRが一定水準以上にあり、相場に十分なボラティリティがあること
        4. 直前の足の終値が短期EMA20付近まで調整していること
        5. 最新の終値が短期EMA20を上抜け、かつ直前の足の高値を更新して再加速すること
        """
        if len(df) < self.slow_ema_len:
            return None

        # 最新の2本分を取得
        curr = df.iloc[-1]
        prev = df.iloc[-2]
        
        # 時間判定 (JST)
        current_time = curr.name if hasattr(curr.name, 'hour') else df.index[-1]
        hour = current_time.hour
        if not (21 <= hour < 23):
            return None

        # 指標の抽出
        close_curr = curr['Close']
        close_prev = prev['Close']
        high_prev = prev['High']
        low_prev = prev['Low']
        ema_f_curr = curr['ema_fast']
        ema_f_prev = prev['ema_fast']
        ema_s_curr = curr['ema_slow']
        atr = curr['atr']

        # ボラティリティフィルター: ATRが0であることや極端に低いことを排除
        if atr == 0 or pd.isna(atr):
            return None

        # --- BUY シグナル ---
        # 1. 環境認識: 長期上昇トレンド
        if close_curr > ema_s_curr:
            # 2. 調整の確認: 前回の終値がEMA20以下、または非常に近い
            if close_prev <= ema_f_prev * 1.0005:
                # 3. 再加速の確定: EMA20を上抜け、かつ前回の高値を更新
                if close_curr > ema_f_curr and close_curr > high_prev:
                    return 'BUY'

        # --- SELL シグナル ---
        # 1. 環境認識: 長期下降トレンド
        if close_curr < ema_s_curr:
            # 2. 調整の確認: 前回の終値がEMA20以上、または非常に近い
            if close_prev >= ema_f_prev * 0.9995:
                # 3. 再加速の確定: EMA20を下抜け、かつ前回の安値を更新
                if close_curr < ema_f_curr and close_curr < low_prev:
                    return 'SELL'

        return None

オマケ:MQL5変換(反面教師用)

「このゴミのようなロジックが、実際のMT5でどう惨敗するかを身をもって体験したい」というマゾヒストな方のために、MQL5に書き換えておいた。どうぞ、資金を溶かす快感に浸ってくれ。

//+------------------------------------------------------------------+
//|                                         NY_Volatility_Value.mq5 |
//|                                  Copyright 2026, OtakuEngineer   |
//|                                             https://www.example.com |
//+------------------------------------------------------------------+
#property strict

input int      FastEMALen = 20;
input int      SlowEMALen = 100;
input int      ATRLen     = 14;
input double   TP_Pips    = 50.0;
input double   SL_Pips    = 20.0;

int handleFastEMA, handleSlowEMA, handleATR;

int OnInit() {
    handleFastEMA = iMA(_Symbol, _Period, FastEMALen, 0, MODE_EMA, PRICE_CLOSE);
    handleSlowEMA = iMA(_Symbol, _Period, SlowEMALen, 0, MODE_EMA, PRICE_CLOSE);
    handleATR     = iATR(_Symbol, _Period, ATRLen);
    return(INIT_SUCCEEDED);
}

void OnTick() {
    MqlDateTime dt;
    TimeCurrent(dt);
    
    // 時間判定 (JST 21:00-23:00) -> サーバー時間への変換は適宜必要
    if(!(dt.hour >= 21 && dt.hour < 23)) return;

    double emaFast[], emaSlow[], atr[];
    MqlRates rates[];
    
    ArraySetAsSeries(emaFast, true);
    ArraySetAsSeries(emaSlow, true);
    ArraySetAsSeries(atr, true);
    ArraySetAsSeries(rates, true);

    if(CopyBuffer(handleFastEMA, 0, 0, 2, emaFast) < 2) return;
    if(CopyBuffer(handleSlowEMA, 0, 0, 2, emaSlow) < 2) return;
    if(CopyBuffer(handleATR, 0, 0, 1, atr) < 1) return;
    if(CopyRates(_Symbol, _Period, 0, 2, rates) < 2) return;

    if(atr[0] <= 0) return;

    double closeCurr = rates[0].close;
    double closePrev = rates[1].close;
    double highPrev  = rates[1].high;
    double lowPrev   = rates[1].low;

    // BUY Signal
    if(closeCurr > emaSlow[0]) {
        if(closePrev <= emaFast[1] * 1.0005) {
            if(closeCurr > emaFast[0] && closeCurr > highPrev) {
                TradeOrder(ORDER_TYPE_BUY);
            }
        }
    }
    // SELL Signal
    else if(closeCurr < emaSlow[0]) {
        if(closePrev >= emaFast[1] * 0.9995) {
            if(closeCurr < emaFast[0] && closeCurr < lowPrev) {
                TradeOrder(ORDER_TYPE_SELL);
            }
        }
    }
}

void TradeOrder(ENUM_ORDER_TYPE type) {
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    
    double price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double sl = (type == ORDER_TYPE_BUY) ? price - SL_Pips * _Point * 10 : price + SL_Pips * _Point * 10;
    double tp = (type == ORDER_TYPE_BUY) ? price + TP_Pips * _Point * 10 : price - TP_Pips * _Point * 10;

    request.action = TRADE_ACTION_DEAL;
    request.symbol = _Symbol;
    request.volume = 0.1;
    request.type = type;
    request.price = price;
    request.sl = sl;
    request.tp = tp;
    request.magic = 123456;
    
    OrderSend(request, result);
}