こんにちは。投資の世界へようこそ! 皆さんは「教科書通りの手法を組み合わせれば勝てる」と思っていませんか?……あぁ、もう。AIにそれをやらせたらどうなるか、今回の事例が残酷なまでに証明してくれました。

今回紹介するのは、AIが自信満々に書き上げた**「NYバリューゾーン・バウンス戦略」**。 名前だけは立派ですが、結果は悲惨な「ボツ」となりました。

そもそも、AIは何をしようとしたのか?

このEAの意図は非常にシンプル(かつ、いかにもAIらしい)ものです。 Pythonコードから読み取れるロジックは以下の通り。

  1. 環境認識: EMA20 > 50 > 100 という「パーフェクトオーダー」が発生している強いトレンド状態のみを狙う。
  2. 待ち伏せ: 価格が一時的に短期EMAを割り込み、中期EMA付近(いわゆるバリューゾーン)まで押し戻されるのを待つ。
  3. トリガー: 押し目から反発し、直近の数本の高値を更新して「陽線」で確定した瞬間にエントリー。
  4. 時間制限: 最もボラティリティが出るニューヨーク市場オープン時(21:00〜23:00)に限定。

「トレンド方向に、安く買って、反発を確認して入る」。 至極真っ当なロジックに見えますよね? 初心者の方なら「これなら勝てるのでは?」と思うかもしれません。

なぜ、このロジックは破綻したのか?

結果を見てみましょう。プロフィットファクター(PF)は 0.92。 つまり、100円稼ごうとして108円失う計算です。投資としては「不合格」どころか、「やってはいけない」レベルです。

なぜ、こんなに綺麗なロジックが通用しなかったのか。技術的な視点から、AIの甘さを解説しますね。

1. 「NY時間の暴力」をなめていた

AIは「NYオープン=チャンス」と考えましたが、現実は「NYオープン=ノイズの嵐」です。 この時間帯は急激な価格変動が起きるため、EMAのような遅行指標に基づいた「押し目待ち」をしている間に、価格がバリューゾーンを突き抜けてトレンド転換したり、激しい上下の振幅(ダマシ)に巻き込まれたりします。

2. 固定ピップス設定の限界

TP(利確)30pips / SL(損切)15pips という固定設定。これが致命的です。 相場のボラティリティは日々変わります。NY時間の激しい動きの中では、15pipsの損切りは「ただのノイズ」で狩られるほど浅すぎます。一方で、トレンドが出ている時に30pipsで利確してしまうのは、利益を早々に切り捨てているだけです。

3. 「構造的ブレイク」の定義が浅すぎる

コードにある last_row['Close'] > recent_high という条件。直近2本の高値を超えただけで「反発した!」と判断していますが、これはあまりにも単純すぎます。 大口投資家のストップロスを巻き込んだ一瞬の跳ね上がり(ダマシ)に反応してエントリーし、そのまま元の方向に突き落とされる……という展開が目に浮かびますね。

結論:教科書的な手法を機械的に組み合わせただけでは、現代の複雑な相場(特に激動のNY時間)では通用しないということです。


失敗したPythonコード

AIが書き残した、あまりにナイーブなコードを公開します。研究用としてどうぞ。

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

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

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

class NYValueZoneBounceStrategy(BaseStrategy):
    """
    ニューヨークオープン時間帯のバリューゾーン・バウンス戦略。
    EMA20 > EMA50 > EMA100 のパーフェクトオーダーを確認し、
    価格がEMA20〜EMA50の「バリューゾーン」まで引きつけられた後の
    反発(構造的ブレイク)のみを狙い撃つ。
    """
    def __init__(self):
        # リスクリワード比を 2:1 (TP 30 / SL 15) に設定し、PFの底上げを狙う
        super().__init__(name="NY_ValueZone_Bounce", default_tp_pips=30.0, default_sl_pips=15.0)
        
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15m", "1h"]
        
        # パラメータ
        self.ema_short_window = 20    # 短期EMA
        self.ema_mid_window = 50     # 中期EMA(バリューゾーン境界)
        self.ema_long_window = 100    # 長期EMA(環境認識)

    def calculate_indicators(self, df):
        """
        テクニカル指標の計算
        """
        # 指数平滑移動平均線 (EMA) の3本使い
        df['ema_short'] = df['Close'].ewm(span=self.ema_short_window, adjust=False).mean()
        df['ema_mid'] = df['Close'].ewm(span=self.ema_mid_window, adjust=False).mean()
        df['ema_long'] = df['Close'].ewm(span=self.ema_long_window, adjust=False).mean()
        
        # ローソク足の実体サイズ(ノイズ除去用)
        df['body_size'] = (df['Close'] - df['Open']).abs()
        df['avg_body'] = df['body_size'].rolling(window=20).mean()
        
        return df

    def generate_signal(self, df):
        """
        バリューゾーンへの回帰と反発を確認してシグナルを生成
        """
        if len(df) < self.ema_long_window + 1:
            return None

        # 最新の確定足と過去の足を取得
        last_row = df.iloc[-1]
        prev_row = df.iloc[-2]
        df_recent = df.tail(3)
        
        # 【エントリー時間帯の限定】
        # ニューヨークオープン:21:00 〜 23:00 (JST)
        current_hour = last_row.name.hour if hasattr(last_row.name, 'hour') else df.index[-1].hour
        if not (21 <= current_hour < 23):
            return None

        # --- 1. 環境認識(パーフェクトオーダーの確認) ---
        # 強気:EMA20 > EMA50 > EMA100
        is_bullish_perfect = (last_row['ema_short'] > last_row['ema_mid']) and \
                             (last_row['ema_mid'] > last_row['ema_long'])
        # 弱気:EMA20 < EMA50 < EMA100
        is_bearish_perfect = (last_row['ema_short'] < last_row['ema_mid']) and \
                             (last_row['ema_mid'] < last_row['ema_long'])

        # --- 2. バリューゾーン判定(押し目・戻りの深さ) ---
        # BUYの場合:価格が短期EMAを割り込み、中期EMA付近まで引きつけられているか
        in_bullish_value_zone = (prev_row['Close'] < prev_row['ema_short']) and \
                                (prev_row['Close'] > prev_row['ema_long'])
        # SELLの場合:価格が短期EMAを上回り、中期EMA付近まで引きつけられているか
        in_bearish_value_zone = (prev_row['Close'] > prev_row['ema_short']) and \
                                (prev_row['Close'] < prev_row['ema_long'])

        # --- 3. 反発の確定(構造的ブレイク) ---
        # 実体が平均的なサイズ以上であることを確認
        has_momentum = last_row['body_size'] > (last_row['avg_body'] * 0.6)

        # BUY条件:
        # - 強力な上昇トレンド(パーフェクトオーダー)
        # - バリューゾーンまで十分に押し目を作った
        # - 最新足が直近2本の高値を上抜き、かつ陽線で確定した
        if is_bullish_perfect and in_bullish_value_zone:
            recent_high = df_recent.iloc[:-1]['High'].max()
            if last_row['Close'] > recent_high and \
               last_row['Close'] > last_row['Open'] and \
               has_momentum:
                return 'BUY'

        # SELL条件:
        # - 強力な下降トレンド(パーフェクトオーダー)
        # - バリューゾーンまで十分に戻りを作った
        # - 最新足が直近2本の安値を下抜き、かつ陰線で確定した
        if is_bearish_perfect and in_bearish_value_zone:
            recent_low = df_recent.iloc[:-1]['Low'].min()
            if last_row['Close'] < recent_low and \
               last_row['Close'] < last_row['Open'] and \
               has_momentum:
                return 'SELL'

        return None

