English Deutsch
preview
MQL5での取引戦略の自動化(第17回):ダイナミックダッシュボードで実践するグリッドマーチンゲールスキャルピング戦略

MQL5での取引戦略の自動化(第17回):ダイナミックダッシュボードで実践するグリッドマーチンゲールスキャルピング戦略

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

はじめに

前回の記事(第16回)では、ミッドナイトレンジブレイクアウト戦略を「Break of Structure」と組み合わせ、自動化によって価格ブレイクアウトを捉える方法を解説しました。今回の第17回では、グリッドマーチンゲールスキャルピング戦略(Grid-Mart Scalping Strategy)をMetaQuotes Language 5 (MQL5)で自動化し、グリッド型マーチンゲール取引を実行するエキスパートアドバイザー(EA)を開発します。このEAにはリアルタイム監視用のダイナミックダッシュボードを搭載します。この記事では以下のトピックを取り上げます。

  1. グリッドマーチンゲールスキャルピング戦略を理解する
  2. MQL5での実装
  3. バックテスト
  4. 結論

この記事を読み終える頃には、市場を精密にスキャルピングし、取引指標を可視化する完全機能搭載のMQL5プログラムを手に入れることができます。さっそく始めましょう。


グリッドマーチンゲールスキャルピング戦略を理解する

グリッドマーチンゲールスキャルピング戦略は、グリッド型のマーチンゲール手法を用います。具体的には、一定の価格間隔(例:2.0 pips)ごとに買いまたは売り注文を配置し、市場の小さな変動から利益を獲得します。損失が発生した場合はロットサイズを増加させ、資本の迅速な回復を狙います。この戦略は高頻度取引に依存しており、取引ごとにわずかな利益(例:4 pips)を目標とします。ただし、ロットサイズが指数関数的に増加するため、リスク管理が不可欠です。最大グリッド段数や日次ドローダウン閾値など、設定可能な制限により制御します。この戦略は変動性の高い市場で優位性を発揮しますが、長期的なトレンド相場では大幅なドローダウンにつながる可能性があり、緻密な設定が求められます。

私たちの実装計画は、MQL5のEAを作成し、グリッドマーチンゲール戦略を自動化することから始まります。このEAはグリッド間隔を計算し、ロットサイズの進行を管理しながら、あらかじめ設定されたストップロスおよびテイクプロフィットに基づいて取引を実行します。また、スプレッドやアクティブロットサイズ、口座ステータスなどのリアルタイム指標を表示するダイナミックダッシュボードを備え、色分けされたビジュアルによって意思決定を支援します。さらに、ドローダウン制限やグリッド段数制限などの堅牢なリスク管理を組み込むことで、さまざまな市場環境においても一貫したパフォーマンスを確保することを目指しています。要するに、これが私たちの目指す作成物です。

戦略計画


MQL5での実装

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

//+------------------------------------------------------------------+
//|                                      GridMart Scalper MT5 EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+

#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade/Trade.mqh>

//--- Declare trade object to execute trading operations
CTrade obj_Trade;

//--- Trading state variables
double dailyBalance = 0;           //--- Store the daily starting balance for drawdown monitoring
datetime dailyResetTime = 0;       //--- Track the last daily reset time
bool tradingEnabled = true;        //--- Indicate if trading is allowed based on drawdown limits
datetime lastBarTime = 0;          //--- Store the timestamp of the last processed bar
bool hasBuyPosition = false;       //--- Flag if there is an active buy position
bool hasSellPosition = false;      //--- Flag if there is an active sell position
bool openNewTrade = false;         //--- Signal when a new trade should be opened
int activeOrders = 0;              //--- Count the number of active orders
double latestBuyPrice = 0;         //--- Store the price of the latest buy order
double latestSellPrice = 0;        //--- Store the price of the latest sell order
double calculatedLot = 0;          //--- Hold the calculated lot size for new orders
bool modifyPositions = false;      //--- Indicate if position SL/TP need modification
double weightedPrice = 0;          //--- Track the weighted average price of open positions
double targetTP = 0;               //--- Store the target take-profit price
double targetSL = 0;               //--- Store the target stop-loss price
bool updateSLTP = false;           //--- Signal when SL/TP updates are needed
int cycleCount = 0;                //--- Count the number of trading cycles
double totalVolume = 0;            //--- Accumulate the total volume of open positions
bool dashboardVisible = true;      //--- Control visibility of the dashboard

//--- Dashboard dragging and hover state variables
bool panelDragging = false;        //--- Indicate if the dashboard is being dragged
int panelDragX = 0;                //--- Store the X-coordinate of the mouse during dragging
int panelDragY = 0;                //--- Store the Y-coordinate of the mouse during dragging
int panelStartX = 0;               //--- Store the initial X-position of the dashboard
int panelStartY = 0;               //--- Store the initial Y-position of the dashboard
bool closeButtonHovered = false;   //--- Track if the close button is hovered
bool headerHovered = false;        //--- Track if the header is hovered

input group "Main EA Settings"
input string EA_NAME = "GridMart Scalper";    // EA Name
input bool CONTINUE_TRADING = true;           // Continue Trading After Cycle
input int MAX_CYCLES = 1000;                  // Max Trading Cycles
input int START_HOUR = 0;                     // Start Trading Hour
input int END_HOUR = 23;                      // End Trading Hour
input int LOT_MODE = 1;                       // Lot Mode (1=Multiplier, 2=Fixed)
input double BASE_LOT = 0.01;                 // Base Lot Size
input int STOP_LOSS_PIPS = 100;               // Stop Loss (Pips)
input int TAKE_PROFIT_PIPS = 
3 ;               // Take Profit (Pips)
input double GRID_DISTANCE = 3.0;             // Grid Distance (Pips)
input double LOT_MULTIPLIER = 1.3;            // Lot Multiplier
input int MAX_GRID_LEVELS = 30;               // Max Grid Levels
input int LOT_PRECISION = 2;                  // Lot Decimal Precision
input int MAGIC = 1234567890;                 // Magic Number
input color TEXT_COLOR = clrWhite;            // Dashboard Text Color

input group "EA Risk Management Settings"
input bool ENABLE_DAILY_DRAWDOWN = false;     // Enable Daily Drawdown Limiter
input double DRAWDOWN_LIMIT = -1.0;           // Daily Drawdown Threshold (-%)
input bool CLOSE_ON_DRAWDOWN = false;         // Close Positions When Threshold Hit

input group "Dashboard Settings"
input int PANEL_X = 30;                       // Initial X Distance (pixels)
input int PANEL_Y = 50;                       // Initial Y Distance (pixels)

//--- Pip value for price calculations
double pipValue;

//--- Dashboard constants
const int DASHBOARD_WIDTH = 300;                   //--- Width of the dashboard in pixels
const int DASHBOARD_HEIGHT = 260;                  //--- Height of the dashboard in pixels
const int HEADER_HEIGHT = 30;                      //--- Height of the header section
const int CLOSE_BUTTON_WIDTH = 40;                 //--- Width of the close button
const int CLOSE_BUTTON_HEIGHT = 28;                //--- Height of the close button
const color HEADER_NORMAL_COLOR = clrGold;         //--- Normal color of the header
const color HEADER_HOVER_COLOR = C'200,150,0';     //--- Header color when hovered
const color BACKGROUND_COLOR = clrDarkSlateGray;   //--- Background color of the dashboard
const color BORDER_COLOR = clrBlack;               //--- Border color of dashboard elements
const color SECTION_TITLE_COLOR = clrLightGray;    //--- Color for section titles
const color CLOSE_BUTTON_NORMAL_BG = clrCrimson;   //--- Normal background color of the close button
const color CLOSE_BUTTON_HOVER_BG = clrDodgerBlue; //--- Hover background color of the close button
const color CLOSE_BUTTON_NORMAL_BORDER = clrBlack; //--- Normal border color of the close button
const color CLOSE_BUTTON_HOVER_BORDER = clrBlue;   //--- Hover border color of the close button
const color VALUE_POSITIVE_COLOR = clrLimeGreen;   //--- Color for positive values (e.g., profit, low spread)
const color VALUE_NEGATIVE_COLOR = clrOrange;      //--- Color for negative or warning values (e.g., loss, high spread)
const color VALUE_LOSS_COLOR = clrHotPink;         //--- Color for negative profit
const color VALUE_ACTIVE_COLOR = clrGold;          //--- Color for active states (e.g., open orders, medium spread)
const color VALUE_DRAWDOWN_INACTIVE = clrAqua;     //--- Color for inactive drawdown state
const color VALUE_DRAWDOWN_ACTIVE = clrRed;        //--- Color for active drawdown state
const int FONT_SIZE_HEADER = 12;                   //--- Header text font size (pt)
const int FONT_SIZE_SECTION_TITLE = 11;            //--- Section title font size (pt)
const int FONT_SIZE_METRIC = 9;                    //--- Metric label/value font size (pt)
const int FONT_SIZE_BUTTON = 12;                   //--- Button font size (pt)

