English Deutsch
preview
MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築

MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築

MetaTrader 5トレーディング | 19 6月 2025, 08:16
64 9
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第6回)では、MetaQuotes Language 5 (MQL5)で自動化されたオーダーブロック検出システムを開発しました。今回の第7回では、「グリッドトレーディング」に焦点を当てます。これは、一定の価格間隔で取引を仕掛ける戦略で、動的ロットスケーリングを組み合わせることで、リスクとリターンの最適化を目指します。このアプローチでは、市場状況に応じてポジションサイズを調整することで、収益性の向上とリスク管理の強化を図ります。以下の内容をカバーします。

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

この記事を読み終える頃には、動的ロットスケーリング機能を備えたグリッドトレーディングのプログラムが完成し、テストおよび最適化の準備が整っている状態になるでしょう。それでは、始めましょう。


戦略設計図

グリッドトレーディングは、あらかじめ設定された価格間隔で買い注文と売り注文を配置する体系的なアプローチであり、明確なトレンド予測を必要とせずに市場の変動を活用することができます。この戦略は、一定の価格帯内で継続的に取引を開閉することで、市場のボラティリティから利益を得ることを目的としています。本戦略では、さらに動的ロットスケーリングを導入し、口座残高、ボラティリティ、過去の取引結果などの事前定義された条件に基づいてポジションサイズを調整することで、パフォーマンスの向上を図ります。私たちのグリッドトレーディングシステムは、以下の主要コンポーネントで構成されます。

  • グリッド構造:注文間の間隔(グリッド幅)を定義します。
  • エントリーおよび実行ルール移動平均インジケーターを活用し、一定の価格間隔に基づいてグリッド注文を発動する条件を設定します。
  • 動的ロットスケーリング:市場状況や事前に設定したリスクパラメータに応じて、ロットサイズを動的に調整する仕組みを実装します。
  • 取引管理:ストップロス、テイクプロフィット、ブレイクイーブン(損益分岐点)機能などを組み込み、リスクを効果的にコントロールします。
  • エグジット戦略:利益目標、リスク制限、トレンド反転などの条件に基づいてポジションをクローズするロジックを構築します。

要約すると、以下はこの戦略全体の設計図を視覚的に示したものです。

グリッドレイアウト

構造化されたグリッドシステムと適応型のロットサイズ調整を組み合わせることで、リターンの最大化とリスク管理の両立を図るEAを構築していきます。次に、これらのコンセプトをMQL5で実装していきましょう。


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 Grid Strategy"
#property strict

#include <Trade/Trade.mqh>                     //--- Include trading library
CTrade obj_Trade;                            //--- Trading object instance

//--- Closure Mode Enumeration and Inputs
enum ClosureMode {
   CLOSE_BY_PROFIT,      //--- Use total profit (in currency) to close positions
   CLOSE_BY_POINTS       //--- Use a points threshold from breakeven to close positions
};

input group "General EA Inputs"
input ClosureMode closureMode = CLOSE_BY_POINTS;    //Select closure mode

double breakevenPoints = 50 * _Point;                 //--- Points offset to add/subtract to/from breakeven

//--- Global Variables
double TakeProfit;                           //--- Current take profit level
double initialLotsize      = 0.1;            //--- Initial lot size for the first trade
double takeProfitPts       = 200 * _Point;    //--- Take profit distance in points
double profitTotal_inCurrency = 100;          //--- Profit target (in currency) to close positions

double gridSize;                             //--- Price level at which grid orders are triggered
double gridSize_Spacing   = 500 * _Point;      //--- Grid spacing in points
double LotSize;                              //--- Current lot size (increased with grid orders)

bool isTradeAllowed      = true;              //--- Flag to allow trade on a new bar
int totalBars            = 0;                 //--- Count of bars seen so far
int handle;                                  //--- Handle for the Moving Average indicator
double maData[];                             //--- Array for Moving Average data

