直近2年間の激しい金利変動を乗り越えたロジックを公開します。AIが導き出した、極めて低リスクな設計です。

これが直近2年間を生き抜いた資産推移の証拠です。
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.27 |
| 勝率 | 43.7% |
| 総取引数 | 71回 |
| 純利益 | $139.88 |
| 最大ドローダウン | 1.26% |
なぜこのロジックは生き残れたのか
結論から述べます。相場環境の厳格なフィルタリングを徹底したからです。
理由は3点あります。1つ目は、上位足のADXで強いトレンドを排除したこと。2つ目は、RSIとストキャスティクスを組み合わせた高精度の反転判定。3つ目は、ボラティリティが安定する時間帯に限定して取引したことです。
具体例を挙げます。1時間足のADXが30を超えたときは、エントリーを完全に停止します。これにより、トレンドに逆らう危険な逆張り注文を回避しました。
結論として、取引回数を絞り、勝率よりも「負けない局面」を優先した設計が功を奏したといえます。
少ない取引数で最大DDを抑える。これがプロの設計思想です。
戦略の技術的詳細
このロジックは、平均回帰(ミーンリバージョン)をベースとしています。
| 項目 | 設定・条件 |
|---|---|
| 使用時間足 | 5分足(メイン)、1時間足(環境認識) |
| ボリンジャーバンド | 期間20 / 偏差2.0 |
| RSI | 期間14 / 境界値 35以下(買)・65以上(売) |
| ストキャスティクス | 14, 3, 3 / 25以下でGC(買)・75以上でDC(売) |
| ADXフィルター | 1時間足 < 30 かつ 5分足 < 25 |
| 取引時間 | 日本時間 16:00 〜 21:00 |
| リスク管理 | TP 40pips / SL 20pips / トレーリングストップ有効 |
ここがポイント
単なるインジケーターの組み合わせではなく、時間軸を分けた「階層的なフィルタリング」を実装している点にあります。
公開Pythonコード
AIが生成した、バックテスト合格済みのソースコードです。
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
class MeanReversionStrategy(BaseStrategy):
def __init__(self):
# トレーリングストップを有効にし、利益を最大限に伸ばしつつリスクを限定
super().__init__(
name="Robust_MeanReversion_SweetSpot",
default_tp_pips=40.0,
default_sl_pips=20.0,
enable_trailing_stop=True,
trail_start_pips=10.0
)
# デイトレ・スキャルピング特化の時間足
self.base_timeframe = "5m"
# AI解析用時間足
self.vision_timeframes = ["5m", "15min", "1h"]
def _safe_extract(self, res, col_idx=0):
"""
戻り値がDataFrameの場合は指定したインデックスの列を抽出し、
Seriesの場合はそのまま返すヘルパー関数
"""
if isinstance(res, pd.DataFrame):
return res.iloc[:, col_idx]
return res
def calculate_indicators(self, df):
"""
テクニカル指標の計算(型安全な抽出とMTF分析)
"""
# --- Bollinger Bands (20, 2.0) ---
bbands = df.ta.bbands(length=20, std=2.0)
df['BBL'] = self._safe_extract(bbands, 0)
df['BBM'] = self._safe_extract(bbands, 1)
df['BBU'] = self._safe_extract(bbands, 2)
# --- RSI (14) ---
rsi_res = df.ta.rsi(length=14)
df['RSI'] = self._safe_extract(rsi_res)
# --- Stochastic (14, 3, 3) ---
stoch_res = df.ta.stoch(high='High', low='Low', close='Close', length=14, k=3, d=3)
df['STOCHk'] = self._safe_extract(stoch_res, 0)
df['STOCHd'] = self._safe_extract(stoch_res, 1)
# --- ADX (14) - 5min ---
adx_res = df.ta.adx(length=14)
df['ADX'] = self._safe_extract(adx_res, 0)
# --- Multi-Timeframe Analysis (MTF) ---
# ルックアヘッドバイアスを完全に排除するため .shift(1) を適用
# 1時間足のADX: 大トレンドの有無を判定
df_1h = df.resample('1h').agg({
'High': 'max',
'Low': 'min',
'Close': 'last'
}).shift(1)
adx_1h_res = ta.adx(df_1h['High'], df_1h['Low'], df_1h['Close'], length=14)
df['ADX_1h'] = self._safe_extract(adx_1h_res, 0).reindex(df.index, method='ffill')
return df
def generate_signal(self, df):
"""
最新の行に基づいたエントリーシグナルの生成
"""
if len(df) < 30:
return None
row = df.iloc[-1]
# --- 時間帯フィルター (16:00 - 21:00 JST) ---
# ボラティリティが高く、かつ反転が起きやすい時間帯に限定
hour = row.name.hour if hasattr(row.name, 'hour') else df.index[-1].hour
is_active_window = 16 <= hour < 21
if not is_active_window:
return None
# --- 相場環境フィルター (階層的なレンジ判定) ---
# 上位足(1h)で強すぎるトレンドが出ておらず、かつ下位足(5m)でレンジ状態であること
is_range_market = (
row['ADX_1h'] < 30 and
row['ADX'] < 25
)
if not is_range_market:
return None
# --- エントリーロジック (質重視のAND条件) ---
# BUY Signal
# 1. 価格がBB下限を下回っている AND RSIが35以下(統計的な売られすぎ)
# 2. ストキャスティクスが「売られすぎ圏(20以下)」でゴールデンクロス(反転のトリガー)
buy_condition = (
(row['Close'] < row['BBL'] and row['RSI'] < 35) and
(row['STOCHk'] > row['STOCHd'] and row['STOCHk'] < 25)
)
# SELL Signal
# 1. 価格がBB上限を上回っている AND RSIが65以上(統計的な買われすぎ)
# 2. ストキャスティクスが「買われすぎ圏(80以上)」でデッドクロス(反転のトリガー)
sell_condition = (
(row['Close'] > row['BBU'] and row['RSI'] > 65) and
(row['STOCHk'] < row['STOCHd'] and row['STOCHk'] > 75)
)
if buy_condition:
return 'BUY'
elif sell_condition:
return 'SELL'
return None
MT5用 MQL5コードへの翻訳
Pythonロジックを忠実に再現したMQL5コードです。そのままコンパイルして利用できます。
//+------------------------------------------------------------------+
//| Robust_MeanReversion_SweetSpot.mq5|
//| Copyright 2026, AI Researcher |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, AI Researcher"
#property link ""
#property version "1.00"
#property strict
// 入力パラメータ
input int InpBBPeriod = 20; // Bollinger Bands Period
input double InpBBDev = 2.0; // Bollinger Bands Deviation
input int InpRSIPeriod = 14; // RSI Period
input int InpStochK = 14; // Stochastic K Period
input int InpStochD = 3; // Stochastic D Period
input int InpStochSlowing = 3; // Stochastic Slowing
input int InpADXPeriod = 14; // ADX Period
input double InpLotSize = 0.1; // Lot Size
input double InpTP = 400; // Take Profit (points)
input double InpSL = 200; // Stop Loss (points)
input double InpTrailStart = 100; // Trailing Stop Start (points)
// ハンドル
int handleBB, handleRSI, handleStoch, handleADX_5m, handleADX_1h;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handleBB = iBands(_Symbol, PERIOD_M5, InpBBPeriod, 0, InpBBDev, PRICE_CLOSE);
handleRSI = iRSI(_Symbol, PERIOD_M5, InpRSIPeriod, PRICE_CLOSE);
handleStoch = iStochastic(_Symbol, PERIOD_M5, InpStochK, InpStochD, InpStochSlowing, MODE_SMA, STO_LOWHIGH);
handleADX_5m = iADX(_Symbol, PERIOD_M5, InpADXPeriod);
handleADX_1h = iADX(_Symbol, PERIOD_H1, InpADXPeriod);
if(handleBB == INVALID_HANDLE || handleRSI == INVALID_HANDLE ||
handleStoch == INVALID_HANDLE || handleADX_5m == INVALID_HANDLE || handleADX_1h == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 時間帯フィルター (16:00 - 21:00 JST)
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(dt.hour < 16 || dt.hour >= 21) return;
// インジケーター値の取得
double bbLower[], bbUpper[], rsi[], stochK[], stochD[], adx5[], adx1h[];
ArraySetAsSeries(bbLower, true); ArraySetAsSeries(bbUpper, true);
ArraySetAsSeries(rsi, true); ArraySetAsSeries(stochK, true);
ArraySetAsSeries(stochD, true); ArraySetAsSeries(adx5, true);
ArraySetAsSeries(adx1h, true);
if(CopyBuffer(handleBB, 2, 0, 2, bbLower) <= 0) return;
if(CopyBuffer(handleBB, 1, 0, 2, bbUpper) <= 0) return; // Simplified for BB
// Note: iBands buffer 0 is Middle, 1 is Upper, 2 is Lower
double upper = bbUpper[0];
double lower = bbLower[0];
// Correcting indices for BB
CopyBuffer(handleBB, 1, 0, 2, bbUpper);
CopyBuffer(handleBB, 2, 0, 2, bbLower);
if(CopyBuffer(handleRSI, 0, 0, 2, rsi) <= 0) return;
if(CopyBuffer(handleStoch, 0, 0, 2, stochK) <= 0) return;
if(CopyBuffer(handleStoch, 1, 0, 2, stochD) <= 0) return;
if(CopyBuffer(handleADX_5m, 0, 0, 2, adx5) <= 0) return;
if(CopyBuffer(handleADX_1h, 0, 0, 2, adx1h) <= 0) return;
double close = iClose(_Symbol, PERIOD_M5, 0);
// 相場環境フィルター
if(adx1h[0] >= 30 || adx5[0] >= 25) return;
// ポジション確認
if(PositionsTotal() > 0)
{
ManageTrailingStop();
return;
}
// エントリー条件
bool buySignal = (close < bbLower[0] && rsi[0] < 35) && (stochK[0] > stochD[0] && stochK[0] < 25);
bool sellSignal = (close > bbUpper[0] && rsi[0] > 65) && (stochK[0] < stochD[0] && stochK[0] > 75);
if(buySignal) TradeOpen(ORDER_TYPE_BUY);
else if(sellSignal) TradeOpen(ORDER_TYPE_SELL);
}
//+------------------------------------------------------------------+
//| Order Open Function |
//+------------------------------------------------------------------+
void TradeOpen(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 - InpSL * _Point : price + InpSL * _Point;
double tp = (type == ORDER_TYPE_BUY) ? price + InpTP * _Point : price - InpTP * _Point;
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = InpLotSize;
request.type = type;
request.price = price;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.magic = 123456;
request.type_filling = ORDER_FILLING_IOC;
OrderSend(request, result);
}
//+------------------------------------------------------------------+
//| Trailing Stop Management |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double currentSL = PositionGetDouble(POSITION_SL);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
if(currentPrice - openPrice > InpTrailStart * _Point)
{
double newSL = currentPrice - InpTrailStart * _Point;
if(newSL > currentSL) ModifySL(ticket, newSL);
}
}
else
{
if(openPrice - currentPrice > InpTrailStart * _Point)
{
double newSL = currentPrice + InpTrailStart * _Point;
if(newSL < currentSL || currentSL == 0) ModifySL(ticket, newSL);
}
}
}
}
}
void ModifySL(ulong ticket, double newSL)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_SLTP;
request.position = ticket;
request.sl = newSL;
OrderSend(request, result);
}