ここでは、戦略をMQL5で実装し、グリッド型マーチンゲール取引を自動化しながらダイナミックダッシュボードをサポートするためのプログラムのコアコンポーネントを初期化します。まず、#include <Trade/Trade.mqh>を用いてCTradeオブジェクトをobj_Tradeとして宣言し、取引の実行を管理します。次に、口座残高を追跡するためのdailyBalance、iTime関数でバーのタイムスタンプを保存するためのlastBarTime、アクティブな取引を判定するためのhasBuyPositionとhasSellPosition、そして保有ポジション数を数えるためのactiveOrdersなどの変数を定義します。

inputsパラメータとしては、グリッド間隔を設定するGRID_DISTANCE = 3.0、ロット拡大を制御するLOT_MULTIPLIER = 1.3、利益目標を示すTAKE_PROFIT_PIPS = 3を指定し、取引識別用にMAGIC = 1234567890を設定します。また、リスク管理のためにENABLE_DAILY_DRAWDOWNとDRAWDOWN_LIMITを導入し、取引サイクルを制御するcycleCountとMAX_CYCLESを設けています。ダッシュボードの構成としては、DASHBOARD_WIDTH = 300やFONT_SIZE_METRIC = 9を用いて表示を調整し、panelDraggingによってパネルをドラッグ可能にし、closeButtonHoveredによるホバー効果を組み込みます。さらに、数値の表示にはVALUE_POSITIVE_COLOR = clrLimeGreenを適用し、pipValueによって価格精度を管理します。これらによって、以下のようなユーザーインターフェースが実現されます。

ユーザーインターフェース

画像から分かるように、定義されたユーザーインターフェースを通じてプログラムを操作できます。ここからは、通貨ペアやポジションタイプといった基本的かつ頻繁に必要となる処理をおこなうために利用する、いくつかのヘルパー関数を定義していく必要があります。以下に、そのために採用したロジックを示します。

//+------------------------------------------------------------------+
//| Retrieve the current account balance                             |
//+------------------------------------------------------------------+
double GetAccountBalance() {
   //--- Return the current account balance
   return AccountInfoDouble(ACCOUNT_BALANCE);
}

//+------------------------------------------------------------------+
//| Retrieve the magic number of the selected position               |
//+------------------------------------------------------------------+
long GetPositionMagic() {
   //--- Return the magic number of the selected position
   return PositionGetInteger(POSITION_MAGIC);
}

//+------------------------------------------------------------------+
//| Retrieve the open price of the selected position                 |
//+------------------------------------------------------------------+
double GetPositionOpenPrice() {
   //--- Return the open price of the selected position
   return PositionGetDouble(POSITION_PRICE_OPEN);
}

//+------------------------------------------------------------------+
//| Retrieve the stop-loss price of the selected position            |
//+------------------------------------------------------------------+
double GetPositionSL() {
   //--- Return the stop-loss price of the selected position
   return PositionGetDouble(POSITION_SL);
}

//+------------------------------------------------------------------+
//| Retrieve the take-profit price of the selected position          |
//+------------------------------------------------------------------+
double GetPositionTP() {
   //--- Return the take-profit price of the selected position
   return PositionGetDouble(POSITION_TP);
}

//+------------------------------------------------------------------+
//| Retrieve the symbol of the selected position                     |
//+------------------------------------------------------------------+
string GetPositionSymbol() {
   //--- Return the symbol of the selected position
   return PositionGetString(POSITION_SYMBOL);
}

//+------------------------------------------------------------------+
//| Retrieve the ticket number of the selected position              |
//+------------------------------------------------------------------+
ulong GetPositionTicket() {
   //--- Return the ticket number of the selected position
   return PositionGetInteger(POSITION_TICKET);
}

//+------------------------------------------------------------------+
//| Retrieve the open time of the selected position                  |
//+------------------------------------------------------------------+
datetime GetPositionOpenTime() {
   //--- Return the open time of the selected position as a datetime
   return (datetime)PositionGetInteger(POSITION_TIME);
}

//+------------------------------------------------------------------+
//| Retrieve the type of the selected position                       |
//+------------------------------------------------------------------+
int GetPositionType() {
   //--- Return the type of the selected position (buy/sell)
   return (int)PositionGetInteger(POSITION_TYPE);
}

AccountInfoDouble関数を利用したGetAccountBalance関数を用いて現在の口座残高を取得し、リスク管理のための残高トラッキングを可能にしています。

さらに、ポジションのマジックナンバーを取得するためにPositionGetInteger関数を使ったGetPositionMagic関数を実装し、始値を取得するためにPositionGetDoubleを利用したGetPositionOpenPrice関数を定義します。同様に、ストップロスおよびテイクプロフィット水準にアクセスするため、PositionGetDoubleを用いたGetPositionSL関数とGetPositionTP関数を定義し、精密な取引計算を支援します。

加えて、ポジションの銘柄を検証するためにPositionGetStringを利用したGetPositionSymbol関数、ポジション識別子や建玉の開始時間を追跡するためにPositionGetIntegerを利用したGetPositionTicket関数およびGetPositionOpenTime関数を定義します。そして、買いか売りかの状態を判定するためにGetPositionType関数を実装し、正確なポジション監視と取引ロジックを実現します。これでダッシュボードの作成に進むことができ、作業を容易にするためのヘルパー関数も必要となります。

//+------------------------------------------------------------------+
//| Create a rectangular object for the dashboard                    |
//+------------------------------------------------------------------+
void CreateRectangle(string name, int x, int y, int width, int height, color bgColor, color borderColor) {
   //--- Check if object does not exist
   if (ObjectFind(0, name) < 0) {
      //--- Create a rectangle label object
      ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   }
   //--- Set X-coordinate
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   //--- Set Y-coordinate
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   //--- Set width
   ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
   //--- Set height
   ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
   //--- Set background color
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor);
   //--- Set border type to flat
   ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   //--- Set border color
   ObjectSetInteger(0, name, OBJPROP_COLOR, borderColor);
   //--- Set border width
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   //--- Set object to foreground
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   //--- Disable object selection
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   //--- Hide object from object list
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

//+------------------------------------------------------------------+
//| Create a text label for the dashboard                            |
//+------------------------------------------------------------------+
void CreateTextLabel(string name, int x, int y, string text, color clr, int fontSize, string font = "Arial") {
   //--- Check if object does not exist
   if (ObjectFind(0, name) < 0) {
      //--- Create a text label object
      ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   }
   //--- Set X-coordinate
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   //--- Set Y-coordinate
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   //--- Set label text
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   //--- Set font
   ObjectSetString(0, name, OBJPROP_FONT, font);
   //--- Set font size
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
   //--- Set text color
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   //--- Set object to foreground
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   //--- Disable object selection
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   //--- Hide object from object list
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

//+------------------------------------------------------------------+
//| Create a button for the dashboard                                |
//+------------------------------------------------------------------+
void CreateButton(string name, string text, int x, int y, int width, int height, color textColor, color bgColor, int fontSize, color borderColor, bool isBack, string font = "Arial") {
   //--- Check if object does not exist
   if (ObjectFind(0, name) < 0) {
      //--- Create a button object
      ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
   }
   //--- Set X-coordinate
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   //--- Set Y-coordinate
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   //--- Set width
   ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
   //--- Set height
   ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
   //--- Set button text
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   //--- Set font
   ObjectSetString(0, name, OBJPROP_FONT, font);
   //--- Set font size
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
   //--- Set text color
   ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);
   //--- Set background color
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor);
   //--- Set border color
   ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, borderColor);
   //--- Set background rendering
   ObjectSetInteger(0, name, OBJPROP_BACK, isBack);
   //--- Reset button state
   ObjectSetInteger(0, name, OBJPROP_STATE, false);
   //--- Disable button selection
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   //--- Hide button from object list
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

ここでは、ObjectCreate関数とObjectSetInteger関数を用いたCreateRectangle関数を定義し、ダッシュボードの背景やヘッダなどの矩形要素を描画します。この際、位置やサイズ、カラーなどのプロパティを設定し、洗練されたレイアウトを実現します。次に、ObjectCreateとObjectSetStringを利用したCreateTextLabel関数を実装し、スプレッドやロットサイズといった指標を表示します。この関数ではフォントやカラーをカスタマイズできるため、視認性の高い表示が可能になります。

さらに、インタラクティブなボタンを追加するためにCreateButton関数を定義し、たとえばクローズボタンを設けてユーザー操作を可能にします。この際、スタイルやホバー効果を組み込み、直感的でシームレスなダッシュボード体験を提供します。これらの関数を用いることで、新しい関数内にダッシュボード要素を構築できるようになります。ただし、総利益を取得する必要があるため、そのための関数を定義していきます。

//+------------------------------------------------------------------+
//| Calculate the total unrealized profit of open positions          |
//+------------------------------------------------------------------+
double CalculateTotalProfit() {
   //--- Initialize profit accumulator
   double profit = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Accumulate unrealized profit
         profit += PositionGetDouble(POSITION_PROFIT);
      }
   }
   //--- Return total profit
   return profit;
}

