English Deutsch
preview
MQL5での取引戦略の自動化(第10回):トレンドフラットモメンタム戦略の開発

MQL5での取引戦略の自動化(第10回):トレンドフラットモメンタム戦略の開発

MetaTrader 5トレーディング |
192 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第9回)では、MetaQuotes Language 5 (MQL5)を使用して、主要なセッションレベルと動的リスク管理を活用したアジアブレイクアウト戦略の自動化エキスパートアドバイザー(EA)を開発しました。今回の第10回では、トレンドフラットモメンタム戦略に焦点を移します。これは、2本の移動平均線のクロスオーバーに、RSI(相対力指数)およびCCI(商品チャネル指数)といったモメンタムフィルターを組み合わせることで、トレンドの動きを高精度で捉える手法です。この記事は次のトピックに沿って構成されます。

  1. 戦略の設計図
  2. MQL5での実装
  3. バックテストと最適化
  4. 結論

最終的に、トレンドフラットモメンタム戦略を自動化した、完全動作のEAを完成させます。それでは、さっそく始めましょう。


戦略設計図

トレンドフラットモメンタム戦略は、シンプルな移動平均クロスオーバーと強力なモメンタムフィルタリングを組み合わせることで、市場のトレンドを捉えることを目的とした戦略です。基本的な考え方は、短期の移動平均が長期の移動平均を上抜けた場合に上昇トレンドとみなし、買いエントリーを検討します。ただし、その際にはRSIと2種類のCCIを用いて、モメンタムが十分に強いかどうかを確認します。逆に、短期移動平均が長期移動平均を下回り、かつモメンタム指標が売り方向を示している場合には、売りエントリーのシグナルと見なします。インジケーターの設定は次のとおりです。

  • CCI(36期間、終値)
  • CCI(55期間、終値)
  • 低速RSI(27期間、終値)
  • 高速移動平均(11期間、平滑化、中央値/2)
  • 低速移動平均(25期間、平滑化、中央値/2)

出口戦略については、ロングポジションの場合は直近のスイングローをストップロスとし、ショートポジションの場合は直近のスイングハイにストップを設定します。テイクプロフィットは、エントリープライスから300ポイント離れた位置に固定します。この多面的なアプローチは、騙しのシグナルを排除するのに役立ち、トレンドの方向性とモメンタムが一致していることを確認することで、エントリーの質の向上を目指します。まとめて、以下の図でこの戦略の簡略化された計画を視覚的に示します。

戦略計画


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲータに移動して、インジケーターフォルダを見つけ、[新規]タブをクリックして、表示される手順に従ってファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用するグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA trades based on Trend Flat Momentum Strategy"
#property strict

#include <Trade\Trade.mqh> //--- Include the Trade library for order management.
CTrade obj_Trade; //--- Create an instance of the CTrade class to handle trading operations.

// Input parameters
input int    InpCCI36Period      = 36;              //--- CCI period 1
input int    InpCCI55Period      = 55;              //--- CCI period 2
input int    InpRSIPeriod        = 27;              //--- RSI period
input int    InpMAFastPeriod     = 11;              //--- Fast MA period
input int    InpMASlowPeriod     = 25;              //--- Slow MA period
input double InpRSIThreshold     = 58.0;            //--- RSI threshold for Buy signal (Sell uses 100 - Threshold)
input int    InpTakeProfitPoints = 300;             //--- Take profit in points
input double InpLotSize          = 0.1;             //--- Trade lot size

// Pivot parameters for detecting swing highs/lows
input int    PivotLeft  = 2;  //--- Number of bars to the left for pivot detection
input int    PivotRight = 2;  //--- Number of bars to the right for pivot detection

// Global indicator handles
int handleCCI36;  //--- Handle for the CCI indicator with period InpCCI36Period
int handleCCI55;  //--- Handle for the CCI indicator with period InpCCI55Period
int handleRSI;    //--- Handle for the RSI indicator with period InpRSIPeriod
int handleMA11;   //--- Handle for the fast moving average (MA) with period InpMAFastPeriod
int handleMA25;   //--- Handle for the slow moving average (MA) with period InpMASlowPeriod

