
От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (IX) — Управление несколькими символами на одном графике для торговли на новостях
Разделы
- Введение
- Ознакомление с концепцией
- Реализация>
- Изменение класса CtradingButtons для торговли несколькими символами
- Интеграция функционала торговли несколькими символами с советником «Заголовки новостей»
- Тестирование
- Заключение
- Основные уроки
- Содержимое вложения
Введение
В периоды высокой волатильности — например, во время выхода экономических новостей — трейдеры часто делают ставку на пробои, поскольку немедленная реакция рынка непредсказуема. Когда выходит важная новость, цена обычно резко подскакивает, за чем следует коррекция и возможное продолжение тренда. В таких условиях трейдеры могут захотеть торговать несколькими символами одновременно, но этого трудно достичь при настройках MetaTrader 5 по умолчанию. По замыслу, один график поддерживает только один советник, что означает, что трейдеры должны открывать несколько графиков и подключать отдельный советник к каждому символу.
В сегодняшнем обсуждении мы представляем решение этого ограничения - функцию торговли несколькими символами, интегрированную в советник «Заголовки новостей» . Благодаря этому усовершенствованию трейдеры могут управлять несколькими парами с одного графика, используя интуитивно понятные торговые кнопки. Мы рассмотрим, как мощь MQL5, использующего как стандартную библиотеку, так и пользовательские торговые классы, позволяет создавать сложный советник, способный легко работать с несколькими символами на одном графике.
Рис. 1: На каждом графике в терминале MetaTrader 5 может быть только один советник
На изображении выше показано ограничение настройки MetaTrader 5, при котором на одном графике может работать только один советник. Чтобы эффективно торговать несколькими символами, нам нужен сложный советник, способный управлять как текущей графической парой, так и другими парами одновременно — даже при работе только с одним графиком.
К концу этого обсуждения мы стремимся достичь следующих результатов:
- Разработать более сложный советник.
- Расширить существующий класс заголовков MQL5 новыми функциями.
- Использоватьмодель стандартную библиотеку MQL5 для создания новых классов.
- Интегрировать новый функционал в существующий советник.
- Применять модульность и структурированную группировку входных данных.
Ознакомление с концепцией
Данный этап начинается с краткого обзора нашей предыдущей работы. Мы начали с простого советника с анимированными заголовками новостей, который извлекал данные из экономического календаря и внешних новостных API, таких как Alpha Vantage. Со временем мы интегрировали локальные модели искусственного интеллекта, автоматизированные стратегии торговли на новостях и кнопки ручной торговли, чтобы сделать советник более надежным.
Хотя эти нововведения улучшили систему, они не были полным решением. Алгоритмическая торговля продолжает развиваться, и с каждым технологическим прогрессом возникают новые задачи, которые заставляют нас модернизировать наши системы. Сегодня мы решаем одну из таких задач: обеспечение многопарной торговли в рамках одного советника.
Почему это необходимо?
Уместный вопрос, который можно задать, заключается в следующем: зачем нам нужна эта функция?
Во время событий с высокой волатильностью, таких как выпуски экономических новостей, трейдеры должны реагировать быстро, часто управляя несколькими позициями и символами в течение нескольких секунд. Эта разработка обеспечивает важнейшее преимущество, объединяя алгоритмическую и ручную торговлю в одном месте, повышая эффективность и контроль. Одним щелчком мыши трейдер может совершать сделки по нескольким символам и управлять несколькими позициями одновременно, повышая как скорость, так и эффективность работы.
Процесс интеграции функций
Учитывая этот контекст, давайте вкратце опишем, как будет добавлена новая функция. Чтобы расширить возможности нашего советника, мы используем включение заголовков и пользовательские классы торговых кнопок, которые поддерживают чистоту и модульность основной кодовой базы.
Для торговли несколькими символами нам нужна возможность выбирать нужные пары, которые будут выполняться рядом с парой текущего графика при нажатии кнопок ручной торговли. Для достижения этой цели будем использовать классы CCheckBox и CLabel из стандартной библиотеки MQL5. Эти компоненты позволят нам отображать выбираемые пары, управлять пользовательским вводом и связывать выбранные параметры непосредственно с обработчиками событий кнопок.
Наконец, наш класс CTradingButtons будет расширен для беспрепятственного включения этих новых функций.
Реализация
Мы будем подходить к этому в два основных этапа. Во-первых, изменим класс CTradingButtons в заголовке TradingButtons, чтобы реализовать функции торговли несколькими символами, описанные в нашем проекте. Второй этап будет сосредоточен на адаптации советника «Заголовки новостей» для поддержки этих новых возможностей.
Внимательно следите за тем, как мы разберем код и объясним, как каждая часть способствует воплощению идеи в жизнь. Для наглядности каждый раздел кода и пояснения к нему будут пронумерованы последовательно сверху вниз, с акцентом на новые функциональные возможности.
Если вы хотите ознакомиться с основополагающими частями кода, я рекомендую вам вернуться к более ранним публикациям из этой серии, в которых мы подробно рассматривали начальные версии.
Изменение класса CtradingButtons для торговли несколькими символами
Мы впервые представили этот заголовок в предыдущей статье, которую можно прочитать для большей ясности. В этом разделе мы расширим его с помощью новой функции.
Обзор на высоком уровне
Этот класс (CTradingButtons) объединяет в себе три функции, поэтому действует как компактный многопарный торговый модуль, который вы можете установить в советник: (1) пользовательский интерфейс (холст + кнопки + динамически создаваемые флажки для символов), (2) небольшая торговая обёртка (экземпляр CTrade, который отправляет ордера) и (3) механизм разрешения символов и многопары (сопоставляет запрашиваемые базовые названия, такие как EURUSD, с символами брокера и применяет действия ко всем выбранным символам). Высокоуровневый дизайн обеспечивает выравнивание индексов по массивам: запрошенный список (то, что передает советник), разрешенные символы брокера (то, чем на самом деле торгует терминал) и флажки (то, что переключает пользователь) - индекс i представляет одну и ту же пару во всех массивах. Это позволяет сделать подключение интерфейса пользователя к массивам выбора советника простым и предсказуемым.
// top-of-file: class skeleton + key members (from TradingButtons.mqh) class CTradingButtons { private: // UI & buttons CButton btnMultiToggle; CButton btnBuy, btnSell, btnCloseAll, btnDeleteOrders, btnCloseProfit, btnCloseLoss, btnBuyStop, btnSellStop; CCanvas buttonPanel; // trading CTrade trade; // multipair UI & resolution CCheckBox *pairChecks[]; // dynamic checkbox pointers, index-aligned with requested list string availablePairs[]; // resolved broker symbols (index-aligned) string resolvedBases[]; // original requested bases (for logging) bool multiEnabled; public: double LotSize; int StopLoss; int TakeProfit; int StopOrderDistancePips; double RiskRewardRatio; CTradingButtons() { /* default init inlined in full file */ } void Init(); void Deinit(); // ... other methods follow };
Поля и конструктор — что хранит класс
В классе хранятся параметры макета (ширина/высота/интервал между кнопками), размер флажка и начальные координаты, динамический массив флажков, разрешенные массивы символов и конфигурация торговли (размер лота, стопы, риск/прибыль). Его конструктор устанавливает разумные значения по умолчанию (например, LotSize=0.01, StopLoss=50, TakeProfit=100, multiEnabled=true), чтобы советник мог начать с рабочей конфигурации и переопределить то, что ему понадобится позже. Сохранение этих полей как открытых (для торговых параметров), так и закрытых (для внутренних элементов пользовательского интерфейса) делает интерфейс простым и безопасным.
// constructor + key field defaults (actual defaults in your file) CTradingButtons() : buttonWidth(100), buttonHeight(30), buttonSpacing(10), checkWidth(120), checkHeight(20), checkSpacing(6), checkStartX(10), LotSize(0.01), StopLoss(50), TakeProfit(100), StopOrderDistancePips(8), RiskRewardRatio(2.0), multiEnabled(true) { // constructor body intentionally minimal — Init() performs heavier setup }
Инициализация и очистка
Init() настраивает обертку CTrade (магическое число, отклонение) и создает пользовательский интерфейс (панель, кнопки, мульти-переключатель). Функция Deinit() аккуратно уничтожает все динамические объекты (флажки, кнопки, холст) и освобождает массивы, чтобы избежать потерянных объектов диаграммы или утечек памяти. Очистка выполняется по циклу над массивом pairChecks[] и вызывает Destroy() и delete для динамических указателей, а затем освобождает массив, что особенно важно при повторном запуске/выгрузке советника во время разработки.
// Init & Deinit excerpt void Init() { trade.SetExpertMagicNumber(123456); trade.SetDeviationInPoints(10); CreateButtonPanel(); CreateButtons(); CreateMultiToggle(); UpdateMultiToggleVisual(); } void Deinit() { // destroy checkboxes for(int i = 0; i < ArraySize(pairChecks); i++) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) pairChecks[i].Destroy(); delete pairChecks[i]; } } ArrayFree(pairChecks); // destroy buttons and panel btnMultiToggle.Destroy(); btnBuy.Destroy(); btnSell.Destroy(); btnCloseAll.Destroy(); btnDeleteOrders.Destroy(); btnCloseProfit.Destroy(); btnCloseLoss.Destroy(); btnBuyStop.Destroy(); btnSellStop.Destroy(); buttonPanel.Destroy(); ObjectDelete(0, "ButtonPanel"); }
Разрешение символов (важно для многопары)
Чтобы торговать по "понятному" названию, такому как EURUSD, советник должен точно сопоставить его со строкой символов брокера (это может быть EURUSD, EURUSD.ecn, FX.EURUSD и т.д.). ResolveSymbol(base) сначала пытается найти точное соответствие (быстрый путь). Если это не удается, он перебирает все символы терминала, выполняет поиск начальных символов, а затем находит совпадения (предпочтительно, начальные символы) и исключает отключенные символы. На этом этапе разрешения создаются записи availablePairs[i], используемые торговыми процедурами и флажками пользовательского интерфейса — это связующее звено между запрашиваемыми советником названиями и фактическими торгуемыми символами брокера.
// ResolveSymbol implementation (exact + starts-with + contains search) string ResolveSymbol(const string base) { if(StringLen(base) == 0) return(""); // 1) Try exact symbol name first string baseName = base; if(SymbolInfoInteger(baseName, SYMBOL_SELECT) != 0 || SymbolSelect(baseName, false)) { if(SymbolInfoInteger(baseName, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("ResolveSymbol: exact match found %s", baseName); return(baseName); } } // 2) search all terminal symbols int total = SymbolsTotal(false); string base_u = base; StringToUpper(base_u); string firstStarts = ""; string firstContains = ""; for(int i = 0; i < total; i++) { string sym = SymbolName(i, false); string sym_u = sym; StringToUpper(sym_u); if(sym_u == base_u) continue; if(SymbolInfoInteger(sym, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) continue; if(StringFind(sym_u, base_u) == 0) { if(firstStarts == "") firstStarts = sym; } else if(StringFind(sym_u, base_u) >= 0) { if(firstContains == "") firstContains = sym; } } if(firstStarts != "") { PrintFormat("ResolveSymbol: resolved %s -> %s (starts-with)", base, firstStarts); return(firstStarts); } if(firstContains != "") { PrintFormat("ResolveSymbol: resolved %s -> %s (contains)", base, firstContains); return(firstContains); } PrintFormat("ResolveSymbol: no match for %s", base); return(""); }
Creating the multipair UI — CreatePairCheckboxes(...)
CreatePairCheckboxes(inMajorPairs[], inPairSelected[], yPos) - это процедура, которая: (а) преобразует каждую запрошенную базу в символ брокера, (б) гарантирует, что символ присутствует в обзоре рынка (SymbolSelect) и доступен для торговли, и (в) динамически создает CCheckBox для каждого разрешенного символа, в то же время сохраняя упорядочение индекса с массивами советника. Неразрешенные или отключенные записи сохраняются в качестве заполнителей, чтобы availablePairs[i] правильно отображались в исходных inMajorPairs[i]. Начальное отмеченное состояние для каждого созданного флажка берется из inPairSelected[i], поэтому массивы выбора пользовательского интерфейса и советника синхронизируются с самого начала.
// CreatePairCheckboxes: resolve requested bases -> create checkboxes aligned by index void CreatePairCheckboxes(string &inMajorPairs[], bool &inPairSelected[], int yPos) { // cleanup previous for(int i = 0; i < ArraySize(pairChecks); i++) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) { pairChecks[i].Destroy(); delete pairChecks[i]; } } ArrayFree(pairChecks); ArrayResize(availablePairs, ArraySize(inMajorPairs)); ArrayResize(resolvedBases, ArraySize(inMajorPairs)); for(int k = 0; k < ArraySize(availablePairs); k++) { availablePairs[k] = ""; resolvedBases[k] = ""; } int count = ArraySize(inMajorPairs); if(count == 0) return; // Resolve each requested base for(int i = 0; i < count; i++) { string requested = inMajorPairs[i]; string resolved = ResolveSymbol(requested); if(resolved == "") { PrintFormat("CreatePairCheckboxes: could not resolve %s -> skipping checkbox", requested); availablePairs[i] = ""; resolvedBases[i] = requested; continue; } if(!SymbolSelect(resolved, true)) PrintFormat("CreatePairCheckboxes: SymbolSelect failed for %s (from %s) Err=%d", resolved, requested, GetLastError()); if(SymbolInfoInteger(resolved, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("CreatePairCheckboxes: resolved symbol %s is disabled (from %s) - skipping", resolved, requested); availablePairs[i] = ""; resolvedBases[i] = requested; continue; } availablePairs[i] = resolved; resolvedBases[i] = requested; } // Create checkbox controls (preserve index alignment) ArrayResize(pairChecks, count); int xPos = checkStartX; int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int wrapX = chartW - (buttonWidth * 3) - 30; for(int i = 0; i < count; i++) { if(StringLen(availablePairs[i]) == 0) { pairChecks[i] = NULL; continue; } pairChecks[i] = new CCheckBox(); string objName = "Chk_" + availablePairs[i]; if(!pairChecks[i].Create(ChartID(), objName, 0, xPos, yPos, xPos + checkWidth, yPos + checkHeight)) { PrintFormat("CreatePairCheckboxes: failed to create checkbox %s Err=%d", objName, GetLastError()); delete pairChecks[i]; pairChecks[i] = NULL; availablePairs[i] = ""; continue; } pairChecks[i].Text(" " + availablePairs[i]); pairChecks[i].Color(clrBlack); bool checked = false; if(i < ArraySize(inPairSelected)) checked = inPairSelected[i]; pairChecks[i].Checked(checked); xPos += checkWidth + checkSpacing; if(xPos + checkWidth > wrapX) { xPos = checkStartX; yPos += checkHeight + checkSpacing; } } ChartRedraw(); PrintFormat("CreatePairCheckboxes: created checkboxes (resolved count=%d)", CountResolvedPairs()); }
Подсчет/помощник — CountResolvedPairs()
Малые помощники помогают поддерживать удобочитаемость кода. Функция CountResolvedPairs() просто подсчитывает непустые записи availablePairs[] и используется для ведения лога или обновления текста пользовательского интерфейса. Это простой способ, но он полезен при инициализации и устранении неполадок.
// Count resolved availablePairs entries int CountResolvedPairs() { int c = 0; for(int i = 0; i < ArraySize(availablePairs); i++) if(StringLen(availablePairs[i]) > 0) c++; return c; }
Обработка событий — HandleChartEvent(...)
Все щелчки по объектам диаграммы направляются в HandleChartEvent. В ней выделяются три категории: (А) щелчки по флажкам (имена объектов с префиксом Chk_ — определяет, по какому разрешенному символу был сделан щелчок, и синхронизирует массив inPairSelected[i]), (Б) кнопка с несколькими переключателями (переключает multiEnabled и обновляет визуальные элементы) и (В) кнопки действий (Buy/Sell/Close all/Delete pending/Place stops) — каждое нажатие кнопки делегирует соответствующую операцию, передавая советнику запрашиваемые пары и флаги выбора. Эта функция называется UI → engine router и синхронизирует массивы пользовательского интерфейса и советника.
// HandleChartEvent: routes object clicks to checkboxes / toggle / actions void HandleChartEvent(const int id, const string &sparam, string &inMajorPairs[], bool &inPairSelected[]) { if(id == CHARTEVENT_OBJECT_CLICK) { // Checkbox click handling if(StringFind(sparam, "Chk_") == 0) { for(int i = 0; i < ArraySize(availablePairs); i++) { if(StringLen(availablePairs[i]) == 0) continue; string expected = "Chk_" + availablePairs[i]; if(expected == sparam) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) { bool current = pairChecks[i].Checked(); if(i < ArraySize(inPairSelected)) inPairSelected[i] = current; else { ArrayResize(inPairSelected, i+1); inPairSelected[i] = current; } PrintFormat("HandleChartEvent: checkbox for %s toggled -> %s", availablePairs[i], current ? "true":"false"); } break; } } return; } // Multi toggle if(sparam == btnMultiToggle.Name()) { multiEnabled = !multiEnabled; UpdateMultiToggleVisual(); PrintFormat("HandleChartEvent: Multi toggle clicked. New multiEnabled=%s", (multiEnabled ? "true":"false")); return; } // Buttons - delegate to command handlers if(sparam == btnBuy.Name()) OpenBuyOrder(inMajorPairs, inPairSelected); else if(sparam == btnSell.Name()) OpenSellOrder(inMajorPairs, inPairSelected); else if(sparam == btnCloseAll.Name()) CloseAllPositions(inMajorPairs, inPairSelected); else if(sparam == btnDeleteOrders.Name()) DeleteAllPendingOrders(inMajorPairs, inPairSelected); else if(sparam == btnCloseProfit.Name()) CloseProfitablePositions(inMajorPairs, inPairSelected); else if(sparam == btnCloseLoss.Name()) CloseLosingPositions(inMajorPairs, inPairSelected); else if(sparam == btnBuyStop.Name()) PlaceBuyStop(inMajorPairs, inPairSelected); else if(sparam == btnSellStop.Name()) PlaceSellStop(inMajorPairs, inPairSelected); } }
Помощники по созданию пользовательского интерфейса
Создание пользовательского интерфейса разделено на небольшие вспомогательные элементы: CreateButtonPanel() создает растровое изображение на «холсте» (фон панели и декоративный прямоугольник), CreateButtons() создает экземпляры и стилизует каждую кнопку действия с совместимыми шрифтом/размером/ расположением, а CreateMultiToggle() создает кнопку переключения, расположенную над основными кнопками действий. Функция UpdateMultiToggleVisual() обновляет текст и цвет переключателя, чтобы показать, активен ли многопарный режим. Эти помощники отделяют визуальный код от бизнес-логики и упрощают изменение стиля.
// Create panel + buttons + multi-toggle visual helpers void CreateButtonPanel() { int panelWidthLocal = buttonWidth + 20; int panelHeightLocal = (buttonHeight + buttonSpacing) * 9 + buttonSpacing + 40; int x = 0, y = 40; if(!buttonPanel.CreateBitmap(0, 0, "ButtonPanel", x, y, panelWidthLocal, panelHeightLocal, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Failed to create button panel: Error=", GetLastError()); return; } ObjectSetInteger(0, "ButtonPanel", OBJPROP_ZORDER, 10); buttonPanel.FillRectangle(0, 0, panelWidthLocal, panelHeightLocal, ColorToARGB(clrDarkGray, 200)); buttonPanel.Rectangle(0, 0, panelWidthLocal - 1, panelHeightLocal - 1, ColorToARGB(clrRed, 255)); buttonPanel.Update(true); ChartRedraw(0); } void CreateMultiToggle() { int x = 10, y = 120; string font = "Calibri"; int fontSize = 8; color buttonBgColor = clrBlack; if(btnMultiToggle.Create(0, "btnMultiToggle", 0, x, y, x + buttonWidth, y + buttonHeight)) { ObjectSetString(0, "btnMultiToggle", OBJPROP_FONT, font); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_BGCOLOR, buttonBgColor); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_ZORDER, 11); } } void UpdateMultiToggleVisual() { if(multiEnabled) { btnMultiToggle.Text("MULTI:ON"); btnMultiToggle.ColorBackground(clrGreen); btnMultiToggle.Color(clrWhite); } else { btnMultiToggle.Text("MULTI:OFF"); btnMultiToggle.ColorBackground(clrRed); btnMultiToggle.Color(clrWhite); } }
Функции помощников для торговли
Эти помощники инкапсулируют низкоуровневую торговую механику. PipSize(symbol) возвращает величину в пипсах (в вашем коде используется SYMBOL_POINT * 10.0), IsSymbolValid(symbol) проверяет, существуют ли предложения/спрос, а TradeBuySingle()/TradeSellSingle() проверяет возможность торговли, границы лота, вычисляет цену/SL/TP, используя размер в пипсах, устанавливает тип заполнения и отправляет ордер через trade.Buy()/trade.Sell(). Эти вспомогательные функции централизуют логику отправки ордеров, поэтому мультипарные циклы просто вызывают их для каждого символа.
// Pip size, validation, and single-symbol trade helpers double PipSize(string symbol) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(point <= 0) { Print("Invalid point size for ", symbol, ": Error=", GetLastError()); return 0; } return point * 10.0; } bool IsSymbolValid(string symbol) { bool inMarketWatch = SymbolInfoDouble(symbol, SYMBOL_BID) > 0 && SymbolInfoDouble(symbol, SYMBOL_ASK) > 0; if(!inMarketWatch) Print("Symbol ", symbol, " invalid: Not in Market Watch or no valid bid/ask price."); return inMarketWatch; } bool TradeBuySingle(const string symbol) { if(!IsSymbolValid(symbol)) return false; long tradeMode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE); if(tradeMode != SYMBOL_TRADE_MODE_FULL) { Print("TradeBuySingle: Skipping ", symbol, ": Trading disabled"); return false; } double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(LotSize < minLot || LotSize > maxLot) { Print("TradeBuySingle: invalid lot"); return false; } double price = SymbolInfoDouble(symbol, SYMBOL_ASK); double sl = StopLoss > 0 ? price - StopLoss * PipSize(symbol) : 0; double tp = TakeProfit > 0 ? price + TakeProfit * PipSize(symbol) : 0; trade.SetTypeFillingBySymbol(symbol); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); price = NormalizeDouble(price, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); if(trade.Buy(LotSize, symbol, price, sl, tp)) { Print("Buy order placed on ", symbol, ": Ticket #", trade.ResultOrder()); return true; } else { Print("Buy order failed on ", symbol, ": Retcode=", trade.ResultRetcode()); return false; } } bool TradeSellSingle(const string symbol) { if(!IsSymbolValid(symbol)) return false; long tradeMode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE); if(tradeMode != SYMBOL_TRADE_MODE_FULL) { Print("TradeSellSingle: Skipping ", symbol, ": Trading disabled"); return false; } double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(LotSize < minLot || LotSize > maxLot) { Print("TradeSellSingle: invalid lot"); return false; } double price = SymbolInfoDouble(symbol, SYMBOL_BID); double sl = StopLoss > 0 ? price + StopLoss * PipSize(symbol) : 0; double tp = TakeProfit > 0 ? price - TakeProfit * PipSize(symbol) : 0; trade.SetTypeFillingBySymbol(symbol); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); price = NormalizeDouble(price, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); if(trade.Sell(LotSize, symbol, price, sl, tp)) { Print("Sell order placed on ", symbol, ": Ticket #", trade.ResultOrder()); return true; } else { Print("Sell order failed on ", symbol, ": Retcode=", trade.ResultRetcode()); return false; } }
Основные операции — как применяются мультипарные команды
Основные операции (например, OpenBuyOrder, OpenSellOrder, CloseAllPositions, PlaceBuyStop, PlaceSellStop) используют предоставляемые советником массивы inMajorPairs[] и inPairSelected[]. Когда значение multiEnabled равно true, подпрограммы перебирают все индексы и вызывают торговых помощников, используя availablePairs[i] для каждого выбранного индекса. Если значение multiEnabled равно false, подпрограмма торгует только символом, указанным на графике. OpenBuyOrder/OpenSellOrder также отслеживает, торговался ли символ на графике с помощью флажков, и возвращается к торговле символом на графика, если нет. Это гарантирует ожидания пользователя при переключении между режимами, т.е. ориентированным на график режимом, и мультипарным режимом.
// OpenBuyOrder / OpenSellOrder excerpt (multipair iteration + fallback to chart symbol) void OpenBuyOrder(string &inMajorPairs[], bool &inPairSelected[]) { Print("Starting OpenBuyOrder"); string chartSym = Symbol(); if(!multiEnabled) { PrintFormat("OpenBuyOrder: multipair disabled => trading only chart symbol %s", chartSym); if(SymbolInfoInteger(chartSym, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { Print("chart symbol not tradeable"); return; } TradeBuySingle(chartSym); return; } bool chartTraded = false; for(int i = 0; i < ArraySize(inMajorPairs); i++) { if(i < ArraySize(inPairSelected) && inPairSelected[i] && StringLen(availablePairs[i]) > 0) { string symbol = availablePairs[i]; Print("Attempting Buy order on ", symbol, " (requested ", resolvedBases[i], ")"); if(TradeBuySingle(symbol) && symbol == chartSym) chartTraded = true; } else { if(i < ArraySize(inMajorPairs)) Print("Skipping ", inMajorPairs[i], ": Not selected or unresolved"); } } if(!chartTraded && SymbolInfoInteger(chartSym, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("OpenBuyOrder: attempting BUY on chart symbol %s", chartSym); TradeBuySingle(chartSym); } } void OpenSellOrder(string &inMajorPairs[], bool &inPairSelected[]) { // (same structure as OpenBuyOrder but calling TradeSellSingle) // Implementation mirrors the buy flow but uses SELL specifics. }
Интеграция функционала торговли несколькими символами с советником «Заголовки новостей»
Где внедряется мультипарная система (includes и inputs)
Советник выводит вверху пользовательский интерфейс/торговый заголовок с поддержкой мультипарной торговли и предоставляет input для включения/выключения мультипарной торговли при запуске. Это единственное место, где советник объявляет: (а) он будет использовать внешний мультипарный интерфейс/торговый объект, и (б) пользователь может установить начальный мультипарный режим. Это делает функцию доступной и видимой для пользователя советника.
#include <TradingButtons.mqh> // header that implements the multipair UI & trading logic. // ... other includes ... input bool EnableMultipair = true; // initial multipair enabled state
Нам нужно включить заголовок и предоставить input, чтобы пользователи могли выбрать ожидаемое поведение при инициализации.
Где хранятся данные о мультипарах (majorPairs и флажки выбора)
Советник определяет строковый массив majorPairs[] с именами запрашиваемых пар и параллельный логический массив pairSelected[], который отслеживает, какие пары проверены. Эти два массива являются контрактом между советником и заголовком: индекс i в обоих массивах относится к одной и той же валютной паре. Заголовок устанавливает флажки и использует логический массив, чтобы узнать, какие пары выбраны.
// MULTIPAIR arrays (provided to the header) // default major pairs (you can edit or later replace with resolved broker symbols) string majorPairs[] = {"EURUSD","GBPUSD","USDJPY","USDCHF","AUDUSD","USDCAD","NZDUSD"}; bool pairSelected[];
Для сохранения простого, совместимого с индексом списка пар + флажки выбора. Он удобочитаем, его легко передать по ссылке, и он упрощает синхронизацию.
Инициализируем настройки выбора по умолчанию и передаем входные данные в заголовок (настройка OnInit)
Во время OnInit() советник изменяет размер pairSelected, чтобы они соответствовали majorPairs, и по умолчанию для каждого элемента устанавливается значение true. Затем советник настраивает открытые параметры buttonsEA (размер лота, стопы, настройки рисков) и инициализирует заголовок, вызывая Init() и SetMultiEnabled(EnableMultipair). Это гарантирует, что заголовок запустится в выбранном советником режиме и будет использовать те же торговые параметры.
// In OnInit() ArrayResize(pairSelected, ArraySize(majorPairs)); for(int i = 0; i < ArraySize(pairSelected); i++) pairSelected[i] = true; // Initialize TradingButtons buttonsEA.LotSize = ButtonLotSize; buttonsEA.StopLoss = ButtonStopLoss; buttonsEA.TakeProfit = ButtonTakeProfit; buttonsEA.StopOrderDistancePips = StopOrderDistancePips; buttonsEA.RiskRewardRatio = RiskRewardRatio; buttonsEA.Init(); buttonsEA.SetMultiEnabled(EnableMultipair); // pass initial multipair state
Синхронизируем конфигурацию перед инициализацией пользовательского интерфейса/торгового объекта — сначала установим открытые поля и режим, затем вызываем Init(), чтобы заголовок имел корректные параметры времени выполнения.
Создание флажков (настройка пользовательского интерфейса) — вызов CreatePairCheckboxes
Здесь мы позволяем советнику рассчитать смещение флажка по Y (checkboxY) (чтобы флажки отображались под полосами новостей) и вызываем CreatePairCheckboxes(majorPairs, pairSelected, checkboxY). Это создает флажки в заголовке, сохраняя при этом соответствие индекса и majorPairs. Заголовок также установит начальное состояние каждого флажка из pairSelected[], чтобы пользовательский интерфейс и советник были синхронизированы. Таким образом, это позволяет компоненту пользовательского интерфейса отображать элементы управления, но передавать массивы EA по ссылке. Таким образом, советник остается авторитетным источником информации о том, какие пары существуют и какие из них выбраны (заголовок управляет теми же массивами).
// create pair checkboxes aligned below the canvas lanes int checkboxY = InpTopOffset + (InpSeparateLanes ? 8 : 28) * lineH + 6; // adjust +6 px margin if needed buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY);
Маршрутизация событий — перенаправление событий диаграммы в заголовок
Советник сам по себе не реализует логику нажатия кнопки, вместо этого он перенаправляет все нажатия на объекты графика в заголовок, вызывая buttonsEA.HandleChartEvent(...) из OnChartEvent. Этот контракт с одним вызовом упрощает работу советника, поскольку заголовок отвечает за мультипарное переключение, щелчки флажков и действия с кнопками ручной торговли.
// OnChartEvent: forward to the header with majorPairs and pairSelected arrays void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Header will handle multipair toggle and trade behaviour buttonsEA.HandleChartEvent(id, sparam, majorPairs, pairSelected); }
Применяя отдельную маршрутизацию событий, советник становится каналом передачи событий, в то время как заголовок четко обрабатывает события пользовательского интерфейса и торговые решения. Это позволяет четко ограничить круг обязанностей.
Как запускаются ручные мультипарные операции (роль заголовка)
Заголовок - это механизм исполнения мультиплексированных сделок вручную, который предоставляет простые массивы и позволяет заголовку перебирать и разрешать символы. Благодаря этому советник остается незагроможденным.К огда пользователь нажимает кнопку Buy/Sell или переключает мультипару, HandleChartEvent в заголовке использует переданные массивы majorPairs и pairSelected, чтобы решить, как действовать — например, он будет перебирать индексы и торговать только теми, где pairSelected[i] == true. Советник предоставляет только массивы и конфигурацию; заголовок выполняет разрешение и торговлю по нескольким символам. (Смотрите в заголовке информацию о торговых помощниках по каждому символу и мультипарной итерации.)// (conceptual) header receives arrays and performs per-index iteration: // Pseudocode excerpt of header behavior (actual code in TradingButtons.mqh) for(i = 0; i < ArraySize(majorPairs); i++) { if(i < ArraySize(pairSelected) && pairSelected[i]) { // resolve broker symbol for majorPairs[i] // call TradeBuySingle(resolvedSymbol) or TradeSellSingle(...) } }
Логика автоматизированных ордеров остается привязанной к графику (как мультисимвол сосуществует с автоматизацией)
Автоматизированные стоп-ордера до события и после него в этом советнике работают с символом, представленным на графике (_Symbol), а не с majorPairs. Мультипарная ручная система отличается: ручные мультипарные сделки (кнопки) и автоматизированные управляемые событиями сделки - это разные потоки. Такое разделение позволяет избежать случайных автоматических мультисимвольных ордеров, если только вы явно не расширите автоматизацию для использования majorPairs.
// Example: automated BuyStop/SellStop placement uses _Symbol (chart symbol) if(trade.BuyStop(InpOrderVolume, buyPrice, _Symbol, buySL, buyTP)) ticketBuyStop = trade.ResultOrder(); if(trade.SellStop(InpOrderVolume, sellPrice, _Symbol, sellSL, sellTP)) ticketSellStop = trade.ResultOrder();
Из приведенного выше кода мы извлекли ключевой урок: следует отделять ручное управление несколькими парами от автоматизированных стратегий для работы с конкретными графиками, если только вы намеренно не хотите, чтобы автоматизация работала с большим количеством символов. Четкое разделение помогает предотвратить неожиданности.
Модель синхронизации: советник владеет данными, заголовок владеет пользовательским интерфейсом/логикой
Советник определяет и сохраняет majorPairs[] и pairSelected[] (авторитетное состояние). Заголовок считывает их (создает элементы управления, воздействует на отмеченные элементы) и записывает обратно изменения (количество кликов на флажках устанавливает pairSelected[i]), поскольку массивы передаются по ссылке. Эта модель двусторонней синхронизации проста и надежна: советник может проверить pairSelected[] в любое время (например, в OnTimer), а заголовок обновляет её при взаимодействии с пользователем.
// EA owns arrays; header is given references and updates them when checkboxes are toggled buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY); ... // header's HandleChartEvent updates pairSelected[] in-place when checkboxes are clicked
Мы используем передачу по ссылке для общего состояния среды выполнения. Это не требует больших затрат времени и обеспечивает синхронизацию пользовательского интерфейса и советника без дополнительной передачи сообщений.
Рекомендации по размещению и визуальному оформлению (рамки под полосами новостей)
Советник вычисляет checkboxY на основе полос новостей и параметров конфигурации (InpTopOffset, InpSeparateLanes, lineH), поэтому мультипарные флажки отображаются визуально под полосами новостей / индикаторов. Интеграция элементов пользовательского интерфейса из других модулей — это такая же задача компоновки, как и задача логики: динамическое вычисление смещений означает, что пользовательский интерфейс адаптируется при изменении высоты или положения полос.
// compute vertical position for checkboxes so they sit below the lanes int checkboxY = InpTopOffset + (InpSeparateLanes ? 8 : 28) * lineH + 6; buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY);
При объединении холстов и внешних панелей пользовательского интерфейса централизуйте математику компоновки в советнике, чтобы обе системы использовали согласованные правила интервалов.
Тестирование
Использование советника в терминале MetaTrader 5 дало отличные результаты. Я смог выбрать пары, которыми хотел торговать, и они мгновенно отреагировали на торговые кнопки. Ордера исполнялись с алгоритмической скоростью, и одним щелчком мыши я мог закрыть все позиции по нескольким парам — бесценная функция для торговли на новостях и других стратегий скальпинга с высокой волатильностью.
На рисунке ниже показаны результаты тестирования графика в режиме реального времени. Важно отметить, что ручные функции требуют взаимодействия с графиком в режиме реального времени, в то время как автоматизированные компоненты советника можно тщательно оценить в тестере стратегий для обеспечения эффективности.
Торговля несколькими символами с помощью советника «Заголовки новостей»
Заключение
В части IX была отмечена еще одна важная веха в развитии советника «Заголовки новостей»: интеграция торговли несколькими символами. Это усовершенствование устраняет давнее ограничение, давая трейдерам возможность управлять несколькими валютными парами с помощью одного графика. Хотя эта функция и не является полностью автоматизированной торговой системой, она действует как ручной торговый интерфейс, основанный на алгоритмическом исполнении, обеспечивая скорость и точность, оставляя принятие решений трейдеру в условиях высокой волатильности.
Сам процесс разработки был тщательно продуманным, поскольку мы применили принципы модульности и разделения задач для создания компактной, но мощной системы. То, что начиналось как простое отображение календаря и ленты новостей, теперь превратилось в универсальную платформу, объединяющую ручные и автоматизированные функции для решения практических задач, с которыми сталкиваются новостные трейдеры. Несмотря на то, что система разработана с учетом основных валютных пар, она может быть адаптирована для пользовательских символов с незначительными корректировками совместимости.
Одной из ключевых проблем, с которой мы столкнулись, было наименование символов, специфическое для конкретного брокера. Например, сделки изначально завершились неудачей из-за того, что на счете использовалось название EURUSD.0, а не стандартное EURUSD. Чтобы преодолеть это, мы усовершенствовали советник, чтобы он динамично адаптировался к брокерской номенклатуре основных валютных пар.
Мы также использовали класс CCheckBox из стандартной библиотеки MQL5, чтобы обеспечить плавный выбор пар, демонстрируя гибкость и расширяемость языка MQL5.
Надеюсь, что это обсуждение дало полезные идеи и практические уроки. Полный исходный код прилагается ниже, используйте его в настоящей статье, документирующей реализацию. Для удобства я также кратко изложил основные выводы в табличном формате ниже. Всегда рад обратной связи и комментариям.
Основные уроки
Основные уроки | Описание |
---|---|
Настроенные по индексу массивы | Оба проекта используют массивы majorPairs[] и pairSelected[], настроенные по индексу. Это гарантирует, что флажок, запрашиваемое базовое имя и разрешенный символ брокера будут согласованно указывать на одну и ту же валютную пару. |
Синхронизация "Передача по ссылке" | Массивы передаются по ссылке из советника в TradingButtons.mqh, что позволяет заголовку напрямую обновлять состояния выбора при переключении флажков, мгновенно синхронизируя состояние советника. |
Явная переадресация событий | Советник не обрабатывает нажатия кнопок напрямую. Вместо этого OnChartEvent перенаправляет события в buttonsEA.HandleChartEvent(), где заголовок интерпретирует мультипарные переключения, щелчки флажков и торговые действия. |
Абстракция разрешения символов | Функция ResolveSymbol() в заголовке сопоставляет удобные для пользователя базовые названия (например, EURUSD) с символами, специфичными для брокера (например, EURUSD.ecn). Советник может не зависеть от особенностей именования брокеров. |
Отдельные ручные и автоматизированные потоки | В советнике автоматизированные ордера до/после события всегда действуют по символу графика, в то время как функционал мультипары зарезервирован для ручных действий с кнопками. Такое разделение позволяет избежать неожиданных массовых сделок. |
Создание и очистка динамического пользовательского интерфейса | Заголовок динамически создает/удаляет флажки и кнопки во время Init()/Deinit(). Советник рассчитывает смещения раскладки (под полосами новостей), чтобы компоненты легко вписывались в его пользовательский интерфейс. |
Инициализировать перед Init() | В OnInit() советник устанавливает LotSize, StopLoss, TakeProfit, and EnableMultipair перед вызовом функции buttonsEA.Init(). Это гарантирует, что заголовок будет создан сам по себе с правильной конфигурацией. |
Централизованные торговые помощники | Торговая логика заключена в многоразовых помощниках, таких как TradeBuySingle() и TradeSellSingle(). Это позволяет избежать дублирования кода в мультипарных циклах и обработчиках кнопок. |
Мультипарное переключение | Кнопка btnMultiToggle позволяет переключаться между режимами работы с одним символом и несколькими парами. В мультипарном режиме действия повторяются по всем выбранным парам; в одиночном режиме действия применяются только к символу графика. |
Возврат к символу на графике | Если включен мультипарный режим, но символ на графике не выбран, заголовок по-прежнему гарантирует, что сделка будет заключена по символу на графике. Это обеспечивает предсказуемые результаты для пользователей, сосредоточенных на своем графике. |
Координация компоновки: | Советник рассчитывает значение checkboxY таким образом, чтобы мультипарные флажки располагались аккуратно под холстом с прокруткой новостей. Здесь показано, как интегрировать без перекрытия сторонние панели пользовательского интерфейса с пользовательскими оверлеями индикаторов. |
Регистрация ошибок и ясность | Оба модуля выводят подробные логи (например, ошибки в разрешении символов, повторные коды размещения ордеров). Такая прослеживаемость помогает программистам и пользователям быстро диагностировать проблемы с конфигурацией. |
Модель двусторонней синхронизации | Состояния флажков инициализируются из pairSelected[] (советник в пользовательский интерфейс), и при нажатии они обновляют pairSelected[] (пользовательский интерфейс в советник). Этот непрерывный цикл гарантирует, что оба модуля используют одно и то же состояние выбора. |
Безопасное использование динамической памяти | Заголовок использует новый CCheckBox() для каждой пары и аккуратно удаляет их в Deinit(). Это учит программистов на MQL5 безопасному управлению объектами GUI в более длительно работающих советниках. |
Расширяемость за счет модульности | Инкапсулируя мультипарную торговлю в отдельный заголовок, один и тот же класс можно повторно использовать в нескольких советниках (например, в советнике «Заголовки новостей») без переписывания логики мультипарной торговли — масштабируемый паттерн для повторного использования кода. |
Содержимое вложения
Имя файла | Версия | Описание |
---|---|---|
News_Headline_EA.mq5 | 1.13 | Советник, интегрирующий события экономического календаря и заголовки новостей непосредственно на графике. Он управляет стоп-ордерами до события, сделками после события и отображает новости с прокруткой. Версия 1.13 расширяет его функциональность за счет поддержки мультипарной торговли с помощью модуля TradingButtons, обеспечивая исполнение мультипарных ордеров вручную наряду с автоматизированной торговлей на основе графиков событий. |
TradingButtons.mqh | 1 | Модульный заголовок, обеспечивающий мультипарный торговый интерфейс. Он создает кнопки для ордеров на покупку, продажу, закрытие, удаление и стоп-ордеров, а также флажки для выбора нескольких валютных пар. Включает в себя логику разрешения символов, помощников в размещении ордеров и возможность переключения между торговлей по одному символу и мультипаре. Предназначен для многократного использования в различных советниках, включая советник «Заголовки новостей». |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19008
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.




- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования