直近2年間の激しい金利変動相場を勝ち抜いた優秀なEAロジックを公開します。

これが直近2年間を生き抜いた資産推移の証拠です。(※本ロジックは 5分足 専用です)
| 項目 | 実績値 |
|---|---|
| プロフィットファクター (PF) | 1.22 |
| 勝率 | 47.2% |
| 総取引数 | 53 回 |
| 純利益 (0.1ロット/1万通貨時) | $92.10 |
| 最大ドローダウン | 1.78% |
なぜこのロジックは生き残れたのか
結論から述べます。ボラティリティの蓄積と爆発を正確に捉えたからです。
理由は3点あります。1つ目はスクイーズ判定の導入です。ボリンジャーバンドがケルトナーチャネルの内側に収まる状態を検知し、エネルギーの蓄積を判断します。2つ目はマルチタイムフレーム分析の採用です。15分足と1時間足のトレンド方向へのみエントリーを限定しました。3つ目は時間帯フィルターの適用です。流動性が極めて高いロンドン・NY時間のみに取引を絞っています。
具体例を挙げます。欧州時間の開始直後にボラティリティが急拡大した際、トレンド方向へのブレイクアウトのみを抽出してエントリーします。これにより、レンジ相場での往復ビンタを完全に回避できました。
結論として、不要な取引を徹底的に排除した設計が、低ドローダウンでの生存を可能にしました。
取引回数が少ないのは、それだけ厳選したポイントでしか撃っていない証拠ですね。
戦略の技術的詳細
| 要素 | 設定・条件 | 役割 |
|---|---|---|
| ベース時間足 | 5分足 | エントリータイミングの決定 |
| 環境認識足 | 15分足(EMA10) / 1時間足(EMA50) | 上位足のトレンド方向の判定 |
| スクイーズ判定 | BB(20, 2.0) $\subset$ KC(20, 2.0) | 相場のエネルギー蓄積を検知 |
| ブレイク判定 | BBがKCを突き抜け、直近10本高安値を更新 | ボラティリティ爆発の検知 |
| モメンタム | MACDヒストグラムの加速 $\text{and}$ ADX上昇 | トレンドの勢いを確認 |
| 時間フィルター | 16-18時 / 21-24時 (サーバー時間) | 高流動性時間帯への限定 |
最大ドローダウンを1.78%という極めて低い水準に抑えつつ、純利益を積み上げている点がこのロジックの最大の強みです。
Pythonコード
🔍 プロはどうやってダマシを回避しているのか?
今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。
from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
import numpy as np
class VolatilityBreakoutStrategy(BaseStrategy):
def __init__(self):
# 親クラスの初期化: 期待値を高めるため、TPを少し伸ばし、トレーリングストップで利益を確保
super().__init__(
name="Alpha_Squeeze_Expansion_V4",
default_tp_pips=60.0,
default_sl_pips=20.0,
enable_trailing_stop=True,
trail_start_pips=15.0
)
self.base_timeframe = "5min"
self.vision_timeframes = ["5min", "15min", "1h"]
def calculate_indicators(self, df):
"""
ボラティリティの収縮(Squeeze)と拡散(Expansion)を検知するロジック
"""
# --- 5分足 (ベース) ---
# ボリンジャーバンド (20, 2.0)
bb = df.ta.bbands(length=20, std=2.0)
df['BBL'] = bb.iloc[:, 0]
df['BBM'] = bb.iloc[:, 1]
df['BBU'] = bb.iloc[:, 2]
# ケルトナーチャネル (20, 2.0) - スクイーズ判定の基準
kc = df.ta.kc(length=20, scalar=2.0)
df['KCL'] = kc.iloc[:, 0]
df['KCU'] = kc.iloc[:, 2]
# スクイーズ判定: BBがKCの内側に収まっている = エネルギー蓄積
df['Squeeze'] = (df['BBU'] < df['KCU']) & (df['BBL'] > df['KCL'])
# MACD - モメンタムの加速を判定
macd = df.ta.macd(fast=12, slow=26, signal=9)
df['MACD_Hist'] = macd.iloc[:, 2]
# ADX - トレンドの強さ
adx_df = df.ta.adx(length=14)
df['ADX'] = adx_df.iloc[:, 0]
# Price Action: 直近10本の最高値・最安値
df['HH_10'] = df['High'].rolling(window=10).max().shift(1)
df['LL_10'] = df['Low'].rolling(window=10).min().shift(1)
# --- マルチタイムフレーム (MTF) 分析 ---
# 未来のカンニングを完全に防ぐため、resample後に .shift(1) を適用
# 15分足トレンド (EMA 10) - より短期的な方向性へ感度を上げる
df_15m_close = df['Close'].resample('15min').last().shift(1)
ema_15m = ta.ema(df_15m_close, length=10)
df['EMA_15m'] = ema_15m.reindex(df.index, method='ffill')
# 1時間足トレンド (EMA 50) - 中期的な環境認識
df_1h_close = df['Close'].resample('1h').last().shift(1)
ema_1h = ta.ema(df_1h_close, length=50)
df['EMA_1h'] = ema_1h.reindex(df.index, method='ffill')
return df
def generate_signal(self, df):
"""
スクイーズからの脱出とモメンタムの加速を捉えるエントリーロジック
"""
if len(df) < 200:
return None
last = df.iloc[-1]
prev = df.iloc[-2]
# --- 1. エントリー時間帯フィルター (流動性のピーク時間) ---
try:
current_hour = df.index[-1].hour
except AttributeError:
return None
# 16:00-18:00 (ロンドン) または 21:00-24:00 (NY)
if not ((16 <= current_hour < 18) or (21 <= current_hour < 24)):
return None
# --- 2. 相場環境フィルター (MTF) ---
# 上位足のトレンド方向にのみエントリー
long_env = (last['Close'] > last['EMA_1h']) and (last['Close'] > last['EMA_15m'])
short_env = (last['Close'] < last['EMA_1h']) and (last['Close'] < last['EMA_15m'])
# --- 3. スクイーズ・リリース判定 (核心部分) ---
# 前回の足でスクイーズしており、今回の足でBBがKCを突き抜けた = ボラティリティの爆発
squeeze_release_long = prev['Squeeze'] and (last['BBU'] > last['KCU'])
squeeze_release_short = prev['Squeeze'] and (last['BBL'] < last['KCL'])
# --- 4. モメンタムの加速判定 ---
# MACDヒストグラムが正方向へ加速しているか
momentum_up = (last['MACD_Hist'] > 0) and (last['MACD_Hist'] > prev['MACD_Hist'])
momentum_down = (last['MACD_Hist'] < 0) and (last['MACD_Hist'] < prev['MACD_Hist'])
# --- 最終エントリー判定 ---
# ロング条件:
# - MTF環境が上昇トレンド
# - スクイーズ状態からボラティリティが爆発(Release)
# - モメンタムが正方向に加速
# - 終値がBBUを上抜け、かつ直近高値を更新
if long_env and squeeze_release_long and momentum_up:
if (last['Close'] > last['BBU']) and (last['Close'] > last['HH_10']):
# ADXが上昇傾向にあるときのみ、トレンドの持続性を期待してエントリー
if last['ADX'] > prev['ADX']:
return 'BUY'
# ショート条件:
# - MTF環境が下降トレンド
# - スクイーズ状態からボラティリティが爆発(Release)
# - モメンタムが負方向に加速
# - 終値がBBLを下抜け、かつ直近安値を更新
if short_env and squeeze_release_short and momentum_down:
if (last['Close'] < last['BBL']) and (last['Close'] < last['LL_10']):
# ADXが上昇傾向にあるときのみ、トレンドの持続性を期待してエントリー
if last['ADX'] > prev['ADX']:
return 'SELL'
return None
MQL5コード
//+------------------------------------------------------------------+
//| Alpha_Squeeze_Expansion_V4.mq5 |
//| Copyright 2026, Quant Engineer |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Quant Engineer"
#property link ""
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
// 入力パラメータ
input double InpLotSize = 0.1;
input int InpTP = 600; // Take Profit (points)
input int InpSL = 200; // Stop Loss (points)
input int InpTrailStart = 150; // Trailing Stop Start (points)
input int InpBBPeriod = 20; // BB Period
input double InpBBStdDev = 2.0; // BB StdDev
input int InpKCPeriod = 20; // KC Period
input double InpKCScalar = 2.0; // KC Scalar
// グローバル変数
int handleBB, handleMACD, handleADX, handleEMA15, handleEMA1h;
CTrade trade;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
handleBB = iBands(_Symbol, PERIOD_M5, InpBBPeriod, 0, InpBBStdDev, PRICE_CLOSE);
handleMACD = iMACD(_Symbol, PERIOD_M5, 12, 26, 9, PRICE_CLOSE);
handleADX = iADX(_Symbol, PERIOD_M5, 14);
handleEMA15 = iMA(_Symbol, PERIOD_M15, 10, 0, MODE_EMA, PRICE_CLOSE);
handleEMA1h = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_EMA, PRICE_CLOSE);
if(handleBB == INVALID_HANDLE || handleMACD == INVALID_HANDLE || handleADX == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 時間帯フィルター
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
bool timeWindow = ((dt.hour >= 16 && dt.hour < 18) || (dt.hour >= 21 && dt.hour < 24));
if(!timeWindow) return;
// インジケーター値の取得
double bbu[], bbl[], macd_main[], macd_sig[], adx[], ema15[], ema1h[];
ArraySetAsSeries(bbu, true); ArraySetAsSeries(bbl, true);
ArraySetAsSeries(macd_main, true); ArraySetAsSeries(macd_sig, true);
ArraySetAsSeries(adx, true); ArraySetAsSeries(ema15, true); ArraySetAsSeries(ema1h, true);
CopyBuffer(handleBB, 1, 0, 3, bbu);
CopyBuffer(handleBB, 2, 0, 3, bbl);
CopyBuffer(handleMACD, 0, 0, 3, macd_main);
CopyBuffer(handleADX, 0, 0, 3, adx);
CopyBuffer(handleEMA15, 0, 0, 3, ema15);
CopyBuffer(handleEMA1h, 0, 0, 3, ema1h);
// ケルトナーチャネルの簡易計算 (SMA + 2*ATR)
double kc_u = GetKCU(1);
double kc_l = GetKCL(1);
double prev_kc_u = GetKCU(2);
double prev_kc_l = GetKCL(2);
// スクイーズ判定
bool isSqueezePrev = (bbu[1] < prev_kc_u && bbl[1] > prev_kc_l);
bool isSqueezeCurr = (bbu[0] < kc_u && bbl[0] > kc_l);
// 最高値・最安値
double hh10 = iHigh(_Symbol, PERIOD_M5, iHighest(_Symbol, PERIOD_M5, MODE_HIGH, 10, 1));
double ll10 = iLow(_Symbol, PERIOD_M5, iLowest(_Symbol, PERIOD_M5, MODE_LOW, 10, 1));
double close0 = iClose(_Symbol, PERIOD_M5, 0);
double close1 = iClose(_Symbol, PERIOD_M5, 1);
// エントリーロジック
if(PositionsTotal() == 0)
{
// BUY
if(close0 > ema1h[0] && close0 > ema15[0]) // MTF Filter
{
if(isSqueezePrev && bbu[0] > kc_u) // Release
{
if(macd_main[0] > 0 && macd_main[0] > macd_main[1]) // Momentum
{
if(close0 > bbu[0] && close0 > hh10 && adx[0] > adx[1])
{
double sl = close0 - InpSL * _Point;
double tp = close0 + InpTP * _Point;
trade.Buy(InpLotSize, _Symbol, close0, sl, tp);
}
}
}
}
// SELL
else if(close0 < ema1h[0] && close0 < ema15[0]) // MTF Filter
{
if(isSqueezePrev && bbl[0] < kc_l) // Release
{
if(macd_main[0] < 0 && macd_main[0] < macd_main[1]) // Momentum
{
if(close0 < bbl[0] && close0 < ll10 && adx[0] > adx[1])
{
double sl = close0 + InpSL * _Point;
double tp = close0 - InpTP * _Point;
trade.Sell(InpLotSize, _Symbol, close0, sl, tp);
}
}
}
}
}
ManageTrailingStop();
}
double GetKCU(int shift) {
double sma = iMA(_Symbol, PERIOD_M5, InpKCPeriod, 0, MODE_SMA, PRICE_CLOSE); // Simple for example
double atr = iATR(_Symbol, PERIOD_M5, InpKCPeriod, shift);
return iMA(_Symbol, PERIOD_M5, InpKCPeriod, 0, MODE_SMA, PRICE_CLOSE) + InpKCScalar * atr;
}
double GetKCL(int shift) {
double atr = iATR(_Symbol, PERIOD_M5, InpKCPeriod, shift);
return iMA(_Symbol, PERIOD_M5, InpKCPeriod, 0, MODE_SMA, PRICE_CLOSE) - InpKCScalar * atr;
}
void ManageTrailingStop() {
for(int i=PositionsTotal()-1; i>=0; i--) {
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket)) {
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(price - PositionGetDouble(POSITION_PRICE_OPEN) > InpTrailStart * _Point) {
double newSL = price - InpTrailStart * _Point;
if(newSL > PositionGetDouble(POSITION_SL)) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
}
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(PositionGetDouble(POSITION_PRICE_OPEN) - price > InpTrailStart * _Point) {
double newSL = price + InpTrailStart * _Point;
if(newSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0) trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
}
}
}
}