// Global dynamic storage buffers
double ma11_buffer[];  //--- Dynamic array to store fast MA values
double ma25_buffer[];  //--- Dynamic array to store slow MA values
double rsi_buffer[];   //--- Dynamic array to store RSI values
double cci36_buffer[]; //--- Dynamic array to store CCI values (period 36)
double cci55_buffer[]; //--- Dynamic array to store CCI values (period 55)

// To detect a new bar
datetime lastBarTime = 0;  //--- Variable to store the time of the last processed bar

まず、#includeを使用してファイル「Trade\Trade.mqh」をインクルードし、組み込みの取引関数にアクセスして、売買注文を実行するためのCTradeクラスのインスタンスである「obj_Trade」を作成します。戦略設定の主要な入力パラメータを定義します。これには、CCIインジケーターのInpCCI36PeriodとInpCCI55Period、RSIのInpRSIPeriod、2つの移動平均のInpMAFastPeriodとInpMASlowPeriodが含まれます。InpRSIThresholdは取引フィルタリングの条件を設定し、InpTakeProfitPointsは固定テイクプロフィットレベルを決定し、InpLotSizeはポジションサイズを制御します。

取引の実行精度を高めるために、「PivotLeft」と「PivotRight」というパラメーターを導入します。これらは、スイングハイおよびスイングローを検出する際に参照するバーの本数を定義し、ストップロスの設定に活用されます。インジケーターの値を効率よく取得するため、handleCCI36、handleCCI55、handleRSI、handleMA11、handleMA25といったグローバルなインジケーターハンドルを使用します。これらの値は、ma11_buffer、ma25_buffer、rsi_buffer、cci36_buffer、cci55_bufferなどの動的配列に格納され、スムーズなデータ処理が可能になります。さらに、lastBarTime変数で、同じローソク足で複数の取引がおこなわれないよう、最後に処理されたバーの時刻を記録して制御します。その後、OnInitイベントハンドラでインジケーターを初期化できます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   //--- Create CCI handle for period InpCCI36Period using the close price.
   handleCCI36 = iCCI(_Symbol, _Period, InpCCI36Period, PRICE_CLOSE); //--- Create the CCI36 indicator handle.
   if (handleCCI36 == INVALID_HANDLE) { //--- Check if the CCI36 handle is valid.
      Print("Error creating CCI36 handle"); //--- Print an error message if invalid.
      return (INIT_FAILED); //--- Return failure if handle creation failed.
   }
   
   //--- Create CCI handle for period InpCCI55Period using the close price.
   handleCCI55 = iCCI(_Symbol, _Period, InpCCI55Period, PRICE_CLOSE); //--- Create the CCI55 indicator handle.
   if (handleCCI55 == INVALID_HANDLE) { //--- Check if the CCI55 handle is valid.
      Print("Error creating CCI55 handle"); //--- Print an error message if invalid.
      return (INIT_FAILED); //--- Return failure if handle creation failed.
   }
   
   //--- Create RSI handle for period InpRSIPeriod using the close price.
   handleRSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); //--- Create the RSI indicator handle.
   if (handleRSI == INVALID_HANDLE) { //--- Check if the RSI handle is valid.
      Print("Error creating RSI handle"); //--- Print an error message if invalid.
      return (INIT_FAILED); //--- Return failure if handle creation failed.
   }
   
   //--- Create fast MA handle using MODE_SMMA on the median price with period InpMAFastPeriod.
   handleMA11 = iMA(_Symbol, _Period, InpMAFastPeriod, 0, MODE_SMMA, PRICE_MEDIAN); //--- Create the fast MA handle.
   if (handleMA11 == INVALID_HANDLE) { //--- Check if the fast MA handle is valid.
      Print("Error creating MA11 handle"); //--- Print an error message if invalid.
      return (INIT_FAILED); //--- Return failure if handle creation failed.
   }
   
   //--- Create slow MA handle using MODE_SMMA on the median price with period InpMASlowPeriod.
   handleMA25 = iMA(_Symbol, _Period, InpMASlowPeriod, 0, MODE_SMMA, PRICE_MEDIAN); //--- Create the slow MA handle.
   if (handleMA25 == INVALID_HANDLE) { //--- Check if the slow MA handle is valid.
      Print("Error creating MA25 handle"); //--- Print an error message if invalid.
      return (INIT_FAILED); //--- Return failure if handle creation failed.
   }
   
   //--- Set the dynamic arrays as time series (index 0 = most recent closed bar).
   ArraySetAsSeries(ma11_buffer, true); //--- Set ma11_buffer as a time series.
   ArraySetAsSeries(ma25_buffer, true); //--- Set ma25_buffer as a time series.
   ArraySetAsSeries(rsi_buffer, true);  //--- Set rsi_buffer as a time series.
   ArraySetAsSeries(cci36_buffer, true); //--- Set cci36_buffer as a time series.
   ArraySetAsSeries(cci55_buffer, true); //--- Set cci55_buffer as a time series.
   
   return (INIT_SUCCEEDED); //--- Return success after initialization.
}

