English Deutsch 日本語
preview
Торговые инструменты на языке MQL5 (Часть 10): Разработка системы отслеживания стратегии с визуальными уровнями и показателями эффективности

Торговые инструменты на языке MQL5 (Часть 10): Разработка системы отслеживания стратегии с визуальными уровнями и показателями эффективности

MetaTrader 5Торговые системы |
98 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В своей предыдущей статье (Часть 9) мы разработали мастер первого запуска на языке MetaQuotes Language 5 (MQL5) для советников, с прокручиваемым руководством с интерактивными элементами панели, динамическим форматированием текста и элементами управления для упрощения процесса первоначальной настройки и ознакомления с системой. В части 10 мы разрабатываем систему отслеживания стратегии с визуальными уровнями и показателями успешного выполнения. Эта система обнаруживает сигналы пересечения скользящих средних, отфильтрованные с помощью долгосрочной скользящей средней, отслеживает виртуальные или реальные сделки с несколькими уровнями тейк-профита и стоп-лоссами, визуализирует точки входа, попадания и результаты на графике, а также предоставляет панель для статистики эффективности в реальном времени, включая выигрыши/проигрыши, точки прибыли и процент успешных сделок. В статье рассмотрим следующие темы:

  1. Роль и преимущества системы отслеживания стратегий в торговле
  2. Реализация средствами MQL5
  3. Тестирование системы отслеживания стратегий
  4. Заключение

В итоге у вас будет функциональный, настраиваемый инструмент в MQL5 для мониторинга эффективности стратегий. Давайте погрузимся в процесс!


Роль и преимущества системы отслеживания стратегий в торговле

Роль и преимущества системы отслеживания стратегий в торговле заключаются в ее способности обеспечивать мониторинг и анализ эффективности сигналов в режиме реального времени, помогая нам оценивать эффективность наших подходов, не полагаясь исключительно на тестирование на исторических данных или ручное ведение логов, что может быть трудоемким и грозить ошибками. Визуализируя точки входа, срабатывания тейк-профитов, триггеры стоп-лоссов и кумулятивную статистику, такую как процент выигрышных сделок и пункты прибыли на графике и панели, система обеспечивает мгновенную обратную связь о жизнеспособности стратегии, позволяя быстро корректировать такие параметры, как периоды скользящих средних или уровни риска, для улучшения результатов на реальных рынках. В конечном итоге, такой инструмент улучшает процесс принятия решений, укрепляет уверенность благодаря прозрачному отслеживанию и поддерживает итеративное совершенствование.

Наш подход заключается в выявлении быстрых и медленных пересечений скользящих средних, подтверждаемых фильтрующей скользящей средней: для покупок цена должна находиться выше нее, для продаж — ниже. Это лишь произвольно выбранная простая стратегия; при желании вы можете заменить ее любой другой стратегией. Моделируйте виртуальные позиции или совершайте реальные сделки с настраиваемыми уровнями тейк-профита и стоп-лосса в пунктах, визуализируйте входы с помощью стрелок, пунктирных линий для совпадений и значков для результатов на графике, одновременно обновляя интерактивную панель со статистикой по сигналам, выигрышам / проигрышам, возрасту, прибыли и коэффициенту успешности, создавая комплексный инструмент для оценки и уточнения стратегии. В двух словах, здесь приведена визуализация того, к чему мы стремимся.

SYSTEM VISUALIZATION


Реализация средствами MQL5

Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку «Советники» (Experts), щелкните кнопкой мыши на вкладке "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нужно будет объявить некоторые входные параметры и глобальные переменные, которые будем использовать во всей программе.

//+------------------------------------------------------------------+
//|                                       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

Во-первых, определяем два перечисления для параметров конфигурации: В режиме "TradeMode" есть опция "Visual_Only" для симуляции без реальных ордеров и опция "Open_Trades" для исполнения реальных позиций, если вы хотите торговать по этой стратегии, а также опция "TPLevel", предлагающая "Level_1", "Level_2" или "Level_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", "pos_type" для сделок buy (1) или sell (-1), сведения о входе, уровни тейк-профита и стоп-лосса, флаги срабатывания и время закрытия, инстанцируемые как "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" для создания прямоугольных меток для панелей, принимая такие параметры, как имя, положение ("xD", "yD"), размер ("xS", "yS"), цвет фона "clrBg", ширина рамки и опциональный цвет рамки "clrBorder" по умолчанию clrNONE, тип BORDER_FLAT, стиль STYLE_SOLID и угол CORNER_LEFT_UPPER. Сбрасываем ошибки с помощью ResetLastError, создавая объект типа OBJ_RECTANGLE_LABEL, устанавливаем свойства с помощью ObjectSetInteger для расстояний, размеров, угла, цветов, деталей рамки и невыбираемого состояния переднего плана, перерисовываем с помощью ChartRedraw, выводя сообщения об ошибках, если таковые имеются.