ここでは、PositionsTotalPositionGetTicketを利用したCalculateTotalProfit関数を実装し、保有中のポジションを順に走査します。各ポジションはPositionSelectByTicketを用いて選択し、GetPositionSymbolおよびGetPositionMagicによって銘柄とマジックナンバーを検証することで、総利益の計算に利用します。そのうえで、PositionGetDouble関数を使って各ポジションの含み益・含み損を取得し、それらを加算してprofit変数に格納します。これにより、取引結果を正確にモニタリングすることが可能になります。次に、ダッシュボード作成関数を次のように構成していきます。

//+------------------------------------------------------------------+
//| Update the dashboard with real-time trading metrics              |
//+------------------------------------------------------------------+
void UpdateDashboard() {
   //--- Exit if dashboard is not visible
   if (!dashboardVisible) return;

   //--- Create dashboard background rectangle
   CreateRectangle("Dashboard", panelStartX, panelStartY, DASHBOARD_WIDTH, DASHBOARD_HEIGHT, BACKGROUND_COLOR, BORDER_COLOR);

   //--- Create header rectangle
   CreateRectangle("Header", panelStartX, panelStartY, DASHBOARD_WIDTH, HEADER_HEIGHT, headerHovered ? HEADER_HOVER_COLOR : HEADER_NORMAL_COLOR, BORDER_COLOR);
   //--- Create header text label
   CreateTextLabel("HeaderText", panelStartX + 10, panelStartY + 8, EA_NAME, clrBlack, FONT_SIZE_HEADER, "Arial Bold");

   //--- Create close button
   CreateButton("CloseButton", CharToString(122), panelStartX + DASHBOARD_WIDTH - CLOSE_BUTTON_WIDTH, panelStartY + 1, CLOSE_BUTTON_WIDTH, CLOSE_BUTTON_HEIGHT, clrWhite, closeButtonHovered ? CLOSE_BUTTON_HOVER_BG : CLOSE_BUTTON_NORMAL_BG, FONT_SIZE_BUTTON, closeButtonHovered ? CLOSE_BUTTON_HOVER_BORDER : CLOSE_BUTTON_NORMAL_BORDER, false, "Wingdings");

   //--- Initialize dashboard content layout
   //--- Set initial Y-position below header
   int sectionY = panelStartY + HEADER_HEIGHT + 15;
   //--- Set left column X-position for labels
   int labelXLeft = panelStartX + 15;
   //--- Set right column X-position for values
   int valueXRight = panelStartX + 160;
   //--- Set row height for metrics
   int rowHeight = 15;

   //--- Pre-calculate values for conditional coloring
   //--- Calculate total unrealized profit
   double profit = CalculateTotalProfit();
   //--- Set profit color based on value
   color profitColor = (profit > 0) ? VALUE_POSITIVE_COLOR : (profit < 0) ? VALUE_LOSS_COLOR : TEXT_COLOR;

   //--- Get current equity
   double equity = AccountInfoDouble(ACCOUNT_EQUITY);
   //--- Get current balance
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   //--- Set equity color based on comparison with balance
   color equityColor = (equity > balance) ? VALUE_POSITIVE_COLOR : (equity < balance) ? VALUE_NEGATIVE_COLOR : TEXT_COLOR;

   //--- Set balance color based on comparison with daily balance
   color balanceColor = (balance > dailyBalance) ? VALUE_POSITIVE_COLOR : (balance < dailyBalance) ? VALUE_NEGATIVE_COLOR : TEXT_COLOR;

   //--- Set open orders color based on active orders
   color ordersColor = (activeOrders > 0) ? VALUE_ACTIVE_COLOR : TEXT_COLOR;

   //--- Set drawdown active color based on trading state
   color drawdownColor = tradingEnabled ? VALUE_DRAWDOWN_INACTIVE : VALUE_DRAWDOWN_ACTIVE;

   //--- Set lot sizes color based on active orders
   color lotsColor = (activeOrders > 0) ? VALUE_ACTIVE_COLOR : TEXT_COLOR;

   //--- Calculate dynamic spread and color
   //--- Get current ask price
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   //--- Get current bid price
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   //--- Calculate spread in points
   double spread = (ask - bid) / Point();
   //--- Format spread with 1 decimal place for display
   string spreadDisplay = DoubleToString(spread, 1);
   //--- Initialize spread color
   color spreadColor;
   //--- Check if spread is low (favorable)
   if (spread <= 2.0) {
      //--- Set color to lime green for low spread
      spreadColor = VALUE_POSITIVE_COLOR;
   }
   //--- Check if spread is medium (moderate)
   else if (spread <= 5.0) {
      //--- Set color to gold for medium spread
      spreadColor = VALUE_ACTIVE_COLOR;
   }
   //--- Spread is high (costly)
   else {
      //--- Set color to orange for high spread
      spreadColor = VALUE_NEGATIVE_COLOR;
   }

   //--- Account Information Section
   //--- Create section title
   CreateTextLabel("SectionAccount", labelXLeft, sectionY, "Account Information", SECTION_TITLE_COLOR, FONT_SIZE_SECTION_TITLE, "Arial Bold");
   //--- Move to next row
   sectionY += rowHeight + 5;
   //--- Create account number label
   CreateTextLabel("AccountNumberLabel", labelXLeft, sectionY, "Account:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create account number value
   CreateTextLabel("AccountNumberValue", valueXRight, sectionY, DoubleToString(AccountInfoInteger(ACCOUNT_LOGIN), 0), TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create account name label
   CreateTextLabel("AccountNameLabel", labelXLeft, sectionY, "Name:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create account name value
   CreateTextLabel("AccountNameValue", valueXRight, sectionY, AccountInfoString(ACCOUNT_NAME), TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create leverage label
   CreateTextLabel("LeverageLabel", labelXLeft, sectionY, "Leverage:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create leverage value
   CreateTextLabel("LeverageValue", valueXRight, sectionY, "1:" + DoubleToString(AccountInfoInteger(ACCOUNT_LEVERAGE), 0), TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;

   //--- Market Information Section
   //--- Create section title
   CreateTextLabel("SectionMarket", labelXLeft, sectionY, "Market Information", SECTION_TITLE_COLOR, FONT_SIZE_SECTION_TITLE, "Arial Bold");
   //--- Move to next row
   sectionY += rowHeight + 5;
   //--- Create spread label
   CreateTextLabel("SpreadLabel", labelXLeft, sectionY, "Spread:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create spread value with dynamic color
   CreateTextLabel("SpreadValue", valueXRight, sectionY, spreadDisplay, spreadColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;

   //--- Trading Statistics Section
   //--- Create section title
   CreateTextLabel("SectionTrading", labelXLeft, sectionY, "Trading Statistics", SECTION_TITLE_COLOR, FONT_SIZE_SECTION_TITLE, "Arial Bold");
   //--- Move to next row
   sectionY += rowHeight + 5;
   //--- Create balance label
   CreateTextLabel("BalanceLabel", labelXLeft, sectionY, "Balance:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create balance value with dynamic color
   CreateTextLabel("BalanceValue", valueXRight, sectionY, DoubleToString(balance, 2), balanceColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create equity label
   CreateTextLabel("EquityLabel", labelXLeft, sectionY, "Equity:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create equity value with dynamic color
   CreateTextLabel("EquityValue", valueXRight, sectionY, DoubleToString(equity, 2), equityColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create profit label
   CreateTextLabel("ProfitLabel", labelXLeft, sectionY, "Profit:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create profit value with dynamic color
   CreateTextLabel("ProfitValue", valueXRight, sectionY, DoubleToString(profit, 2), profitColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create open orders label
   CreateTextLabel("OrdersLabel", labelXLeft, sectionY, "Open Orders:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create open orders value with dynamic color
   CreateTextLabel("OrdersValue", valueXRight, sectionY, IntegerToString(activeOrders), ordersColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create drawdown active label
   CreateTextLabel("DrawdownLabel", labelXLeft, sectionY, "Drawdown Active:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create drawdown active value with dynamic color
   CreateTextLabel("DrawdownValue", valueXRight, sectionY, tradingEnabled ? "No" : "Yes", drawdownColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;

   //--- Active Lot Sizes
   //--- Create active lots label
   CreateTextLabel("ActiveLotsLabel", labelXLeft, sectionY, "Active Lots:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create active lots value with dynamic color
   CreateTextLabel("ActiveLotsValue", valueXRight, sectionY, GetActiveLotSizes(), lotsColor, FONT_SIZE_METRIC);

   //--- Redraw the chart to update display
   ChartRedraw(0);
}

ここでは、リアルタイムの取引指標を表示するダイナミックダッシュボードを描画するためにUpdateDashboard関数を定義し、パフォーマンスを監視するための包括的なインターフェースを提供します。まず、ダッシュボードがアクティブな場合にのみ更新がおこなわれるように、dashboardVisible変数を確認し、不要な処理を回避します。次に、CreateRectangle関数を用いてダッシュボードのメインパネルとヘッダを描画し、DASHBOARD_WIDTHやHEADER_HEIGHTで寸法を設定します。さらに、headerHoveredの状態に応じてBACKGROUND_COLOR、HEADER_NORMAL_COLOR、HEADER_HOVER_COLORなどを適用し、視覚的なフィードバックを実現します。

続いて、CreateTextLabel関数を利用し、口座残高、純資産(エクイティ)、利益、スプレッド、建玉数、ドローダウン状況、アクティブロットサイズといった重要な指標を表示します。これらは「口座情報」「市場情報」「取引統計」といったセクションに整理されます。スプレッドはSymbolInfoDouble関数を用いてアスク価格とビッド価格を取得し算出します。さらに、条件付きのカラーコードを適用し、スプレッドが2.0ポイント以下の場合はVALUE_POSITIVE_COLOR、2.1~5.0ポイントの場合はVALUE_ACTIVE_COLOR、5.0ポイント超の場合はVALUE_NEGATIVE_COLORを使用します(この閾値は好みに応じて変更可能です)。これを好みの範囲に変更できます。AccountInfoDoubleによって残高やエクイティを取得し、CalculateTotalProfit関数で含み損益を算出したうえで、その値に応じてprofitColorを設定し、直感的に把握できるようにします。

さらに、インタラクティブなクローズボタンを追加するためにCreateButton関数を統合し、CLOSE_BUTTON_WIDTH、FONT_SIZE_BUTTONなどのスタイルを設定します。加えて、closeButtonHoveredの状態に応じてCLOSE_BUTTON_NORMAL_BGまたはCLOSE_BUTTON_HOVER_BGを適用し、ユーザー操作性を高めます。また、GetActiveLotSizes関数を呼び出して、最大3つまでのロットサイズを昇順で表示し、lotsColorを用いて視覚的に区別します。レイアウトはsectionY、labelXLeft、valueXRightなどの変数を使って精密に管理します。最後に、ChartRedraw関数を呼び出すことでスムーズなレンダリングを実現し、この関数をOnInitイベントハンドラで呼び出すことで、最初のダッシュボード表示を作成できるようになります。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   //--- Calculate pip value based on symbol digits (3 or 5 digits: multiply by 10)
   pipValue = (_Digits == 3 || _Digits == 5) ? 10.0 * Point() : Point();
   //--- Set the magic number for trade operations
   obj_Trade.SetExpertMagicNumber(MAGIC);
   //--- Initialize dashboard visibility
   dashboardVisible = true;
   //--- Set initial X-coordinate of the dashboard
   panelStartX = PANEL_X;
   //--- Set initial Y-coordinate of the dashboard
   panelStartY = PANEL_Y;
   //--- Initialize the dashboard display
   UpdateDashboard();
   //--- Enable mouse move events for dragging and hovering
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
   //--- Return successful initialization
   return INIT_SUCCEEDED;
}

OnInitイベントハンドラでは、まずPoint関数を用いてpipValueを計算し、次にSetExpertMagicNumberを使ってobj_TradeにMAGICを設定します。その後、dashboardVisibleを有効化し、PANEL_XおよびPANEL_Yを利用してpanelStartXとpanelStartYによってダッシュボードの位置を決定します。続いてUpdateDashboard関数を呼び出してダッシュボードを表示し、ChartSetIntegerを用いてマウス操作を有効化します。最終的に、INIT_SUCCEEDEDを返すことで初期化処理を完了します。OnDeInitイベントハンドラでは、作成されたオブジェクトを確実に削除する処理をおこないます。以下にその方法を示します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   //--- Remove all graphical objects from the chart
   ObjectsDeleteAll(0);
   //--- Disable mouse move events
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
}

不要になったため、ObjectsDeleteAll関数を使用してすべてのチャートオブジェクトを削除します。コンパイルすると、結果は次のようになります。

初期パネル

パネルをインタラクティブにするために、OnChartEventイベントハンドラを呼び出し、ホバーやドラッグといった効果を処理します。以下に、それを実現するために実装したロジックを示します。

//+------------------------------------------------------------------+
//| Expert chart event handler                                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   //--- Exit if dashboard is not visible
   if (!dashboardVisible) return;

   //--- Handle mouse click events
   if (id == CHARTEVENT_CLICK) {
      //--- Get X-coordinate of the click
      int x = (int)lparam;
      //--- Get Y-coordinate of the click
      int y = (int)dparam;
      //--- Calculate close button X-position
      int buttonX = panelStartX + DASHBOARD_WIDTH - CLOSE_BUTTON_WIDTH - 5;
      //--- Calculate close button Y-position
      int buttonY = panelStartY + 1;
      //--- Check if click is within close button bounds
      if (x >= buttonX && x <= buttonX + CLOSE_BUTTON_WIDTH && y >= buttonY && y <= buttonY + CLOSE_BUTTON_HEIGHT) {
         //--- Hide the dashboard
         dashboardVisible = false;
         //--- Remove all graphical objects
         ObjectsDeleteAll(0);
         //--- Redraw the chart
         ChartRedraw(0);
      }
   }

   //--- Handle mouse move events
   if (id == CHARTEVENT_MOUSE_MOVE) {
      //--- Get X-coordinate of the mouse
      int mouseX = (int)lparam;
      //--- Get Y-coordinate of the mouse
      int mouseY = (int)dparam;
      //--- Get mouse state (e.g., button pressed)
      int mouseState = (int)sparam;

      //--- Update close button hover state
      //--- Calculate close button X-position
      int buttonX = panelStartX + DASHBOARD_WIDTH - CLOSE_BUTTON_WIDTH - 5;
      //--- Calculate close button Y-position
      int buttonY = panelStartY + 1;
      //--- Check if mouse is over the close button
      bool isCloseHovered = (mouseX >= buttonX && mouseX <= buttonX + CLOSE_BUTTON_WIDTH && mouseY >= buttonY && mouseY <= buttonY + CLOSE_BUTTON_HEIGHT);
      //--- Update close button hover state if changed
      if (isCloseHovered != closeButtonHovered) {
         //--- Set new hover state
         closeButtonHovered = isCloseHovered;
         //--- Update close button background color
         ObjectSetInteger(0, "CloseButton", OBJPROP_BGCOLOR, isCloseHovered ? CLOSE_BUTTON_HOVER_BG : CLOSE_BUTTON_NORMAL_BG);
         //--- Update close button border color
         ObjectSetInteger(0, "CloseButton", OBJPROP_BORDER_COLOR, isCloseHovered ? CLOSE_BUTTON_HOVER_BORDER : CLOSE_BUTTON_NORMAL_BORDER);
         //--- Redraw the chart
         ChartRedraw(0);
      }

      //--- Update header hover state
      //--- Set header X-position
      int headerX = panelStartX;
      //--- Set header Y-position
      int headerY = panelStartY;
      //--- Check if mouse is over the header
      bool isHeaderHovered = (mouseX >= headerX && mouseX <= headerX + DASHBOARD_WIDTH && mouseY >= headerY && mouseY <= headerY + HEADER_HEIGHT);
      //--- Update header hover state if changed
      if (isHeaderHovered != headerHovered) {
         //--- Set new hover state
         headerHovered = isHeaderHovered;
         //--- Update header background color
         ObjectSetInteger(0, "Header", OBJPROP_BGCOLOR, isHeaderHovered ? HEADER_HOVER_COLOR : HEADER_NORMAL_COLOR);
         //--- Redraw the chart
         ChartRedraw(0);
      }

      //--- Handle panel dragging
      //--- Store previous mouse state for click detection
      static int prevMouseState = 0;
      //--- Check for mouse button press (start dragging)
      if (prevMouseState == 0 && mouseState == 1) {
         //--- Check if header is hovered to initiate dragging
         if (isHeaderHovered) {
            //--- Enable dragging mode
            panelDragging = true;
            //--- Store initial mouse X-coordinate
            panelDragX = mouseX;
            //--- Store initial mouse Y-coordinate
            panelDragY = mouseY;
            //--- Get current dashboard X-position
            panelStartX = (int)ObjectGetInteger(0, "Dashboard", OBJPROP_XDISTANCE);
            //--- Get current dashboard Y-position
            panelStartY = (int)ObjectGetInteger(0, "Dashboard", OBJPROP_YDISTANCE);
            //--- Disable chart scrolling during dragging
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         }
      }

      //--- Update dashboard position during dragging
      if (panelDragging && mouseState == 1) {
         //--- Calculate X movement delta
         int dx = mouseX - panelDragX;
         //--- Calculate Y movement delta
         int dy = mouseY - panelDragY;
         //--- Update dashboard X-position
         panelStartX += dx;
         //--- Update dashboard Y-position
         panelStartY += dy;
         //--- Refresh the dashboard with new position
         UpdateDashboard();
         //--- Update stored mouse X-coordinate
         panelDragX = mouseX;
         //--- Update stored mouse Y-coordinate
         panelDragY = mouseY;
         //--- Redraw the chart
         ChartRedraw(0);
      }

      //--- Stop dragging when mouse button is released
      if (mouseState == 0) {
         //--- Check if dragging is active
         if (panelDragging) {
            //--- Disable dragging mode
            panelDragging = false;
            //--- Re-enable chart scrolling
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
         }
      }

      //--- Update previous mouse state
      prevMouseState = mouseState;
   }
}

OnChartEventイベントハンドラでは、まずdashboardVisible変数を確認し、ダッシュボードがアクティブな場合にのみイベント処理を行うようにします。これにより、不要な更新をスキップし、パフォーマンスを最適化します。マウスクリックイベントに対しては、panelStartX、DASHBOARD_WIDTH、CLOSE_BUTTON_WIDTH、panelStartYを用いてクローズボタンの位置を計算します。クリックがその範囲内にある場合、dashboardVisibleをfalseに設定し、ObjectsDeleteAll関数を呼び出してすべてのグラフィカルオブジェクトを削除し、さらにChartRedraw関数でチャートをリフレッシュすることで、ダッシュボードを非表示にします。

マウス移動イベントに対しては、mouseXおよびmouseYでカーソル位置を追跡し、mouseStateを監視してクリックや解放といったアクションを検出します。クローズボタンのホバー状態はmouseXとmouseYを座標と比較して更新し、closeButtonHoveredを設定します。その際、ObjectSetInteger関数を使ってOBJPROP_BGCOLORやOBJPROP_BORDER_COLORをCLOSE_BUTTON_HOVER_BGまたはCLOSE_BUTTON_NORMAL_BGに切り替え、視覚的なフィードバックを提供します。同様に、ヘッダのホバー状態もheaderHoveredによって管理し、ObjectSetIntegerを介してHEADER_HOVER_COLORまたはHEADER_NORMAL_COLORを適用することで、インタラクティブ性を高めます。

さらに、ダッシュボードのドラッグ機能を実現するために、静的変数prevMouseStateを利用してマウスボタンの押下を検知します。ヘッダがホバー状態のときにpanelDraggingを有効化し、初期座標をpanelDragXおよびpanelDragYに保存します。続いて、ObjectGetInteger関数を用いてOBJPROP_XDISTANCEおよびOBJPROP_YDISTANCEによるダッシュボードの現在位置を取得し、ChartSetIntegerを使ってチャートスクロールを無効化します。その後、マウス移動の差分に基づいてpanelStartXとpanelStartYを更新し、UpdateDashboard関数を呼び出すことでリアルタイムにダッシュボードを再配置します。マウスボタンが解放されるとpanelDraggingをリセットし、ChartSetIntegerによってスクロールを再有効化します。最後にChartRedrawを呼び出して各更新を確定させ、シームレスなユーザー体験を実現します。コンパイルすると、結果は次のようになります。

ダッシュボードUI GIF

可視化結果から、パネルをドラッグしたり、ボタンにホバーしたり、指標を更新したり、ダッシュボード全体を閉じたりできることが確認できます。次に進むのは、最も重要な部分であるポジションのオープンと管理です。ダッシュボードは、進捗状況を動的に可視化することで、この作業を大幅に容易にします。そのために、ロットサイズの計算などをおこなうヘルパー関数が必要となります。以下にその内容を示します。

//+------------------------------------------------------------------+
//| Calculate the lot size for a new trade                           |
//+------------------------------------------------------------------+
double CalculateLotSize(ENUM_POSITION_TYPE tradeType) {
   //--- Initialize lot size
   double lotSize = 0;
   //--- Select lot size calculation mode
   switch (LOT_MODE) {
      //--- Fixed lot size mode
      case 0:
         //--- Use base lot size
         lotSize = BASE_LOT;
         break;
      //--- Multiplier-based lot size mode
      case 1:
         //--- Calculate lot size with multiplier based on active orders
         lotSize = NormalizeDouble(BASE_LOT * MathPow(LOT_MULTIPLIER, activeOrders), LOT_PRECISION);
         break;
      //--- Fixed lot size with multiplier on loss mode
      case 2: {
         //--- Initialize last close time
         datetime lastClose = 0;
         //--- Set default lot size
         lotSize = BASE_LOT;
         //--- Select trade history for the last 24 hours
         HistorySelect(TimeCurrent() - 24 * 60 * 60, TimeCurrent());
         //--- Iterate through trade history
         for (int i = HistoryDealsTotal() - 1; i >= 0; i--) {
            //--- Get deal ticket
            ulong ticket = HistoryDealGetTicket(i);
            //--- Select deal by ticket
            if (HistoryDealSelect(ticket) && HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT &&
                HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(ticket, DEAL_MAGIC) == MAGIC) {
               //--- Check if deal is more recent
               if (lastClose < HistoryDealGetInteger(ticket, DEAL_TIME)) {
                  //--- Update last close time
                  lastClose = (int)HistoryDealGetInteger(ticket, DEAL_TIME);
                  //--- Check if deal resulted in a loss
                  if (HistoryDealGetDouble(ticket, DEAL_PROFIT) < 0) {
                     //--- Increase lot size by multiplier
                     lotSize = NormalizeDouble(HistoryDealGetDouble(ticket, DEAL_VOLUME) * LOT_MULTIPLIER, LOT_PRECISION);
                  } else {
                     //--- Reset to base lot size
                     lotSize = BASE_LOT;
                  }
               }
            }
         }
         break;
      }
   }
   //--- Return calculated lot size
   return lotSize;
}

//+------------------------------------------------------------------+
//| Count the number of active orders                                |
//+------------------------------------------------------------------+
int CountActiveOrders() {
   //--- Initialize order counter
   int count = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Check if position is buy or sell
         if (GetPositionType() == POSITION_TYPE_BUY || GetPositionType() == POSITION_TYPE_SELL) {
            //--- Increment order counter
            count++;
         }
      }
   }
   //--- Return total active orders
   return count;
}

//+------------------------------------------------------------------+
//| Return a formatted string of active lot sizes in ascending order |
//+------------------------------------------------------------------+
string GetActiveLotSizes() {
   //--- Check if no active orders
   if (activeOrders == 0) {
      //--- Return waiting message
      return "[Waiting]";
   }
   //--- Initialize array for lot sizes
   double lotSizes[];
   //--- Resize array to match active orders
   ArrayResize(lotSizes, activeOrders);
   //--- Initialize counter
   int count = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0 && count < activeOrders; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Store position volume (lot size)
         lotSizes[count] = PositionGetDouble(POSITION_VOLUME);
         //--- Increment counter
         count++;
      }
   }
   //--- Sort lot sizes in ascending order
   ArraySort(lotSizes);
   //--- Initialize result string
   string result = "";
   //--- Determine maximum number of lots to display (up to 3)
   int maxDisplay = (activeOrders > 3) ? 3 : activeOrders;
   //--- Format lot sizes
   for (int i = 0; i < maxDisplay; i++) {
      //--- Add comma and space for subsequent entries
      if (i > 0) result += ", ";
      //--- Convert lot size to string with specified precision
      result += DoubleToString(lotSizes[i], LOT_PRECISION);
   }
   //--- Append ellipsis if more than 3 orders
   if (activeOrders > 3) result += ", ...";
   //--- Return formatted lot sizes in brackets
   return "[" + result + "]";
}

ここでは、取引サイズの計算、ポジションの追跡、ダッシュボードの更新を支援するために、CalculateLotSize、CountActiveOrders、GetActiveLotSizes関数を定義し、正確な取引実行と包括的なリアルタイム監視を可能にします。

まず、CalculateLotSize関数では、入力パラメータLOT_MODEに基づいて取引ロット数を決定します。サポートするモードは3種類です。1つ目は固定ロットモードで、単純にBASE_LOTを返します。2つ目はマーチンゲール拡大モードで、LOT_MULTIPLIERとactiveOrdersを用いてMathPow関数によりロット数を指数的に増加させます。3つ目は損失ベースの調整モードで、HistorySelect関数を使って過去24時間の取引を確認します。

損失ベースのモードでは、HistoryDealsTotalで取引件数を反復処理し、HistoryDealGetTicketHistoryDealGetDoubleで取引の詳細を取得します。直近の取引が損失であればNormalizeDoubleによりlotSizeを調整し、そうでなければBASE_LOTにリセットします。これにより、取引結果に応じた正確なロット計算が可能となります。

次に、CountActiveOrders関数を利用して、保有ポジション数を正確にカウントします。これはマーチンゲール拡大やダッシュボードの精度に不可欠です。PositionsTotalで全ポジションを反復処理し、PositionGetTicketで各ポジションを選択、さらにGetPositionSymbolとGetPositionMagicで該当ポジションかを確認します。GetPositionTypeによって買い・売りポジションを判定し、該当すればcount変数をインクリメントすることで、activeOrdersを正確に更新します。

さらに、GetActiveLotSizes関数では、ダッシュボード上にロットサイズを整形して表示し、ユーザーがアクティブポジションを把握しやすくします。まずactiveOrdersを確認し、存在しない場合は[Waiting]を返します。存在する場合はArrayResizeでロットサイズを格納する配列を初期化し、PositionsTotalでポジションを反復、PositionGetTicketで選択し、PositionGetDoubleでロット数を収集して配列lotSizesに格納します。その後、ArraySortで昇順にソートし、最大3つまでのロットをDoubleToStringとLOT_PRECISIONでフォーマットします。3件を超える場合はカンマと省略記号を付加し、最終的に角括弧で囲んで結果を返すことで、リアルタイム監視において明確でプロフェッショナルな表示を提供します。次に、発注を支援するための関数を定義する必要があります。

//+------------------------------------------------------------------+
//| Place a new trade order                                          |
//+------------------------------------------------------------------+
int PlaceOrder(ENUM_ORDER_TYPE orderType, double lot, double price, double slPrice, int gridLevel) {
   //--- Calculate stop-loss price
   double sl = CalculateSL(slPrice, STOP_LOSS_PIPS);
   //--- Calculate take-profit price
   double tp = CalculateTP(price, TAKE_PROFIT_PIPS, orderType);
   //--- Initialize ticket number
   int ticket = 0;
   //--- Set maximum retry attempts
   int retries = 100;
   //--- Create trade comment with grid level
   string comment = "GridMart Scalper-" + IntegerToString(gridLevel);

   //--- Attempt to place order with retries
   for (int i = 0; i < retries; i++) {
      //--- Open position with specified parameters
      ticket = obj_Trade.PositionOpen(_Symbol, orderType, lot, price, sl, tp, comment);
      //--- Get last error code
      int error = GetLastError();
      //--- Exit loop if order is successful
      if (error == 0) break;
      //--- Check for retryable errors (server busy, trade context busy, etc.)
      if (!(error == 4 || error == 137 || error == 146 || error == 136)) break;
      //--- Wait before retrying
      Sleep(5000);
   }
   //--- Return order ticket (or error code if negative)
   return ticket;
}

//+------------------------------------------------------------------+
//| Calculate the stop-loss price for a trade                        |
//+------------------------------------------------------------------+
double CalculateSL(double price, int points) {
   //--- Check if stop-loss is disabled
   if (points == 0) return 0;
   //--- Calculate stop-loss price (subtract points from price)
   return price - points * pipValue;
}

//+------------------------------------------------------------------+
//| Calculate the take-profit price for a trade                      |
//+------------------------------------------------------------------+
double CalculateTP(double price, int points, ENUM_ORDER_TYPE orderType) {
   //--- Check if take-profit is disabled
   if (points == 0) return 0;
   //--- Calculate take-profit for buy order (add points)
   if (orderType == ORDER_TYPE_BUY) return price + points * pipValue;
   //--- Calculate take-profit for sell order (subtract points)
   return price - points * pipValue;
}

//+------------------------------------------------------------------+
//| Retrieve the latest buy order price                              |
//+------------------------------------------------------------------+
double GetLatestBuyPrice() {
   //--- Initialize price
   double price = 0;
   //--- Initialize latest ticket
   int latestTicket = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC && GetPositionType() == POSITION_TYPE_BUY) {
         //--- Check if ticket is more recent
         if ((int)ticket > latestTicket) {
            //--- Update price
            price = GetPositionOpenPrice();
            //--- Update latest ticket
            latestTicket = (int)ticket;
         }
      }
   }
   //--- Return latest buy price
   return price;
}

//+------------------------------------------------------------------+
//| Retrieve the latest sell order price                             |
//+------------------------------------------------------------------+
double GetLatestSellPrice() {
   //--- Initialize price
   double price = 0;
   //--- Initialize latest ticket
   int latestTicket = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC && GetPositionType() == POSITION_TYPE_SELL) {
         //--- Check if ticket is more recent
         if ((int)ticket > latestTicket) {
            //--- Update price
            price = GetPositionOpenPrice();
            //--- Update latest ticket
            latestTicket = (int)ticket;
         }
      }
   }
   //--- Return latest sell price
   return price;
}

//+------------------------------------------------------------------+
//| Check if trading is allowed                                      |
//+------------------------------------------------------------------+
int IsTradingAllowed() {
   //--- Always allow trading
   return 1;
}

ここでは、取引を開始するためのPlaceOrder関数を実装します。obj_TradeのPositionOpen関数を使用して、lot、price、およびIntegerToStringを用いたgridLevelをコメントに含めたパラメータでポジションをオープンします。さらに、GetLastErrorSleepを活用した再試行ロジックを組み込み、堅牢な実行を確保します。ストップロス価格はCalculateSL関数で計算し、slPriceからSTOP_LOSS_PIPSにpipValueを掛けた値を引くことで求めます。テイクプロフィット水準はCalculateTP関数で設定し、買い注文の場合はTAKE_PROFIT_PIPSを加算、売り注文の場合は減算することで、適切な利益確定ポイントを決定します。

さらに、GetLatestBuyPrice関数を作成し、直近の買いポジションの価格を特定します。PositionsTotalでポジションを順に反復し、PositionGetTicketで選択した後、GetPositionSymbol、GetPositionMagic、GetPositionTypeで該当ポジションかを確認します。最も大きいticketの価格をGetPositionOpenPriceで取得し、priceを更新します。

同様に、GetLatestSellPrice関数では直近の売りポジションの価格を取得し、正確なグリッド配置を確保するため同じロジックを適用します。さらに、IsTradingAllowed関数を定義し、常に1を返すことで取引制限なく継続的な取引を可能にします。これにより、本戦略の高頻度取引アプローチをサポートします。これらの関数を用いて、次にOnTickイベントハンドラ内で実際の取引ロジックを定義することができます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Get current ask price
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
   //--- Get current bid price
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);

   //--- Update dashboard if visible
   if (dashboardVisible) UpdateDashboard();

   //--- Check if trading is allowed
   if (IsTradingAllowed()) {
      //--- Get current bar time
      datetime currentBarTime = iTime(_Symbol, _Period, 0);
      //--- Exit if the bar hasn’t changed
      if (lastBarTime == currentBarTime) return;
      //--- Update last bar time
      lastBarTime = currentBarTime;
      //--- Count active orders
      activeOrders = CountActiveOrders();
      //--- Reset SL/TP update flag if no active orders
      if (activeOrders == 0) updateSLTP = false;

      //--- Reset position flags
      hasBuyPosition = false;
      hasSellPosition = false;
      //--- Iterate through open positions
      for (int i = PositionsTotal() - 1; i >= 0; i--) {
         //--- Get position ticket
         ulong ticket = PositionGetTicket(i);
         //--- Select position by ticket
         if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
            //--- Check for buy position
            if (GetPositionType() == POSITION_TYPE_BUY) {
               //--- Set buy position flag
               hasBuyPosition = true;
               //--- Clear sell position flag
               hasSellPosition = false;
               //--- Exit loop after finding a position
               break;
            }
            //--- Check for sell position
            else if (GetPositionType() == POSITION_TYPE_SELL) {
               //--- Set sell position flag
               hasSellPosition = true;
               //--- Clear buy position flag
               hasBuyPosition = false;
               //--- Exit loop after finding a position
               break;
            }
         }
      }

      //--- Check conditions to open new trades
      if (activeOrders > 0 && activeOrders <= MAX_GRID_LEVELS) {
         //--- Get latest buy price
         latestBuyPrice = GetLatestBuyPrice();
         //--- Get latest sell price
         latestSellPrice = GetLatestSellPrice();
         //--- Check if a new buy trade is needed
         if (hasBuyPosition && latestBuyPrice - ask >= GRID_DISTANCE * pipValue) openNewTrade = true;
         //--- Check if a new sell trade is needed
         if (hasSellPosition && bid - latestSellPrice >= GRID_DISTANCE * pipValue) openNewTrade = true;
      }
      //--- Allow new trades if no active orders
      if (activeOrders < 1) {
         //--- Clear position flags
         hasBuyPosition = false;
         hasSellPosition = false;
         //--- Signal to open a new trade
         openNewTrade = true;
      }

      //--- Execute new trade if signaled
      if (openNewTrade) {
         //--- Update latest buy price
         latestBuyPrice = GetLatestBuyPrice();
         //--- Update latest sell price
         latestSellPrice = GetLatestSellPrice();
         //--- Handle sell position
         if (hasSellPosition) {
            //--- Calculate lot size for sell order
            calculatedLot = CalculateLotSize(POSITION_TYPE_SELL);
            //--- Check if lot size is valid and trading is enabled
            if (calculatedLot > 0 && tradingEnabled) {
               //--- Place sell order
               int ticket = PlaceOrder(ORDER_TYPE_SELL, calculatedLot, bid, ask, activeOrders);
               //--- Check for order placement errors
               if (ticket < 0) {
                  //--- Log error message
                  Print("Sell Order Error: ", GetLastError());
                  return;
               }
               //--- Update latest sell price
               latestSellPrice = GetLatestSellPrice();
               //--- Clear new trade signal
               openNewTrade = false;
               //--- Signal to modify positions
               modifyPositions = true;
            }
         }
         //--- Handle buy position
         else if (hasBuyPosition) {
            //--- Calculate lot size for buy order
            calculatedLot = CalculateLotSize(POSITION_TYPE_BUY);
            //--- Check if lot size is valid and trading is enabled
            if (calculatedLot > 0 && tradingEnabled) {
               //--- Place buy order
               int ticket = PlaceOrder(ORDER_TYPE_BUY, calculatedLot, ask, bid, activeOrders);
               //--- Check for order placement errors
               if (ticket < 0) {
                  //--- Log error message
                  Print("Buy Order Error: ", GetLastError());
                  return;
               }
               //--- Update latest buy price
               latestBuyPrice = GetLatestBuyPrice();
               //--- Clear new trade signal
               openNewTrade = false;
               //--- Signal to modify positions
               modifyPositions = true;
            }
         }

         //---

      }
   }
}

ここでは、プログラムのコア取引ロジックを実行するOnTickイベントハンドラを定義し、リアルタイムでの取引実行とポジション管理を統括します。まず、SymbolInfoDouble関数を用いて現在の市場価格(askおよびbid)を取得し、NormalizeDoubleで正規化します。dashboardVisibleがtrueの場合は、UpdateDashboard関数を呼び出してダッシュボードを更新します。次に、IsTradingAllowed関数で取引が許可されているかを確認し、条件が整っている場合のみ取引を実行します。さらに、iTime関数で現在バーのタイムスタンプを取得し、currentBarTimeに格納して、lastBarTimeと比較することで冗長な処理を防ぎます。

ポジション管理では、CountActiveOrders関数を呼び出してactiveOrdersを更新し、ポジションが存在しない場合はupdateSLTPをリセットします。さらに、PositionsTotalおよびPositionGetTicketを用いて全ポジションを反復処理し、GetPositionTypeに基づきhasBuyPositionまたはhasSellPositionを設定します。

グリッド条件の評価では、GetLatestBuyPriceとGetLatestSellPriceを用いて価格変動を監視し、openNewTradeを判定します。価格変動がGRID_DISTANCE × pipValueを超える場合、またはactiveOrdersがMAX_GRID_LEVELS未満の場合に新規取引を開始します。openNewTradeがtrueの場合は、CalculateLotSize関数でロットサイズを計算し、PlaceOrder関数で買い/売り注文を実行します。注文の実行に失敗した場合は、PrintとGetLastErrorでエラーを記録します。さらに、latestBuyPrice、latestSellPrice、modifyPositionsを更新して、進行中の取引を管理します。これにより、正確かつ効率的なスキャルピング運用が可能となります。新規ポジションをオープンするためのシグナル生成ロジックは、以下のように定義されます。

//--- Check conditions to open a new trade without existing positions
MqlDateTime timeStruct;
//--- Get current time
TimeCurrent(timeStruct);
//--- Verify trading hours, cycle limit, and new trade conditions
if (timeStruct.hour >= START_HOUR && timeStruct.hour < END_HOUR && cycleCount < MAX_CYCLES && CONTINUE_TRADING && openNewTrade && activeOrders < 1) {
   //--- Get previous bar close price
   double closePrev = iClose(_Symbol, PERIOD_CURRENT, 2);
   //--- Get current bar close price
   double closeCurrent = iClose(_Symbol, PERIOD_CURRENT, 1);
   //--- Check if no existing positions
   if (!hasSellPosition && !hasBuyPosition) {
      //--- Check for bearish signal (previous close > current close)
      if (closePrev > closeCurrent) {
         //--- Calculate lot size for sell order
         calculatedLot = CalculateLotSize(POSITION_TYPE_SELL);
         //--- Check if lot size is valid and trading is enabled
         if (calculatedLot > 0 && tradingEnabled) {
            //--- Place sell order
            int ticket = PlaceOrder(ORDER_TYPE_SELL, calculatedLot, bid, bid, activeOrders);
            //--- Check for order placement errors
            if (ticket < 0) {
               //--- Log error message
               Print("Sell Order Error: ", GetLastError());
               return;
            }
            //--- Increment cycle count
            cycleCount++;
            //--- Update latest buy price
            latestBuyPrice = GetLatestBuyPrice();
            //--- Signal to modify positions
            modifyPositions = true;
         }
      }
      //--- Check for bullish signal (previous close <= current close)
      else {
         //--- Calculate lot size for buy order
         calculatedLot = CalculateLotSize(POSITION_TYPE_BUY);
         //--- Check if lot size is valid and trading is enabled
         if (calculatedLot > 0 && tradingEnabled) {
            //--- Place buy order
            int ticket = PlaceOrder(ORDER_TYPE_BUY, calculatedLot, ask, ask, activeOrders);
            //--- Check for order placement errors
            if (ticket < 0) {
               //--- Log error message
               Print("Buy Order Error: ", GetLastError());
               return;
            }
            //--- Increment cycle count
            cycleCount++;
            //--- Update latest sell price
            latestSellPrice = GetLatestSellPrice();
            //--- Signal to modify positions
            modifyPositions = true;
         }
      }
   }
}

新規取引を開始するために、まずTimeCurrent関数を使用して現在時刻をtimeStruct変数に取得します。次に、timeStruct.hourがSTART_HOURとEND_HOURの範囲内であること、cycleCountがMAX_CYCLES未満であること、CONTINUE_TRADINGとopenNewTradeがtrueであること、さらにactiveOrdersが1未満であることを確認します。続いて、前のバーと現在のバーの終値をiClose関数で取得し、それぞれclosePrevとcloseCurrentに格納します。また、hasSellPositionとhasBuyPositionを確認してポジションが存在しないことを確認します。

弱気シグナルの場合(closePrevがcloseCurrentを上回る場合)、CalculateLotSize関数を使用して売り注文用のロットサイズを計算します。その後、calculatedLotとtradingEnabledを確認し、PlaceOrder関数で取引を実行します。必要に応じてPrintとGetLastErrorでエラーを記録します。強気シグナルの場合(closePrevがcloseCurrent以下の場合)も同様の手順で買い注文を行います。その際、cycleCountとlatestBuyPriceまたはlatestSellPriceをGetLatestBuyPriceまたはGetLatestSellPriceで更新し、modifyPositionsをtrueに設定します。これにより、正確な取引開始とポジション管理が可能となります。

この部分は任意の取引戦略に置き換えることができます。ここでは単純なシグナル生成戦略を使用しましたが、主な目的は戦略を適用してポジションを管理することです。ポジションをオープンした後は、価格の進行に応じてポジションを確認し、修正する必要があります。以下の手順でおこないます。

//--- Update active orders count
activeOrders = CountActiveOrders();
//--- Reset weighted price and total volume
weightedPrice = 0;
totalVolume = 0;
//--- Calculate weighted price and total volume
for (int i = PositionsTotal() - 1; i >= 0; i--) {
   //--- Get position ticket
   ulong ticket = PositionGetTicket(i);
   //--- Select position by ticket
   if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
      //--- Accumulate weighted price (price * volume)
      weightedPrice += GetPositionOpenPrice() * PositionGetDouble(POSITION_VOLUME);
      //--- Accumulate total volume
      totalVolume += PositionGetDouble(POSITION_VOLUME);
   }
}

//--- Normalize weighted price if there are active orders
if (activeOrders > 0) weightedPrice = NormalizeDouble(weightedPrice / totalVolume, _Digits);
//--- Check if positions need SL/TP modification
if (modifyPositions) {
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Handle buy positions
         if (GetPositionType() == POSITION_TYPE_BUY) {
            //--- Set take-profit for buy position
            targetTP = weightedPrice + TAKE_PROFIT_PIPS * pipValue;
            //--- Set stop-loss for buy position
            targetSL = weightedPrice - STOP_LOSS_PIPS * pipValue;
            //--- Signal SL/TP update
            updateSLTP = true;
         }
         //--- Handle sell positions
         else if (GetPositionType() == POSITION_TYPE_SELL) {
            //--- Set take-profit for sell position
            targetTP = weightedPrice - TAKE_PROFIT_PIPS * pipValue;
            //--- Set stop-loss for sell position
            targetSL = weightedPrice + STOP_LOSS_PIPS * pipValue;
            //--- Signal SL/TP update
            updateSLTP = true;
         }
      }
   }
}

//--- Apply SL/TP modifications if needed
if (modifyPositions && updateSLTP) {
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Modify position with new SL/TP
         if (obj_Trade.PositionModify(ticket, targetSL, targetTP)) {
            //--- Clear modification signal on success
            modifyPositions = false;
         }
      }
   }
}

