こんにちは。投資の世界へようこそ! 皆さんは「教科書通りの手法を組み合わせれば勝てる」と思っていませんか?……あぁ、もう。AIにそれをやらせたらどうなるか、今回の事例が残酷なまでに証明してくれました。
今回紹介するのは、AIが自信満々に書き上げた**「NYバリューゾーン・バウンス戦略」**。 名前だけは立派ですが、結果は悲惨な「ボツ」となりました。
そもそも、AIは何をしようとしたのか?
このEAの意図は非常にシンプル(かつ、いかにもAIらしい)ものです。 Pythonコードから読み取れるロジックは以下の通り。
- 環境認識: EMA20 > 50 > 100 という「パーフェクトオーダー」が発生している強いトレンド状態のみを狙う。
- 待ち伏せ: 価格が一時的に短期EMAを割り込み、中期EMA付近(いわゆるバリューゾーン)まで押し戻されるのを待つ。
- トリガー: 押し目から反発し、直近の数本の高値を更新して「陽線」で確定した瞬間にエントリー。
- 時間制限: 最もボラティリティが出るニューヨーク市場オープン時(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が書き残した、あまりにナイーブなコードを公開します。研究用としてどうぞ。
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);
}