ここでは、#includeを使用してTrade/Trade.mqhライブラリをインクルードし、取引を処理するためにオブジェクト「obj_Trade」をインスタンス化します。ポジションをクローズするためのオプションを含むClosureMode列挙体を定義し、closureModeやbreakevenPointsなどのユーザー入力を設定します。次に、テイクプロフィットレベル、初期ロットサイズ、グリッド間隔、動的ロットサイズの管理に使用する変数を宣言します。加えて、取引の制御や移動平均インジケーターのデータ管理に必要なフラグやカウンターも定義します。その後、プログラムの構造を整理するために、主要な関数のプロトタイプを宣言していきます。

//--- Function Prototypes
void   CheckAndCloseProfitTargets();         //--- Closes all positions if total profit meets target
void   ExecuteInitialTrade(double ask, double bid); //--- Executes the initial BUY/SELL trade (initial positions)
void   ManageGridPositions(double ask, double bid); //--- Adds grid orders when market moves to grid level (grid positions)
void   UpdateMovingAverage();                //--- Updates MA indicator data from its buffer
bool   IsNewBar();                           //--- Checks if a new bar has formed
double CalculateWeightedBreakevenPrice();    //--- Calculates the weighted average entry price for positions
void   CheckBreakevenClose(double ask, double bid); //--- Closes positions if price meets breakeven+/- threshold
void   CloseAllPositions();                  //--- Closes all open positions

関数の実装では、まず全体の利益状況を監視し、目標利益に達した時点でポジションをクローズするCheckAndCloseProfitTargetsを作成します。また、戦略を開始するために最初のBUYまたはSELL注文を発動するExecuteInitialTradeも実装します。ManageGridPositionsは、市場の動きに応じて設定されたグリッド間隔で追加注文を出します。一方、UpdateMovingAverageは、意思決定のためのインジケーター情報を常に最新の状態に保ちます。IsNewBarは、新しいバー(ローソク足)を検出して、同じローソク足上で複数の取引がおこなわれるのを防ぎます。CalculateWeightedBreakevenPriceは、複数ポジションの平均建値を計算し、CheckBreakevenCloseはその情報を利用して、有利な条件が整った際にポジションを決済します。最後にCloseAllPositionsは、必要に応じてすべてのポジションを段階的に決済します。

これらすべてをグローバルスコープに設定した後、OnInitイベントハンドラでプログラムの初期化処理を進めます。

//+------------------------------------------------------------------+
//--- Expert initialization function
//+------------------------------------------------------------------+
int OnInit(){
   //--- Initialize the Moving Average indicator (Period: 21, SMA, Price: Close)
   handle = iMA(_Symbol, _Period, 21, 0, MODE_SMA, PRICE_CLOSE);
   if (handle == INVALID_HANDLE){
      Print("ERROR: UNABLE TO INITIALIZE THE INDICATOR. REVERTING NOW!");
      return (INIT_FAILED);
   }
   ArraySetAsSeries(maData, true);            //--- Ensure MA data array is in series order
   return(INIT_SUCCEEDED);
}

ここでは、iMA関数を使って、期間21、SMAタイプ、PRICE_CLOSEを用いた移動平均インジケーターを設定することで、プログラムの初期化をおこないます。まず、インジケーターのハンドルが有効かどうかを確認し、無効(INVALID_HANDLE)であればエラーメッセージを表示し、INIT_FAILEDを返してプログラムの実行を中止します。次に、maData配列に対してArraySetAsSeries関数を呼び出し、移動平均のデータが正しい順序で配置されるようにします。すべてが正常に完了したら、INIT_SUCCEEDEDを返して初期化が成功したことを確認します。正しく初期化された後は、OnTickイベントハンドラに進み、ポジションのエントリーおよび管理ロジックを構築していきます。

//+------------------------------------------------------------------+
//--- Expert tick function
//+------------------------------------------------------------------+
void OnTick(){
   
   //--- Allow new trade signals on a new bar
   if(IsNewBar())
      isTradeAllowed = true;
   
   //--- Update the Moving Average data
   UpdateMovingAverage();
   
}

ティックごとに取引のチェックをおこなうのではなく、各バーごとにおこないたいため、IsNewBar関数を呼び出し、新しいバーが形成された場合にisTradeAllowed変数をtrueに設定します。その後、移動平均の値を取得するための関数を呼び出します。以下に、その関数の定義を示します。