OnInitイベントハンドラでは、インジケーターハンドルの作成と検証を通じてEAの初期化をおこないます。iCCI関数を使って、InpCCI36PeriodおよびInpCCI55Periodの期間に基づくCCIハンドルを作成しiRSI関数でRSIハンドルを、iMA関数でInpMAFastPeriodおよびInpMASlowPeriodに基づいた高速・低速のSMMAハンドルをそれぞれ作成します。いずれかのハンドルが無効(INVALID_HANDLE)であった場合は、エラーメッセージを出力して初期化失敗(INIT_FAILED)として処理を終了します。次に、ArraySetAsSeries関数を使用して各バッファを時系列データとしてフォーマットします。すべての初期化が正常に完了した場合には、成功を返します。また、リソースの節約のため、プログラムが削除される際には作成したハンドルを解放する必要があります。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (handleCCI36 != INVALID_HANDLE) { //--- If the CCI36 handle is valid,
      IndicatorRelease(handleCCI36); //--- release the CCI36 indicator.
   }
   if (handleCCI55 != INVALID_HANDLE) { //--- If the CCI55 handle is valid,
      IndicatorRelease(handleCCI55); //--- release the CCI55 indicator.
   }
   if (handleRSI != INVALID_HANDLE) { //--- If the RSI handle is valid,
      IndicatorRelease(handleRSI); //--- release the RSI indicator.
   }
   if (handleMA11 != INVALID_HANDLE) { //--- If the fast MA handle is valid,
      IndicatorRelease(handleMA11); //--- release the fast MA indicator.
   }
   if (handleMA25 != INVALID_HANDLE) { //--- If the slow MA handle is valid,
      IndicatorRelease(handleMA25); //--- release the slow MA indicator.
   }
}

OnDeinit関数内でEAの初期化解除処理をおこない、インジケーターのリソースを解放します。CCI36、CCI55、RSI、短期移動平均、長期移動平均の各インジケーターハンドルが有効かどうかを確認し、有効であればIndicatorRelease関数を使って割り当てられたリソースを解放します。これにより、メモリの効率的な管理が可能になります。これで、OnTickイベントハンドラに進むことができます。ここでは、すべてのデータ処理と売買判断がおこなわれます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Get the time of the current bar.
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Retrieve the current bar's time.
   if (currentBarTime != lastBarTime) { //--- If a new bar has formed,
      lastBarTime = currentBarTime; //--- update lastBarTime with the current bar's time.
      OnNewBar(); //--- Process the new bar.
   }
}

ここでは、OnTickイベントハンドラを使用して価格の更新を監視し、新しいバーを検出します。iTime関数を使って現在のバーの時刻を取得し、それを保持しているlastBarTimeの値と比較します。新しいバーが検出された場合には、lastBarTimeを更新して、1つのバーにつき1回のみ取引がおこなわれるようにし、OnNewBar関数を呼び出して新しいバーのデータを処理します。このOnNewBar関数内で、すべてのシグナル生成をおこないます。関数の内容は以下の通りです。