ここでは、CountActiveOrdersでactiveOrdersを更新し、weightedPriceとtotalVolumeをリセットします。次に、PositionsTotalPositionGetTicketを使ってポジションを反復処理し、GetPositionOpenPriceおよびPositionGetDouble関数でweightedPriceとtotalVolumeを累積します。activeOrdersが存在する場合は、NormalizeDoubleでweightedPriceを正規化します。さらに、modifyPositionsがtrueの場合は、GetPositionTypeとpipValueを使ってtargetTPとtargetSLを設定します。updateSLTPが有効な場合は、obj_TradeのPositionModifyで更新を適用し、成功したらmodifyPositionsをリセットします。コンパイルすると、次の結果が得られます。

注文を出す

画像から、すでに管理モードでオープン中の注文があることが確認できます。ここからは、日次ドローダウンを監視し制限するためのリスク管理ロジックを適用する必要があります。そのために、すべてのトラッキングを行い、必要に応じてプログラムを停止する関数を定義します。

//+------------------------------------------------------------------+
//| Monitor daily drawdown and control trading state                 |
//+------------------------------------------------------------------+
void MonitorDailyDrawdown() {
   //--- Initialize daily profit accumulator
   double totalDayProfit = 0.0;
   //--- Get current time
   datetime end = TimeCurrent();
   //--- Get current date as string
   string sdate = TimeToString(TimeCurrent(), TIME_DATE);
   //--- Convert date to datetime (start of day)
   datetime start = StringToTime(sdate);
   //--- Set end of day (24 hours later)
   datetime to = start + (1 * 24 * 60 * 60);

   //--- Check if daily reset is needed
   if (dailyResetTime < to) {
      //--- Update reset time
      dailyResetTime = to;
      //--- Store current balance as daily starting balance
      dailyBalance = GetAccountBalance();
   }
   //--- Select trade history for the day
   HistorySelect(start, end);
   //--- Get total number of deals
   int totalDeals = HistoryDealsTotal();

   //--- Iterate through trade history
   for (int i = 0; i < totalDeals; i++) {
      //--- Get deal ticket
      ulong ticket = HistoryDealGetTicket(i);
      //--- Check if deal is a position close
      if (HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) {
         //--- Calculate deal profit (including commission and swap)
         double latestDayProfit = (HistoryDealGetDouble(ticket, DEAL_PROFIT) +
                                  HistoryDealGetDouble(ticket, DEAL_COMMISSION) +
                                  HistoryDealGetDouble(ticket, DEAL_SWAP));
         //--- Accumulate daily profit
         totalDayProfit += latestDayProfit;
      }
   }

   //--- Calculate starting balance for the day
   double startingBalance = GetAccountBalance() - totalDayProfit;
   //--- Calculate daily profit/drawdown percentage
   double dailyProfitOrDrawdown = NormalizeDouble((totalDayProfit * 100 / startingBalance), 2);

   //--- Check if drawdown limit is exceeded
   if (dailyProfitOrDrawdown <= DRAWDOWN_LIMIT) {
      //--- Close all positions if configured
      if (CLOSE_ON_DRAWDOWN) CloseAllPositions();
      //--- Disable trading
      tradingEnabled = false;
   } else {
      //--- Enable trading
      tradingEnabled = true;
   }
}