//+-------------------------------------------------------------------+
//--- Function: UpdateMovingAverage
//--- Description: Copies the latest data from the MA indicator buffer.
//+-------------------------------------------------------------------+
void UpdateMovingAverage(){
   if(CopyBuffer(handle, 0, 1, 3, maData) < 0)
      Print("Error: Unable to update Moving Average data.");
}
  
//+-------------------------------------------------------------------+
//--- Function: IsNewBar
//--- Description: Checks if a new bar has been formed.
//+-------------------------------------------------------------------+
bool IsNewBar(){
   int bars = iBars(_Symbol, _Period);
   if(bars > totalBars){
      totalBars = bars;
      return true;
   }
   return false;
}

ここでは、CopyBuffer関数を実装し、CopyBuffer関数を使って移動平均バッファから最新の値を取得し、インジケーターのデータを更新します。この関数の呼び出しが失敗した場合には、更新が正常におこなわれなかったことを示すエラーメッセージを出力します。次に、IsNewBar関数では、iBars関数を使用して現在のバー数を取得し、保存されているtotalBarsの値と比較することで、新しいバーが形成されたかどうかを確認します。バー数が増加していれば、totalBarsを更新し、trueを返すことで新しいバーが生成されたことを示し、取引判断の準備が整ったことになります。その後、ティック関数に進み、取得したインジケーターの値に基づいて取引の実行をおこないます。

//--- Reset lot size if no positions are open
if(PositionsTotal() == 0)
   LotSize = initialLotsize;

//--- Retrieve recent bar prices for trade signal logic
double low1  = iLow(_Symbol, _Period, 1);
double low2  = iLow(_Symbol, _Period, 2);
double high1 = iHigh(_Symbol, _Period, 1);
double high2 = iHigh(_Symbol, _Period, 2);

//--- Get current Ask and Bid prices (normalized)
double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);

//--- If no positions are open and trading is allowed, check for an initial trade signal
if(PositionsTotal() == 0 && isTradeAllowed){
   ExecuteInitialTrade(ask, bid);
}

ここでは、まずPositionsTotal関数を使ってポジションが一つも開かれていないことを確認し、その場合はLotSizeをinitialLotsizeにリセットします。次に、直近2本のローソク足の高値と安値を取得するために、iLowiHighを呼び出し、直近のバー価格情報を取得します。これらの情報は取引シグナルの判断に使用されます。その後、SymbolInfoDouble関数を使用して現在のaskおよびbid価格を取得しNormalizeDoubleを使って精度を確保した上で価格を正規化します。最後に、isTradeAllowedがtrueであり、かつポジションが未保有であることを条件として、ExecuteInitialTrade関数をaskとbid価格を引数にして呼び出し、最初の取引を実行します。関数の定義は以下のとおりです。

//+---------------------------------------------------------------------------+
//--- Function: ExecuteInitialTrade
//--- Description: Executes the initial BUY or SELL trade based on MA criteria.
//---              (These are considered "initial positions.")
//+---------------------------------------------------------------------------+
void ExecuteInitialTrade(double ask, double bid){
   //--- BUY Signal: previous bar's low above MA and bar before that below MA
   if(iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){
      gridSize = ask - gridSize_Spacing;     //--- Set grid trigger below current ask
      TakeProfit = ask + takeProfitPts;      //--- Set TP for BUY
      if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Initial Buy"))
         Print("Initial BUY order executed at ", ask, " with LotSize: ", LotSize);
      else
         Print("Initial BUY order failed at ", ask);
      isTradeAllowed = false;
   }
   //--- SELL Signal: previous bar's high below MA and bar before that above MA
   else if(iHigh(_Symbol, _Period, 1) < maData[1] && iHigh(_Symbol, _Period, 2) > maData[1]){
      gridSize = bid + gridSize_Spacing;     //--- Set grid trigger above current bid
      TakeProfit = bid - takeProfitPts;      //--- Set TP for SELL
      if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Initial Sell"))
         Print("Initial SELL order executed at ", bid, " with LotSize: ", LotSize);
      else
         Print("Initial SELL order failed at ", bid);
      isTradeAllowed = false;
   }
}