Аналогично, "createLabel" создает текстовые метки с именем, позицией, текстом "txt", цветом "clrTxt", черным по умолчанию, размером 12, шрифтом "Arial Rounded MT Bold" и углом "CORNER_LEFT_UPPER", создавая как OBJ_LABEL, размещая объект на переднем плане и делая его недоступным для выбора, перерисовывая и обрабатывая ошибки. Для "createTrendline" создаём линии тренда с указанием названия, времени/цен для конечных точек, цвета "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" для основного контейнера. Создаём подпанель заголовка слегка утопленной с фоном тёмно-синего цвета (midnight blue), без рамки, и добавляем метку "Панель отслеживания стратегий" ("Strategy Tracker Dashboard") жирным шрифтом Arial 12 среднего ярко-зелёного цвета (medium spring green). Увеличив относительный y "rel_y" на 30, рисуем подпанель сигнала темно аспидно-голубого цвета (dark slate blue), высотой 160, с заголовком "Текущий сигнал" ("Current Signal") светло-голубого цвета (light cyan), выделенным жирным шрифтом размером 11 пикселей.

Продвигая "rel_y" на 25, добавляем метки для "Symbol:" белого цвета, жирным шрифтом размером 10, значение как символ плюс подстрока таймфрейма из EnumToString глубокого оттенка синего цвета (deep sky blue); затем метку "Signal:", заполнитель цены входа белым жирным шрифтом и заполнитель значка направления шрифтом Wingdings белого цвета, размером 12. Для каждого тейк-профита и стоп-лосса добавляем метки типа "TP1:", значения в виде пробелов белым шрифтом Arial размером 10 и значки белым шрифтом Wingdings размером 12, каждый раз увеличивая "rel_y" на 20. Еще через 20 мы создаем подпанель статистики цвета индиго (Indigo), высотой 140 пикселей, с заголовком "Статистика" ("Statistics"), выделенным светло-голубым (light cyan) жирным шрифтом 11. Увеличивая "rel_y" на 25, мы размещаем метки статистики, такие как "Total Signals:", выделенные белым жирным шрифтом 10, с заполнителями значений, выделенными белым шрифтом Arial 10; аналогично для "Win/Loss:", "Last Signal Age:", "Profit in Points:", "Success Rate:", интервал на 20. Наконец, еще через 20 мы добавляем подпанель нижнего колонтитула темно-синего цвета (midnight blue) высотой 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 создаем хэндлы для трех скользящих средних, используя iMA с символом _Symbol, текущим таймфреймом PERIOD_CURRENT, соответствующими периодами ("fast_ma_period", "slow_ma_period", "filter_ma_period"), нулевым сдвигом, методом "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, чтобы подтвердить успешную загрузку. В программировании принято всегда выполнять тестирование фрагментов кода на каждом этапе. После тестирования мы получаем следующий результат.

INITIALIZATION

Мы видим, что успешно инициализировали программу. Теперь нам нужно обнаруживать сигналы и отслеживать их, сохранять информацию о них и визуализировать ее для облегчения отслеживания. Чтобы достичь этого, мы определим некоторые полезные функции для визуализации, как показано ниже.

//+------------------------------------------------------------------+
//| 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 (140 для TP1, 141 для TP2, 142 для TP3) с помощью ObjectCreate установим шрифт Wingdings размером 12, левую привязку и добавим метку пунктов с "+" и преобразованные в строку пункты синего цвета размером 10, правую привязку. Для обозначения стоп-лосса используем зеркальное отображение пурпурного цвета (magenta), код 164 для пузырьков, текст с пунктами "-". Что касается кода, то MQL5 предоставляет коды Wingdings, с которыми вы можете взаимодействовать, чтобы получить наилучший результат. Смотрите ниже.