//+------------------------------------------------------------------+
//| Close all open positions managed by the EA                       |
//+------------------------------------------------------------------+
void CloseAllPositions() {
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (ticket > 0 && PositionSelectByTicket(ticket)) {
         //--- Check if position belongs to this EA
         if (GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
            //--- Close the position
            obj_Trade.PositionClose(ticket);
         }
      }
   }
}

リスク管理を実施するために、日次パフォーマンスを追跡するMonitorDailyDrawdown関数を実装します。TimeCurrentTimeToStringを使用して当日の時間範囲を設定し、HistorySelectで取引履歴にアクセスします。HistoryDealGetTicket関数とHistoryDealGetDouble関数で総利益を計算し、NormalizeDoubleでドローダウン率を正規化します。その後、DRAWDOWN_LIMITに基づいてtradingEnabledを調整し、CLOSE_ON_DRAWDOWNがtrueの場合はCloseAllPositions関数を呼び出します。

さらに、CloseAllPositions関数を定義し、PositionsTotalとPositionGetTicketでポジションを反復処理します。GetPositionSymbolとGetPositionMagicで対象ポジションを確認した後、obj_TradeのPositionCloseを使用して該当取引をクローズします。これにより、堅牢なドローダウン管理が可能となります。この関数は毎ティック呼び出すことで、条件に応じたリスク管理を実行できます。