ここでは、maDataの値に基づいて初回取引をおこなうためのExecuteInitialTrade関数を実装します。まず、iLow関数を使用して直近2本のローソク足の安値を取得し、同様にiHigh関数を使って高値を取得します。買いシグナルの場合は、一つ前のバーの安値がmaDataより上にあり、その前のバーの安値がmaDataより下にあるかどうかを確認します。この条件が満たされた場合には、ask価格からgridSize_Spacingを引いた値を基にgridSizeを設定します。TakeProfitはaskにtakeProfitPtsを加えた価格として計算されます。その後、obj_Trade.Buyメソッドを使って買い取引を実行します。

売りシグナルでは、一つ前のバーの高値がmaDataより下にあり、その前のバーの高値がmaDataより上にあるかどうかを確認します。条件を満たした場合には、bidにgridSize_Spacingを加えてgridSizeを設定し、TakeProfitはbidからtakeProfitPtsを引いて計算します。そしてobj_Trade.Sellメソッドを用いて売り取引を実行します。取引が実行されたあとは、isTradeAllowedをfalseに設定して、さらなるエントリーがおこなわれないように制御します。以下はその結果です。

取引執行

画像から、取引が正常に実行されていることが確認できます。次のステップとしては、グリッドポジションを展開して取引を管理していく処理に進む必要があります。

//--- If positions exist, manage grid orders
if(PositionsTotal() > 0){
   ManageGridPositions(ask, bid);
}

PositionsTotal関数を使用して、ポジションがあるかどうかを確認します。ポジション数が0より大きい場合は、ManageGridPositions関数を呼び出して、追加のグリッドトレードを管理します。この関数は、askとbidを引数として受け取り、市場の動きに応じた適切な価格レベルを判断し、新たなグリッド注文を発注するために使用されます。関数のコードスニペットの実装は次のとおりです。

//+------------------------------------------------------------------------+
//--- Function: ManageGridPositions
//--- Description: When an initial position exists, grid orders are added 
//---              if the market moves to the grid level. (These orders are 
//---              considered "grid positions.") The lot size is doubled 
//---              with each grid order.
//+------------------------------------------------------------------------+
void ManageGridPositions(double ask, double bid){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         int positionType = (int)PositionGetInteger(POSITION_TYPE);
         //--- Grid management for BUY positions
         if(positionType == POSITION_TYPE_BUY){
            if(ask <= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Grid Position BUY"))
                  Print("Grid BUY order executed at ", ask, " with LotSize: ", LotSize);
               else
                  Print("Grid BUY order failed at ", ask);
               gridSize = ask - gridSize_Spacing; //--- Update grid trigger
            }
         }
         //--- Grid management for SELL positions
         else if(positionType == POSITION_TYPE_SELL){
            if(bid >= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Grid Position SELL"))
                  Print("Grid SELL order executed at ", bid, " with LotSize: ", LotSize);
               else
                  Print("Grid SELL order failed at ", bid);
               gridSize = bid + gridSize_Spacing; //--- Update grid trigger
            }
         }
      }
   }
}

ManageGridPositions関数を実装し、グリッド注文を管理します。まず、forループですべてのポジションを逆順に繰り返し処理し、PositionGetTicket関数で各ポジションのチケット番号を取得します。次に、PositionSelectByTicketを使ってそのポジションを選択し、PositionGetInteger関数のPOSITION_TYPEパラメータで買いか売りかどうかを判定します。買いポジションの場合、市場価格のaskがgridSizeに達するか、あるいはそれを下回っているかどうかを確認します。条件が満たされれば、LotSizeを倍に増やし、obj_Trade.Buy関数で新たなグリッド買い注文を発注します。注文が成功したら確認メッセージを出力し、失敗した場合はエラーメッセージを表示します。その後、gridSizeを次のグリッドレベル(下のほう)に更新します。

同様に、売りポジションの場合はbidがgridSizeに達するか、またはそれを上回っているかどうかをチェックし、条件を満たせばLotSizeを倍にしてobj_Trade.Sellで新たなグリッド売り注文を出します。その後、gridSizeを次のグリッドレベル(上のほう)に更新します。グリッドポジションを開いた後は、以下のように定義した条件に達したらポジションを決済し、管理していく必要があります。