MQL5 WINGDINGS

Затем функция "DrawTPHit" отображает графические элементы для определения тейк-профита, принимая номер уровня, время достижения, цену и пункты; она создает пунктирную темно-серую линию тренда от входа до достижения "createTrendline" сзади без лучей, сплошную горизонтальную линию при достижении цены и центрированный тик Wingdings (код 254) синего цвета, размер 12. Теперь вы знаете, как появляется тик. Аналогично, "DrawSLHit" отображает достижение стоп-лосса пунктирной линией, горизонтальным соединением и значком Wingdings по центру (код 253) пурпурного цвета (magenta) размером 12.

Для "DrawEarlyClose", обрабатывая преждевременные закрытия, мы рисуем пунктирную линию до цены закрытия, определяем синий цвет (blue) для положительных пунктов или пурпурный (magenta) для отрицательных, помещаем пузырь (код 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 для buy, -1 для sell), время и цену входа, вычисляя уровни тейк-профита при входе с поправкой на их количество пунктов _Point и направление типа. Со стоп-лоссом аналогично, но вычитается, снимаются флажки попадания и время закрытия, сбрасываются настройки и вызываются "DrawInitialLevels" для визуальных элементов. Функция "CloseVirtualPosition" завершает моделирование с заданной ценой закрытия и флажком досрочного закрытия, завершает работу, если она неактивна, рассчитывает пункты прибыли как разницу со знаком "_Point" и обновляет время закрытия до текущего. При досрочном выходе, она выводит индикаторы с помощью "DrawEarlyClose" и в режиме реального времени с действительным тикетом формирует противоположный запрос на закрытие рынка по цене с отклонением, отправляет с помощью OrderSend, выводит сообщения о сбоях и очищает тикет.

В режиме реального времени оценивает, был ли достигнут выбранный тейк-профит с помощью переключателя "tp_level", и если сработал стоп-лосс, выбранный тейк-профит или если не досрочно, накапливает прибыль, подсчитывает сигнал и добавляет к выигрышам, если положительный, или к убыткам. В визуальном режиме проверяет выбранное значение тейк-профита и указывает пункты с помощью переключателя, выводит эффективную прибыль в виде пунктов в случае достижения, отрицательную прибыль в случае стоп-лосса или фактическую прибыль, если нет ни того, ни другого, добавляет к общему результату, увеличивает сигналы, подсчитывает выигрыш в случае достижения или положительной эффективности. Затем мы отключаем сигнал, установив для параметра "active" значение false. Теперь мы полностью оснащены необходимыми служебными функциями и можем перейти к организации нашей логики в обработчике 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 в MqlRates массив, проверяя наличие нового бара путем сравнения последнего значения времени с "last_bar_time" и обновляя его, если это так. Объявляем буферы для скользящих средних: "fast_buf[]", "slow_buf[]", "filter_buf[]", устанавливая их как временные ряды, копируя данные из хэндлов с помощью CopyBuffer в основном буфере 0 и возвращая значение при недостаточности данных. Присваиваем значения типа "fast_1" из buffer[1], "fast_2" из [2], аналогично для slow и filter в [1], close из rates[1].close. При появлении нового бара мы обнаруживаем сигналы: buy (1), если предыдущий быстрый ниже или медленный, но текущий - над и непосредственно над фильтром; sell (-1), если предыдущий быстрый выше или медленный, но текущий ниже и непосредственно ниже фильтра.

По сигналу, если активен и противоположен текущему типу, вычисляем цену закрытия как bid для сделок типа buy или ask для сделок типа sell, вызываем "CloseVirtualPosition" с досрочным значением true. Если не активен, выводим запись как ask для сделок типа buy или bid для сделок типа sell, вызываем "OpenVirtualPosition", указав тип, rates[1].time, выполняем ввод. Создаем объект со стрелкой входа с именем "Signal_Entry_", а также строку времени в виде OBJ_ARROW, расположенную в rates[1].time и минимум для сделок типа buy или максимум для сделок типа sell, задаем код стрелки 236 для покупки или 238 для продажи шрифтом Wingdings зеленого/красного цвета, размер 12, привязку ниже сделок типа buy или выше для сделок типа sell. В режиме реального времени ("trade_mode" как "Open_Trades") мы создаем запрос MqlTradeRequest на рыночную сделку по инструменту объемом 0.1, вводим buy/sell, цену ask/bid, отклонение 3; выбираем тейк-профит, включив "tp_level" из tp1/tp2/tp3 сигнала, устанавливаем стоп-лосс от signal.sl, отправляем с помощью OrderSend, выводим сообщение об ошибках или сохраняем тикет сделки в "position_ticket" в случае успеха. После компиляции получаем следующий результат.

