
MQL5での取引戦略の自動化(第17回):ダイナミックダッシュボードで実践するグリッドマーチンゲールスキャルピング戦略
はじめに
前回の記事(第16回)では、ミッドナイトレンジブレイクアウト戦略を「Break of Structure」と組み合わせ、自動化によって価格ブレイクアウトを捉える方法を解説しました。今回の第17回では、グリッドマーチンゲールスキャルピング戦略(Grid-Mart Scalping Strategy)をMetaQuotes Language 5 (MQL5)で自動化し、グリッド型マーチンゲール取引を実行するエキスパートアドバイザー(EA)を開発します。このEAにはリアルタイム監視用のダイナミックダッシュボードを搭載します。この記事では以下のトピックを取り上げます。
この記事を読み終える頃には、市場を精密にスキャルピングし、取引指標を可視化する完全機能搭載の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; }
ここでは、PositionsTotalとPositionGetTicketを利用した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を呼び出して各更新を確定させ、シームレスなユーザー体験を実現します。コンパイルすると、結果は次のようになります。
可視化結果から、パネルをドラッグしたり、ボタンにホバーしたり、指標を更新したり、ダッシュボード全体を閉じたりできることが確認できます。次に進むのは、最も重要な部分であるポジションのオープンと管理です。ダッシュボードは、進捗状況を動的に可視化することで、この作業を大幅に容易にします。そのために、ロットサイズの計算などをおこなうヘルパー関数が必要となります。以下にその内容を示します。
//+------------------------------------------------------------------+ //| 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で取引件数を反復処理し、HistoryDealGetTicketとHistoryDealGetDoubleで取引の詳細を取得します。直近の取引が損失であれば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をコメントに含めたパラメータでポジションをオープンします。さらに、GetLastErrorとSleepを活用した再試行ロジックを組み込み、堅牢な実行を確保します。ストップロス価格は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をリセットします。次に、PositionsTotalとPositionGetTicketを使ってポジションを反復処理し、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関数を実装します。TimeCurrentとTimeToStringを使用して当日の時間範囲を設定し、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を調整し、必要に応じてポジションをクローズします。これにより、口座が過度の損失から保護されます。コンパイルすると、次の結果が得られます。
可視化から、ポジションをオープンし、動的に管理し、目標達成後にクローズできることが確認できます。これにより、グリッドマーチン戦略のセットアップを作成するという目的が達成されました。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
徹底的なバックテストの結果、次の結果が得られました。
バックテストグラフ
バックテストレポート
結論
結論として、私たちはMQL5プログラムを開発し、グリッドマーチンスキャルピング戦略を自動化しました。これにより、グリッドベースのマーチンゲール取引を実行し、スプレッド、利益、ロットサイズなどの主要指標をリアルタイムで監視できるダイナミックダッシュボードを提供します。正確な取引実行、ドローダウンによる堅牢なリスク管理、インタラクティブなインターフェースにより、このプログラムはさらにパラメータを調整したり、追加の戦略を統合したりして、自身の取引スタイルに最適化することが可能です。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
これらの技術を習得することで、プログラムをさらに強化したり、他の取引戦略開発の基盤として活用したりでき、アルゴリズム取引の取り組みをより効果的に進めることができます。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18038
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
いい記事だが、CalculateSLには重大なバグがある。
確かに。どうしたんだ?
ありがとう。どうしたんだい?
売りサイドの処理を忘れていますよ。修正版を添付します。
売り側の処理を忘れている。修正版を添付する。
おお。そうだね。もちろん、他の人にも大いに役立つだろう。ありがとう。