//--- Check if total profit meets the target (only used if closureMode == CLOSE_BY_PROFIT)
if(closureMode == CLOSE_BY_PROFIT)
   CheckAndCloseProfitTargets();

closureModeがCLOSE_BY_PROFITに設定されている場合、CheckAndCloseProfitTargets関数を呼び出して、合計利益があらかじめ設定された目標に達しているかどうかを確認し、それに応じてすべてのポジションを決済します。関数の宣言は以下の通りです。

//+----------------------------------------------------------------------------+
//--- Function: CheckAndCloseProfitTargets
//--- Description: Closes all positions if the combined profit meets or exceeds
//---              the user-defined profit target.
//+----------------------------------------------------------------------------+
void CheckAndCloseProfitTargets(){
   if(PositionsTotal() > 1){
      double totalProfit = 0;
      for(int i = PositionsTotal()-1; i >= 0; i--){
         ulong tkt = PositionGetTicket(i);
         if(PositionSelectByTicket(tkt))
            totalProfit += PositionGetDouble(POSITION_PROFIT);
      }
      if(totalProfit >= profitTotal_inCurrency){
         Print("Profit target reached (", totalProfit, "). Closing all positions.");
         CloseAllPositions();
      }
   }
}

合計利益があらかじめ設定された利益目標に達しているか、あるいはそれを超えている場合にすべてのポジションを決済するため、まずPositionsTotalを使ってポジションが1つ以上あるかどうかを確認します。次に、totalProfitを初期化し、全ポジションの合計利益を管理します。その後、forループですべてのポジションを順に処理し、PositionGetTicketでチケット番号を取得、PositionSelectByTicketでポジションを選択します。選択した各ポジションについて、PositionGetDoublePOSITION_PROFITパラメータを使い利益額を取得し、それをtotalProfitに加算します。totalProfitがprofitTotal_inCurrencyに達した、もしくはそれを超えた場合、利益目標に到達したことを示すメッセージを表示し、以下に示すCloseAllPositions関数を呼び出してすべてのポジションを決済します。

//+------------------------------------------------------------------+
//--- Function: CloseAllPositions
//--- Description: Iterates through and closes all open positions.
//+------------------------------------------------------------------+
void CloseAllPositions(){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong posTkt = PositionGetTicket(i);
      if(PositionSelectByTicket(posTkt)){
         if(obj_Trade.PositionClose(posTkt))
            Print("Closed position ticket: ", posTkt);
         else
            Print("Failed to close position ticket: ", posTkt);
      }
   }
}

この関数は、すべてのポジションを順に繰り返し処理し、選択された各ポジションに対してobj_Trade.PositionCloseメソッドを使って決済をおこないます。最後に、ポジションをブレイクイーブンで決済するためのロジックを定義します。

//--- If using CLOSE_BY_POINTS and more than one position exists (i.e. grid), check breakeven closure
if(closureMode == CLOSE_BY_POINTS && PositionsTotal() > 1)
   CheckBreakevenClose(ask, bid);

closureModeがCLOSE_BY_POINTSに設定されていて、ポジションが1つ以上ある場合、askとbidを引数にしてCheckBreakevenClose関数を呼び出します。この関数は、価格がブレイクイーブンの閾値に達したかどうかを判定し、あらかじめ定められたブレイクイーブンからのポイントに基づいてポジションを決済できるかどうかを判断します。以下に関数の定義を示します。

//+----------------------------------------------------------------------------+
//--- Function: CalculateWeightedBreakevenPrice
//--- Description: Calculates the weighted average entry price (breakeven)
//---              of all open positions (assumed to be in the same direction).
//+----------------------------------------------------------------------------+
double CalculateWeightedBreakevenPrice(){
   double totalCost = 0;
   double totalVolume = 0;
   int posType = -1;
   //--- Determine the type from the first position
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   //--- Sum the cost and volume for positions matching the type
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         if(PositionGetInteger(POSITION_TYPE) == posType){
            double price = PositionGetDouble(POSITION_PRICE_OPEN);
            double volume = PositionGetDouble(POSITION_VOLUME);
            totalCost += price * volume;
            totalVolume += volume;
         }
      }
   }
   if(totalVolume > 0)
      return(totalCost / totalVolume);
   else
      return(0);
}
  