//+------------------------------------------------------------------+
//| Function called on every new bar (bar close)                     |
//+------------------------------------------------------------------+
void OnNewBar() {
   
   //--- Resize the dynamic arrays to hold 2 values (last closed bar and the one before).
   ArrayResize(ma11_buffer, 2); //--- Resize ma11_buffer to 2 elements.
   ArrayResize(ma25_buffer, 2); //--- Resize ma25_buffer to 2 elements.
   ArrayResize(rsi_buffer, 2);  //--- Resize rsi_buffer to 2 elements.
   ArrayResize(cci36_buffer, 2); //--- Resize cci36_buffer to 2 elements.
   ArrayResize(cci55_buffer, 2); //--- Resize cci55_buffer to 2 elements.
   
   //--- Copy indicator values into the dynamic arrays.
   if (CopyBuffer(handleMA11, 0, 1, 2, ma11_buffer) != 2) { //--- Copy 2 values from the fast MA indicator.
      return; //--- Exit the function if copying fails.
   }
   if (CopyBuffer(handleMA25, 0, 1, 2, ma25_buffer) != 2) { //--- Copy 2 values from the slow MA indicator.
      return; //--- Exit the function if copying fails.
   }
   if (CopyBuffer(handleRSI, 0, 1, 2, rsi_buffer) != 2) { //--- Copy 2 values from the RSI indicator.
      return; //--- Exit the function if copying fails.
   }
   if (CopyBuffer(handleCCI36, 0, 1, 2, cci36_buffer) != 2) { //--- Copy 2 values from the CCI36 indicator.
      return; //--- Exit the function if copying fails.
   }
   if (CopyBuffer(handleCCI55, 0, 1, 2, cci55_buffer) != 2) { //--- Copy 2 values from the CCI55 indicator.
      return; //--- Exit the function if copying fails.
   }
   
   //--- For clarity, assign the values from the arrays.
   //--- Index 0: last closed bar, Index 1: bar before.
   double ma11_current   = ma11_buffer[0]; //--- Fast MA value for the last closed bar.
   double ma11_previous  = ma11_buffer[1]; //--- Fast MA value for the previous bar.
   double ma25_current   = ma25_buffer[0]; //--- Slow MA value for the last closed bar.
   double ma25_previous  = ma25_buffer[1]; //--- Slow MA value for the previous bar.
   double rsi_current    = rsi_buffer[0];  //--- RSI value for the last closed bar.
   double cci36_current  = cci36_buffer[0]; //--- CCI36 value for the last closed bar.
   double cci55_current  = cci55_buffer[0]; //--- CCI55 value for the last closed bar.
}

作成するvoid関数OnNewBar内では、まずインジケーター値を格納する動的配列が、直近の2本の確定バーを保持できるようにArrayResize関数を使ってリサイズされていることを確認します。その後、CopyBuffer関数を用いて、短期移動平均、長期移動平均、RSI、および2つのCCIインジケーターの値を取得します。これらのいずれかの処理で失敗した場合は、エラーを回避するために関数を途中で終了します。インジケーター値の取得が正常におこなわれたら、それぞれの値を扱いやすくするために変数へ代入し、直近の確定バーとその1本前のバーとで区別して扱います。これにより、常に最新の市場データを基に取引の判断を下すことが可能になります。データ取得が成功していれば、次は取引の判断のフェーズに進むことができます。まずは買いエントリーのロジックから始めます。

//--- Check for Buy Conditions:
bool maCrossoverBuy    = (ma11_previous < ma25_previous) && (ma11_current > ma25_current); //--- True if fast MA crosses above slow MA.
bool rsiConditionBuy   = (rsi_current > InpRSIThreshold); //--- True if RSI is above the Buy threshold.
bool cci36ConditionBuy = (cci36_current > 0); //--- True if CCI36 is positive.
bool cci55ConditionBuy = (cci55_current > 0); //--- True if CCI55 is positive.

