English
preview
MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築

MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築

MetaTrader 5トレーディングシステム |
22 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第9回)では、MetaQuotes Language 5 (MQL5)でエキスパートアドバイザー(EA)向けの初回ユーザーセットアップウィザードを作成しました。このウィザードには、スクロール可能なガイド、インタラクティブなダッシュボード要素、動的なテキストフォーマット、ユーザー操作コントロールが組み込まれており、初期設定と操作習熟をスムーズにすることを目的としていました。今回の第10回では、戦略トラッカーシステムを開発します。このシステムでは、移動平均線のクロスオーバーシグナルを検知し、長期移動平均線でフィルタリングします。さらに、仮想取引または実取引を追跡し、複数の利益確定(利確)レベルや損切りを設定することができます。チャート上にはエントリー、利確到達、損切り到達などの結果が可視化され、ダッシュボードでリアルタイムのパフォーマンス統計(勝率/敗率、獲得ポイント数、成功率など)が表示されます。本記事では以下のトピックを扱います。

  1. 取引における戦略トラッカーシステムの役割とメリット
  2. MQL5での実装
  3. 戦略トラッカーのテスト
  4. 結論

この記事を読み終える頃には、戦略パフォーマンスを監視するための機能的でカスタマイズ可能なMQL5ツールを手に入れることができます。それでは、さっそく始めましょう。


取引における戦略トラッカーシステムの役割とメリット

取引における戦略トラッカーシステムの役割とメリットは、シグナルのパフォーマンスをリアルタイムで監視および分析できる点にあります。これにより、時間がかかりエラーが起こりやすいバックテストや手動での記録に頼らず、戦略の有効性を評価できます。エントリー、利確到達、SL発動、勝率や獲得ポイント数などの累積統計をチャートやダッシュボード上に可視化することで、戦略の妥当性について即座にフィードバックを得られます。これにより、移動平均期間やリスクレベルなどのパラメータを迅速に調整し、実際の市場での成果を改善することが可能となります。最終的に、このようなツールは意思決定を強化し、透明性のある追跡によって信頼性を高め、戦略を反復的に改善するためのサポートを提供します。

私たちのアプローチは、売買のシグナルとして、価格がフィルタ移動平均の上または下に位置していることを条件とした、高速および低速移動平均のクロスオーバーを検出することです。これはあくまで一例のシンプルな戦略であり、もちろんお好みの戦略に置き換えることも可能です。さらに、仮想ポジションをシミュレーションすることも実際の取引を実行することもでき、利確や損切りをポイント単位で設定することができます。チャート上では、エントリーを矢印で表示し、利確や損切りに達した場合は点線で示し、結果をアイコンで可視化します。また、シグナル数、勝敗、経過時刻、利益、成功率などの統計情報をインタラクティブなダッシュボードで更新し、戦略評価や改善に役立つ総合的なツールを作成します。要するに、以下のようなイメージを目指しています。

システムの可視化


MQL5での実装

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

//+------------------------------------------------------------------+
//|                                       1. Strategy Tracker 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"

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum TradeMode {                                                  // Define trade mode enum
   Visual_Only,                                                   // Visual Only
   Open_Trades                                                    // Open Trades
};

enum TPLevel {                                                    // Define TP level enum
   Level_1,                                                       // TP1
   Level_2,                                                       // TP2
   Level_3                                                        // TP3
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input TradeMode        trade_mode      = Visual_Only;             // Trading Mode
input int              fast_ma_period  = 10;                      // Fast MA Period
input int              slow_ma_period  = 20;                      // Slow MA Period
input int              filter_ma_period = 200;                    // Filter MA Period
input ENUM_MA_METHOD   ma_method       = MODE_SMA;                // MA Method
input ENUM_APPLIED_PRICE ma_price      = PRICE_CLOSE;             // MA Applied Price
input int              tp1_points      = 50;                      // TP1 Points
input int              tp2_points      = 100;                     // TP2 Points
input int              tp3_points      = 150;                     // TP3 Points
input TPLevel          tp_level        = Level_1;                 // Select TP Level
input int              sl_points       = 150;                     // SL Points
input int              dash_x          = 30;                      // Dashboard X Offset
input int              dash_y          = 30;                      // Dashboard Y Offset

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
// Handles for indicators
int h_fast_ma, h_slow_ma, h_filter_ma;                            //--- MA handles
// Active signal structure
struct ActiveSignal {                                             // Define active signal structure
   bool     active;                                               //--- Signal active flag
   int      pos_type;                                             //--- Position type (1 buy, -1 sell)
   datetime entry_time;                                           //--- Entry time
   double   entry_price;                                          //--- Entry price
   double   tp1, tp2, tp3, sl;                                    //--- TP and SL levels
   bool     hit_tp1, hit_tp2, hit_tp3;                            //--- TP hit flags
   bool     hit_sl;                                               //--- SL hit flag
   datetime close_time;                                           //--- Close time
};
ActiveSignal current_signal;                                      //--- Current signal instance
// Stats
long   total_signals      = 0;                                    //--- Total signals count
long   wins               = 0;                                    //--- Wins count
long   losses             = 0;                                    //--- Losses count
double total_profit_points = 0.0;                                 //--- Total profit in points
// Dashboard prefix
string dash_prefix = "ProDashboard_";                             //--- Dashboard object prefix
// Last bar time
datetime last_bar_time = 0;                                       //--- Last processed bar time
// Position ticket for Open_Trades mode
ulong position_ticket = -1;                                       //--- Position ticket

まず、設定オプション用に2つの列挙型を定義します。TradeModeは、戦略を実際に取引する場合に備えて、実際に注文を出さずにシミュレーションだけをおこなうVisual_Onlyと、実際に注文を出すOpen_Tradesの2つを用意します。また、TPLevelは利確ターゲットを選択するためにLevel_1、Level_2、Level_3の3段階を提供します。

次に、ユーザーがカスタマイズできる入力パラメータを設定します。デフォルトではtrade_modeをVisual_Onlyにし、短期移動平均の期間をfast_ma_period=10、長期移動平均の期間をslow_ma_period=20、長期フィルタ用移動平均をfilter_ma_period=200に設定します。移動平均の計算方法はma_methodをMODE_SMA(単純移動平均)、価格の基準はma_priceをPRICE_CLOSE(終値)にしています。利確幅はtp1_points=50、tp2_points=100、tp3_points=150とし、デフォルトの利確レベルはtp_level=Level_1に設定します。損切り幅はsl_points=150、ダッシュボード表示位置はdash_xとdash_yをそれぞれ30にしています。

さらに、グローバル変数を宣言します。移動平均のハンドルとしてh_fast_ma、h_slow_ma、h_filter_maを用意し、現在のポジションを管理するためにActiveSignal構造体を定義します。この構造体には、取引の有効フラグactive、買い(1)または売り(-1)を示すpos_type、エントリー詳細、利確および損切りレベル、到達フラグ、決済時刻などのフィールドがあり、current_signalとしてインスタンス化します。統計情報のカウンタとしてtotal_signals、wins、losses、total_profit_pointsを0.0で初期化し、ダッシュボード用のオブジェクト命名接頭辞dash_prefixは「ProDashboard_」に設定します。新しいバーの検知にはlast_bar_time=0を使用し、実取引モードでのポジション管理にはposition_ticket=-1を使います。最後に、チャート上に可視化オブジェクトを作成するためのヘルパー関数が必要となります。

//+------------------------------------------------------------------+
//| Function to create rectangle label                               |
//+------------------------------------------------------------------+
bool createRecLabel(string objName, int xD, int yD, int xS, int yS,
                    color clrBg, int widthBorder, color clrBorder = clrNONE,
                    ENUM_BORDER_TYPE borderType = BORDER_FLAT,
                    ENUM_LINE_STYLE borderStyle = STYLE_SOLID,
                    ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset last error
   if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label
      Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log error
      return (false);                                             //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set X size
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set Y size
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg);          //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type
   ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle);      //--- Set border style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder);      //--- Set border width
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder);        //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selected
   ChartRedraw(0);                                                //--- Redraw chart
   return (true);                                                 //--- Return success
}