//+-----------------------------------------------------------------------------+
//--- Function: CheckBreakevenClose
//--- Description: When using CLOSE_BY_POINTS and multiple positions exist,
//---              calculates the weighted breakeven price and checks if the
//---              current price has moved the specified points in a profitable
//---              direction relative to breakeven. If so, closes all positions.
//+-----------------------------------------------------------------------------+
void CheckBreakevenClose(double ask, double bid){
   //--- Ensure we have more than one position (grid positions)
   if(PositionsTotal() <= 1)
      return;
      
   double weightedBreakeven = CalculateWeightedBreakevenPrice();
   int posType = -1;
   //--- Determine the trade type from one of the positions
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   if(posType == -1)
      return;
      
   //--- For BUY positions, profit when Bid >= breakeven + threshold
   if(posType == POSITION_TYPE_BUY){
      if(bid >= weightedBreakeven + breakevenPoints){
         Print("Closing BUY positions: Bid (", bid, ") >= Breakeven (", weightedBreakeven, ") + ", breakevenPoints);
         CloseAllPositions();
      }
   }
   //--- For SELL positions, profit when Ask <= breakeven - threshold
   else if(posType == POSITION_TYPE_SELL){
      if(ask <= weightedBreakeven - breakevenPoints){
         Print("Closing SELL positions: Ask (", ask, ") <= Breakeven (", weightedBreakeven, ") - ", breakevenPoints);
         CloseAllPositions();
      }
   }
}

ここでは、すべてのポジションのブレイクイーブン価格を計算し、市場価格がその価格から一定の距離を超えたかどうかを判定して、利益確定のためにポジションを決済できるか判断します。CalculateWeightedBreakevenPrice関数では、POSITION_PRICE_OPENを使って各ポジションのコストを合計し、それをPOSITION_VOLUMEで重み付けして加重平均のブレイクイーブン価格を算出します。まず、最初のポジションからPOSITION_TYPEを取得し、買いか売りかを判別します。次に全ポジションをループし、そのタイプと一致するポジションの合計コストと合計取引量を計算します。合計取引量がゼロより大きければ、合計コストを合計取引量で割った値を返し、そうでなければゼロを返します。

CheckBreakevenClose関数では、まずPositionsTotal関数で複数のポジションがあるかどうかを確認します。続いてCalculateWeightedBreakevenPriceを呼び出して加重ブレイクイーブン価格を取得し、選択したポジションのPOSITION_TYPEを調べます。タイプが無効であれば関数を終了します。買いポジションの場合は、bid価格がweightedBreakevenにbreakevenPointsを加えた値以上になったかどうかをチェックし、条件を満たした場合はメッセージを表示してCloseAllPositionsを呼び出します。売りポジションの場合は、ask価格がweightedBreakevenからbreakevenPointsを引いた値を下回ったかどうかを確認し、条件成立時には同様にメッセージを出してCloseAllPositions関数を呼び、利益確定をおこないます。プログラムをコンパイルし実行すると、以上のロジックに基づいた動作が確認できます。

グリッドGIF

視覚化された図から、ポジションがグリッドシステムに従ってオープン・管理され、定義された決済条件に達した時点で適切にクローズされていることが確認できます。これにより、動的なロットサイズ調整を備えたグリッドトレーディングシステムの構築という目的は達成されました。残された作業はプログラムのバックテストであり、それについては次のセクションで取り扱います。


バックテスト

徹底的なバックテストの結果、次の結果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート

こちらは、2024年の1年間にわたるストラテジーのバックテスト全体を紹介する動画形式の資料です。



結論

本稿では、動的なグリッドトレーディング戦略を活用したMQL5 EAの開発プロセスを解説しました。グリッド注文の配置、ロットサイズの動的調整、利益確定およびブレイクイーブン管理といった重要な要素を組み合わせることで、市場の変動に柔軟に対応し、リスクとリワードのバランスを最適化しながら、逆行する価格からの回復を目指すシステムを構築しました。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な経済的リスクが伴い、市場の動きは極めて予測困難です。本稿で紹介した戦略は、構造的なアプローチを提供しますが、将来の利益を保証するものではありません。実運用前には、徹底したバックテストとリスク管理が不可欠です。

