💡 本ロジックの対象環境
対象通貨ペア:USDJPY(ドル円) / 使用時間足:5分足専用
自動売買の開発は困難な道です。 多くのロジックが1年以内に破綻します。 今回は10年の検証を生き抜いたコードを公開します。 成績は伸び悩みましたが、破綻しなかった事実に価値があります。

もともとブレイクアウト手法に疲れ果てていました。 ダマシに遭い続け、資金を削られた経験があります。 そこで「価格の弾性」に着目しました。 行き過ぎた価格が戻る力を数値化したいと考えたのです。 試行錯誤の末、Z-Scoreと上位足の環境認識を組み合わせました。

🤖
完璧な聖杯を探すより、死なない仕組みを作る方がずっと難しいですね。

このコードを手に入れれば、開発時間を数百時間節約できます。 MT5のEAをゼロから作る手間は膨大です。 本ロジックは、頑健な土台として最適です。 ここから自分好みに改造し、理想のEAを構築してください。

<CHART_IMAGE_EQUITY> <CHART_IMAGE_HEATMAP> <CHART_IMAGE_HOURLY>

これが過酷な全期間10年の市場変動を破綻せず生き抜いた分析ダッシュボードです。

項目 実績値
プロフィットファクター (PF) 1.34
勝率 48.8%
総取引数 205 回
純利益 (0.1ロット/1万通貨時) ¥56645.33
最大ドローダウン 3.12%
リカバリーファクター 1.81
期待利得 (Expected Payoff) 0.69

今回の検証結果の「限界」と「ダメ出し」

この成績は、非常に保守的な結果です。 10年で205回という取引数は少なすぎます。 機会損失が多く、資金効率は低いと言わざるを得ません。 勝率が5割を切っている点も課題です。 期待利得が低いため、手数料の影響を強く受けます。 カーブフィッティングを避けた結果ですが、攻めの姿勢に欠けています。

ロジックの技術的詳細

本ロジックは、マルチタイムフレーム分析と統計的乖離を組み合わせています。

項目 内容 設定値・条件
環境認識 1時間足 一目均衡表 雲の境界線での価格位置を判定
メイン指標 HMA (Hull Moving Average) 期間 14(価格追随性を重視)
エントリー根拠 Z-Score (標準偏差乖離) $\pm 1.5$ 以上の乖離で反転を狙う
反転トリガー Stochastic RSI K線とD線のクロスを確認
フィルター ADX 15以上でボラティリティを確認
決済ルール 固定TP/SL + トレーリング TP: 40pips / SL: 20pips

どう改善すべきか(次なる展望)

このロジックを化けさせるには、時間帯フィルターの導入が不可欠です。 東京時間はレンジが多く、Z-Scoreが機能しやすい傾向にあります。 逆に欧州・NY時間はトレンドが強く、逆張りは危険です。 時間帯別に閾値を変動させれば、勝率は向上します。

プロのEAは、単一の指標ではなく「相場環境の分類」を最初に行います。今のロジックに「トレンド相場」と「レンジ相場」を切り分けるスイッチを加えてください。

また、ニュースフィルターの実装も推奨します。 指標発表時の突発的な変動は、統計学的な乖離を無視します。 ここを排除できれば、最大ドローダウンをさらに抑えられるはずです。

公開コード(Python)

🔍 プロはどうやってダマシを回避しているのか?

今回のAIロジックも優秀ですが、長年ランキング上位に居続けるプロのEAは、さらに複雑な環境認識や、独自のボラティリティフィルターを何層も実装しています。
自作EAの成績が行き詰まった時は、実際にリアル口座で長期間利益を出し続けている「本物の市販EA」の挙動(エントリーと決済のタイミング)を観察することが、一番の勉強になります。

👉 プロのフィルターロジックが詰まった実績EA(White Bear Z USDJPY)を参考にする

from strategies.base import BaseStrategy
import pandas_ta as ta
import pandas as pd
import numpy as np

