English Deutsch
preview
古典的な戦略を再構築する(第12回):EURUSDブレイクアウト戦略

古典的な戦略を再構築する(第12回):EURUSDブレイクアウト戦略

MetaTrader 5 | 14 4月 2025, 08:20
167 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

この記事では、MQL5で取引戦略を一緒に構築していきます。ブレイクアウト取引戦略を実装し、それを反復的に改善してその潜在能力を最大限に引き出します。戦略の詳細についていくつか議論していきましょう。

私たちはEURUSDペアに焦点を当て、その動きをH1時間枠で取引します。ブレイクアウト戦略では、まずEURUSDペアで現在提供されている高値と安値を記録します。時間が経過すると、記録した最初の高値と安値に基づいて形成されたチャネルの外で価格レベルが完全に開閉するのを待ちます。

これが発生すると、取引戦略は市場が特定の方向に動き続ける可能性が高いというバイアスを形成します。ただし、この時点ではポジションを取るわけではありません。バイアスが確認された段階でポジションに入ります。価格が完全に開き、最初のチャネルを突破したローソク足の極値を超えて閉じると、チャネルを上回っていればロングポジションを開き、そうでなければショートポジションを開きます。

ここまでで、私たちの指定したシステムは取引の数が多すぎます。収益性の低い取引を除外するために、強さや弱さの他の指標を設定する必要があります。移動平均は市場のトレンドを素早く識別するのに役立ちます。

私たちのシステムは、まず現在市場で提供されている価格を監視し、次に価格がどの方向にチャネルを突破するか、またその突破が後続の価格変動によってサポートされているかを観察します。観察したブレイクアウトが後続の価格変動と一致する場合、その後、移動平均を用いて注文実行のタイミングを図ります。

短期移動平均が長期移動平均を上回っていればロングポジションを好み、その逆の場合はショートポジションを取ります。すべての取引はATRインジケーターを用いて更新され、ストップロスとテイクプロフィットが計算されます。

2020年1月1日から2024年11月30日までの期間に、H1時間枠で取引戦略をテストします。

テクニカル指標は以下のように設定されます。

  1. 短期移動平均:終値に適用される5期間指数移動平均
  2. 長期移動平均:終値に適用される60期間指数移動平均
  3. Average True Range:14期間ATRインジケーター