INITIAL SIGNALS

Мы видим, что при обнаружении сигнала мы закрываем позиции и открываем новую схему сигналов. Нам просто нужно обновить сигналы при достижении уровней прямо сейчас. Мы также видим, что панель не обновляется, но это последнее, что нам надо сделать после получения всей необходимой статистики. Сделаем это прямо сейчас.

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", проверяем, соответствует ли стоп-лоссу в пределах допуска в 5 пунктов с помощью MathAbs, устанавливаем флаг "hit_sl" и рисуем с помощью "DrawSLHit" текущее время и цену стоп-лосса, если это значение равно true, затем вызываем "CloseVirtualPosition" без досрочного завершения. В противном случае, для текущих позиций проверяем каждый тейк-профит, если он не достигнут: для TP1 устанавливаем флаг true, если цена bid достигает или превышает значение для покупок, либо цена ask падает до или ниже значения для продаж, затем отмечаем "hit_tp1" и вызываем "DrawTPHit" с уровнем 1, текущим временем, ценой TP1 и "tp1_points". Повторяем аналогично для TP2 и TP3; для TP3 в визуальном режиме ("trade_mode" как "Visual_Only") также закроем симуляцию, указав цену закрытия как bid/ask без досрочного закрытия.

Мы отдельно отслеживаем срабатывания стоп-лосса в случаях визуального или ручного управления: отмечаем true, если bid находится на уровне или ниже для сделок buy, или ask находится на уровне или выше для сделок sell, и, если еще не установлено, проверяем с помощью включения "tp_level", был ли достигнут выбранный тейк-профит (устанавливая значение "already_won" равным true), действуя только в том случае, если это не так, чтобы избежать подсчета проигрышей после выигрышей — тогда установим "hit_sl", отрисуем с помощью "DrawSLHit" на текущее время и цену стоп-лосса. В визуальном режиме завершим симуляцию, с помощью bid/ask без досрочного завершения. Получаем следующий результат.

MARKED ENTRY & HIT LEVELS

Теперь мы можем отметить достигнутые уровни, завершив визуализацию графика. Поскольку у нас есть вся необходимая статистика, можно обновить панель. Мы определим функцию, которая будет содержать логику для этого.

//+------------------------------------------------------------------+
//| 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 для сделок buy или 234 для сделок sell, цвет лаймовый для сделок buy или красный для сделок sell, обновляя текст и цвет меток с помощью функций ObjectSetString и ObjectSetInteger. Мы форматируем метку входа как "BUY" или "SELL" плюс цену входа, приведенную к значениям _Digits с помощью DoubleToString, лаймовым/красным цветом и устанавливаем значения тейк-профита 1-3 и стоп-лосса аналогичным образом белым цветом.

В качестве значков для тейк-профита 1 используются Wingdings 252 (галочка), если попадает в лаймовый цвет, либо 183 (точка) белого цвета, повторяющиеся для 2 и 3; стоп-лосс 251 (x), если попадает в красный цвет, либо 183 белым цветом. Мы вычисляем возраст сигнала в барах, используя iBarShift от времени входа минус сдвиг по времени закрытия, если он закрыт, задавая возрастную метку в виде строки плюс "bars". Если сигнала нет, очистите все связанные с сигналом метки, в пустое пространство. Независимо от этого, обновите метку total signals на строку "total_signals", win/loss - как выигрыши слэш потери, profit - как строка со знаком, округленная до 0 знаков после запятой, лаймового цвета, если положительный, / красного, если отрицательный / белого, если нулевой, success в процентах до 2 знаков после запятой, если сигналы существуют, иначе 0.0. В заключение перерисовываем график с помощью функции ChartRedraw для применения изменений. Вызываем эту функцию в обработчике тиков, чтобы применить обновления. Теперь нам нужно удалить объекты, созданные при завершении работы системы.