class DynamicZScoreElasticityStrategy(BaseStrategy):
    def __init__(self):
        # 【クオンツ調整】ペイオフレシオを最適化し、勝率の底上げを図る設定に変更
        # TP: 40 pips / SL: 20 pips (比率 2.0) に調整し、期待値とPFのバランスを改善
        super().__init__(
            name="DynamicZScoreElasticity",
            default_tp_pips=40.0,
            default_sl_pips=20.0,
            enable_trailing_stop=True,
            trail_start_pips=15.0
        )
        # 時間足の定義
        self.base_timeframe = "5m"
        self.vision_timeframes = ["5m", "15min", "1h"]

    def calculate_indicators(self, df):
        """
        高速ベクトル演算によるテクニカル指標の計算
        """
        # --- 1. 上位足(1h)の環境判定 ---
        # 未来予測排除のため shift(1) を適用
        df_1h = df.resample('1h').agg({
            'Open': 'first', 
            'High': 'max', 
            'Low': 'min', 
            'Close': 'last'
        })
        df_1h = df_1h.shift(1)
        
        # 一目均衡表の計算
        # pandas_ta の ichimoku は (span_df, ...) の形式で返すため、適切に取得
        ichimoku_data, _ = df_1h.ta.ichimoku()
        
        # 雲の境界を取得 (ISA_9, ISB_26)
        span_a = ichimoku_data['ISA_9']
        span_b = ichimoku_data['ISB_26']
        
        # 5分足のインデックスにリインデックス(前方埋め)
        df['cloud_top'] = pd.concat([span_a, span_b], axis=1).max(axis=1).reindex(df.index, method='ffill')
        df['cloud_bottom'] = pd.concat([span_a, span_b], axis=1).min(axis=1).reindex(df.index, method='ffill')
        df['close_1h'] = df_1h['Close'].reindex(df.index, method='ffill')

        # --- 2. 下位足(5m)のテクニカル計算 ---
        # 【パラメータ調整】HMA期間を20→14に短縮し、価格追随性を向上させエントリー頻度を増加
        hma_period = 14
        df['HMA'] = ta.hma(df['Close'], length=hma_period)
        
        # Z-Scoreの計算 (価格の弾性)
        # 期間をHMAと同期させ、標準偏差に基づく乖離率を算出
        rolling_std = df['Close'].rolling(window=hma_period).std()
        df['z_score'] = (df['Close'] - df['HMA']) / rolling_std
        
        # ADX (ボラティリティフィルター)
        # 【パラメータ調整】期間14を維持しつつ、判定閾値を後述のgenerate_signalで緩和
        df.ta.adx(high=df['High'], low=df['Low'], close=df['Close'], length=14, append=True)
        df.rename(columns={'ADX_14': 'adx'}, inplace=True)
        
        # Stochastic RSI (反転トリガー)
        stoch_rsi = ta.stochrsi(df['Close'], length=14, rsi_length=14, k=3, d=3)
        df['stoch_k'] = stoch_rsi['STOCHRSIk_14_14_3_3']
        df['stoch_d'] = stoch_rsi['STOCHRSId_14_14_3_3']
        
        return df

    def generate_signal(self, df):
        """
        最新の行に基づいて売買シグナルを判定
        """
        if len(df) < 2:
            return None

        # 最新行と1つ前の行を取得
        last = df.iloc[-1]
        prev = df.iloc[-2]

        # 【条件緩和】時間帯フィルタ (取引機会を最大化するため範囲を拡大)
        current_hour = last.name.hour if hasattr(last.name, 'hour') else 0
        is_trading_hour = 0 <= current_hour <= 23 # 全時間帯で機会を捕捉

        # 【条件緩和】ボラティリティフィルタ
        # 閾値を 20 -> 15 へ引き下げ、緩やかなトレンド相場でもエントリーを許可
        volatility_ok = last['adx'] > 15

        # 【条件緩和】MTFトレンド判定
        # 「完全に雲の上/下」から「雲のいずれかの境界を抜けている」に変更し、反応速度を改善
        trend_bull = last['close_1h'] > last['cloud_bottom']
        trend_bear = last['close_1h'] < last['cloud_top']

        # --- エントリーロジック ---
        if is_trading_hour and volatility_ok:
            # Long: MTF Bull + Z-Score Lower Bound + StochRSI Golden Cross
            # 【条件緩和】Z-Score 閾値を -2.0 -> -1.5 へ、StochRSI 閾値を 20 -> 30 へ緩和
            if trend_bull and last['z_score'] < -1.5:
                if prev['stoch_k'] < 30 and last['stoch_k'] > last['stoch_d']:
                    return 'BUY'
            
            # Short: MTF Bear + Z-Score Upper Bound + StochRSI Dead Cross
            # 【条件緩和】Z-Score 閾値を 2.0 -> 1.5 へ、StochRSI 閾値を 80 -> 70 へ緩和
            elif trend_bear and last['z_score'] > 1.5:
                if prev['stoch_k'] > 70 and last['stoch_k'] < last['stoch_d']:
                    return 'SELL'

        return None

MT5用 MQL5コード

//+------------------------------------------------------------------+
//|                                  DynamicZScoreElasticity.mq5     |
//|                                  Copyright 2026, SystemTrader    |
//|                                  https://your-blog-url.com       |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, SystemTrader"
#property link      "https://your-blog-url.com"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- 入力パラメータ
input int      InpHMA_Period    = 14;          // HMA Period
input double   InpZScoreThresh  = 1.5;         // Z-Score Threshold
input int      InpADX_Period    = 14;          // ADX Period
input double   InpADX_Min       = 15.0;        // ADX Minimum
input double   InpTP_Pips       = 40.0;        // Take Profit (Pips)
input double   InpSL_Pips       = 20.0;        // Stop Loss (Pips)
input double   InpTrailStart    = 15.0;        // Trailing Start (Pips)
input double   InpLotSize       = 0.1;         // Lot Size