if (maCrossoverBuy) { //--- If crossover for MA Buy is true...
   bool conditionsOk = true; //--- Initialize a flag to track if all conditions are met.
   
   //--- Check RSI condition for Buy.
   if (!rsiConditionBuy) { //--- If the RSI condition is not met...
      Print("Buy signal rejected: RSI condition not met. RSI=", rsi_current, " Threshold=", InpRSIThreshold); //--- Notify the user.
      conditionsOk = false; //--- Mark the conditions as not met.
   }
   //--- Check CCI36 condition for Buy.
   if (!cci36ConditionBuy) { //--- If the CCI36 condition is not met...
      Print("Buy signal rejected: CCI36 condition not met. CCI36=", cci36_current); //--- Notify the user.
      conditionsOk = false; //--- Mark the conditions as not met.
   }
   //--- Check CCI55 condition for Buy.
   if (!cci55ConditionBuy) { //--- If the CCI55 condition is not met...
      Print("Buy signal rejected: CCI55 condition not met. CCI55=", cci55_current); //--- Notify the user.
      conditionsOk = false; //--- Mark the conditions as not met.
   }

ここでは、買い取引をおこなう条件を評価します。maCrossoverBuy変数は、短期移動平均(ma11)が長期移動平均(ma25)を上抜けたかどうかを確認し、買いエントリーの初期シグナルとなり得るかを判断します。rsiConditionBuyは、RSIの値が設定されたInpRSIThresholdを上回っているかどうかを確認し、強い上昇モメンタムがあるかどうかを判断します。cci36ConditionBuyとcci55ConditionBuyは、それぞれのCCIがプラスであるかどうかをチェックし、市場が好ましいトレンドにあることを示します。maCrossoverBuyの条件が満たされていれば、残りの条件を検証に進みます。いずれかの条件が満たされない場合には、なぜ買いシグナルが却下されたのかを示すメッセージを出力し、条件の合否を示すフラグであるconditionsOkをfalseに設定して、以降の取引が実行されないようにします。この包括的なチェックによって、強い上昇の確認が取れた場合にのみ、買い取引が実行されるようになります。条件がすべて整っていれば、次にポジションを建てる処理に進むことができます。

if (conditionsOk) { //--- If all Buy conditions are met...
   //--- Get stop loss from previous swing low.
   double stopLoss = GetPivotLow(PivotLeft, PivotRight); //--- Use pivot low as the stop loss.
   if (stopLoss <= 0) { //--- If no valid pivot low is found...
      stopLoss = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Fallback to current bid price.
   }
   
   double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Determine entry price as current ask price.
   double tp         = entryPrice + InpTakeProfitPoints * _Point; //--- Calculate take profit based on fixed points.
   
   //--- Print the swing point (pivot low) used as stop loss.
   Print("Buy signal: Swing Low used as Stop Loss = ", stopLoss); //--- Notify the user of the pivot low used.
   
   if (obj_Trade.Buy(InpLotSize, NULL, entryPrice, stopLoss, tp, "Buy Order")) { //--- Attempt to open a Buy order.
      Print("Buy order opened at ", entryPrice); //--- Notify the user if the order is opened successfully.
   }
   else {
      Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Notify the user if the order fails.
   }
   
   return; //--- Exit after processing a valid Buy signal.
}
else {
   Print("Buy signal not executed due to failed condition(s)."); //--- Notify the user if Buy conditions failed.
}

すべての買い条件が満たされたことを確認した後、取引のストップロスを決定します。GetPivotLow関数を使って直近のスイングロー(安値の転換点)を取得し、それをストップロスに設定します。有効なピボットローが見つからなかった場合(つまりストップロスがゼロ以下の場合)は、代わりに現在のビッド価格をストップロスとして使用します。エントリー価格は、SymbolInfoDouble関数のSYMBOL_ASKパラメータを用いて現在のAsk価格から取得します。テイクプロフィット(tp)は、指定されたInpTakeProfitPointsをエントリープライスに加算し、市場のポイント値(_Point)で調整して計算します。

エントリー価格、ストップロス、テイクプロフィットが決定したら、obj_Trade.Buyメソッドを使って買い注文の発注を試みます。注文が正常に約定した場合は、Print関数で確認メッセージを出力します。注文が失敗した場合は、obj_Trade.ResultRetcodeDescriptionを使って失敗理由を出力します。買い条件が満たされなかった場合は、買いシグナルが却下された旨のメッセージを表示し、取引は実行されません。ここで用いたGetPivotLowのカスタム関数とその実装例は以下の通りです。

//+------------------------------------------------------------------+
//| Function to find the most recent swing low (pivot low)           |
//+------------------------------------------------------------------+
double GetPivotLow(int left, int right) {
   MqlRates rates[]; //--- Declare an array to store rate data.
   int copied = CopyRates(_Symbol, _Period, 0, 100, rates); //--- Copy the last 100 bars into the rates array.
   if (copied <= (left + right)) { //--- Check if sufficient data was copied.
      return (0); //--- Return 0 if there are not enough bars.
   }
   
   //--- Loop through the bars to find a pivot low.
   for (int i = left; i <= copied - right - 1; i++) {
      bool isPivot = true; //--- Assume the current bar is a pivot low.
      double currentLow = rates[i].low; //--- Get the low value of the current bar.
      for (int j = i - left; j <= i + right; j++) { //--- Loop through neighboring bars.
         if (j == i) { //--- Skip the current bar.
            continue;
         }
         if (rates[j].low <= currentLow) { //--- If any neighbor's low is lower or equal,
            isPivot = false; //--- then the current bar is not a pivot low.
            break;
         }
      }
      if (isPivot) { //--- If a pivot low is confirmed,
         return (currentLow); //--- return the low value of the pivot.
      }
   }
   return (0); //--- Return 0 if no pivot low is found.
}

この関数では、CopyRates関数を使って過去100本のバーの市場データを取得し、直近のスイングロー(ピボットロー)を特定することを目的としています。MqlRates構造体の配列であるratesに価格データが格納され、計算に必要なバー数が確保されているかどうかを、コピーしたデータ数がleftとrightの合計以上かどうかでチェックします。関数はバーをループ処理し、指定されたleftとrightの範囲内で現在のバーの安値と周囲のバーの安値を比較することでピボットローかどうかを判定します。周囲のいずれかのバーの安値が現在のバーの安値よりも低いか同じであれば、そのバーはピボットローではありません。ピボットローが見つかった場合は、その安値の値を返します。すべてのバーをチェックしてもピボットローが見つからない場合、関数は0を返します。

ピボットハイを取得する場合も同様の方法を用いますが、比較ロジックは逆になります。

//+------------------------------------------------------------------+
//| Function to find the most recent swing high (pivot high)         |
//+------------------------------------------------------------------+
double GetPivotHigh(int left, int right) {
   MqlRates rates[]; //--- Declare an array to store rate data.
   int copied = CopyRates(_Symbol, _Period, 0, 100, rates); //--- Copy the last 100 bars into the rates array.
   if (copied <= (left + right)) { //--- Check if sufficient data was copied.
      return (0); //--- Return 0 if there are not enough bars.
   }
   
   //--- Loop through the bars to find a pivot high.
   for (int i = left; i <= copied - right - 1; i++) {
      bool isPivot = true; //--- Assume the current bar is a pivot high.
      double currentHigh = rates[i].high; //--- Get the high value of the current bar.
      for (int j = i - left; j <= i + right; j++) { //--- Loop through neighboring bars.
         if (j == i) { //--- Skip the current bar.
            continue;
         }
         if (rates[j].high >= currentHigh) { //--- If any neighbor's high is higher or equal,
            isPivot = false; //--- then the current bar is not a pivot high.
            break;
         }
      }
      if (isPivot) { //--- If a pivot high is confirmed,
         return (currentHigh); //--- return the high value of the pivot.
      }
   }
   return (0); //--- Return 0 if no pivot high is found.
}

これらの関数を活用することで、買いポジションの場合と同様の逆のアプローチを使用して売り取引シグナルを処理できます。

//--- Check for Sell Conditions:
bool maCrossoverSell    = (ma11_previous > ma25_previous) && (ma11_current < ma25_current); //--- True if fast MA crosses below slow MA.
bool rsiConditionSell   = (rsi_current < (100.0 - InpRSIThreshold)); //--- True if RSI is below the Sell threshold.
bool cci36ConditionSell = (cci36_current < 0); //--- True if CCI36 is negative.
bool cci55ConditionSell = (cci55_current < 0); //--- True if CCI55 is negative.

if (maCrossoverSell) { //--- If crossover for MA Sell is true...
   bool conditionsOk = true; //--- Initialize a flag to track if all conditions are met.
   
   //--- Check RSI condition for Sell.
   if (!rsiConditionSell) { //--- If the RSI condition is not met...
      Print("Sell signal rejected: RSI condition not met. RSI=", rsi_current, " Required below=", (100.0 - InpRSIThreshold)); //--- Notify the user.
      conditionsOk = false; //--- Mark the conditions as not met.
   }
   //--- Check CCI36 condition for Sell.
   if (!cci36ConditionSell) { //--- If the CCI36 condition is not met...
      Print("Sell signal rejected: CCI36 condition not met. CCI36=", cci36_current); //--- Notify the user.
      conditionsOk = false; //--- Mark the conditions as not met.
   }
   //--- Check CCI55 condition for Sell.
   if (!cci55ConditionSell) { //--- If the CCI55 condition is not met...
      Print("Sell signal rejected: CCI55 condition not met. CCI55=", cci55_current); //--- Notify the user.
      conditionsOk = false; //--- Mark the conditions as not met.
   }
   
   if (conditionsOk) { //--- If all Sell conditions are met...
      //--- Get stop loss from previous swing high.
      double stopLoss = GetPivotHigh(PivotLeft, PivotRight); //--- Use pivot high as the stop loss.
      if (stopLoss <= 0) { //--- If no valid pivot high is found...
         stopLoss = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Fallback to current ask price.
      }
      
      double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Determine entry price as current bid price.
      double tp         = entryPrice - InpTakeProfitPoints * _Point; //--- Calculate take profit based on fixed points.
      
      //--- Print the swing point (pivot high) used as stop loss.
      Print("Sell signal: Swing High used as Stop Loss = ", stopLoss); //--- Notify the user of the pivot high used.
      
      if (obj_Trade.Sell(InpLotSize, NULL, entryPrice, stopLoss, tp, "Sell Order")) { //--- Attempt to open a Sell order.
         Print("Sell order opened at ", entryPrice); //--- Notify the user if the order is opened successfully.
      }
      else {
         Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Notify the user if the order fails.
      }
      
      return; //--- Exit after processing a valid Sell signal.
   }
   else {
      Print("Sell signal not executed due to failed condition(s)."); //--- Notify the user if Sell conditions failed.
   }
}

ここでは、買いシグナルのロジックを逆転させて、売りシグナルの条件をチェックします。まず、短期移動平均が長期移動平均を下抜けたかどうかをmaCrossoverSell条件で確認します。次に、RSIが売りの閾値を下回っているかどうかを検証し、さらにCCI36とCCI55の両方がマイナスであることを確認します。

すべての条件が満たされた場合、GetPivotHigh関数を使って直近のスイングハイ(高値の転換点)を特定し、これをストップロスとして設定します。また、テイクプロフィットは固定のポイント距離を基に算出します。その後、obj_Trade.Sellメソッドで売り注文を試みます。注文が成功すれば確認メッセージを出力し、失敗した場合はエラーメッセージを表示します。条件のいずれかが満たされなかった場合は、売りシグナルが却下された旨の通知をおこないます。プログラムをコンパイルして実行すると、次の結果が得られます。

買い取引が確認された

画像からわかるように、プログラムはすべてのエントリー条件を正確に特定・検証し、条件が満たされた場合にはそれぞれのパラメーターでポジションをオープンしています。これで、狙い通りの動作が実現できていることがわかります。 残された作業はプログラムのバックテストであり、それについては次のセクションで取り扱います。


バックテスト

バックテストを集中的におこなった結果、スイングポイントを見つける際に、最も古いデータを最初に比較に使用していたことが判明しました。そのため、まれではあるものの無効なスイングポイントが発生し、それによりストップロスの無効なストップ値が設定されることがありました。

無効ストップエラー

この問題を軽減するために、ArraySetAsSeries関数を使用して検索データを時系列として設定するアプローチを採用しました。これにより、ストレージ配列の先頭に最新のデータが配置されるようになり、以下のように分析時には最新のデータを優先して使用できるようになりました。

//+------------------------------------------------------------------+
//| Function to find the most recent swing low (pivot low)           |
//+------------------------------------------------------------------+
double GetPivotLow(int left, int right) {
   MqlRates rates[]; //--- Declare an array to store rate data.
   ArraySetAsSeries(rates, true);

//---



}

さらに確認テストをおこなったところ、次の結果が得られました。

修正されたスイングポイント

画像から分かるように、実際の直近のスイングポイントを正しく取得できており、それにより「無効なストップ」のエラーは解消されました。したがって、取引から締め出されることもなくなり、2023年から2024年にかけての綿密なテストの結果、以下のような成果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

結論として、複数のトレンドおよびモメンタム指標を組み合わせ、売買シグナルの両方に対応する包括的な「トレンドフラットモメンタム」取引戦略を自動化するためのMQL5 EAを開発することに成功しました。インジケーターのクロスオーバーや閾値チェックといった主要な条件を組み込むことで、市場のトレンドに応じて正確なエントリーポイントおよびイグジットポイントで反応するダイナミックなシステムを構築しています。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場状況は急変する可能性があります。本戦略は構造化された取引手法を提供しますが、利益を保証するものではありません。ライブ環境で本システムを適用する前に、十分なバックテストと適切なリスク管理をおこなうことが不可欠です。

これらのコンセプトを実装することで、アルゴリズム取引のスキルを高め、テクニカル分析に対するアプローチをより洗練させることができます。今後の戦略開発と改善において、さらなる成功をお祈りします。

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

添付されたファイル |
外国為替平均回帰戦略のためのカルマンフィルター 外国為替平均回帰戦略のためのカルマンフィルター
カルマンフィルターは、価格変動のノイズを除去して金融時系列の真の状態を推定するために、アルゴリズム取引で用いられる再帰的なアルゴリズムです。新しい市場データに基づいて予測を動的に更新するため、平均回帰のような適応型戦略において非常に有用です。本記事ではまず、カルマンフィルターの計算方法と実装について紹介します。次に、このフィルターをクラシックな平均回帰型の外国為替(FX)戦略に適用する例を示します。最後に、異なる通貨ペアにおいてカルマンフィルターと移動平均を比較し、さまざまな統計分析をおこないます。
プライスアクション分析ツールキットの開発(第15回):クォーターズ理論の紹介(I) - Quarters Drawerスクリプト プライスアクション分析ツールキットの開発(第15回):クォーターズ理論の紹介(I) - Quarters Drawerスクリプト
サポートとレジスタンスのポイントは、トレンドの反転や継続の可能性を示す重要なレベルです。これらのレベルを見つけるのは難しいこともありますが、一度特定できれば、市場をより的確に捉える準備が整います。さらなるサポートとして、本記事で紹介されているQuarters Drawerツールをぜひご活用ください。このツールは、主要およびマイナーなサポート・レジスタンスレベルの特定に役立ちます。
PythonとMQL5による多銘柄分析(第3回):三角為替レート PythonとMQL5による多銘柄分析(第3回):三角為替レート
トレーダーは、誤ったシグナルによるドローダウンに直面することが多い一方で、確認を待ちすぎることで、有望な機会を逃すこともあります。本稿では、ドル建て銀価格(XAGUSD)、ユーロ建て銀価格(XAGEUR)、およびEURUSD為替レートを用いた三角裁定取引戦略を紹介し、市場のノイズをフィルタリングする方法を解説します。市場間の相関関係を活用することで、隠れた市場センチメントをリアルタイムで捉え、エントリータイミングをより洗練させることが可能になります。
MQL5経済指標カレンダーを使った取引(第6回):ニュースイベント分析とカウントダウンタイマーによるトレードエントリーの自動化 MQL5経済指標カレンダーを使った取引(第6回):ニュースイベント分析とカウントダウンタイマーによるトレードエントリーの自動化
本記事では、MQL5経済指標カレンダーを活用して、ユーザー定義のフィルターと時間オフセットに基づいた自動取引エントリーを実装します。対象となる経済指標イベントを検出し、予想値と前回値の比較により、買うか売るかの判断を下します。動的なカウントダウンタイマーは、ニュース公開までの残り時間を表示し、取引後には自動的にリセットされます。