オマケ:反面教師としてMT5でも試したい物好きな方へ

「いや、やっぱりこの絶望感をMT5で体感したい」という物好きな方のために、上記のロジックをMQL5に書き換えました。どうぞ、ご自身のデモ口座で「AIの敗北」を追体験してください。

//+------------------------------------------------------------------+
//|                                      NY_ValueZone_Bounce_Fail.mq5|
//|                                  Copyright 2026, AI Failure Lab  |
//+------------------------------------------------------------------+
#property strict

input int      EMA_Short = 20;
input int      EMA_Mid = 50;
input int      EMA_Long = 100;
input double   TP_Pips = 30.0;
input double   SL_Pips = 15.0;
input int      StartHour = 21;
input int      EndHour = 23;

int handleShort, handleMid, handleLong;

int OnInit() {
    handleShort = iMA(_Symbol, _Period, EMA_Short, 0, MODE_EMA, PRICE_CLOSE);
    handleMid = iMA(_Symbol, _Period, EMA_Mid, 0, MODE_EMA, PRICE_CLOSE);
    handleLong = iMA(_Symbol, _Period, EMA_Long, 0, MODE_EMA, PRICE_CLOSE);
    return(INIT_SUCCEEDED);
}

void OnTick() {
    MqlDateTime dt;
    TimeCurrent(dt);
    if(dt.hour < StartHour || dt.hour >= EndHour) return;

    double emaS[], emaM[], emaL[];
    CopyBuffer(handleShort, 0, 0, 3, emaS);
    CopyBuffer(handleMid, 0, 0, 3, emaM);
    CopyBuffer(handleLong, 0, 0, 3, emaL);
    
    MqlRates rates[];
    CopyRates(_Symbol, _Period, 0, 3, rates);
    
    ArraySetAsSeries(emaS, true);
    ArraySetAsSeries(emaM, true);
    ArraySetAsSeries(emaL, true);
    ArraySetAsSeries(rates, true);

    bool bullishPerfect = (emaS[0] > emaM[0]) && (emaM[0] > emaL[0]);
    bool bearishPerfect = (emaS[0] < emaM[0]) && (emaM[0] < emaL[0]);
    
    bool inBullishZone = (rates[1].close < emaS[1]) && (rates[1].close > emaL[1]);
    bool inBearishZone = (rates[1].close > emaS[1]) && (rates[1].close < emaL[1]);
    
    double high2 = MathMax(rates[1].high, rates[2].high);
    double low2 = MathMin(rates[1].low, rates[2].low);

    if(bullishPerfect && inBullishZone && rates[0].close > high2 && rates[0].close > rates[0].open) {
        TradeBuy();
    } else if(bearishPerfect && inBearishZone && rates[0].close < low2 && rates[0].close < rates[0].open) {
        TradeSell();
    }
}

void TradeBuy() {
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    request.action = TRADE_ACTION_DEAL;
    request.symbol = _Symbol;
    request.volume = 0.1;
    request.type = ORDER_TYPE_BUY;
    request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    request.sl = request.price - SL_Pips * _Point * 10;
    request.tp = request.price + TP_Pips * _Point * 10;
    OrderSend(request, result);
}

void TradeSell() {
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    request.action = TRADE_ACTION_DEAL;
    request.symbol = _Symbol;
    request.volume = 0.1;
    request.type = ORDER_TYPE_SELL;
    request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    request.sl = request.price + SL_Pips * _Point * 10;
    request.tp = request.price - TP_Pips * _Point * 10;
    OrderSend(request, result);
}