//+------------------------------------------------------------------+
//| Function to create text label                                    |
//+------------------------------------------------------------------+
bool createLabel(string objName, int xD, int yD,
                 string txt, color clrTxt = clrBlack, int fontSize = 12,
                 string font = "Arial Rounded MT Bold",
                 ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset last error
   if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) {           //--- Create label
      Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log error
      return (false);                                             //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set X distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set Y distance
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, txt);                //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);           //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);      //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);               //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selected
   ChartRedraw(0);                                                //--- Redraw chart
   return (true);                                                 //--- Return success
}

//+------------------------------------------------------------------+
//| Function to create trend line                                    |
//+------------------------------------------------------------------+
bool createTrendline(string objName, datetime time1, double price1, datetime time2, double price2, color clr, ENUM_LINE_STYLE line_style = STYLE_SOLID, bool isBack = false, bool ray_right = false) {
   ResetLastError();                                              //--- Reset last error
   if (!ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2)) { //--- Create trendline
      Print(__FUNCTION__, ": Failed to create trendline: Error Code: ", GetLastError()); //--- Log error
      return (false);                                             //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);              //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_STYLE, line_style);       //--- Set style
   ObjectSetInteger(0, objName, OBJPROP_BACK, isBack);            //--- Set back
   ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, ray_right);    //--- Set ray right
   ChartRedraw(0);                                                //--- Redraw chart
   return (true);                                                 //--- Return success
}

createRecLabel関数を定義して、ダッシュボードパネル用の矩形ラベルを生成します。この関数は、名前name、位置xDとyD、サイズxSとyS、背景色clrBg、枠線の幅border_width、およびオプションで枠線色clrBorder(デフォルトはclrNONE)、枠タイプBORDER_FLAT、スタイルSTYLE_SOLID、角の位置CORNER_LEFT_UPPERといったパラメータを受け取ります。ResetLastErrorでエラーをリセットした後、OBJ_RECTANGLE_LABELとしてオブジェクトを作成し、ObjectSetIntegerを用いて位置、サイズ、角、色、枠線の詳細、前景表示および非選択状態などのプロパティを設定します。その後ChartRedrawで再描画し、もし失敗した場合はログに記録します。

同様に、createLabelはテキストラベルを作成します。名前name、位置xDとyD、テキストtxt、文字色clrTxt(デフォルトは黒)、文字サイズ12、フォント「Arial Rounded MT Bold」、角CORNER_LEFT_UPPERを受け取り、OBJ_LABELとしてオブジェクトを作成します。テキスト内容とフォントプロパティを設定し、前景かつ非選択状態にして再描画し、エラーを処理します。createTrendlineではトレンドラインを構築します。名前name、始点と終点の時刻/価格、色clr、スタイルSTYLE_SOLID(デフォルト)、バックフラグfalse、右方向へのレイfalseといったパラメータを指定し、OBJ_TRENDとして作成します。色、スタイル、バック、レイを設定して再描画し、作成に失敗した場合はログに記録します。これらの関数を用いることで、初期ダッシュボードを作成できます。さらに、処理をモジュール化して整理することも可能です。