//--- Monitor daily drawdown if enabled
if (ENABLE_DAILY_DRAWDOWN) MonitorDailyDrawdown();

ENABLE_DAILY_DRAWDOWN条件を確認してドローダウン制御が有効かどうかを判定し、trueの場合はMonitorDailyDrawdown関数を呼び出して日次損益を評価し、tradingEnabledを調整し、必要に応じてポジションをクローズします。これにより、口座が過度の損失から保護されます。コンパイルすると、次の結果が得られます。

最終ポジション管理GIF

可視化から、ポジションをオープンし、動的に管理し、目標達成後にクローズできることが確認できます。これにより、グリッドマーチン戦略のセットアップを作成するという目的が達成されました。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

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

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

結論として、私たちはMQL5プログラムを開発し、グリッドマーチンスキャルピング戦略を自動化しました。これにより、グリッドベースのマーチンゲール取引を実行し、スプレッド、利益、ロットサイズなどの主要指標をリアルタイムで監視できるダイナミックダッシュボードを提供します。正確な取引実行、ドローダウンによる堅牢なリスク管理、インタラクティブなインターフェースにより、このプログラムはさらにパラメータを調整したり、追加の戦略を統合したりして、自身の取引スタイルに最適化することが可能です。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。

これらの技術を習得することで、プログラムをさらに強化したり、他の取引戦略開発の基盤として活用したりでき、アルゴリズム取引の取り組みをより効果的に進めることができます。

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (5)
Ahmet Parlakbilek
Ahmet Parlakbilek | 14 5月 2025 において 21:27
いい記事だが、CalculateSLには重大なバグがある。
Allan Munene Mutiiria
Allan Munene Mutiiria | 14 5月 2025 において 22:45
Ahmet Parlakbilek #:
いい記事だが、CalculateSLには重大なバグがある。