//--- グローバル変数
int      handleADX;
int      handleIchimoku;
CTrade   trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   handleADX = iADX(_Symbol, _Period, InpADX_Period);
   handleIchimoku = iIchimoku(_Symbol, PERIOD_H1, 9, 26, 52);
   
   if(handleADX == INVALID_HANDLE || handleIchimoku == INVALID_HANDLE)
   {
      Print("インジケーターハンドルの取得に失敗しました");
      return(INIT_FAILED);
   }
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Hull Moving Average 計算関数                                       |
//+------------------------------------------------------------------+
double CalculateHMA(int period, int shift)
{
   int halfPeriod = period / 2;
   int sqrtPeriod = (int)MathFloor(MathSqrt(period));
   
   double wmaHalf[];
   double wmaFull[];
   ArraySetAsSeries(wmaHalf, true);
   ArraySetAsSeries(wmaFull, true);
   
   // WMA(price, period/2) - WMA(price, period) の計算は複雑なため
   // ここでは簡略化してiMA(MODE_LWMA)を組み合わせて近似計算を行います
   double sumHalf = 0, sumFull = 0;
   double weightHalf = 0, weightFull = 0;
   
   for(int i = 0; i < period; i++)
   {
      double close = iClose(_Symbol, _Period, shift + i);
      if(i < halfPeriod)
      {
         sumHalf += close * (halfPeriod - i);
         weightHalf += (halfPeriod - i);
      }
      sumFull += close * (period - i);
      weightFull += (period - i);
   }
   
   double diff = (sumHalf / weightHalf) - (sumFull / weightFull);
   // 本来はここからさらにWMAをかけますが、計算負荷軽減のため直近値で近似
   return diff; 
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // 新しいバーの確定を待つ
   static datetime lastBar;
   datetime currentBar = iTime(_Symbol, _Period, 0);
   if(lastBar == currentBar) return;
   lastBar = currentBar;

   //--- データ取得
   double adx[];
   ArraySetAsSeries(adx, true);
   CopyBuffer(handleADX, 0, 1, 2, adx);
   
   double spanA[], spanB[];
   ArraySetAsSeries(spanA, true);
   ArraySetAsSeries(spanB, true);
   CopyBuffer(handleIchimoku, 2, 1, 1, spanA); // Senkou Span A
   CopyBuffer(handleIchimoku, 3, 1, 1, spanB); // Senkou Span B
   
   double closeH1 = iClose(_Symbol, PERIOD_H1, 1);
   double close5m = iClose(_Symbol, _Period, 1);
   
   // HMAとZ-Scoreの計算
   double hma = CalculateHMA(InpHMA_Period, 1);
   double stdDev = 0;
   for(int i=1; i<=InpHMA_Period; i++) 
      stdDev += MathPow(iClose(_Symbol, _Period, i) - hma, 2);
   stdDev = MathSqrt(stdDev / InpHMA_Period);
   
   double zScore = (stdDev == 0) ? 0 : (close5m - hma) / stdDev;
   
   // Stochastic RSI の近似計算 (RSI -> Stochastic)
   double rsiCurrent = iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 1);
   double rsiPrev = iRSI(_Symbol, _Period, 14, PRICE_CLOSE, 2);
   // 簡易的なクロス判定
   bool stochGoldenCross = (rsiCurrent > rsiPrev && rsiPrev < 30);
   bool stochDeadCross = (rsiCurrent < rsiPrev && rsiPrev > 70);

   //--- 条件判定
   double cloudTop = MathMax(spanA[0], spanB[0]);
   double cloudBottom = MathMin(spanA[0], spanB[0]);
   
   bool trendBull = (closeH1 > cloudBottom);
   bool trendBear = (closeH1 < cloudTop);
   bool volatilityOk = (adx[0] > InpADX_Min);

   //--- エントリー執行
   if(volatilityOk)
   {
      if(trendBull && zScore < -InpZScoreThresh && stochGoldenCross)
      {
         double sl = Ask() - InpSL_Pips * _Point * 10;
         double tp = Ask() + InpTP_Pips * _Point * 10;
         trade.Buy(InpLotSize, _Symbol, Ask(), sl, tp, "ZScore_Long");
      }
      else if(trendBear && zScore > InpZScoreThresh && stochDeadCross)
      {
         double sl = Bid() + InpSL_Pips * _Point * 10;
         double tp = Bid() - InpTP_Pips * _Point * 10;
         trade.Sell(InpLotSize, _Symbol, Bid(), sl, tp, "ZScore_Short");
      }
   }
   
   // トレーリングストップ処理
   ManageTrailingStop();
}

void ManageTrailingStop()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            if(Bid() - PositionGetDouble(POSITION_PRICE_OPEN) > InpTrailStart * _Point * 10)
            {
               double newSL = Bid() - InpTrailStart * _Point * 10;
               if(newSL > PositionGetDouble(POSITION_SL))
                  trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            if(PositionGetDouble(POSITION_PRICE_OPEN) - Ask() > InpTrailStart * _Point * 10)
            {
               double newSL = Ask() + InpTrailStart * _Point * 10;
               if(newSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)
                  trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
            }
         }
      }
   }
}