これらの手法を取り入れることで、グリッドトレーディングシステムの精度を高めて市場分析の質を向上させ、アルゴリズム取引戦略の強化につながるでしょう。皆様の取引の成功をお祈りしております。

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (9)
testtestmio71
testtestmio71 | 4 4月 2025 において 14:49
double low1  = iLow(_Symbol, _Period, 1);

例えば......買いシグナルに iLowを使い、Low1 variabileを使わない。

if (iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){

これは私の勉強のためだけです!

testtestmio71
testtestmio71 | 4 4月 2025 において 14:58

この4行はコメントすることができる。

// double low1  = iLow(_Symbol, _Period, 1);
// double low2  = iLow(_Symbol, _Period, 2);
// double high1 = iHigh(_Symbol, _Period, 1);
// double high2 = iHigh(_Symbol, _Period, 2);
Allan Munene Mutiiria
Allan Munene Mutiiria | 4 4月 2025 において 15:13
testtestmio71 #:

この4行はコメントすることができる。

もちろんです。

testtestmio71
testtestmio71 | 4 4月 2025 において 15:19

最高のEAだ。

初心者には4本の線がわかりにくい。

Allan Munene Mutiiria
Allan Munene Mutiiria | 4 4月 2025 において 16:35
testtestmio71 #:

最高のEAだ。

初心者には4本の線がわかりにくい。

オーケー

プライスアクション分析ツールキットの開発(第14回):Parabolic Stop and Reverseツール プライスアクション分析ツールキットの開発(第14回):Parabolic Stop and Reverseツール
プライスアクション分析にテクニカルインジケーターを取り入れることは、非常に有効なアプローチです。これらのインジケーターは、反転や押し戻しの重要なレベルを示すことが多く、市場の動きを把握する上での貴重な手がかりとなります。本記事では、パラボリックSARインジケーターを用いてシグナルを生成する自動ツールをどのように開発したかを紹介します。
プライスアクション分析ツールキットの開発(第13回):RSIセンチネルツール プライスアクション分析ツールキットの開発(第13回):RSIセンチネルツール
プライスアクションは、ダイバージェンスを特定することで効果的に分析することができます。RSI(相対力指数)などのテクニカル指標は、その確認シグナルとして重要な役割を果たします。本記事では、自動化されたRSIダイバージェンス分析によって、トレンドの継続や反転をどのように識別できるかを解説し、市場心理を読み解く上で理解を深める手助けをします。
MQL5で自己最適化エキスパートアドバイザーを構築する(第6回):ストップアウト防止 MQL5で自己最適化エキスパートアドバイザーを構築する(第6回):ストップアウト防止
本日は、勝ちトレードでストップアウトされる回数を最小限に抑えるためのアルゴリズム的手法を探るディスカッションにご参加ください。この問題は非常に難易度が高く、取引コミュニティで見られる多くの提案は、明確で一貫したルールに欠けているのが実情です。私たちはこの課題に対してアルゴリズム的なアプローチを用いることで、トレードの収益性を高め、1回あたりの平均損失を減らすことに成功しました。とはいえ、ストップアウトを完全に排除するには、まださらなる改良が必要です。私たちの解決策は、それには至らないものの、誰にとっても試す価値のある良い第一歩です。
MQL5で取引管理者パネルを作成する(第9回):コード編成(I)モジュール化 MQL5で取引管理者パネルを作成する(第9回):コード編成(I)モジュール化
本ディスカッションでは、MQL5プログラムをより小さく扱いやすいモジュールに分割する一歩を踏み出します。これらのモジュール化されたコンポーネントをメインプログラムに統合することで、構造が整理され保守性が向上します。この手法によりメインプログラムの構造が簡素化されるだけでなく、各コンポーネントを他のエキスパートアドバイザー(EA)やインジケーター開発にも再利用可能にします。モジュール設計を採用することで、将来的な機能拡張の基盤を確立し、私たちのプロジェクトだけでなく広く開発者コミュニティにも貢献できるものとなります。