確かに。どうしたんだ?

Ahmet Parlakbilek
Ahmet Parlakbilek | 15 5月 2025 において 06:36
Allan Munene Mutiiria #:

ありがとう。どうしたんだい?

売りサイドの処理を忘れていますよ。修正版を添付します。

Allan Munene Mutiiria
Allan Munene Mutiiria | 15 5月 2025 において 15:44
Ahmet Parlakbilek #:

売り側の処理を忘れている。修正版を添付する。

おお。そうだね。もちろん、他の人にも大いに役立つだろう。ありがとう。

Pramendra
Pramendra | 18 5月 2025 において 11:28
先生、直してください。
MQL5経済指標カレンダーを使った取引(第8回):ニュース駆動型バックテストの最適化 - スマートなイベントフィルタリングと選択的ログ MQL5経済指標カレンダーを使った取引(第8回):ニュース駆動型バックテストの最適化 - スマートなイベントフィルタリングと選択的ログ
本記事では、スマートなイベントフィルタリングと選択的ログ出力を用いて経済カレンダーを最適化し、ライブおよびオフラインモードでのバックテストをより高速かつ明確に実施できるようにします。イベント処理を効率化し、ログを重要な取引やダッシュボードイベントに絞ることで、戦略の可視化を向上させます。これらの改善により、ニュース駆動型取引戦略のテストと改善をシームレスにおこなえるようになります。
データサイエンスとML(第38回):外国為替市場におけるAI転移学習 データサイエンスとML(第38回):外国為替市場におけるAI転移学習
AIの画期的な進歩、たとえばChatGPTや自動運転車などは、単独のモデルから生まれたわけではなく、複数のモデルや共通の分野から得られた累積的な知識を活用することで実現しています。この「一度学習した知識を他に応用する」というアプローチは、アルゴリズム取引におけるAIモデルの変革にも応用可能です。本記事では、異なる金融商品の情報を活用し、他の銘柄における予測精度向上に役立てる方法として、転移学習の活用方法について解説します。
データサイエンスとML(第39回):ニュース × 人工知能、それに賭ける価値はあるか データサイエンスとML(第39回):ニュース × 人工知能、それに賭ける価値はあるか
ニュースは金融市場を動かす力を持っており、特に非農業部門雇用者数(NFP)のような主要指標の発表は大きな影響を与えます。私たちは、単一のヘッドラインが急激な価格変動を引き起こす様子を何度も目にしてきました。本記事では、ニュースデータと人工知能(AI)の強力な融合について探っていきます。
MQL5開発用のカスタムデバッグおよびプロファイリングツール(第1回):高度なロギング MQL5開発用のカスタムデバッグおよびプロファイリングツール(第1回):高度なロギング
MQL5で、単なるPrint文を超えた強力なカスタムロギングフレームワークを実装する方法を学びましょう。このフレームワークは、ログの重要度レベル、複数の出力ハンドラ、自動ファイルローテーションをサポートし、実行中にすべて設定可能です。シングルトン設計のCLoggerをConsoleLogHandlerとFileLogHandlerに統合することで、[エキスパート]タブと永続ファイルの両方に、文脈情報やタイムスタンプ付きのログを記録できます。明確でカスタマイズ可能なログ形式と集中管理により、エキスパートアドバイザー(EA)のデバッグとパフォーマンストレースを効率化します。