取引アプリケーションは基本的な取引ルールに従って動作します。最初にシステムがロードされると、前のローソク足の高値と安値をマークし、どちらかの側で価格が突破するのを待機します。この状態ではバイアスは0のままとなり、確認や取引はおこなわれません。


    図1:ブレイクアウト取引アプリケーションの初期状態

    しばらくすると、価格レベルがついにチャネルの外側で開閉します。この極値が私たちのバイアスとなり、市場が従うと予測する方向になります。もしその後、価格レベルがバイアスの反対側で閉じると、バイアスが確認されます。それ以外の場合は、取引はおこないません。


    図2:取引アプリケーションが市場のバイアスを発見した

    価格レベルが私たちのバイアスを確認した場合、市場でポジションを開く自信を持つことができます。私たちの戦略は最初はトレンドフォローです。したがって、価格がチャネルを上抜けた場合、買いの機会を探します。

      図3:バイアスが確認された後にポジションが開かれる


      MQL5の始め方

      私たちの取引アプリケーションは、取引ロジックと基本的なテクニカル分析の概念を組み合わせて作られています。コードに含まれる主要な要素を強調してみましょう。

      システムの部分
      意図した目的
      定数とパラメータ
      移動平均の期間、ロットサイズ、ストップロスとテイクプロフィットの幅など、すべてのテストで一貫性を持たせるために、取引アルゴリズムのいくつかの側面を固定します。
      グローバル変数
      これらの変数はコードの異なる部分で使用され、使用する際には常に同じ値を指し示すことが重要です。アプリケーション内のグローバル変数のいくつかには、チャネルの高値と安値、市場が従うと信じる方向(バイアス)、その他のテクニカル指標の値などがあります。

      また、市場の状態を追跡するために取引アプリケーションで必要なその他の重要な変数も定義する必要があります。重要な変数に慣れていきましょう。

      変数
      意図した目的
      Bias
      価格が動いている方向を表します。トレンドが強気の場合は値1、トレンドが弱気の場合は値-1が許可されます。それ以外の場合は0に設定されます。
      Moving averages
      短期移動平均(ma_f)と長期移動平均(ma_s)によってトレンドが決まります。ma_f[0]>ma_s[0]、かつ価格(c)が短期移動平均線を上回っている場合、買い注文を出します。それ以外の場合、ma_f[0]<ma_s[0]、かつ価格が長期移動平均を下回っている場合、売り注文を出します。
      Breakout
      チャネルレベル(上限または下限)を突破すると、移動方向(バイアス)が設定されます。
      Breakout levels
      ブレイクアウトレベルは、市場が今後どの方向に進み続けると私たちが考えるかを教えてくれます。市場が上限を上回ると、感情は強気になります。
      Signal confirmation
      シグナルの確認がなければ、取引はおこなわれません。ブレイクアウト後に市場がその方向を維持すれば、シグナルが確認されます。確認が失われた場合、ポジションを調整またはクローズすることができます。
      Order management おこなう取引は、現在市場で観察されているバイアスによって異なります。上昇トレンド(bias == 1)の場合、コマンド「Trade.Buy(vol,Symbol(),ask,channel_low,0,"VolatilityDoctorAI");」が送信されます。それ以外の場合、下降トレンド(bias==-1)の場合は、コマンド「Trade.Sell(vol, Symbol(), bid, channel_high, 0, "Volatility Doctor AI");」が送信されます。
       Stop loss  最初は買いの場合はchannel_low、売りの場合はchannel_highに設定され、その後ATR値を使用して更新されます。

      これで、戦略の構成要素の概念的なレイアウトができたので、一緒に取引戦略の構築を始めましょう。まず、取引アプリケーションの詳細を指定する必要があります。

      //+------------------------------------------------------------------+
      //|                                                MTF Channel 2.mq5 |
      //|                                        Gamuchirai Zororo Ndawana |
      //|                          https://www.mql5.com/ja/gamuchiraindawa |
      //+------------------------------------------------------------------+
      #property copyright "Gamuchirai Zororo Ndawana"
      #property link      "https://www.mql5.com/ja/gamuchiraindawa"
      #property version   "1.00"
      

      次に、取引ライブラリをロードします。

      //+------------------------------------------------------------------+
      //| Library                                                          |
      //+------------------------------------------------------------------+
      #include  <Trade/Trade.mqh>
      CTrade Trade;
      

      一部のテクニカル指標の期間など、取引アプリケーションの定数を定義します。

      //+------------------------------------------------------------------+
      //| Constants                                                        |
      //+------------------------------------------------------------------+
      const  int ma_f_period = 5; //Slow MA
      const  int ma_s_period = 60; //Slow MA
      

      ここで、エンドユーザーが調整できる入力を定義しましょう。テクニカル指標を固定しているため、エンドユーザーは多数のパラメータに圧倒されることはありません。

      //+------------------------------------------------------------------+
      //| Inputs                                                           |
      //+------------------------------------------------------------------+
      input  group "Money Management"
      input int lot_multiple = 5; //Lot Multiple
      input int atr_multiple = 5; //ATR Multiple
      

      グローバル変数は、ほとんどのプログラムで使用します。

      //+------------------------------------------------------------------+
      //| Global varaibles                                                 |
      //+------------------------------------------------------------------+
      double channel_high = 0;
      double channel_low  = 0;
      double o,h,l,c;
      int    bias = 0;
      double bias_level = 0;
      int    confirmation = 0;
      double vol,bid,ask,initial_sl;
      int    atr_handler,ma_fast,ma_slow;
      double atr[],ma_f[],ma_s[];
      double bo_h,bo_l;
      

      取引アプリケーションが初めて読み込まれると、テクニカル指標を読み込み、その他の必要な市場データを準備するための特殊な関数が呼び出されます。

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //---
         setup();
      //---
         return(INIT_SUCCEEDED);
        }
      

      エキスパートアドバイザー(EA)を使用しなくなった場合は、使用しなくなったリソースを解放する必要があります。

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //---
            IndicatorRelease(atr_handler);
            IndicatorRelease(ma_fast);
            IndicatorRelease(ma_slow);
        }

      更新された価格を受け取るたびに、グローバル変数を更新し、新しい取引機会を確認します。

      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
        {
      //--- If we have positions open
         if(PositionsTotal() > 0)
            manage_setup();
      
      //--- Keep track of time
         static datetime timestamp;
         datetime time = iTime(Symbol(),PERIOD_CURRENT,0);
         if(timestamp != time)
           {
            //--- Time Stamp
            timestamp = time;
            if(PositionsTotal() == 0)
               find_setup();
           }
        }
      

      次の関数は、テクニカル指標を読み込み、市場データを取得する役割を担います。

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
         channel_high = iHigh(Symbol(),PERIOD_M30,1);
         channel_low  = iLow(Symbol(),PERIOD_M30,1);
         vol = lot_multiple * SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
         atr_handler = iATR(Symbol(),PERIOD_CURRENT,14);
         ma_fast     = iMA(Symbol(),PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow     = iMA(Symbol(),PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
        }
      

      戦略が初めて読み込まれると、市場で提供されている現在の高値と安値がマークされます。こうすることで、今後観察するすべての価格をコンテキストとともに観察でき、最初に到着したときに見た初期価格レベルと比較することができます。

      //+---------------------------------------------------------------+
      //| Update channel                                                |
      //+---------------------------------------------------------------+
      void update_channel(double new_high, double new_low)
        {
         channel_high = new_high;
         channel_low  = new_low;
         ObjectDelete(0,"Channel High");
         ObjectDelete(0,"Channel Low");
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
        }
      

      ポジションを開いている場合は、それに応じてストップロスとテイクプロフィットの値を更新する必要があります。リスク設定が市場の現在のボラティリティレベルに関係するように、ATRの倍数を使用してリスク設定を調整します。

      //+---------------------------------------------------------------+
      //| Manage setup                                                  |
      //+---------------------------------------------------------------+
      void manage_setup(void)
        {
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         CopyBuffer(atr_handler,0,0,1,atr);
         Print("Managing Position");
      
         if(PositionSelect(Symbol()))
           {
            Print("Position Found");
            initial_sl = PositionGetDouble(POSITION_SL);
           }
      
         if(bias == 1)
           {
            Print("Position Buy");
            double new_sl = (ask - (atr[0] * atr_multiple));
            Print("Initial: ",initial_sl,"\nNew: ",new_sl);
            if(initial_sl < new_sl)
              {
               Trade.PositionModify(Symbol(),new_sl,0);
               Print("DONE");
              }
           }
      
         if(bias == -1)
           {
            Print("Position Sell");
            double new_sl = (bid + (atr[0] * atr_multiple));
            Print("Initial: ",initial_sl,"\nNew: ",new_sl);
            if(initial_sl > new_sl)
              {
               Trade.PositionModify(Symbol(),new_sl,0);
               Print("DONE");
              }
           }
      
        }
      

      ポジションがない場合は、先に概説したルールに従って取引の機会を特定します。価格が見つかる最初のチャネルから抜け出す強力な価格変動を観察しようとしていることを思い出してください。その後、価格レベルが同じ方向に動き続け、先ほど作成したオープンチャネルを越えなければ、取引を実行するのに十分な自信が得られます。

      //+---------------------------------------------------------------+
      //| Find Setup                                                    |
      //+---------------------------------------------------------------+
      void find_setup(void)
        {
      //--- We are updating the system
         o = iOpen(Symbol(),PERIOD_CURRENT,1);
         h = iHigh(Symbol(),PERIOD_CURRENT,1);
         l = iLow(Symbol(),PERIOD_CURRENT,1);
         c = iClose(Symbol(),PERIOD_CURRENT,1);
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         CopyBuffer(atr_handler,0,0,1,atr);
         CopyBuffer(ma_fast,0,0,1,ma_f);
         CopyBuffer(ma_slow,0,0,1,ma_s);
      
      //--- If we have no market bias
         if(bias == 0)
           {
            //--- Our bias is bullish
            if
            (
               (o > channel_high) &&
               (h > channel_high) &&
               (l > channel_high) &&
               (c > channel_high)
            )
              {
               bias = 1;
               bias_level = h;
               bo_h = h;
               bo_l = l;
               mark_bias(h);
              }
      
            //--- Our bias is bearish
            if
            (
               (o < channel_low) &&
               (h < channel_low) &&
               (l < channel_low) &&
               (c < channel_low)
            )
              {
               bias = -1;
               bias_level = l;
               bo_h = h;
               bo_l = l;
               mark_bias(l);
              }
           }
      
      //--- Is our bias valid?
         if(bias != 0)
           {
      
            //--- Our bearish bias has been violated
            if
            (
               (o > channel_high) &&
               (h > channel_high) &&
               (l > channel_high) &&
               (c > channel_high) &&
               (bias == -1)
            )
              {
               forget_bias();
              }
            //--- Our bullish bias has been violated
            if
            (
               (o < channel_low) &&
               (h < channel_low) &&
               (l < channel_low) &&
               (c < channel_low) &&
               (bias == 1)
            )
              {
               forget_bias();
              }
      
            //--- Our bullish bias has been violated
            if
            (
               ((o < channel_high) && (c > channel_low))
            )
              {
               forget_bias();
              }
      
            //--- Check if we have confirmation
            if((confirmation == 0) && (bias != 0))
              {
               //--- Check if we are above the bias level
               if
               (
                  (o > bias_level) &&
                  (h > bias_level) &&
                  (l > bias_level) &&
                  (c > bias_level) &&
                  (bias == 1)
               )
                 {
                  confirmation = 1;
                 }
      
               //--- Check if we are below the bias level
               if
               (
                  (o < bias_level) &&
                  (h < bias_level) &&
                  (l < bias_level) &&
                  (c < bias_level) &&
                  (bias == -1)
               )
                 {
                  confirmation = 1;
                 }
              }
           }
      
      //--- Check if our confirmation is still valid
         if(confirmation == 1)
           {
            //--- Our bias is bullish
            if(bias == 1)
              {
               //--- Confirmation is lost if we fall beneath the breakout level
               if
               (
                  (o < bias_level) &&
                  (h < bias_level) &&
                  (l < bias_level) &&
                  (c < bias_level)
               )
                 {
                  confirmation = 0;
                 }
              }
      
            //--- Our bias is bearish
            if(bias == -1)
              {
               //--- Confirmation is lost if we rise above the breakout level
               if
               (
                  (o > bias_level) &&
                  (h > bias_level) &&
                  (l > bias_level) &&
                  (c > bias_level)
               )
                 {
                  confirmation = 0;
                 }
              }
           }
      
      //--- Do we have a setup?
         if((confirmation == 1) && (bias == 1))
           {
            if(ma_f[0] > ma_s[0])
              {
               if(c > ma_f[0])
                 {
                  Trade.Buy(vol,Symbol(),ask,channel_low,0,"Volatility Doctor AI");
                  initial_sl = channel_low;
                 }
              }
           }
      
         if((confirmation == 1) && (bias == -1))
           {
            if(ma_f[0] < ma_s[0])
              {
               if(c < ma_s[0])
                 {
                  Trade.Sell(vol,Symbol(),bid,channel_high,0,"Volatility Doctor AI");
                  initial_sl = channel_high;
                 }
              }
           }
         Comment("O: ",o,"\nH: ",h,"\nL: ",l,"\nC:",c,"\nC H: ",channel_high,"\nC L:",channel_low,"\nBias: ",bias,"\nBias Level: ",bias_level,"\nConfirmation: ",confirmation,"\nMA F: ",ma_f[0],"\nMA S: ",ma_s[0]);
        }
      

      価格レベルが最初に設定したチャネルを突破したらと、チャネルを突破したローソク足によって作られた極値をマークします。その極値が私たちのバイアスレベルとなります。

      //+---------------------------------------------------------------+
      //| Mark our bias levels                                          |
      //+---------------------------------------------------------------+
      void mark_bias(double f_level)
        {
         ObjectCreate(0,"Bias",OBJ_HLINE,0,0,f_level);the
        }
      

      最後に、価格レベルが以前に突破した後に取引チャネル内に戻った場合、古いチャネルは無効であるとみなし、チャネルの新しい位置をブレイクアウトローソク足によって作成されたレベルに更新します。

      //+---------------------------------------------------------------+
      //| Forget our bias levels                                        |
      //+---------------------------------------------------------------+
      void forget_bias()
        {
         update_channel(bo_h,bo_l);
         bias = 0;
         bias_level = 0;
         confirmation = 0;
         ObjectDelete(0,"Bias");
        }
      //+------------------------------------------------------------------+
      

      これで、ブレイクアウト取引戦略をバックテストする準備が整いました。私はこのアプリケーションに「MTF Channel 2」という名前を付けました。これは、「Multiple Time Frame Channel」の略です。H1時間枠でEURUSD銘柄を選択しました。テストの日程は先ほど指定した日程と同じです。読者は、これら3つの特定の設定が3つのテストすべてで固定されていることに気付くでしょう。

      初期設定

      図4:最初のバックテストに使用された最初の設定バッチ

      これらは設定したすべてのパラメータではありません。実際の取引シナリオを模倣するためにランダムな遅延設定を選択しました。これにより、経験する遅延が異なる場合があります。また、実際のティックに基づいてテストをモデル化することを選択し、実際の取引の体験に忠実に近いものを得るようにしました。

      2番目の設定

      図5:戦略をテストするために選択された2番目の設定

      EAで使用する設定はすべてのテストで同じに固定します。これらの設定を同じに保つことで、より良い取引ルールを選択することによって引き起こされる収益性を明確に分離するのに役立ちます。

      システム設定

      図6:資金管理設定

      戦略を実際に見てみましょう。下の図7では、スクリーンショットの右側に、アプリケーションが決定を下すために使用している内部変数が表示されています。すべての取引は、確認が1に設定されている場合にのみ実行されることに注意してください。

      システムの実例

      図7:EURUSDペアでの取引戦略のバックテスト

      残念ながら、戦略が損失を生んでいたことがわかります。これは改善の余地があることを示しています。

      時間の経過に伴う口座残高

      図8:バックテストに関連するグラフを表示する

      先ほどおこなったテストの詳細を見てみましょう。戦略は合計53件の取引を識別し、そのうち70%が不利益でした。シャープレシオは負の値であり、これらは良くないパフォーマンス指標です。

      一方で、平均利益は平均損失よりも大きいという良い点があります。では、どのようにしてより良いパフォーマンスを達成できるか見ていきましょう。私たちは、総損失と平均損失に対するコントロールを強化し、同時に平均利益と利益のある取引の割合を最大化したいと考えています。

      システム1の詳細な分析

      図9:バックテストの詳細

      最初の結果を改善する

      バックテストを見ていると、EAが同じミスを繰り返しているのを見ていて非常にイラつきました。損失のほとんどは、価格が単なる一時的な変動であるにもかかわらず、すべての条件を満たしてしまったために取引をしてしまったことが原因です。この問題を解決する唯一の方法は、市場の弱い動きと強い動きをより正確に区別できる条件を選ぶことです。

      その一つの方法として、EURとUSDのパフォーマンスを共通のベンチマークに対して比較する方法があります。ここではGBPを使います。ポジションを開く前に、EURGBPとGBPUSDのペアの動きが一致しているかを確認します。つまり、チャートでEURUSDが強い強気トレンドを示している場合、EURGBPも同じ方向に動いていることを確認し、さらにGBPUSDも強気トレンドであることを期待します。

      言い換えれば、もしEURUSDの価格がユーロがドルよりも高くなる方向に動いているなら、その信頼性を高めるために、ユーロがGBPに対しても上昇し、同時にドルがGBPに対して下落していることを確認する必要があります。この3つの通貨ペアにおける動きが一致すれば、偽のブレイクアウトを識別する助けになると考えています。3つの市場すべてに同時に影響を及ぼす変動は、利益を得られる非常に強力な動きである可能性が高いと考えています。

      この変更を実装するために、元の取引戦略にいくつかのコードを追加します。まず、EURGBPとGBPUSDの価格を追跡するための新しいグローバル変数を作成します。また、これらの通貨ペアの動向を追跡するために、技術指標も適用する必要があります。

      //+------------------------------------------------------------------+
      //| Global variables                                                 |
      //+------------------------------------------------------------------+
      double channel_high = 0;
      double channel_low  = 0;
      double o,h,l,c;
      int    bias = 0;
      double bias_level = 0;
      int    confirmation = 0;
      double vol,bid,ask,initial_sl;
      int    atr_handler,ma_fast,ma_slow;
      double atr[],ma_f[],ma_s[];
      double bo_h,bo_l;
      int    last_trade_state,current_state;
      int    eurgbp_willr, gbpusd_willr;
      string symbols[] = {"EURGBP","GBPUSD"};
      

      EAを初めて読み込むときは、ベンチマーク銘柄で発生しているプライスアクションを追跡するために、いくつかの追加手順を実行する必要があります。これらの更新はsetup関数に実装されます。

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
      //--- Select the symbols we need
         SymbolSelect("EURGBP",true);
         SymbolSelect("GBPUSD",true);
      //--- Reset our last trade state
         last_trade_state = 0;
      //--- Mark the current high and low
         channel_high = iHigh("EURUSD",PERIOD_M30,1);
         channel_low  = iLow("EURUSD",PERIOD_M30,1);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
      //--- Our trading volums
         vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN);
      //--- Our technical indicators
         atr_handler  = iATR("EURUSD",PERIOD_CURRENT,14);
         eurgbp_willr = iWPR(symbols[0],PERIOD_CURRENT,wpr_period);
         gbpusd_willr = iWPR(symbols[1],PERIOD_CURRENT,wpr_period);
         ma_fast      = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow      = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
        }
      

      同様に、この取引アプリケーションが使用されなくなった場合、リリースする追加のテクニカル指標がいくつかあります。

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //---
         IndicatorRelease(eurgbp_willr);
         IndicatorRelease(gbpusd_willr);
         IndicatorRelease(atr_handler);
         IndicatorRelease(ma_fast);
         IndicatorRelease(ma_slow);
        }
      

      OnTick関数は同じままです。ただし、呼び出される関数は変更されます。まず、チャネルを更新するたびに、フォローしている市場の3つのチャネルを更新する必要があります。1つはEURUSD、2つ目はEURGBP、最後はGBPUSDです。

      //+---------------------------------------------------------------+
      //| Update channel                                                |
      //+---------------------------------------------------------------+
      void update_channel(double new_high, double new_low)
        {
         channel_high = new_high;
         channel_low  = new_low;
         ObjectDelete(0,"Channel High");
         ObjectDelete(0,"Channel Low");
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
        }
      

      プログラムの大部分は同じままですが、最も重要な変更点は、取引アプリケーションが取引を実行するかどうかを決定する前に、他の2つの市場をチェックするようになったことです。もしEUR/USDで見られるブレイクアウトが真の強さに支えられているというファンダメンタルズの信頼が得られた場合、ポジションを取ることにします。これらの更新は、find_setup関数に反映されます。

      また、この関数が、ブレイクアウト戦略アプリケーションの前バージョンでは定義されていなかった新しい関数を呼び出していることにもお気づきかもしれません。追加された確認機能は、2つのベンチマーク市場をチェックし、基本的な取引条件が満たされているかどうかを確認します。

      //+---------------------------------------------------------------+
      //| Find Setup                                                    |
      //+---------------------------------------------------------------+
      void find_setup(void)
        {
      //--- I have omitted code pieces that were unchanged
      //--- Do we have a setup?
         if((confirmation == 1) && (bias == 1) && (current_state != last_trade_state))
           {
            if(ma_f[0] > ma_s[0])
              {
               if(c > ma_f[0])
                 {
                  if(additional_confirmation(1))
                    {
                     Trade.Buy(vol,"EURUSD",ask,channel_low,0,"Volatility Doctor");
                     initial_sl = channel_low;
                     last_trade_state = 1;
                    }
                 }
              }
           }
      
         if((confirmation == 1) && (bias == -1)  && (current_state != last_trade_state))
           {
            if(ma_f[0] < ma_s[0])
              {
               if(c < ma_s[0])
                 {
                  if(additional_confirmation(-1))
                    {
                     Trade.Sell(vol,"EURUSD",bid,channel_high,0,"Volatility Doctor");
                     initial_sl = channel_high;
                     last_trade_state = -1;
                    }
                 }
              }
           }
      }

      この関数は、市場のノイズと真の強さを見分けるのに役立つはずです。関連する他の市場で確認をおこなうことで、常に最も有力な取引を選択できるようにしたいと考えています。

      //+---------------------------------------------------------------+
      //| Check for true strength                                       |
      //+---------------------------------------------------------------+
      bool additional_confirmation(int flag)
        {
      //--- Do we have additional confirmation from our benchmark pairs?
      
      //--- Record the average change in the EURGBP and GBPUSD Market
         vector eurgbp_willr_f = vector::Zeros(1);
         vector gbpusd_willr_f = vector::Zeros(1);
      
         eurgbp_willr_f.CopyIndicatorBuffer(eurgbp_willr,0,0,1);
         gbpusd_willr_f.CopyIndicatorBuffer(gbpusd_willr,0,0,1);
      
         if((flag == 1) && (eurgbp_willr_f[0] > -50) && (gbpusd_willr_f[0] < -50))
            return(true);
         if((flag == -1) && (eurgbp_willr_f[0] < -50) && (gbpusd_willr_f[0] > -50))
            return(true);
      
         Print("EURGBP WPR: ",eurgbp_willr_f[0],"\nGBPUSD WPR: ",gbpusd_willr_f[0]);
         return(false);
        }
      

      このバージョンのアプリケーションは「MTF EURUSD Channel」と名付けられます。最初に作成したバージョンはより汎用的で、ターミナル内の他のシンボルでも簡単に取引できる設計でした。しかし今回のバージョンでは、EURGBPとGBPUSDをベンチマークとして使用するため、より専門的な構成となっており、EURUSDペアの取引に特化しています。読者の皆さんは、今回のテスト条件が最初のテストとすべて同一であることにお気づきになるでしょう。このバックテストは、最初のテストと同様に、2020年1月1日から2024年11月30日までの期間、同じ時間足を用いて実施されます。

      テストする2番目のバッチEA

      図10:EURUSDチャネルブレイクアウト戦略のバックテスト用の最初の設定

      こちらの手順に沿って設定を進める場合は、[Modelling]オプションを[Every tick based on real ticks]に設定すると、インターネット接続の状況によっては時間がかかる可能性があることにご注意ください。MT5ターミナルがブローカーから詳細なデータを取得して、できる限り現実に近い市場の動きを再現しようとするためです。そのため、処理が完了するまでに数分かかることがありますが、驚かずにお待ちください。処理中にコンピュータの電源を切ったりしないようにご注意ください。

      取引アプリケーション用の2番目の入力

      図11:2番目の設定は、最初のテストで使用した設定と同じにする必要がある

      ロット倍率を1に設定するということは、すべての取引が最小ロットサイズでおこなわれるという意味です。もし私たちのシステムが最小ロットサイズでも利益を出せるようになれば、ロット倍率を上げることでその利益をさらに増やすことができます。しかし、システムが最小ロットで利益を出せない場合、ロットサイズを増やしても何の意味もありません。

      パラメータ設定

      図12:アプリケーションの動作を制御するために使用するパラメータ

      これで、履歴データに基づいて取引システムがどのように機能するかを確認できるようになりました。なお、このバージョンのシステムでは一度に3つの市場を監視します。まず、EURUSDペアを常に追跡し、そこからバイアスを取得します。

      EURUSDで稼働中のシステム

      図13:EURUSDペアで動作しているシステム

      ポジションを開くことができるのは、以下の図14と15に示すように、EURGBPとGBPUSDのペアが反対方向に推移しているのを確認した場合のみです。ウィリアムズパーセントレンジを使用して、2つの市場のトレンドを判断します。WPRが50レベルを上回っている場合、トレンドは強気であるとみなします。

      最初のベンチマークペア

      図14:最初の確認ペア、GBPUSD

      この例では、EURUSDを購入する取引機会が見つかりました。この機会を特定できたのは、2つの市場におけるWPR(Williams Percent Range)の値が50レベルを挟んで反対側に位置していたからです。このような不均衡は、市場が大きく変動する前兆である可能性が高く、ブレイクアウト戦略にとって理想的な状況となります。

      2番目のベンチマークペア

      図15:2番目のベンチマークペア

      下の図9は、シミュレーション取引口座の残高が時間の経過とともにどのように変化するかを示しています。  私たちの目標は、戦略が失敗する理由を深く理解し、その弱点を改善することです。

      時間の経過に伴う口座残高の変化

      図16:時間の経過に伴う口座残高をグラフ化する

      残念ながら、システムに加えた変更により、取引アプリケーションの収益性が低下しました。平均損失と利益は同じ額だけ増加しました。そして、利益の出る取引の割合はわずかに減少しました。 

      システム2の詳細な分析

      図17:バックテストの詳細な結果

      改善への最後の試み

      私たちは、最も重要なポイントである「収益性」の改善に失敗しました。市場に対して自分たちの見解を押し付けるのではなく、コンピューター自身に、私たちよりも優れた移動平均の活用方法を学ばせることにします。そもそも、「効果的な取引」に対する私たちの考え方には、少なからずバイアスがかかっています。

      そこで、終値と移動平均との関係性をコンピューターに学習させれば、これまでのような反応的な取引スタイルとは異なり、次に起こるであろう動きを予測して、自律的にルールを作りながら取引を行えるようになります。

      その第一歩として、過去の市場データを抽出するためのスクリプトを作成しました。取引したい銘柄のチャートにこのスクリプトをドラッグ&ドロップするだけで、データの取得が始まります。このスクリプトは、過去の市場データを収集するとともに、当戦略に必要な2本の移動平均も、当アプリケーションで使用している形式と同じフォーマットで取り出してくれます。

      //+------------------------------------------------------------------+
      //|                                                      ProjectName |
      //|                                      Copyright 2020, CompanyName |
      //|                                       http://www.companyname.net |
      //+------------------------------------------------------------------+
      #property copyright "Gamuchirai Zororo Ndawana"
      #property link      "https://www.mql5.com/ja/users/gamuchiraindawa"
      #property version   "1.00"
      #property script_show_inputs
      
      //+------------------------------------------------------------------+
      //| Script Inputs                                                    |
      //+------------------------------------------------------------------+
      input int size = 100000; //How much data should we fetch?
      
      //+------------------------------------------------------------------+
      //| Global variables                                                 |
      //+------------------------------------------------------------------+
      int    ma_f_handler,ma_s_handler;
      double ma_f_reading[],ma_s_reading[];
      
      //+------------------------------------------------------------------+
      //| On start function                                                |
      //+------------------------------------------------------------------+
      void OnStart()
        {
      //--- Load indicator
         ma_s_handler  = iMA(Symbol(),PERIOD_CURRENT,60,0,MODE_EMA,PRICE_CLOSE);
         ma_f_handler  = iMA(Symbol(),PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
      
      //--- Load the indicator values
         CopyBuffer(ma_f_handler,0,0,size,ma_f_reading);
         CopyBuffer(ma_s_handler,0,0,size,ma_s_reading);
      
         ArraySetAsSeries(ma_f_reading,true);
         ArraySetAsSeries(ma_s_reading,true);
      
      //--- File name
         string file_name = "Market Data " + Symbol() +" MA Cross" +  " As Series.csv";
      
      //--- Write to file
         int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
      
         for(int i= size;i>=0;i--)
           {
            if(i == size)
              {
               FileWrite(file_handle,"Time","Open","High","Low","Close","MA 5","MA 60");
              }
      
            else
              {
               FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                         iOpen(Symbol(),PERIOD_CURRENT,i),
                         iHigh(Symbol(),PERIOD_CURRENT,i),
                         iLow(Symbol(),PERIOD_CURRENT,i),
                         iClose(Symbol(),PERIOD_CURRENT,i),
                         ma_f_reading[i],
                         ma_s_reading[i]
                        );
              }
           }
      //--- Close the file
         FileClose(file_handle);
      
        }
      //+------------------------------------------------------------------+
      

      Pythonでデータを分析する

      CSV形式で市場データが取得できたので、いよいよAIモデルの構築に取りかかることができます。このモデルによって、偽のブレイクアウトを予測し、それらを回避できるようになることを目指します。

      import pandas as pd
      import numpy  as np
      from   sklearn.model_selection import TimeSeriesSplit,cross_val_score
      from   sklearn.linear_model    import Ridge
      from sklearn.metrics import mean_squared_error
      import matplotlib.pyplot as plt
      import seaborn as sns

      先ほど抽出した市場データを読み取ります。データフレーム内の[Time]列に注目してください。最後のエントリの日付は2019年4月18日になっているのがわかります。これは意図的におこなっています。これまでのテストの開始日がいずれも2020年1月1日であったことを思い出してください。つまり、モデルにテストの「答え」をすべて与えるような真似はしていないということです。

      #Define the forecast horizon
      look_ahead          = 24
      #Read in the data
      data                = pd.read_csv('Market Data EURUSD MA Cross As Series.csv')
      #Drop the last 4 years
      data                =  data.iloc[:(-24 * 365 * 4),:]
      data.reset_index(drop=True,inplace=True)
      #Label the data
      data['Target']      = data['Close'].shift(-look_ahead)
      data['MA 5 Target']      = data['MA 5'].shift(-look_ahead)
      data['MA 5 Close Target']      = data['Target'] - data['MA 5 Target']
      data['MA 60 Target']      = data['MA 60'].shift(-look_ahead)
      data['MA 60 Close Target']      = data['Target'] - data['MA 60 Target']
      data.dropna(inplace=True)
      data.reset_index(drop=True,inplace=True)
      data


      図18:過去の市場データ

      EURUSD市場で、依然として移動平均で価格を予測する方が容易であるかをテストしてみましょう。この仮説をテストするために、30個の同一のニューラルネットワークを訓練し、3つのターゲットを順番に予測します。まず、未来の価格、5期間移動平均、そして60期間移動平均を予測します。すべてのターゲットは24ステップ先の未来を予測します。最初に、価格を直接予測する精度を記録します。

      #Classical error
      classical_error = []
      epochs = 1000
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs')
        classical_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close']],data.loc[:,'Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      次に、5期間の移動平均を予測した精度を記録します。

      #MA Cross Over error
      ma_5_error = []
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs')
        ma_5_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 5']],data.loc[:,'MA 5 Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      最後に、60期間の移動平均を予測した精度を記録します。

      #New error
      ma_60_error = []
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=10000,early_stopping=False,solver='lbfgs')
        ma_60_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 60']],data.loc[:,'MA 60 Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      結果をプロットすると、下の図12からわかるように、60期間の移動平均を予測するとシステムで最も多くの誤差が発生し、5期間の移動平均を予測すると価格を直接予測するよりも誤差が少なくなります。

      plt.plot(classical_error)
      plt.plot(ma_5_error)
      plt.plot(ma_60_error)
      plt.legend(['OHLC','MA 5 ','MA 60'])
      plt.axhline(np.mean(classical_error),color='blue',linestyle='--')
      plt.axhline(np.mean(ma_5_error),color='orange',linestyle='--')
      plt.axhline(np.mean(ma_60_error),color='green',linestyle='--')
      plt.grid()
      plt.ylabel('Cross Validated Error')
      plt.xlabel('Iteration')
      plt.title('Comparing Different The Error Associated With Different Targets')
      plt.show()

      図19:さまざまなターゲットに関連付けられたエラーの視覚化

      ここで、取引アプリケーション用のモデルをエクスポートしてみます。必要なライブラリをインポートします。

      import onnx
      from skl2onnx import convert_sklearn
      from skl2onnx.common.data_types import FloatTensorType
      from sklearn.neural_network import MLPRegressor

      必要なモデルを指定します。このタスクでは2つのモデルを使用します。短期移動平均は予測しやすいため、単純なリッジモデルを使用して予測します。しかし、60期間の移動平均は困難であることが判明しました。したがって、ニューラルネットワークを使用して長期移動平均を予測します。

      ma_5_model = Ridge()
      ma_5_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Target'])
      ma_5_height_model = Ridge()
      ma_5_height_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Close Target'])
      ma_60_model = Ridge()
      ma_60_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Target'])
      ma_60_height_model = Ridge()
      ma_60_height_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Close Target'])

      ONNXへのエクスポートを準備します。

      initial_type = [('float_input', FloatTensorType([1, 5]))]
      ma_5_onx = convert_sklearn(ma_5_model, initial_types=initial_type, target_opset=12 )
      ma_5_height_onx = convert_sklearn(ma_5_height_model, initial_types=initial_type, target_opset=12 )
      ma_60_height_onx = convert_sklearn(ma_60_height_model, initial_types=initial_type, target_opset=12 )
      ma_60_onx = convert_sklearn(ma_60_model, initial_types=initial_type, target_opset=12 )

      ONNX形式で保存します。

      onnx.save(ma_5_onx,'eurchf_ma_5_model.onnx')
      onnx.save(ma_60_onx,'eurchf_ma_60_model.onnx')
      onnx.save(ma_5_height_onx,'eurusd_ma_5_height_model.onnx')
      onnx.save(ma_60_height_onx,'eurusd_ma_60_height_model.onnx')

      MQL5の最終アップデート

      新しく作成したモデルを適用して、市場の偽ブレイクアウトをフィルタリングできるかどうか確認してみましょう。最初におこなうべき更新は、先ほど作成したONNXモデルをインポートすることです。

      //+------------------------------------------------------------------+
      //|                                                MTF Channel 2.mq5 |
      //|                                        Gamuchirai Zororo Ndawana |
      //|                          https://www.mql5.com/ja/gamuchiraindawa |
      //+------------------------------------------------------------------+
      #property copyright "Gamuchirai Zororo Ndawana"
      #property link      "https://www.mql5.com/ja/gamuchiraindawa"
      #property version   "1.00"
      
      //+------------------------------------------------------------------+
      //| ONNX Resources                                                   |
      //+------------------------------------------------------------------+
      #resource "\\Files\\eurusd_ma_5_model.onnx"         as const uchar eurusd_ma_5_buffer[];
      #resource "\\Files\\eurusd_ma_60_model.onnx"        as const uchar eurusd_ma_60_buffer[];
      #resource "\\Files\\eurusd_ma_5_height_model.onnx"  as const uchar eurusd_ma_5_height_buffer[];
      #resource "\\Files\\eurusd_ma_60_height_model.onnx" as const uchar eurusd_ma_60_height_buffer[];

      次に、モデルに関連付けられたいくつかの新しい変数を作成する必要があります。

      //+------------------------------------------------------------------+
      //| Global varaibles                                                 |
      //+------------------------------------------------------------------+
      int     bias = 0;
      int     state = 0;
      int     confirmation = 0;
      int     last_cross_over_state = 0;
      int     atr_handler,ma_fast,ma_slow;
      int     last_trade_state,current_state;
      long    ma_5_model;
      long    ma_60_model;
      long    ma_5_height_model;
      long    ma_60_height_model;
      double  channel_high = 0;
      double  channel_low  = 0;
      double  o,h,l,c;
      double  bias_level = 0;
      double  vol,bid,ask,initial_sl;
      double  atr[],ma_f[],ma_s[];
      double  bo_h,bo_l;
      vectorf ma_5_forecast = vectorf::Zeros(1);
      vectorf ma_60_forecast = vectorf::Zeros(1);
      vectorf ma_5_height_forecast = vectorf::Zeros(1);
      vectorf ma_60_height_forecast = vectorf::Zeros(1);
      

      初期化ルーチンを拡張して、ONNXモデルが自動的に設定されるようにします。

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
      //--- Reset our last trade state
         last_trade_state = 0;
      //--- Mark the current high and low
         channel_high = iHigh("EURUSD",PERIOD_M30,1);
         channel_low  = iLow("EURUSD",PERIOD_M30,1);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
      //--- Our trading volums
         vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN);
      //--- Our technical indicators
         atr_handler  = iATR("EURUSD",PERIOD_CURRENT,14);
         ma_fast      = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow      = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
      //--- Setup our ONNX models
      //--- Define our ONNX model
         ulong input_shape [] = {1,5};
         ulong output_shape [] = {1,1};
      
      //--- Create the model
         ma_5_model = OnnxCreateFromBuffer(eurusd_ma_5_buffer,ONNX_DEFAULT);
         ma_60_model = OnnxCreateFromBuffer(eurusd_ma_60_buffer,ONNX_DEFAULT);
         ma_5_height_model = OnnxCreateFromBuffer(eurusd_ma_5_height_buffer,ONNX_DEFAULT);
         ma_60_height_model = OnnxCreateFromBuffer(eurusd_ma_60_height_buffer,ONNX_DEFAULT);
      
      //--- Store our models in a list
         long onnx_models[] = {ma_5_model,ma_5_height_model,ma_60_model,ma_60_height_model};
      
      //--- Loop over the models and set them up
         for(int i = 0; i < 4; i++)
           {
            if(onnx_models[i] == INVALID_HANDLE)
              {
               Comment("Failed to load AI module correctly: Invalid handle");
              }
      
            //--- Validate I/O
            if(!OnnxSetInputShape(onnx_models[i],0,input_shape))
              {
               Comment("Failed to set input shape correctly:  Wrong input shape ",GetLastError()," Actual shape: ",OnnxGetInputCount(ma_5_model));
              }
      
            if(!OnnxSetOutputShape(onnx_models[i],0,output_shape))
              {
               Comment("Failed to load AI module correctly: Wrong output shape ",GetLastError()," Actual shape: ",OnnxGetOutputCount(ma_5_model));
              }
           }
        }
      

      システムが使用されなくなった場合は、使用しなくなったリソースを解放する必要があります。

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //--- Free the resources we don't need
         IndicatorRelease(atr_handler);
         IndicatorRelease(ma_fast);
         IndicatorRelease(ma_slow);
         OnnxRelease(ma_5_model);
         OnnxRelease(ma_5_height_model);
         OnnxRelease(ma_60_model);
         OnnxRelease(ma_60_height_model);
        }

      価格が更新される際の唯一の大きな違いは、AIモデルからの予測も取得しようとする点です。

      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
        {
      //--- Keep track of time
         static datetime timestamp;
         datetime time = iTime(Symbol(),PERIOD_CURRENT,0);
         if(timestamp != time)
           {
            //--- Time Stamp
            timestamp = time;
            //--- Update system variables
            update();
            //--- Make a new prediction
            model_predict();
            if(PositionsTotal() == 0)
              {
               state = 0;
               find_setup();
              }
           }
      
      //--- If we have positions open
         if(PositionsTotal() > 0)
            manage_setup();
        }
      
      

      MQL5でONNXモデルから予測を取得する関数を定義する必要があります。

      //+------------------------------------------------------------------+
      //| Get a prediction from our model                                  |
      //+------------------------------------------------------------------+
      void model_predict(void)
        {
         //--- Moving average inputs
         float  a = (float) ma_f[0];
         float  b = (float) ma_s[0];
      
         //--- Price quotes
         float op = (float) iOpen("EURUSD",PERIOD_H1,0);
         float hi = (float) iHigh("EURUSD",PERIOD_H1,0);
         float lo = (float) iLow("EURUSD",PERIOD_H1,0);
         float cl = (float) iClose("EURUSD",PERIOD_H1,0);
      
         //--- ONNX inputs
         vectorf fast_inputs = {op,hi,lo,cl,a};
         vectorf slow_inputs = {op,hi,lo,cl,b};
      
         Print("Fast inputs: ",fast_inputs);
         Print("Slow inputs: ",slow_inputs);
      
         //--- Inference
         OnnxRun(ma_5_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_forecast);
         OnnxRun(ma_5_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_height_forecast);
         OnnxRun(ma_60_model,ONNX_DEFAULT,slow_inputs,ma_60_forecast);
         OnnxRun(ma_60_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_60_height_forecast);
        }
      

      最後におこなった変更は、戦略が取引を選択する方法に影響します。戦略では、単に真っ先に取引するのではなく、価格と移動平均の間で学習した関係に基づいて取引をおこなうようになりました。取引アプリケーションは、市場のバイアスに反する場合でも、売買できる柔軟性を備えています。

      有効なセットアップで新しい関数が呼び出されていることに注意してください。この関数は、ブレイクアウト条件がtrueの場合にtrueを返すだけです。

      //+---------------------------------------------------------------+ //| Find a setup | //+---------------------------------------------------------------+ void find_setup(void) { //--- I have skipped parts of the code that remained the same    if(valid_setup())      {       //--- Both models are forecasting rising prices       if((c < (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c < (ma_5_forecast[0] + ma_5_height_forecast[0])))         {          if(last_trade_state != 1)            {             Trade.Buy(vol,"EURUSD",ask,0,0,"Volatility Doctor");             initial_sl = channel_low;             last_trade_state = 1;             last_cross_over_state = current_state;            }         }       //--- Both models are forecasting falling prices       if((c > (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c > (ma_5_forecast[0] + ma_5_height_forecast[0])))         {          if(last_trade_state != -1)            {             Trade.Sell(vol,"EURUSD",bid,0,0,"Volatility Doctor");             initial_sl = channel_high;             last_trade_state = -1;             last_cross_over_state = current_state;            }         }      }

      チャネルを突破したかどうかを確認します。ある場合、関数はtrueを返し、そうでない場合はfalseを返します。

      //+---------------------------------------------------------------+
      //| Do we have a valid setup?                                     |
      //+---------------------------------------------------------------+
      bool valid_setup(void)
        {
         return(((confirmation == 1) && (bias == -1)  && (current_state != last_cross_over_state)) || ((confirmation == 1) && (bias == 1) && (current_state != last_cross_over_state)));
        }
      

      ここまでで、バックテストに指定する設定についてご理解いただけたと思います。繰り返しになりますが、取引ルールに加えた変更に関連する収益性の変化を分離できるように、これらの設定を一貫して維持することが重要です。

      入力設定

      図20:最後の取引戦略をバックテストするために使用する設定の一部

      私たちのモデルは2019年までのデータでのみ訓練されており、テストは2020年から始まることを思い出してください。したがって、これは過去にこのシステムを設計していた場合に実際に何が起こっていたかを忠実にシミュレーションしていることになります。

      3回目のテストのための2つ目の設定

      図21:最後の取引戦略をバックテストするために使用する2番目の設定

      繰り返しますが、設定は3つのテストすべてで同じです。

      アプリケーション設定

      図22:最後のテストでアプリケーションを制御するために使用する設定

      これで、モデルベースのブレイクアウト取引アプリケーションがEURUSDで実際に動作しているのを確認できます。モデルを訓練するときに、このデータはモデルに表示されなかったことを思い出してください。

      AIシステムが稼働中

      図23:ブレイクアウト戦略の最終的なモデルベースバージョンが実行される

      下の図23から、当初からモデルが抱えていた特徴的な負の傾斜をようやく修正することができ、収益性が向上していることがわかります。

      口座残高の推移

      図24:新しいモデルベースの戦略をテストしたバックテストの結果

      私たちの目標は、平均利益を増やし、負け取引の割合を減らすことでしたが、それは確かに達成されました。最初のテストでは総損失が498ドル、2回目では403ドル、そして今回のテストでは298ドルまで減少しました。一方で、総利益は最初のテストでは378ドル、今回の最終テストでは341ドルと、ほぼ同水準を維持しています。つまり、今回の改善により、粗利益を保ちつつ、粗損失を確実に減らすことができたのです。最初のシステムでは、全取引の70%が損失に終わっていましたが、新しいシステムでは、不採算だった取引は55%にまで改善されています。

      モデルベースの取引システムの詳細な分析

      図25:モデルベースの戦略による詳細なバックテスト結果

      結論

      ブレイクアウトは、1日の中でも最も有利に取引できる可能性を秘めたタイミングです。しかし、それを正しく見極めるのは決して簡単なことではありません。この記事では、ブレイクアウト戦略を一から構築し、収益性を高めるためにいくつかのフィルターを追加する作業を一緒におこなってきました。とはいえ、ブレイクアウト戦略がEURUSD市場において必ずしも最適とは限らず、この市場には別のアプローチが必要になるかもしれません。真に有効なブレイクアウト戦略を構築するには、今回ご紹介した内容以上の時間と労力が求められるでしょう。それでも、ここで紹介したアイデアやアプローチは、皆さんが成功を目指す過程で、きっと参考になるはずです。

      ファイル名 
      詳細
      MQL5 EURUSD AI
      EURUSD市場のモデルを構築するために使用したJupyterノートブック
      EURUSD MA 60 Model
      60期間の移動平均を予測するために使用されるONNXモデル
      EURUSD MA 60 Height Model 将来の終値と将来の60MAの差を予測するために使用されるONNXモデル
      EURUSD MA 5 Model
      5期間の移動平均を予測することを目的としたONNXモデル
      EURUSD MA 5 Height Model 将来の終値と将来の5MAの差を予測するために使用されるONNXモデル 
      MTF Channel 2
      ブレイクアウト戦略の最初の実装
      MTF Channel 2 EURUSD
      ベンチマークペアからの確認を使用したブレイクアウト戦略の2番目の実装
      MTF Channel 2 EURUSD AI
      モデルベースのブレイクアウト戦略の3番目の実装





      MetaQuotes Ltdにより英語から翻訳されました。
      元の記事: https://www.mql5.com/en/articles/16569

      MQL5における数値予測を強化するアンサンブル法 MQL5における数値予測を強化するアンサンブル法
      この記事では、MQL5における複数のアンサンブル学習手法の実装を紹介し、それらの手法がさまざまな状況下でどの程度有効かを検証します。
      出来高による取引の洞察:トレンドの確認 出来高による取引の洞察:トレンドの確認
      強化型トレンド確認手法は、プライスアクション、出来高分析、そして機械学習を組み合わせることで、真の市場動向を見極めることを目的としています。この手法では、取引を検証するために、価格のブレイクアウトと平均比50%以上の出来高急増という2つの条件を満たす必要があります。さらに、追加の確認手段としてLSTMニューラルネットワークを活用します。システムはATR (Average True Range)に基づいたポジションサイズ設定と動的リスク管理を採用しており、誤ったシグナルを排除しつつ、多様な市場環境に柔軟に対応できる設計となっています。
      プライスアクション分析ツールキットの開発(第5回):Volatility Navigator EA プライスアクション分析ツールキットの開発(第5回):Volatility Navigator EA
      市場の方向性を判断するのは簡単ですが、いつエントリーするかを知るのは難しい場合があります。連載「プライスアクション分析ツールキットの開発」の一環として、エントリーポイント、テイクプロフィットレベル、ストップロスの配置を提供する別のツールを紹介できることを嬉しく思います。これを実現するために、MQL5プログラミング言語を利用しました。この記事では、各ステップについて詳しく見ていきましょう。
      プライスアクション分析ツールキットの開発(第4回):Analytics Forecaster EA プライスアクション分析ツールキットの開発(第4回):Analytics Forecaster EA
      チャート上に表示された分析済みのメトリックを見るだけにとどまらず、Telegramとの統合によってブロードキャストを拡張するという、より広い視点へと移行しています。この機能強化により、Telegramアプリを通じて、重要な結果がモバイルデバイスに直接配信されるようになります。この記事では、この新たな取り組みを一緒に探っていきましょう。