//+------------------------------------------------------------------+
//| 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 выполняющем очистку при удалении советника с указанием кода причины, мы сначала удаляем все объекты панели, используя ObjectsDeleteAll с "dash_prefix", затем удаляем визуальные элементы, связанные с сигналами, с префиксом "Signal_" и начальные уровни с "Initial_" во всех подокнах. Мы освобождаем ресурсы индикатора скользящей средней с помощью IndicatorRelease для "h_fast_ma", "h_slow_ma" и "h_filter_ma" для освобождения памяти.

Если мы находимся в режиме реальной торговли ("trade_mode" как "Open_Trades") с действительным "position_ticket", мы получаем текущий тик, используя SymbolInfoTick, и в случае успеха вычисляем цену закрытия как bid для сделок на покупку или ask для сделок на продажу на основе "current_signal.pos_type". Мы создаем противоположный запрос на закрытие рынка с помощью действия TRADE_ACTION_DEAL, символ, объем 0.1, тип sell для сделок на покупку или buy для сделок на продажу, цену, отклонение 3 и тикет позиции, отправляем его с помощью OrderSend, выводим в лог сообщение о любом сбое с помощью GetLastError и сбрасываем тикет на -1. После компиляции получаем следующий результат.

FINAL OUTCOME

На изображении видно, что мы корректно настроили систему тестирования стратегий и достигли всех поставленных целей. Теперь осталось протестировать работоспособность системы, и это рассматривается в предыдущем разделе.


Тестирование системы отслеживания стратегий

Мы провели тестирование, а ниже представлена скомпилированная визуализация в едином формате растрового изображения Graphics Interchange Format (GIF).

STRATEGY TRACKER BACKTEST GIF


Заключение

В заключение, мы разработали систему отслеживания стратегий в MQL5, которая распознает сигналы о пересечении скользящих средних с помощью долгосрочного фильтра, который можно переключить на благоприятный, отслеживает результаты по виртуальным или реальным позициям с несколькими уровнями тейк-профита и стоп-лосса, визуализирует входы, достижения и закрытия на графике, используя стрелки, линии и значки, а также предоставляет панель в режиме реального времени для отслеживания статистики, такой как общее количество сигналов, выигрышей / проигрышей, пунктов прибыли и показателей успешного выполнения. Это даёт возможность получить более глубокое представление о ваших торговых подходах с помощью отслеживания на графике и показателей, готовых к дальнейшей настройке в вашем торговом инструментарии. Удачной торговли!

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20229

Прикрепленные файлы |
Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA) Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA)
Статья посвящена реализации алгоритма искусственного поискового роя (ASSA) на MQL5 в составе унифицированного тестового стенда. Разобраны три поведенческих правила движения, механизм сигнала и глобального табло, нормализация пространства, а также параметры stepRatio и Pc. Читатель получит готовую основу для интеграции ASSA, а также ответ на вопрос — насколько тактическая метафора оказалась удачным фундаментом для конкурентоспособности оптимизационного алгоритма.
Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (Основные компоненты) Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (Основные компоненты)
В статье продолжается реализация фреймворка STCA средствами MQL5. Оригинальные оптимизации Self-Attention перенесены в архитектуру FlashAttention-2 и адаптированы под финансовые данные. Особое внимание уделено аккумулированию и распределению градиентов между потоками рабочей группы для анализа длинных временных рядов и многоголового внимания.
Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем
В продолжение нашей предыдущей статьи о паре индикаторов MACD и OBV, мы рассмотрим, как эту пару можно улучшить с помощью машинного обучения. MACD и OBV — это взаимодополняющая пара, отражающая тренд и объем. Наш подход к машинному обучению использует сверточную нейронную сеть (convolution neural network, CNN), которая задействует экспоненциальное ядро (Exponential kernel) для определения размеров своих ядер и каналов при настройке прогнозов этой пары индикаторов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.
Автоматизация торговых стратегий на MQL5 (Часть 21): Улучшение торговли на основе нейронных сетей с помощью адаптивных темпов обучения Автоматизация торговых стратегий на MQL5 (Часть 21): Улучшение торговли на основе нейронных сетей с помощью адаптивных темпов обучения
В этой статье мы улучшим торговую стратегию на основе нейронной сети на MQL5 с помощью адаптивного темпа обучения (adaptive learning rate) для повышения точности. Мы разработаем и внедрим это улучшение, а затем протестируем его работу. В заключении приводятся рекомендации по оптимизации алгоритмической торговли.