//+------------------------------------------------------------------+
//| Create dashboard                                                 |
//+------------------------------------------------------------------+
void CreateDashboard() {
   int panel_x = dash_x;                                          //--- Panel X
   int panel_y = dash_y;                                          //--- Panel Y
   int panel_w = 250;                                             //--- Panel width
   int panel_h = 350;                                             //--- Panel height
   color bg_color = clrNavy;                                      //--- BG color
   color border_color = clrRoyalBlue;                             //--- Border color
   string space = " ";                                            //--- Space string
   createRecLabel(dash_prefix + "Panel", panel_x, panel_y, panel_w, panel_h, bg_color, 1, border_color, BORDER_FLAT); //--- Create panel
   color header_bg = clrMidnightBlue;                             //--- Header BG
   createRecLabel(dash_prefix + "HeaderPanel", panel_x + 1, panel_y + 1, panel_w - 2, 40, header_bg, 0, clrNONE, BORDER_FLAT); //--- Create header
   int rel_y = 7;                                                 //--- Relative Y
   createLabel(dash_prefix + "Header", panel_x + 15, panel_y + rel_y, "Strategy Tracker Dashboard", clrMediumSpringGreen, 12, "Arial Bold"); //--- Create header label
   rel_y += 30;                                                   //--- Increment Y
   color signal_bg = clrDarkSlateBlue;                            //--- Signal BG
   int signal_height = 160;                                       //--- Signal height
   createRecLabel(dash_prefix + "SignalPanel", panel_x + 1, panel_y + rel_y - 10, panel_w - 2, signal_height, signal_bg, 0, clrNONE, BORDER_FLAT); //--- Create signal panel
   createLabel(dash_prefix + "SignalHeader", panel_x + 10, panel_y + rel_y, "Current Signal", clrLightCyan, 11, "Arial Bold"); //--- Create signal header
   rel_y += 25;                                                   //--- Increment Y
   createLabel(dash_prefix + "SymbolLabel", panel_x + 10, panel_y + rel_y, "Symbol:", clrWhite, 10, "Arial Bold"); //--- Create symbol label
   createLabel(dash_prefix + "SymbolValue", panel_x + 100, panel_y + rel_y, _Symbol+" "+StringSubstr(EnumToString(_Period),7), clrDeepSkyBlue, 10, "Arial Bold"); //--- Create symbol value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "DirectionLabel", panel_x + 10, panel_y + rel_y, "Signal:", clrWhite, 10, "Arial Bold"); //--- Create direction label
   createLabel(dash_prefix + "EntryPrice", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial Bold"); //--- Create entry price
   createLabel(dash_prefix + "DirectionValue", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create direction value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "TP1Label", panel_x + 10, panel_y + rel_y, "TP1:", clrWhite, 10, "Arial Bold"); //--- Create TP1 label
   createLabel(dash_prefix + "TP1Value", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create TP1 value
   createLabel(dash_prefix + "TP1Icon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create TP1 icon
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "TP2Label", panel_x + 10, panel_y + rel_y, "TP2:", clrWhite, 10, "Arial Bold"); //--- Create TP2 label
   createLabel(dash_prefix + "TP2Value", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create TP2 value
   createLabel(dash_prefix + "TP2Icon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create TP2 icon
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "TP3Label", panel_x + 10, panel_y + rel_y, "TP3:", clrWhite, 10, "Arial Bold"); //--- Create TP3 label
   createLabel(dash_prefix + "TP3Value", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create TP3 value
   createLabel(dash_prefix + "TP3Icon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create TP3 icon
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "SLLabel", panel_x + 10, panel_y + rel_y, "SL:", clrWhite, 10, "Arial Bold"); //--- Create SL label
   createLabel(dash_prefix + "SLValue", panel_x + 100, panel_y + rel_y, space, clrWhite, 10, "Arial"); //--- Create SL value
   createLabel(dash_prefix + "SLIcon", panel_x + 200, panel_y + rel_y, space, clrWhite, 12, "Wingdings"); //--- Create SL icon
   rel_y += 20;                                                   //--- Increment Y
   color stats_bg = clrIndigo;                                    //--- Stats BG
   int stats_height = 140;                                        //--- Stats height
   createRecLabel(dash_prefix + "StatsPanel", panel_x + 1, panel_y + rel_y + 3, panel_w - 2, stats_height, stats_bg, 0, clrNONE, BORDER_FLAT); //--- Create stats panel
   createLabel(dash_prefix + "StatsHeader", panel_x + 10, panel_y + rel_y + 10, "Statistics", clrLightCyan, 11, "Arial Bold"); //--- Create stats header
   rel_y += 25;                                                   //--- Increment Y
   createLabel(dash_prefix + "TotalLabel", panel_x + 10, panel_y + rel_y+10, "Total Signals:", clrWhite, 10, "Arial Bold"); //--- Create total label
   createLabel(dash_prefix + "TotalValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create total value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "WinLossLabel", panel_x + 10, panel_y + rel_y+10, "Win/Loss:", clrWhite, 10, "Arial Bold"); //--- Create win/loss label
   createLabel(dash_prefix + "WinLossValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create win/loss value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "AgeLabel", panel_x + 10, panel_y + rel_y+10, "Last Signal Age:", clrWhite, 10, "Arial Bold"); //--- Create age label
   createLabel(dash_prefix + "AgeValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create age value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "ProfitLabel", panel_x + 10, panel_y + rel_y+10, "Profit in Points:", clrWhite, 10, "Arial Bold"); //--- Create profit label
   createLabel(dash_prefix + "ProfitValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create profit value
   rel_y += 20;                                                   //--- Increment Y
   createLabel(dash_prefix + "SuccessLabel", panel_x + 10, panel_y + rel_y+10, "Success Rate:", clrWhite, 10, "Arial Bold"); //--- Create success label
   createLabel(dash_prefix + "SuccessValue", panel_x + 150, panel_y + rel_y+10, space, clrWhite, 10, "Arial"); //--- Create success value
   rel_y += 20;                                                   //--- Increment Y
   color footer_bg = clrMidnightBlue;                             //--- Footer BG
   createRecLabel(dash_prefix + "FooterPanel", panel_x + 1, panel_y + rel_y + 5+10, panel_w - 2, 25, footer_bg, 0, clrNONE, BORDER_FLAT); //--- Create footer
   createLabel(dash_prefix + "Footer", panel_x + 30, panel_y + rel_y + 10+10, "Copyright 2025, Allan Munene Mutiiria.", clrYellow, 8, "Arial"); //--- Create footer label
}

CreateDashboard関数を実装して、統計やシグナル用のビジュアルパネルを設定します。まず、パネルの位置をpanel_x=dash_x、panel_y=dash_yに決め、幅250、高さ350のサイズで、ネイビー背景にロイヤルブルーの枠線を持つメインコンテナを作成します(好みに応じて色は変更可能です)。このコンテナにはcreateRecLabelを使用します。次に、わずかに内側に入ったヘッダサブパネルを作成し、背景をミッドナイトブルーにして枠線なしに設定します。そこに「Strategy Tracker Dashboard」というラベルを、ミディアムスプリンググリーン、太字Arial、サイズ12で追加します。相対y位置rel_yを30進め、ダークスレートブルーのシグナルサブパネルを描画し、高さ160に設定します。ヘッダには「Current Signal」と書き、ライトシアン、太字11ピクセルで表示します。

さらにrel_yを25進め、各ラベルを配置します。「Symbol:」ラベルは白、太字10、値としてSymbolにTimeframeの文字列をEnumToStringで結合したものをディープスカイブルーで表示します。「Signal:」ラベル、エントリープライスのプレースホルダーは白太字、方向アイコンプレースホルダーは白のWingdings、サイズ12で配置します。利確(TP1/TP2/TP3)や損切り用には、それぞれ「TP1:」のようなラベル、値プレースホルダーを白Arialサイズ10、アイコンを白Wingdingsサイズ12で追加し、rel_yを20ずつ進めます。さらに20進め、インディゴ背景の統計サブパネルを高さ140で作成し、ヘッダに「Statistics」をライトシアン、太字11で配置します。rel_yを25進め、統計ラベルとして「Total Signals:」白太字10、値は白Arial10でプレースホルダー、同様に「Win/Loss:」「Last Signal Age:」「Profit in Points:」「Success Rate:」を20ずつ間隔を空けて配置します。最後にさらに20進め、フッターサブパネルをミッドナイトブルー、高さ25で作成し、著作権テキストを黄色Arialサイズ8ピクセルで表示します。これでシステムを初期化して、どのように表示されるかを確認できます。最初に何から始めてもよく、これは本当に簡単で、すぐに他のロジックと組み合わせることができます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   h_fast_ma = iMA(_Symbol, PERIOD_CURRENT, fast_ma_period, 0, ma_method, ma_price); //--- Create fast MA
   h_slow_ma = iMA(_Symbol, PERIOD_CURRENT, slow_ma_period, 0, ma_method, ma_price); //--- Create slow MA
   h_filter_ma = iMA(_Symbol, PERIOD_CURRENT, filter_ma_period, 0, ma_method, ma_price); //--- Create filter MA
   if (h_fast_ma == INVALID_HANDLE || h_slow_ma == INVALID_HANDLE || h_filter_ma == INVALID_HANDLE) { //--- Check handles
      Print("Failed to initialize MA handles");                   //--- Log error
      return(INIT_FAILED);                                        //--- Return failure
   }
   current_signal.active = false;                                 //--- Reset active
   current_signal.hit_sl = false;                                 //--- Reset SL hit
   current_signal.close_time = 0;                                 //--- Reset close time
   CreateDashboard();                                             //--- Create dashboard
   return(INIT_SUCCEEDED);                                        //--- Return success
}

OnInitイベントハンドラでは、まず3つの移動平均のハンドルを作成します。iMA関数を使い、銘柄は_Symbol、時間足はPERIOD_CURRENT、各期間はfast_ma_period、slow_ma_period、filter_ma_period、シフトは0、計算方法はma_method、価格タイプはma_priceとして、それぞれh_fast_ma、h_slow_ma、h_filter_maに格納します。いずれかのハンドルがINVALID_HANDLEの場合は、Printでエラーをログに残し、INIT_FAILEDを返してセットアップを中止します。繰り返しになりますが、使用するシグナルの戦略は完全にユーザー次第で、このツールはあくまで戦略を追跡するためのものです。好きな戦略に切り替えて構いません。次にcurrent_signal構造体をリセットし、activeをfalse、hit_slをfalse、close_timeを0に設定してクリーンな状態から開始します。その後、CreateDashboardを呼び出してビジュアルパネルを初期化し、最後にINIT_SUCCEEDEDを返してロードの成功を確認します。プログラミングの良い習慣として、各マイルストーンごとにコードをテストすることが推奨されます。テストをおこなった結果、プログラムは以下のように初期化されました。

初期化

これでプログラムの初期化が成功したことが確認できました。次に、シグナルを検出して追跡し、その情報を保存および可視化してより簡単に追跡できるようにする必要があります。そのために、可視化をおこなうためのユーティリティ関数を以下のように定義します。

//+------------------------------------------------------------------+
//| Draw initial TP and SL visuals                                   |
//+------------------------------------------------------------------+
void DrawInitialLevels() {
   datetime entry_tm = current_signal.entry_time;                 //--- Entry time
   datetime bubble_tm = entry_tm;                                 //--- Bubble time
   datetime points_tm = bubble_tm;                                //--- Points time
   // TP1
   string prefix = "Initial_TP1_" + TimeToString(entry_tm) + "_"; //--- TP1 prefix
   color tp_color = clrBlue;                                      //--- TP color
   double hit_pr = current_signal.tp1;                            //--- TP1 price
   int pts = tp1_points;                                          //--- TP1 points
   char bubble_code = (char)140;                                  //--- Bubble code
   string bubble_name = prefix + "Bubble";                        //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   string points_name = prefix + "Points";                        //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "+" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   // TP2
   prefix = "Initial_TP2_" + TimeToString(entry_tm) + "_";        //--- TP2 prefix
   hit_pr = current_signal.tp2;                                   //--- TP2 price
   pts = tp2_points;                                              //--- TP2 points
   bubble_code = (char)141;                                       //--- Bubble code
   bubble_name = prefix + "Bubble";                               //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   points_name = prefix + "Points";                               //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "+" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   // TP3
   prefix = "Initial_TP3_" + TimeToString(entry_tm) + "_";        //--- TP3 prefix
   hit_pr = current_signal.tp3;                                   //--- TP3 price
   pts = tp3_points;                                              //--- TP3 points
   bubble_code = (char)142;                                       //--- Bubble code
   bubble_name = prefix + "Bubble";                               //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   points_name = prefix + "Points";                               //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "+" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, tp_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   // SL
   prefix = "Initial_SL_" + TimeToString(entry_tm) + "_";         //--- SL prefix
   hit_pr = current_signal.sl;                                    //--- SL price
   color sl_color = clrMagenta;                                   //--- SL color
   pts = sl_points;                                               //--- SL points
   bubble_code = (char)164;                                       //--- Bubble code
   bubble_name = prefix + "Bubble";                               //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, sl_color);     //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   points_name = prefix + "Points";                               //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, "-" + IntegerToString(pts)); //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, sl_color);     //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
}

//+------------------------------------------------------------------+
//| Draw TP hit visuals                                              |
//+------------------------------------------------------------------+
void DrawTPHit(int tp_num, datetime hit_tm, double hit_pr, int pts) {
   string prefix = "Signal_TP" + IntegerToString(tp_num) + "_" + TimeToString(current_signal.entry_time) + "_"; //--- Prefix
   color tp_color = clrBlue;                                      //--- TP color
   createTrendline(prefix + "DottedLine", current_signal.entry_time, current_signal.entry_price, hit_tm, hit_pr, clrDarkGray, STYLE_DOT, true, false); //--- Draw dotted
   createTrendline(prefix + "Connect", current_signal.entry_time, hit_pr, hit_tm, hit_pr, clrDarkGray, STYLE_SOLID, true, false); //--- Draw connect
   string tick_name = prefix + "Tick";                            //--- Tick name
   ObjectCreate(0, tick_name, OBJ_TEXT, 0, hit_tm, hit_pr);       //--- Create tick
   ObjectSetString(0, tick_name, OBJPROP_TEXT, CharToString((char)254)); //--- Set text
   ObjectSetString(0, tick_name, OBJPROP_FONT, "Wingdings");      //--- Set font
   ObjectSetInteger(0, tick_name, OBJPROP_COLOR, tp_color);       //--- Set color
   ObjectSetInteger(0, tick_name, OBJPROP_FONTSIZE, 12);          //--- Set size
   ObjectSetInteger(0, tick_name, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor
}

//+------------------------------------------------------------------+
//| Draw SL hit visuals                                              |
//+------------------------------------------------------------------+
void DrawSLHit(datetime hit_tm, double hit_pr) {
   string prefix = "Signal_SL_" + TimeToString(current_signal.entry_time) + "_"; //--- Prefix
   color sl_color = clrMagenta;                                   //--- SL color
   createTrendline(prefix + "DottedLine", current_signal.entry_time, current_signal.entry_price, hit_tm, hit_pr, clrDarkGray, STYLE_DOT, true, false); //--- Draw dotted
   createTrendline(prefix + "Connect", current_signal.entry_time, hit_pr, hit_tm, hit_pr, clrDarkGray, STYLE_SOLID, true, false); //--- Draw connect
   string tick_name = prefix + "Tick";                            //--- Tick name
   ObjectCreate(0, tick_name, OBJ_TEXT, 0, hit_tm, hit_pr);       //--- Create tick
   ObjectSetString(0, tick_name, OBJPROP_TEXT, CharToString((char)253)); //--- Set text
   ObjectSetString(0, tick_name, OBJPROP_FONT, "Wingdings");      //--- Set font
   ObjectSetInteger(0, tick_name, OBJPROP_COLOR, sl_color);       //--- Set color
   ObjectSetInteger(0, tick_name, OBJPROP_FONTSIZE, 12);          //--- Set size
   ObjectSetInteger(0, tick_name, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor
}

//+------------------------------------------------------------------+
//| Draw early close visuals                                         |
//+------------------------------------------------------------------+
void DrawEarlyClose(datetime hit_tm, double hit_pr, double pts) {
   string prefix = "Signal_Close_" + TimeToString(current_signal.entry_time) + "_"; //--- Prefix
   color close_color = (pts > 0) ? clrBlue : clrMagenta;          //--- Close color
   createTrendline(prefix + "DottedLine", current_signal.entry_time, current_signal.entry_price, hit_tm, hit_pr, clrDarkGray, STYLE_DOT, true, false); //--- Draw dotted
   datetime bubble_tm = current_signal.entry_time;                //--- Bubble time
   char bubble_code = (char)214;                                  //--- Bubble code
   string bubble_name = prefix + "Bubble";                        //--- Bubble name
   ObjectCreate(0, bubble_name, OBJ_TEXT, 0, bubble_tm, hit_pr);  //--- Create bubble
   ObjectSetString(0, bubble_name, OBJPROP_TEXT, CharToString(bubble_code)); //--- Set text
   ObjectSetString(0, bubble_name, OBJPROP_FONT, "Wingdings");    //--- Set font
   ObjectSetInteger(0, bubble_name, OBJPROP_COLOR, close_color);  //--- Set color
   ObjectSetInteger(0, bubble_name, OBJPROP_FONTSIZE, 12);        //--- Set size
   ObjectSetInteger(0, bubble_name, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor
   datetime points_tm = bubble_tm;                                //--- Points time
   string sign = (pts > 0) ? "+" : "";                            //--- Sign
   string points_text = sign + DoubleToString(pts, 0);            //--- Points text
   string points_name = prefix + "Points";                        //--- Points name
   ObjectCreate(0, points_name, OBJ_TEXT, 0, points_tm, hit_pr);  //--- Create points
   ObjectSetString(0, points_name, OBJPROP_TEXT, points_text);    //--- Set text
   ObjectSetInteger(0, points_name, OBJPROP_COLOR, close_color);  //--- Set color
   ObjectSetInteger(0, points_name, OBJPROP_FONTSIZE, 10);        //--- Set size
   ObjectSetInteger(0, points_name, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
   createTrendline(prefix + "Connect", bubble_tm, hit_pr, hit_tm, hit_pr, clrDarkGray, STYLE_SOLID, true, false); //--- Draw connect
}

ここではまず、DrawInitialLevels関数を実装して、シグナルのエントリー時点で利確と損切りのレベルを可視化します。バブルやポイントラベルの時刻はすべてcurrent_signal.entry_timeを使います。利確レベル1~3については、時刻文字列を組み合わせて一意の接頭辞を生成し、青色で描画します。構造体のフィールド(例:current_signal.tp1やtp1_points)から価格とポイントを取得し、バブルとしてWingdingsコード(TP1は140、TP2は141、TP3は142)のテキストオブジェクトをObjectCreateで作成します。フォントはWingdings、サイズ12、左アンカーに設定し、ポイントラベルは「+」に変換したポイント値を青色サイズ10、右アンカーで追加します。損切りについても同様で、色はマゼンタ、バブルコードは164、ポイントテキストは「-」付きで描画します。MQL5ではWingdingsコードを使って自由にアイコンを作ることができます。以下をご覧ください。

MQL5 WINGDINGS

次に、DrawTPHit関数では、利確に到達した場合の可視化をおこないます。利確レベルの番号、到達時刻、価格、ポイントを受け取り、エントリーから到達価格までの点線ダークグレーのトレンドラインをcreateTrendlineでバックに描画(レイなし)し、到達価格での水平線を実線で接続します。その中央にWingdingsのチェックマーク(コード254)を青色サイズ12で表示します。これがチェックマークが出る仕組みです。同様に、DrawSLHitは損切り到達時の描画を行い、到達価格までの点線を引き、水平線で接続し、中央にWingdingsアイコン(コード253)をマゼンタサイズ12で配置します。

DrawEarlyCloseでは早期決済の処理をおこないます。決済価格までの点線を引き、ポイントがプラスなら青、マイナスならマゼンタで色を決定します。エントリー時刻の決済価格にバブル(コード214)をその色サイズ12、左アンカーで配置し、サイン付きポイントテキスト(「+」または空文字)を同じ色サイズ10、右アンカーで追加し、水平線で接続します。これらの関数を使えば、ポジションの開閉、特に仮想ポジションの管理と可視化を簡単におこなえます。以下がそのロジックです。

//+------------------------------------------------------------------+
//| Get close price from history for auto-closed position            |
//+------------------------------------------------------------------+
double GetPositionClosePrice(long ticket) {
   HistorySelectByPosition(ticket);                               //--- Select history
   int deals = HistoryDealsTotal();                               //--- Get deals count
   if (deals > 0) {                                               //--- Check deals
      ulong deal_ticket = HistoryDealGetTicket(deals - 1);        //--- Get last deal
      return HistoryDealGetDouble(deal_ticket, DEAL_PRICE);       //--- Return price
   }
   return 0.0;                                                    //--- Return fallback
}

//+------------------------------------------------------------------+
//| Open virtual position                                            |
//+------------------------------------------------------------------+
void OpenVirtualPosition(int type, datetime ent_time, double ent_price) {
   current_signal.active = true;                                  //--- Set active
   current_signal.pos_type = type;                                //--- Set type
   current_signal.entry_time = ent_time;                          //--- Set time
   current_signal.entry_price = ent_price;                        //--- Set price
   current_signal.tp1 = ent_price + (tp1_points * _Point) * type; //--- Set TP1
   current_signal.tp2 = ent_price + (tp2_points * _Point) * type; //--- Set TP2
   current_signal.tp3 = ent_price + (tp3_points * _Point) * type; //--- Set TP3
   current_signal.sl = ent_price - (sl_points * _Point) * type;   //--- Set SL
   current_signal.hit_tp1 = false;                                //--- Reset TP1 hit
   current_signal.hit_tp2 = false;                                //--- Reset TP2 hit
   current_signal.hit_tp3 = false;                                //--- Reset TP3 hit
   current_signal.hit_sl = false;                                 //--- Reset SL hit
   current_signal.close_time = 0;                                 //--- Reset close time
   position_ticket = -1;                                          //--- Reset ticket
   DrawInitialLevels();                                           //--- Draw levels
}

//+------------------------------------------------------------------+
//| Close virtual position                                           |
//+------------------------------------------------------------------+
void CloseVirtualPosition(double close_price, bool is_early) {
   if (!current_signal.active) return;                            //--- Return if not active
   double profit_pts = (close_price - current_signal.entry_price) / _Point * current_signal.pos_type; //--- Calc profit
   current_signal.close_time = TimeCurrent();                     //--- Set close time
   if (is_early) {                                                //--- Check early
      DrawEarlyClose(TimeCurrent(), close_price, profit_pts);     //--- Draw early close
      if (trade_mode == Open_Trades && position_ticket != -1) {   //--- Check open trades
         MqlTradeRequest close_request = {};                      //--- Close request
         MqlTradeResult close_result = {};                        //--- Close result
         close_request.action = TRADE_ACTION_DEAL;                //--- Set action
         close_request.symbol = _Symbol;                          //--- Set symbol
         close_request.volume = 0.1;                              //--- Set volume
         close_request.type = (current_signal.pos_type == 1) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set type
         close_request.price = close_price;                       //--- Set price
         close_request.deviation = 3;                             //--- Set deviation
         close_request.position = position_ticket;                //--- Set position
         if (!OrderSend(close_request, close_result)) {           //--- Send close
            Print("Failed to close trade: ", GetLastError());     //--- Log error
         }
         position_ticket = -1;                                    //--- Reset ticket
      }
   }
   if (trade_mode == Open_Trades) {                               //--- Check open trades
      bool hit_selected_tp = false;                               //--- Init selected TP
      switch(tp_level) {                                          //--- Select TP
         case Level_1: hit_selected_tp = current_signal.hit_tp1; break; //--- TP1
         case Level_2: hit_selected_tp = current_signal.hit_tp2; break; //--- TP2
         case Level_3: hit_selected_tp = current_signal.hit_tp3; break; //--- TP3
      }
      bool count_it = current_signal.hit_sl || hit_selected_tp || !is_early; //--- Check count
      if (count_it) {                                              //--- Count
         total_profit_points += profit_pts;                        //--- Add profit
         total_signals++;                                          //--- Increment signals
         if (profit_pts > 0) wins++;                               //--- Increment wins
         else losses++;                                            //--- Increment losses
      }
   } else {                                                        //--- Visual only
      bool hit_selected = false;                                   //--- Init selected hit
      int selected_points = 0;                                     //--- Init points
      switch(tp_level) {                                           //--- Select TP
         case Level_1: hit_selected = current_signal.hit_tp1; selected_points = tp1_points; break; //--- TP1
         case Level_2: hit_selected = current_signal.hit_tp2; selected_points = tp2_points; break; //--- TP2
         case Level_3: hit_selected = current_signal.hit_tp3; selected_points = tp3_points; break; //--- TP3
      }
      double effective_profit = 0.0;                               //--- Init effective profit
      if (hit_selected) {                                          //--- Check hit selected
         effective_profit = (double)selected_points;               //--- Set to selected points
      } else if (current_signal.hit_sl) {                          //--- Check SL hit
         effective_profit = - (double)sl_points;                   //--- Set to -SL
      } else {                                                     //--- Else
         effective_profit = profit_pts;                            //--- Set to profit pts
      }
      total_profit_points += effective_profit;                     //--- Add effective profit
      total_signals++;                                             //--- Increment signals
      if (hit_selected || effective_profit > 0) wins++;            //--- Increment wins
      else losses++;                                               //--- Increment losses
   }
   current_signal.active = false;                                  //--- Deactivate
}

ここではまず、GetPositionClosePrice関数を定義し、自動決済されたポジションの決済価格を履歴から取得します。具体的には、チケット番号を使ってHistorySelectByPositionで履歴を選択し、HistoryDealsTotalで対象の取引数を確認します。取引が存在する場合は、直近の取引のチケットをHistoryDealGetTicketで取得し、HistoryDealGetDoubleDEAL_PRICEで価格を取得します。取引が存在しない場合は、デフォルトで0.0を返します。

次に、OpenVirtualPosition関数は仮想取引を開始するために用います。シグナルをアクティブ化し、タイプ(買い=1、売り=-1)、エントリー時刻と価格を設定し、利確レベルはエントリー価格に各ポイント×_Point×タイプ方向で算出、損切りは同様に引き算します。ヒットフラグや決済時刻をクリアし、チケットをリセットした後、DrawInitialLevelsで可視化します。CloseVirtualPosition関数は、指定した決済価格と早期決済フラグでシミュレーションを終了します。非アクティブなら処理を抜け、利益ポイントを(決済価格−建値)/_Pointで計算し、決済時刻を現在に更新します。早期決済の場合はDrawEarlyCloseでインジケーターを描画し、実取引モードで有効なチケットがある場合は逆指値による成行決済注文を作成してOrderSendで送信し、失敗をログに記録してチケットをクリアします。

実取引モードでは、選択された利確レベルに到達したかをtp_levelのスイッチで評価し、損切り到達や利確到達、早期でない場合の条件をチェックして利益を集計し、シグナル数をカウントします。利益がプラスなら勝ち、マイナスなら負けとして集計します。ビジュアルモードでは、選択された利確に到達したかどうかとポイントをスイッチで確認し、利確に到達していればポイントを有効利益として計算し、損切りの場合はマイナスの損失、どちらにも達していなければ実際の利益差分を有効利益として計算します。その値を合計に加算し、シグナル数を増やし、有効利益がプラスまたは利確に達していれば勝ちとしてカウントします。最後にシグナルを非アクティブに設定して終了します。これで必要なユーティリティ関数が揃ったので、次にOnTickイベントハンドラで実際のロジックを整理して実行できる準備が整いました。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   MqlTick tick;                                                  //--- Tick structure
   if (!SymbolInfoTick(_Symbol, tick)) return;                    //--- Get tick or return
   double bid = tick.bid;                                         //--- Get bid
   double ask = tick.ask;                                         //--- Get ask
   MqlRates rates[2];                                             //--- Rates array
   if (CopyRates(_Symbol, PERIOD_CURRENT, 0, 2, rates) < 2) return; //--- Copy rates or return
   bool new_bar = (rates[0].time > last_bar_time);                //--- Check new bar
   if (new_bar) last_bar_time = rates[0].time;                    //--- Update last time
   double fast_buf[], slow_buf[], filter_buf[];                   //--- Buffers
   ArraySetAsSeries(fast_buf, true);
   ArraySetAsSeries(slow_buf, true);
   ArraySetAsSeries(filter_buf, true);
   if (CopyBuffer(h_fast_ma, 0, 0, 3, fast_buf) < 3) return;      //--- Copy fast or return
   if (CopyBuffer(h_slow_ma, 0, 0, 3, slow_buf) < 3) return;      //--- Copy slow or return
   if (CopyBuffer(h_filter_ma, 0, 0, 2, filter_buf) < 2) return;  //--- Copy filter or return
   double fast_1 = fast_buf[1];                                   //--- Fast MA 1
   double fast_2 = fast_buf[2];                                   //--- Fast MA 2
   double slow_1 = slow_buf[1];                                   //--- Slow MA 1
   double slow_2 = slow_buf[2];                                   //--- Slow MA 2
   double filter_1 = filter_buf[1];                               //--- Filter MA 1
   double close_1 = rates[1].close;                               //--- Close 1
   int signal_type = 0;                                           //--- Init signal type
   if (new_bar) {                                                 //--- Check new bar
      if (fast_2 <= slow_2 && fast_1 > slow_1 && close_1 > filter_1) signal_type = 1; //--- Buy signal
      else if (fast_2 >= slow_2 && fast_1 < slow_1 && close_1 < filter_1) signal_type = -1; //--- Sell signal
   }
   if (signal_type != 0) {                                        //--- Check signal
      if (current_signal.active && current_signal.pos_type != signal_type) { //--- Check active opposite
         double close_price = (current_signal.pos_type == 1) ? bid : ask; //--- Get close price
         CloseVirtualPosition(close_price, true);                  //--- Close early
      }
      if (!current_signal.active) {                                //--- Check not active
         double entry_price = (signal_type == 1) ? ask : bid;      //--- Get entry price
         OpenVirtualPosition(signal_type, rates[1].time, entry_price); //--- Open virtual
         string name = "Signal_Entry_" + TimeToString(rates[1].time); //--- Entry name
         ObjectCreate(0, name, OBJ_ARROW, 0, rates[1].time, signal_type == 1 ? rates[1].low : rates[1].high); //--- Create arrow
         ObjectSetInteger(0, name, OBJPROP_ARROWCODE, (signal_type == 1 ? 236 : 238)); //--- Set code
         ObjectSetString(0, name, OBJPROP_FONT, "Wingdings");      //--- Set font
         ObjectSetInteger(0, name, OBJPROP_COLOR, signal_type == 1 ? clrGreen : clrRed); //--- Set color
         ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 12);          //--- Set size
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, signal_type == 1 ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); //--- Set anchor
         if (trade_mode == Open_Trades) {                          //--- Check open trades
            MqlTradeRequest request = {};                          //--- Request
            MqlTradeResult result = {};                            //--- Result
            request.action = TRADE_ACTION_DEAL;                    //--- Set action
            request.symbol = _Symbol;                              //--- Set symbol
            request.volume = 0.1;                                  //--- Set volume
            request.type = signal_type == 1 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; //--- Set type
            request.price = signal_type == 1 ? ask : bid;          //--- Set price
            request.deviation = 3;                                 //--- Set deviation
            double selected_tp = 0;                                //--- Init TP
            switch(tp_level) {                                     //--- Select TP
               case Level_1: selected_tp = current_signal.tp1; break; //--- TP1
               case Level_2: selected_tp = current_signal.tp2; break; //--- TP2
               case Level_3: selected_tp = current_signal.tp3; break; //--- TP3
            }
            request.tp = selected_tp;                              //--- Set TP
            request.sl = current_signal.sl;                        //--- Set SL
            if(!OrderSend(request, result)) {                      //--- Send order
               Print("Failed to open trade: ", GetLastError());    //--- Log error
            } else {                                               //--- Success
               position_ticket = result.deal;                      //--- Set ticket
            }
         }
      }
   }
}

OnTickイベントハンドラでは、まずSymbolInfoTickで現在のティック情報を取得し、MqlTick構造体に格納してbidとaskを取り出します。取得に失敗した場合は処理を早期終了します。次に、CopyRatesで直近2本のレートをMqlRates配列にコピーし、最新の時刻をlast_bar_timeと比較して新しいバーかどうかを判定し、新バーならlast_bar_timeを更新します。移動平均用のバッファとしてfast_buf[]、slow_buf[]、filter_buf[]を宣言し、時系列として設定後、CopyBufferでハンドルからメインバッファ(0番)をコピーし、データ不足なら処理を終了します。各値をfast_1=buffer[1]、fast_2=buffer[2]などとし、slowやfilterも同様、closeはrates[1].closeから取得します。新しいバーの場合、シグナルを検出します。買いシグナル(1)は、前回のfastがslow以下で現在のfastがslowを上回り、かつcloseがfilterより上の場合です。売りシグナル(-1)は、前回のfastがslow以上で現在のfastがslowを下回り、かつcloseがfilterより下の場合です。

シグナルが発生した場合、既にアクティブで現在タイプと逆方向なら、買いならbid、売りならaskを決済価格としてCloseVirtualPositionを早期フラグ付きで呼び出します。非アクティブの場合は、買いならask、売りならbidを建値としてOpenVirtualPositionを呼び出します。エントリー時には矢印オブジェクトを作成します。名前は「Signal_Entry_」+時刻文字列、OBJ_ARROWで、買いはrates[1].timeとlow、売りはhighに配置、Wingdingsフォントで買い236、売り238、色は買いが緑で売りが赤、サイズ12、アンカーは買いが下部で売りが上部に設定します。実取引モード(trade_mode=Open_Trades)では、MqlTradeRequestを作成して、銘柄に対して0.1ロットのマーケット注文(買い/売り、価格はask/bid、スリッページ3)を発注します。利確はtp_levelのスイッチでsignalのtp1/tp2/tp3から選択、損切りはsignal.slを設定し、OrderSendで送信、失敗時はログ、成功時はposition_ticketにチケット番号を格納します。コンパイルすると、次の結果が得られます。

初期シグナル

これで、シグナル検出時に既存ポジションを決済し、新しいポジションを建てるフローが機能していることが確認できます。次は、各レベル到達時にシグナル情報を更新する処理を実装する段階です。また、ダッシュボードの更新も必要ですが、それは全ての統計情報を取得してから最後におこなう処理です。やってみましょう。

if (current_signal.active) {                                   //--- Check active
   // Detect if real position closed automatically (TP/SL)
   if (trade_mode == Open_Trades && position_ticket != -1 && !PositionSelectByTicket(position_ticket)) { //--- Check closed
      double close_price = GetPositionClosePrice(position_ticket); //--- Get close price
      bool hit_sl = MathAbs(close_price - current_signal.sl) < _Point * 5; //--- Check SL hit
      current_signal.hit_sl = hit_sl;                          //--- Set SL hit
      if (hit_sl) DrawSLHit(TimeCurrent(), current_signal.sl); //--- Draw SL
      CloseVirtualPosition(close_price, false);                //--- Close virtual
   } else {                                                    //--- Not auto closed
      // Check for visual hits (TP levels)
      if (!current_signal.hit_tp1) {                           //--- Check TP1
         bool tp1_hit = false;                                 //--- Init hit
         if (current_signal.pos_type == 1 && bid >= current_signal.tp1) tp1_hit = true; //--- Buy hit
         if (current_signal.pos_type == -1 && ask <= current_signal.tp1) tp1_hit = true; //--- Sell hit
         if (tp1_hit) {                                        //--- Hit
            current_signal.hit_tp1 = true;                     //--- Set hit
            DrawTPHit(1, TimeCurrent(), current_signal.tp1, tp1_points); //--- Draw hit
         }
      }
      if (!current_signal.hit_tp2) {                           //--- Check TP2
         bool tp2_hit = false;                                 //--- Init hit
         if (current_signal.pos_type == 1 && bid >= current_signal.tp2) tp2_hit = true; //--- Buy hit
         if (current_signal.pos_type == -1 && ask <= current_signal.tp2) tp2_hit = true; //--- Sell hit
         if (tp2_hit) {                                        //--- Hit
            current_signal.hit_tp2 = true;                     //--- Set hit
            DrawTPHit(2, TimeCurrent(), current_signal.tp2, tp2_points); //--- Draw hit
         }
      }
      if (!current_signal.hit_tp3) {                           //--- Check TP3
         bool tp3_hit = false;                                 //--- Init hit
         if (current_signal.pos_type == 1 && bid >= current_signal.tp3) tp3_hit = true; //--- Buy hit
         if (current_signal.pos_type == -1 && ask <= current_signal.tp3) tp3_hit = true; //--- Sell hit
         if (tp3_hit) {                                        //--- Hit
            current_signal.hit_tp3 = true;                     //--- Set hit
            DrawTPHit(3, TimeCurrent(), current_signal.tp3, tp3_points); //--- Draw hit
            if (trade_mode == Visual_Only) {                   //--- Check visual only
               double close_price = (current_signal.pos_type == 1) ? bid : ask; //--- Get close
               CloseVirtualPosition(close_price, false);       //--- Close virtual
            }
         }
      }
      // SL hit check for Visual_Only or manual if needed
      bool sl_hit = false;                                     //--- Init SL hit
      if (current_signal.pos_type == 1 && bid <= current_signal.sl) sl_hit = true; //--- Buy SL
      if (current_signal.pos_type == -1 && ask >= current_signal.sl) sl_hit = true; //--- Sell SL
      if (sl_hit && !current_signal.hit_sl) {                  //--- Check hit and not set
         bool already_won = false;                             //--- Init won flag
         switch(tp_level) {                                    //--- Check level
            case Level_1: already_won = current_signal.hit_tp1; break; //--- TP1 won
            case Level_2: already_won = current_signal.hit_tp2; break; //--- TP2 won
            case Level_3: already_won = current_signal.hit_tp3; break; //--- TP3 won
         }
         if (!already_won) {                                   //--- Check not won
            current_signal.hit_sl = true;                      //--- Set SL hit
            DrawSLHit(TimeCurrent(), current_signal.sl);       //--- Draw SL
         }
         if (trade_mode == Visual_Only) {                      //--- Check visual
            double close_price = (current_signal.pos_type == 1) ? bid : ask; //--- Get close
            CloseVirtualPosition(close_price, false);          //--- Close virtual
         }
      }
   }
}

current_signalでシグナルがアクティブな場合、まず実取引モード(trade_mode=Open_Trades)で利確や損切りによって実際のポジションが自動決済されたかを確認します。具体的には、チケットが有効であるにもかかわらずPositionSelectByTicketが失敗した場合です。この場合、GetPositionClosePriceで決済価格を取得し、MathAbsで損切り価格との誤差が5ポイント以内かをチェックします。該当する場合はhit_slフラグを立て、DrawSLHitで現在時刻と損切り価格に描画した後、CloseVirtualPositionを非早期フラグで呼び出します。それ以外の場合、進行中のポジションについては、各利確レベルが未達ならチェックします。TP1では、買いならbidが到達または超過、売りならaskが到達または下回った場合にhit_tp1フラグを立て、DrawTPHitでレベル1、現在時刻、TP1価格、tp1_pointsを描画します。同様にTP2とTP3も処理します。TP3に関しては、ビジュアルモード(trade_mode=Visual_Only)では、非早期フラグでbid/askを決済価格としてシミュレーションを終了します。

損切りについては、ビジュアルモードや手動決済の場合も別途監視します。買いならbidが損切り価格以下、売りならaskが損切り価格以上になった場合にフラグを立てます。まだフラグが立っていなければ、tp_levelのスイッチで選択された利確に到達しているかを確認し、すでに利確済みならalready_wonをtrueに設定して、勝利後の損切りをカウントしないようにします。その後、hit_slを立て、DrawSLHitで現在時刻と損切り価格に描画します。ビジュアルモードでは非早期フラグでbid/askを決済価格としてシミュレーションを終了します。次のような結果が得られます。

マークされたエントリーと到達レベル

これで各利確と損切りレベルをチャート上にマークできるようになり、可視化が完了します。必要な統計情報が揃ったため、次にダッシュボードを更新できます。そのためのロジックをまとめる関数を定義します。

//+------------------------------------------------------------------+
//| Update dashboard                                                 |
//+------------------------------------------------------------------+
void UpdateDashboard() {
   string space = " ";                                            //--- Space string
   bool display_signal = current_signal.active || current_signal.hit_tp1 || current_signal.hit_tp2 || current_signal.hit_tp3 || current_signal.hit_sl; //--- Check display
   if (display_signal) {                                          //--- Display signal
      string arrow = (current_signal.pos_type == 1) ? CharToString((char)233) : CharToString((char)234); //--- Arrow char
      color dir_color = (current_signal.pos_type == 1) ? clrLime : clrRed; //--- Direction color
      ObjectSetString(0, dash_prefix + "DirectionValue", OBJPROP_TEXT, arrow); //--- Set direction text
      ObjectSetInteger(0, dash_prefix + "DirectionValue", OBJPROP_COLOR, dir_color); //--- Set color
      color level_color = (current_signal.pos_type == 1) ? clrLime : clrRed; //--- Level color
      string direction = (current_signal.pos_type == 1) ? "BUY " : "SELL "; //--- Direction string
      ObjectSetString(0, dash_prefix + "EntryPrice", OBJPROP_TEXT, direction + DoubleToString(current_signal.entry_price, _Digits)); //--- Set entry text
      ObjectSetInteger(0, dash_prefix + "EntryPrice", OBJPROP_COLOR, level_color); //--- Set color
      ObjectSetString(0, dash_prefix + "TP1Value", OBJPROP_TEXT, DoubleToString(current_signal.tp1, _Digits)); //--- Set TP1 text
      ObjectSetInteger(0, dash_prefix + "TP1Value", OBJPROP_COLOR, clrWhite); //--- Set color
      ObjectSetString(0, dash_prefix + "TP2Value", OBJPROP_TEXT, DoubleToString(current_signal.tp2, _Digits)); //--- Set TP2 text
      ObjectSetInteger(0, dash_prefix + "TP2Value", OBJPROP_COLOR, clrWhite); //--- Set color
      ObjectSetString(0, dash_prefix + "TP3Value", OBJPROP_TEXT, DoubleToString(current_signal.tp3, _Digits)); //--- Set TP3 text
      ObjectSetInteger(0, dash_prefix + "TP3Value", OBJPROP_COLOR, clrWhite); //--- Set color
      ObjectSetString(0, dash_prefix + "SLValue", OBJPROP_TEXT, DoubleToString(current_signal.sl, _Digits)); //--- Set SL text
      ObjectSetInteger(0, dash_prefix + "SLValue", OBJPROP_COLOR, clrWhite); //--- Set color
      int tp1_icon = current_signal.hit_tp1 ? 252 : 183;             //--- TP1 icon
      color tp1_icon_color = current_signal.hit_tp1 ? clrLime : clrWhite; //--- TP1 color
      ObjectSetString(0, dash_prefix + "TP1Icon", OBJPROP_TEXT, CharToString((char)tp1_icon)); //--- Set TP1 icon
      ObjectSetInteger(0, dash_prefix + "TP1Icon", OBJPROP_COLOR, tp1_icon_color); //--- Set color
      int tp2_icon = current_signal.hit_tp2 ? 252 : 183;             //--- TP2 icon
      color tp2_icon_color = current_signal.hit_tp2 ? clrLime : clrWhite; //--- TP2 color
      ObjectSetString(0, dash_prefix + "TP2Icon", OBJPROP_TEXT, CharToString((char)tp2_icon)); //--- Set TP2 icon
      ObjectSetInteger(0, dash_prefix + "TP2Icon", OBJPROP_COLOR, tp2_icon_color); //--- Set color
      int tp3_icon = current_signal.hit_tp3 ? 252 : 183;             //--- TP3 icon
      color tp3_icon_color = current_signal.hit_tp3 ? clrLime : clrWhite; //--- TP3 color
      ObjectSetString(0, dash_prefix + "TP3Icon", OBJPROP_TEXT, CharToString((char)tp3_icon)); //--- Set TP3 icon
      ObjectSetInteger(0, dash_prefix + "TP3Icon", OBJPROP_COLOR, tp3_icon_color); //--- Set color
      int sl_icon = current_signal.hit_sl ? 251 : 183;               //--- SL icon
      color sl_icon_color = current_signal.hit_sl ? clrRed : clrWhite; //--- SL color
      ObjectSetString(0, dash_prefix + "SLIcon", OBJPROP_TEXT, CharToString((char)sl_icon)); //--- Set SL icon
      ObjectSetInteger(0, dash_prefix + "SLIcon", OBJPROP_COLOR, sl_icon_color); //--- Set color
      int entry_shift = iBarShift(_Symbol, PERIOD_CURRENT, current_signal.entry_time, false); //--- Entry shift
      int calc_shift = 0;                                    //--- Init calc shift
      if (!current_signal.active && current_signal.close_time != 0) { //--- Check closed
         calc_shift = iBarShift(_Symbol, PERIOD_CURRENT, current_signal.close_time, false); //--- Close shift
      }
      int age = entry_shift - calc_shift;                    //--- Calc age
      ObjectSetString(0, dash_prefix + "AgeValue", OBJPROP_TEXT, IntegerToString(age) + " bars"); //--- Set age text
   } else {                                                       //--- No signal
      ObjectSetString(0, dash_prefix + "DirectionValue", OBJPROP_TEXT, space); //--- Clear direction
      ObjectSetString(0, dash_prefix + "EntryPrice", OBJPROP_TEXT, space); //--- Clear entry
      ObjectSetString(0, dash_prefix + "TP1Value", OBJPROP_TEXT, space);   //--- Clear TP1
      ObjectSetString(0, dash_prefix + "TP2Value", OBJPROP_TEXT, space);   //--- Clear TP2
      ObjectSetString(0, dash_prefix + "TP3Value", OBJPROP_TEXT, space);   //--- Clear TP3
      ObjectSetString(0, dash_prefix + "SLValue", OBJPROP_TEXT, space);    //--- Clear SL
      ObjectSetString(0, dash_prefix + "AgeValue", OBJPROP_TEXT, space);   //--- Clear age
      ObjectSetString(0, dash_prefix + "TP1Icon", OBJPROP_TEXT, space);    //--- Clear TP1 icon
      ObjectSetString(0, dash_prefix + "TP2Icon", OBJPROP_TEXT, space);    //--- Clear TP2 icon
      ObjectSetString(0, dash_prefix + "TP3Icon", OBJPROP_TEXT, space);    //--- Clear TP3 icon
      ObjectSetString(0, dash_prefix + "SLIcon", OBJPROP_TEXT, space);     //--- Clear SL icon
   }
   ObjectSetString(0, dash_prefix + "TotalValue", OBJPROP_TEXT, (string)total_signals); //--- Set total
   ObjectSetString(0, dash_prefix + "WinLossValue", OBJPROP_TEXT, (string)wins + " / " + (string)losses); //--- Set win/loss
   string profit_str = (total_profit_points > 0 ? "+" : "") + DoubleToString(total_profit_points, 0); //--- Profit string
   color profit_color = total_profit_points > 0 ? clrLime : (total_profit_points < 0 ? clrRed : clrWhite); //--- Profit color
   ObjectSetString(0, dash_prefix + "ProfitValue", OBJPROP_TEXT, profit_str); //--- Set profit text
   ObjectSetInteger(0, dash_prefix + "ProfitValue", OBJPROP_COLOR, profit_color); //--- Set color
   double success = (total_signals > 0) ? (double)wins / total_signals * 100.0 : 0.0; //--- Calc success
   ObjectSetString(0, dash_prefix + "SuccessValue", OBJPROP_TEXT, DoubleToString(success, 2) + "%"); //--- Set success
   ChartRedraw(0);                                                //--- Redraw chart
}

次に、UpdateDashboard関数を定義して、ダッシュボードパネルを最新のシグナルおよび統計データで更新します。まずラベルをクリアするための空文字列を用意します。シグナルを表示するかどうかは、current_signalがアクティブか、あるいはいずれかの利確や損切りフラグが立っているかで判断します。表示すべき場合は、方向アイコンをWingdings文字で設定します。買いなら233、売りなら234、色は買いがライム、売りが赤に設定し、ObjectSetStringObjectSetInteger でラベルのテキストと色を更新します。エントリーラベルは「BUY」または「SELL」に建値を_Digitsに合わせて DoubleToStringで文字列化したものを付加し、色はライムまたは赤で設定します。利確1〜3と損切りも同様に白色で値を表示します。

アイコンについては、利確1はヒット済みならWingdings252(チェック)をライムで、未達なら183(点)を白で表示します。利確2と3も同様です。損切りはヒット済みなら251(×)を赤、未達なら183を白で表示します。シグナルの経過バー数はiBarShiftを使い、エントリー時刻から決済時刻(決済済みの場合はシフト)を引いて計算し、ラベルには「 bars」を付加した文字列で表示します。シグナルがない場合は、すべてのシグナル関連ラベルを空文字にしてクリアします。統計情報については、総シグナル数ラベルをtotal_signalsの文字列で、勝敗は「wins」「losses」で表示し、利益は符号付き文字列で小数0桁に丸め、プラスならライム、マイナスなら赤、ゼロは白で表示します。成功率はシグナルが存在すれば小数点2桁のパーセンテージ、なければ0.0とします。最後にChartRedrawでチャートを再描画して変更を反映させます。この関数はOnTickハンドラ内で呼び出して更新を適用します。システム終了時には、作成したオブジェクトを削除する処理も必要です。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, dash_prefix);                              //--- Delete dashboard objects
   ObjectsDeleteAll(0, "Signal_");                                //--- Delete signal objects
   ObjectsDeleteAll(0, "Initial_");                               //--- Delete initial objects
   IndicatorRelease(h_fast_ma);                                   //--- Release fast MA
   IndicatorRelease(h_slow_ma);                                   //--- Release slow MA
   IndicatorRelease(h_filter_ma);                                 //--- Release filter MA
   if (trade_mode == Open_Trades && position_ticket != -1) {      //--- Check open trades mode
      MqlTick tick;                                               //--- Tick structure
      if (SymbolInfoTick(_Symbol, tick)) {                        //--- Get tick
         double close_price = (current_signal.pos_type == 1) ? tick.bid : tick.ask; //--- Get close price
         MqlTradeRequest close_request = {};                      //--- Close request
         MqlTradeResult close_result = {};                        //--- Close result
         close_request.action = TRADE_ACTION_DEAL;                //--- Set action
         close_request.symbol = _Symbol;                          //--- Set symbol
         close_request.volume = 0.1;                              //--- Set volume
         close_request.type = (current_signal.pos_type == 1) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set type
         close_request.price = close_price;                       //--- Set price
         close_request.deviation = 3;                             //--- Set deviation
         close_request.position = position_ticket;                //--- Set position
         if (!OrderSend(close_request, close_result)) {           //--- Send close
            Print("Failed to close trade on deinit: ", GetLastError()); //--- Log error
         }
         position_ticket = -1;                                    //--- Reset ticket
      }
   }
}

OnDeinitイベントハンドラでは、EAが削除された際にクリーンアップ処理をおこないます。まず、ObjectsDeleteAllでdash_prefix付きのダッシュボードオブジェクトをすべて削除し、さらに「Signal_」接頭辞のシグナル関連の可視化や「Initial_」接頭辞の初期レベルもすべてのサブウィンドウからクリアします。移動平均インジケーターのリソースは、IndicatorReleaseでh_fast_ma、h_slow_ma、h_filter_maを解放し、メモリを開放します。

ライブ取引モード(trade_mode=Open_Trades)で有効なposition_ticketがある場合はSymbolInfoTickで現在のティックを取得します。取得に成功した場合、current_signal.pos_typeに応じて買いならbid、売りならaskを決済価格として計算します。その後、逆方向の成行決済リクエストを作成します。アクションはTRADE_ACTION_DEAL、銘柄は対象銘柄、0.1ロット、タイプは買いポジションなら売り、売りポジションなら買い、価格は前述、スリッページ3、対象チケット番号を指定します。OrderSendで送信し、GetLastErrorで失敗をログに記録、最後にチケット番号を-1にリセットします。コンパイルすると、次の結果が得られます。

最終結果

画像から確認できる通り、ストラテジーテスターシステムは正しくセットアップされ、目標としたすべての機能が実現されています。残るは、システムの動作確認、つまり前のセクションでおこなったテストです。


戦略トラッカーのテスト

テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

戦略トラッカーバックテストGIF


結論

今回、MQL5で構築したストラテジートラッカーシステムでは、短期・長期移動平均のクロスオーバーを検出し、長期フィルタで条件を確認することでシグナルを生成し、仮想ポジションまたは実際のポジションで複数の利確レベルや損切りを追跡できるようにしています。エントリーや利確到達、損切り、早期決済はチャート上に矢印、ライン、アイコンで可視化され、合計シグナル数や勝敗、利益ポイント、勝率などの統計はリアルタイムでダッシュボードに反映されます。これにより、チャート上で直接シグナルと結果を確認しながら取引手法の理解を深め、さらに戦略のカスタマイズや改善に活用できる環境が整います。取引をお楽しみください。

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

添付されたファイル |
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
取引戦略の開発:トリプルサイン平均回帰法 取引戦略の開発:トリプルサイン平均回帰法
新しい数学的指標であるTriple Sine Oscillator (TSO)に基づいて構築された「トリプルサイン平均回帰法」取引戦略を紹介します。TSOは、−1から+1の間で振動する正弦の三乗関数から導出されており、買われ過ぎおよび売られ過ぎの市場状況を特定するのに適しています。本記事では、数学的関数を実践的な取引ツールへと応用できることを示しています。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
オンチャートUIを使用したリスクベースの取引執行EA(第2回):インタラクティブ性とロジックの追加 オンチャートUIを使用したリスクベースの取引執行EA(第2回):インタラクティブ性とロジックの追加
チャート上のコントロールパネルを備えたインタラクティブなMQL5エキスパートアドバイザー(EA)を構築する方法を学びます。リスクベースのロットサイズを計算し、チャート上から直接取引をおこなう方法を理解します。