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

次に、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で取得し、HistoryDealGetDoubleのDEAL_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、色は買いがライム、売りが赤に設定し、ObjectSetStringやObjectSetInteger でラベルのテキストと色を更新します。エントリーラベルは「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)ビットマップ画像形式で示したものです。
![]()
結論
今回、MQL5で構築したストラテジートラッカーシステムでは、短期・長期移動平均のクロスオーバーを検出し、長期フィルタで条件を確認することでシグナルを生成し、仮想ポジションまたは実際のポジションで複数の利確レベルや損切りを追跡できるようにしています。エントリーや利確到達、損切り、早期決済はチャート上に矢印、ライン、アイコンで可視化され、合計シグナル数や勝敗、利益ポイント、勝率などの統計はリアルタイムでダッシュボードに反映されます。これにより、チャート上で直接シグナルと結果を確認しながら取引手法の理解を深め、さらに戦略のカスタマイズや改善に活用できる環境が整います。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20229
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
取引戦略の開発:トリプルサイン平均回帰法
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
オンチャートUIを使用したリスクベースの取引執行EA(第2回):インタラクティブ性とロジックの追加
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索