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

Реализация средствами 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, чтобы подтвердить успешную загрузку. В программировании принято всегда выполнять тестирование фрагментов кода на каждом этапе. После тестирования мы получаем следующий результат.

Мы видим, что успешно инициализировали программу. Теперь нам нужно обнаруживать сигналы и отслеживать их, сохранять информацию о них и визуализировать ее для облегчения отслеживания. Чтобы достичь этого, мы определим некоторые полезные функции для визуализации, как показано ниже.
//+------------------------------------------------------------------+ //| 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, с которыми вы можете взаимодействовать, чтобы получить наилучший результат. Смотрите ниже.

Затем функция "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" в случае успеха. После компиляции получаем следующий результат.

Мы видим, что при обнаружении сигнала мы закрываем позиции и открываем новую схему сигналов. Нам просто нужно обновить сигналы при достижении уровней прямо сейчас. Мы также видим, что панель не обновляется, но это последнее, что нам надо сделать после получения всей необходимой статистики. Сделаем это прямо сейчас.
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 без досрочного завершения. Получаем следующий результат.

Теперь мы можем отметить достигнутые уровни, завершив визуализацию графика. Поскольку у нас есть вся необходимая статистика, можно обновить панель. Мы определим функцию, которая будет содержать логику для этого.
//+------------------------------------------------------------------+ //| 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. После компиляции получаем следующий результат.

На изображении видно, что мы корректно настроили систему тестирования стратегий и достигли всех поставленных целей. Теперь осталось протестировать работоспособность системы, и это рассматривается в предыдущем разделе.
Тестирование системы отслеживания стратегий
Мы провели тестирование, а ниже представлена скомпилированная визуализация в едином формате растрового изображения Graphics Interchange Format (GIF).
![]()
Заключение
В заключение, мы разработали систему отслеживания стратегий в MQL5, которая распознает сигналы о пересечении скользящих средних с помощью долгосрочного фильтра, который можно переключить на благоприятный, отслеживает результаты по виртуальным или реальным позициям с несколькими уровнями тейк-профита и стоп-лосса, визуализирует входы, достижения и закрытия на графике, используя стрелки, линии и значки, а также предоставляет панель в режиме реального времени для отслеживания статистики, такой как общее количество сигналов, выигрышей / проигрышей, пунктов прибыли и показателей успешного выполнения. Это даёт возможность получить более глубокое представление о ваших торговых подходах с помощью отслеживания на графике и показателей, готовых к дальнейшей настройке в вашем торговом инструментарии. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20229
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Алгоритм искусственного поискового роя — Artificial Searching Swarm Algorithm (ASSA)
Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (Основные компоненты)
Возможности Мастера MQL5, которые вам нужно знать (Часть 72): Использование паттернов MACD и OBV с обучением с учителем
Автоматизация торговых стратегий на MQL5 (Часть 21): Улучшение торговли на основе нейронных сетей с помощью адаптивных темпов обучения
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования