Событийная архитектура в MQL5: как превратить советник в полноценную торговую систему
Введение
При разработке советника в MQL5 многие начинают с самого очевидного решения: складывают всю логику в метод OnTick. Так действительно проще стартовать. Но у такого подхода есть скрытая цена. По мере роста проекта в одном обработчике смешиваются торговые правила, проверка условий, работа с ордерами, обновление данных, интерфейс, расчёты и логирование. В итоге код разрастается до состояния, когда он уже не программируется, а удерживается на честном слове. Любое изменение в одном месте начинает затрагивать совсем другие части системы. Исправляешь визуальную панель — внезапно ломается торговый сценарий. Меняешь фильтр входа — всплывает ошибка в фоновой проверке. Такой советник быстро превращается в хрупкий монолит, где сложность растёт быстрее, чем уверенность разработчика.
Платформа MetaTrader 5 устроена гораздо интереснее, чем просто поток котировок. Она живёт событиями. В терминале постоянно возникают тики, сигналы таймера, действия пользователя, изменения торгового состояния и события стакана. Эти сообщения следует обрабатывать раздельно. Для этого в MQL5 предусмотрены разные обработчики, каждый со своей зоной ответственности. OnTick отвечает за рыночные обновления. OnTimer — за периодические и фоновые задачи. OnChartEvent — за реакцию на графический интерфейс и пользовательские действия. Когда логика распределена по назначению, код перестаёт быть перегруженным. Он становится ближе к нормальной инженерной системе, где каждый модуль делает своё дело и не мешает соседям.
Такой стиль проектирования особенно важен, когда советник выходит за рамки одного символа и начинает выполнять несколько функций одновременно. Нужно следить за рынком, поддерживать интерфейс, реагировать на нажатия кнопок, синхронизировать внутреннее состояние, передавать сигналы между компонентами, а иногда ещё и обслуживать несколько инструментов. В этой точке событийная архитектура уже не выглядит красивой теорией. Она становится практической необходимостью. Если вынести фоновую работу в OnTimer, а реакцию на действия пользователя — в OnChartEvent, основной торговый контур освобождается от лишней нагрузки. Это делает поведение системы предсказуемее и заметно упрощает сопровождение.
Отдельную роль в такой архитектуре играют пользовательские события и сервисы. Пользовательские события позволяют организовать внутреннюю шину сообщений между модулями, а сервисы — вынести вспомогательную логику за пределы конкретного графика. Это уже не просто советник, а система компонентов, которые могут обмениваться командами и сигналами, не смешивая всё в одну функцию. В результате появляется возможность строить решения уровня торгового приложения: с панелями управления, фоновой аналитикой, межмодульным обменом и чётким разделением ролей.
Кроме того, событийный подход заметно улучшает тестируемость. Когда логика разнесена по обработчикам, её проще проверять по частям. Можно отдельно анализировать реакцию на таймер, отдельно — на UI-события, отдельно — на торговые транзакции. Это особенно важно в задачах, где ошибка может стоить времени и денег. Чем лучше структура, тем легче контролировать поведение системы и быстрее находить причины сбоев.
В этой статье мы разберём, как перейти от модели всё в OnTick к более зрелой событийной архитектуре. Разберём роли предопределённых обработчиков и пользовательских событий, а также сервисы без привязки к графику. Отдельно рассмотрим типовые ошибки, которые ломают архитектуру ещё до реальной работы. Главная мысль здесь проста: когда MQL5 используется по назначению, он позволяет строить не только торговых роботов, но и полноценные прикладные системы.

Предопределённые события
Предопределённые события в MQL5 — это каркас, на котором держится вся логика программы. Это не просто набор функций-обработчиков, а строго определённая модель реакции на изменения среды. И чем раньше разработчик перестаёт воспринимать их как дополнение к OnTick, тем быстрее код начинает приобретать архитектуру.
Жизненный цикл любой программы начинается с OnInit и завершается в OnDeinit. Это своего рода точки входа и выхода. В OnInit закладывается фундамент: настраиваются таймеры через EventSetTimer, создаются графические объекты, инициализируются внутренние структуры и внешние компоненты. Здесь же удобно запускать сервисные механизмы и подготавливать окружение.
int OnInit() { EventSetTimer(30); // опрашивать каждые 30 секунд return(INIT_SUCCEEDED); }
OnDeinit, в свою очередь, требует дисциплины. Всё, что было создано, должно быть корректно освобождено: удаляются объекты, отключается таймер через EventKillTimer, закрываются ресурсы. Игнорирование этой симметрии — классическая ошибка, которая со временем приводит к загрязнению среды и трудноуловимым сбоям.
void OnDeinit(const int reason) { EventKillTimer(); // выключаем таймер при выгрузке }
OnTick традиционно воспринимается как сердце эксперта. И это справедливо, но только частично. Его задача — реакция на рыночные изменения конкретного символа. Здесь уместны расчёты сигналов, проверка условий входа и управление позициями. Однако принципиальный момент: наличие OnTick не является обязательным. Если логика вынесена в другие события, эксперт может работать без него. Более того, в зрелой архитектуре OnTick часто становится лёгким маршрутизатором, а не центром вселенной.
OnTimer — инструмент, который многие недооценивают. Терминал генерирует эти события с заданным интервалом, что позволяет отделить фоновые задачи от рыночных. Сбор статистики, обновление кэшей, опрос нескольких символов, пересчёт индикаторов — всё это логично переносить в таймер. Такой подход разгружает OnTick и делает поведение системы более стабильным, особенно в периоды высокой волатильности. Важно помнить простое правило: таймер должен быть не только установлен в OnInit, но и обязательно снят в OnDeinit.
OnChartEvent открывает дверь в мир интерактивных приложений. Это обработчик событий графического интерфейса: клики мышью, нажатия клавиш, взаимодействие с объектами и, что особенно важно, пользовательские события. Через него строятся панели управления, кнопки, переключатели режимов. Здесь же реализуется реакция на внешние сигналы, приходящие от других программ. В контексте событийной архитектуры это уже не просто UI-обработчик, а полноценный канал коммуникации.
OnTradeTransaction — это точка контроля торгового состояния. В отличие от упрощённых подходов, где проверяется только результат операции, здесь доступна полная структура MqlTradeTransaction. Это позволяет точно отслеживать жизненный цикл ордера: от отправки до исполнения и последующих модификаций. Такой уровень детализации особенно важен при сложных сценариях управления позициями, где важна не только цель, но и путь к ней.
OnBookEvent используется реже, но в определённых задачах незаменим. Он реагирует на изменения стакана цен и активируется через MarketBookAdd. Это уже территория более тонких стратегий, где важна не только цена, но и структура ликвидности. В высокочастотных подходах или при анализе микро-движений рынка этот обработчик становится ключевым источником сигналов.
Ключевой вывод здесь прост: каждый тип программы в MQL5 получает свой набор событий, и именно через них формируется её поведение. Попытка игнорировать эту модель и свести всё к одному обработчику неизбежно ведёт к усложнению. Напротив, грамотное распределение логики по событиям создаёт чёткую, предсказуемую и масштабируемую систему — ту самую основу, без которой невозможно двигаться дальше.
Пользовательские события
Пользовательские события в MQL5 — это один из самых практичных инструментов для построения событийной архитектуры. С их помощью программа перестаёт быть замкнутой в себе и получает возможность передавать сигналы между своими частями так же естественно, как узлы одной системы обмениваются служебными сообщениями. Для этого служит функция EventChartCustom, которая позволяет программно отправить собственное событие на указанный график, а затем обработать его в OnChartEvent. Это внутренняя шина сообщений. Она особенно ценна там, где одна программа должна реагировать на рынок, координировать работу интерфейса, сервисных модулей и торговой логики.
Главное преимущество такого подхода состоит в том, что он помогает разделять ответственность между модулями. Один компонент может заниматься анализом рынка. Другой — отображением состояния на панели. Третий — исполнением торговых действий. Четвёртый — обслуживанием фоновых расчётов. Если попытаться связать всё это прямыми вызовами и общими глобальными переменными, код быстро начнёт расползаться. Сопровождение станет похожим на ремонт механизма, в котором каждая шестерёнка зацеплена за все остальные. Пользовательские события позволяют избежать этой путаницы: модуль не вмешивается в чужую внутреннюю логику, а лишь отправляет аккуратный сигнал туда, где его должны принять.
Отдельно стоит отметить возможность адресной передачи событий. В EventChartCustom можно указать не только текущий график, но и chartID другого окна. Это открывает путь к коммуникации между разными экземплярами экспертов, индикаторов и сервисов. Такой механизм особенно полезен в мультисимвольных и многографиковых системах. Один компонент собирает или генерирует информацию, а другой — принимает её и выполняет действие. В этом случае пользовательское событие становится связующим звеном, которое объединяет разрозненные части в единую архитектуру.
При этом сама идея событийного обмена особенно хорошо проявляется на этапе инициализации. Иногда OnInit приходится перегружать подготовительными действиями: созданием внутренних объектов, загрузкой данных, построением кэшей, настройкой интерфейса, запуском сервисов. Если всё это выполнять сразу внутри инициализации, метод становится тяжёлым и начинает тормозить старт программы. Более того, если инициализация затягивается, возникает риск упереться в ограничение по времени или просто получить медленный, неуклюжий запуск. Гораздо разумнее оставить в OnInit только минимальную подготовку и сразу создать пользовательское событие, которое перенесёт трудоёмкую часть в отдельный обработчик. Тогда программа стартует быстро, а основная подготовка выполняется уже в событийном режиме.
Это важно ещё и потому, что в MQL5 длительная работа функции блокирует обработку других событий. Последовательный вызов нескольких небольших функций подряд проблему не снимает: пока цепочка не завершится, в поток событий ничего не сможет вклиниться. Это особенно заметно в программах с пользовательским интерфейсом. Пользователь взаимодействует с панелью и ожидает реакции, но программа не отвечает, потому что выполняет длинный сценарий. Возникает ощущение зависания, хотя фактически система просто не успевает отдать управление обработчику событий. Именно поэтому событийная модель здесь архитектурно необходима. Она позволяет передать оркестрацию обработчику событий, который запускает шаги последовательно, но не блокирует интерфейс и не лишает программу отзывчивости.
У этой гибкости есть оборотная сторона: пользовательские события нельзя отправлять без системы. Здесь нужен чёткий протокол: какие идентификаторы что означают, где хранится код сообщения, как определить принадлежность события именно вашему приложению. Обычно для этого задают собственные диапазоны ID, используют понятные метки в строковых параметрах и обязательно проверяют, что событие действительно предназначено этому модулю. Иначе можно легко поймать чужой сигнал и устроить в логике настоящий архитектурный переполох.
Есть и ещё одно правило, которое часто недооценивают: не стоит создавать события слишком часто без реальной необходимости. Очередь событий терминала не любит лишнего шума. Если отправлять события слишком часто, очередь сообщений перегружается, а обработка сигналов деградирует. Лучше передавать только значимые состояния: факт готовности данных, появление сигнала, команду на обновление интерфейса, подтверждение выполнения действия. Тогда события будут работать как аккуратные служебные записки, а не как беспорядочный поток телеграмм.
Именно поэтому пользовательские события особенно ценны в зрелой системе. Они связывают модули без жёсткой сцепки, позволяют строить понятные протоколы взаимодействия и делают MQL5 средой для полноценной событийной разработки.
Сервисы
Сервис в MQL5 — это фоновая служба терминала. Без графика, без привязки к символу, без ожидания тика. Запустили — и он работает сам по себе, как аккуратный дежурный, которому не нужно напоминать, что делать.
Вся логика живёт в функции OnStart. Чаще всего внутри организуют цикл: проверка состояния, выполнение задачи, пауза через Sleep, затем следующий шаг. Такой ритм прост и надёжен. Не требует событий рынка, не зависит от активности графика. Сервис сам задаёт себе темп.
Практическая польза быстро становится очевидной. Нужно регулярно подгружать данные из внешнего источника — сервис справится. Нужно генерировать тики для пользовательского инструмента — тоже его задача. Нужно раз в несколько секунд отправлять сигналы или обновлять состояние системы — опять же, это работа службы. Всё, что повторяется, всё, что не должно зависеть от рыночного шума, логично выносить сюда.
Есть ещё одна деталь, которая делает сервис особенно удобным. Если его не остановить вручную, он автоматически поднимется при следующем запуске терминала. Это превращает его в инструмент для рутинных операций: проверка состояния при старте, подготовка данных, синхронизация — всё это можно выполнять без участия пользователя. Один раз настроили — и служба работает каждый день, как часы.
В архитектуре это выглядит очень здраво. Эксперт на графике реагирует на рынок. Интерфейс — на пользователя. А сервис делает спокойную, регулярную работу в фоне. Без суеты, без перегрузки OnTick. Такое разделение ролей избавляет систему от лишнего напряжения и делает её устойчивее. Когда каждая часть занимается своим делом, вся конструкция начинает работать заметно чище.
Коммуникации между программами
Когда торговое решение состоит из нескольких компонентов, им нужен механизм обмена сообщениями. Один модуль может собирать данные. Второй — показывать их на панели. Третий — принимать торговое решение. Четвёртый — обслуживать фоновую логику. И если каждый из них начнёт жить в собственной вселенной, получится не архитектура, а клубок из флагов, глобальных переменных и случайных проверок. Поэтому здесь особенно хорошо работает событийная схема.
Чистый вариант событийной архитектуры — когда источником сигналов становятся индикаторы. Но не в привычном режиме спросили буфер — получили значение, а как полноценные генераторы событий.
Идея простая. Каждый индикатор оборачивается в лёгкий адаптер. Внутри он делает ровно одну вещь: отслеживает момент появления сигнала и, если условие выполнено, отправляет пользовательское событие. Всё. Никаких постоянных запросов извне, никакого лишнего шума.
int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { if(prev_calculated==rates_total) return prev_calculated; //--- if(BarsCalculated(handle) < rates_total) return(prev_calculated); vector<double> sig, main; if(!main.CopyIndicatorBuffer(handle, 0, 1, 2) || !sig.CopyIndicatorBuffer(handle, 1, 1, 2)) return(prev_calculated); if(sig[0] > main[0] && sig[1] <= main[1]) { if(!EventChartCustom(ChartID(), BuyID, MagicNumber, main[0], IndComment)) return(prev_calculated); } if(sig[0] < main[0] && sig[1] >= main[1]) { if(!EventChartCustom(ChartID(), SellID, MagicNumber, main[0], IndComment)) return(prev_calculated); } //--- return value of prev_calculated for next call return(rates_total); }
Для примера возьмем два классических индикатора — MACD и RSI. Каждый из них работает независимо на своём таймфрейме. Они обрабатывают только новый бар. MACD проверяет пересечение гистограммы и сигнальной линии. RSI — пересечение уровней перекупленности и перепроданности. В момент возникновения сигнала они формируют предварительно заданные пользовательские события. Это принципиальный момент: индикатор не хранит сигнал в буфере, он фиксирует факт события.
Дальше вступает в работу эксперт, но уже в совершенно иной роли. Он не опрашивает индикаторы на каждом тике. Он не перебирает историю, не синхронизирует буферы, не пытается угадать, был ли сигнал. Он просто слушает OnChartEvent.
void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { //--- if(lparam != MagicNumber) return; switch(id - CHARTEVENT_CUSTOM) { case MACD1Buy: indSignals[0] = 1; break; case MACD1Sell: indSignals[0] = -1; break; case MACD2Buy: indSignals[1] = 1; CloseSellSignal = true; CloseBuySignal = false; break; case MACD2Sell: indSignals[1] = -1; CloseBuySignal = true; CloseSellSignal = false; break; case RSI1CrossOverBoughtDown: indSignals[2] = -1; break; case RSI1CrossOverSoldUp: indSignals[2] = 1; break; case RSI2CrossOverBoughtDown: indSignals[3] = -1; CloseBuySignal = true; CloseSellSignal = false; break; case RSI2CrossOverSoldUp: indSignals[3] = 1; CloseSellSignal = true; CloseBuySignal = false; break; } BuySignal = true; SellSignal = true; for(uint i = 0; i < indSignals.Size() && (BuySignal || SellSignal); i++) { BuySignal = BuySignal && (indSignals[i] == 1); SellSignal = SellSignal && (indSignals[i] == -1); } }
Пришло событие — отметил. Не пришло — ничего не делает.
Это резко меняет характер нагрузки. В классической схеме с опросом индикаторов на каждом тике происходит одно и то же: запрос буферов, проверка значений. Иногда — прогон по истории, чтобы не пропустить момент сигнала. Особенно тяжело это становится при нескольких индикаторах и разных таймфреймах. Между сигналами всегда есть временной разрыв, и чтобы его поймать, приходится постоянно смотреть назад.
Событийная модель этот избыточный труд просто убирает. Каждый сигнал — это уже готовое событие. Его не нужно искать, пересчитывать или восстанавливать задним числом. Он приходит в момент возникновения.
В результате получается аккуратная и быстрая схема:
- индикатор отвечает только за свой расчёт и свой сигнал;
- событие фиксирует момент изменения состояния;
- эксперт агрегирует сигналы и принимает решение.
Особенно хорошо это видно в мультииндикаторных стратегиях. Например, один сигнал пришёл с M5, другой — с M30. В классической модели приходится синхронизировать данные, учитывать лаги, проверять историю. Здесь же всё проще: каждый индикатор отметился в нужный момент, эксперт сохранил состояние и дождался полного набора условий.
С точки зрения производительности это даёт ощутимый выигрыш. Нет постоянных обращений к индикаторам, нет пустых проверок на каждом тике. OnTick становится лёгким и быстрым, а значит реакция на рынок — быстрее. В торговле это не академическая деталь, а вполне прикладное преимущество: меньше задержек, чище логика, точнее исполнение.
void OnTick() { //--- if(BuySignal) { cSymbol.Refresh(); cSymbol.RefreshRates(); if(!cTrade.Buy(InpLot, cSymbol.Name(), cSymbol.Ask(), cSymbol.Ask() - SL * cSymbol.Point(), cSymbol.Ask() + TP * cSymbol.Point(), "Event Example")) { PrintFormat("Error open Buy position: %d", GetLastError()); return; } BuySignal = false; ArrayFill(indSignals, 0, indSignals.Size(), 0); } //--- if(SellSignal) { cSymbol.Refresh(); cSymbol.RefreshRates(); if(!cTrade.Sell(InpLot, cSymbol.Name(), cSymbol.Bid(), cSymbol.Bid() + SL * cSymbol.Point(), cSymbol.Bid() - TP * cSymbol.Point(), "Event Example")) { PrintFormat("Error open Sell position: %d", GetLastError()); return; } SellSignal = false; ArrayFill(indSignals, 0, indSignals.Size(), 0); } //--- if(CloseBuySignal) { if(cPosition.SelectByMagic(cSymbol.Name(), MagicNumber) && cPosition.PositionType() == POSITION_TYPE_BUY) { if(!cTrade.PositionClose(cPosition.Ticket())) { PrintFormat("Error close Buy position: %d", GetLastError()); return; } } } //--- if(CloseSellSignal) { if(cPosition.SelectByMagic(cSymbol.Name(), MagicNumber) && cPosition.PositionType() == POSITION_TYPE_SELL) { if(!cTrade.PositionClose(cPosition.Ticket())) { PrintFormat("Error close Sell position: %d", GetLastError()); return; } } } //--- }
И что не менее важно — код становится прозрачнее. Сигнал перестаёт быть значением в буфере, которое нужно интерпретировать, и превращается в чёткое событие: произошло — зафиксировали — обработали. Это именно тот случай, когда архитектура напрямую улучшает и читаемость, и поведение системы.
Прогон советника в тестере стратегий MetaTrader 5 показал любопытную картину. Перед нами система с характером — зарабатывает активно, но при этом держит риск в разумных рамках.

При депозите 1000.0 USD результат составил 2427.52 USD, то есть около +143% за 3 года. Результат сильный, особенно с учётом умеренной просадки. Кривая капитала выглядит аккуратно: рост ступенчатый, откаты есть, но без затяжных провалов. Максимальная просадка держится в районе 12–13%, а Recovery Factor около 4.6 — система умеет выходить из просадок, не залипая в них. Это важный признак устойчивости.
Механика прибыли классическая и рабочая. Всего 18 сделок, при этом winrate 50%. Но средняя прибыль почти в 4 раза больше среднего убытка. За счёт этого достигается Profit Factor на уровне 3.9. Стратегия зарабатывает не частотой, а качеством сигналов и удержанием позиции. Но здесь же кроется и ограничение. Малое количество сделок делает статистику хрупкой.
Кривая капитала при этом достаточно ровная, без хаотичных скачков. Это косвенно подтверждает, что сигналы не случайны.
И здесь важный архитектурный момент. Событийная модель убирает постоянный опрос индикаторов. Эксперт реагирует только на факт сигнала. Меньше лишних операций — быстрее реакция. В тестере это нюанс, в реальной торговле — преимущество.
Итог простой. Перед нами сигнальная модель с хорошим соотношением риск/прибыль и грамотной архитектурой. Но пока это прототип. Нужна проверка вне выборки и расширение статистики.
Типовые ошибки
Когда архитектура начинает опираться на события, код становится чище и быстрее. Но вместе с этим меняется и характер ошибок. Они уже не лежат на поверхности, а проявляются в связях между обработчиками и в дисциплине работы с ресурсами.
Первая и самая живая проблема — всё тот же перегруженный OnTick. Даже после знакомства с событийной моделью возникает соблазн оставить на всякий случай часть логики именно там. В результате получается гибрид: вроде бы есть таймеры и события, но основной объём по-прежнему тянет один метод. Такой компромисс быстро возвращает нас к исходной точке — тяжёлому, плохо поддерживаемому коду. Практика показывает: если уж разделили роли, нужно доводить это до конца.
Следующий слой ошибок связан с управлением ресурсами. Событийная модель предполагает больше сущностей: таймеры, объекты, подписки, вспомогательные структуры. И если ими не управлять аккуратно, система начинает шуметь. Забытый EventKillTimer, не удалённые графические элементы, оставленные глобальные переменные — всё это не ломает программу сразу, но постепенно снижает её предсказуемость. В итоге получаем классическую ситуацию: ошибка проявляется не там, где её допустили.
void OnDeinit(const int reason) { //--- for(uint i = 0; i < handles.Size(); i++) if(handles[i] != INVALID_HANDLE) IndicatorRelease(handles[i]); }
Отдельного внимания требует сама природа событий. Обработчики выполняются последовательно, и длинная операция внутри одного события блокирует остальные. Это особенно заметно в интерфейсе: пользователь взаимодействует с панелью, а программа не отвечает. Она занята расчётами. Здесь снова проявляется ключевая идея архитектуры — дробить выполнение на этапы и не держать поток занятым дольше, чем это действительно необходимо.
Когда в системе появляется несколько источников событий — индикаторы, панель, сервис — на первый план выходит дисциплина идентификации. Если не ввести чёткие соглашения об именах объектов и кодах событий, начинается путаница: сигналы пересекаются, обработчики реагируют не на своё. Простое правило с префиксами и диапазонами ID выглядит почти банально, но именно оно удерживает систему в порядке, когда она начинает расти.
#define MACD1Buy 1 #define MACD1Sell 2 #define MACD2Buy 3 #define MACD2Sell 4 #define RSI1CrossOverBoughtUp 5 #define RSI1CrossOverBoughtDown 6 #define RSI1CrossOverSoldUp 7 #define RSI1CrossOverSoldDown 8 #define RSI2CrossOverBoughtUp 9 #define RSI2CrossOverBoughtDown 10 #define RSI2CrossOverSoldUp 11 #define RSI2CrossOverSoldDown 12
И, наконец, самый коварный момент — старт программы. В событийной модели события могут приходить практически сразу, ещё до того, как полностью завершена инициализация. Если в этот момент структуры не готовы, легко получить обращение к пустым данным или некорректному состоянию. Поэтому инициализация либо должна быть строго завершённой до начала обработки, либо — что часто удобнее — разбита на этапы с контролем готовности через те же пользовательские события.
В итоге видно, что сами по себе события не делают систему надёжной. Они лишь дают инструменты. Надёжность появляется тогда, когда к этим инструментам добавляется инженерная дисциплина. И если она есть, событийная архитектура перестаёт быть просто удобным приёмом — она становится основой устойчивой и предсказуемой торговой системы.
Заключение
Когда логика программы строится на событиях, платформа перестаёт быть просто средой для советников и превращается в основу для полноценных прикладных решений. В такой модели программирование уже напоминает разработку классического десктопного приложения: появляется интерфейс с кнопками и панелями, запускаются фоновые службы, создаются отдельные обработчики для разных типов событий. Всё разнесено по своим местам, и именно поэтому система получается заметно устойчивее в сопровождении.
Событийный подход особенно ценен там, где обычный советник уже не справляется с ролью одного файла на все случаи жизни. Он позволяет строить мультисимвольных ассистентов, гибкие панели управления и комплексные контроллеры, которые реагируют на смысловые изменения состояния. Новую функциональность в такую архитектуру добавлять проще: достаточно ввести ещё одно событие или ещё один обработчик, не перепахивая весь код заново.
В этом и состоит главное преимущество событийной архитектуры: она даёт порядок без потери гибкости. Правильное разделение на обработчики, сервисы и каналы коммуникации позволяет создавать надёжные, расширяемые и по-настоящему живые торговые системы. И именно здесь MQL5 раскрывается шире привычного формата советника — как среда для сложных торговых приложений, где важна не только реакция на рынок, но и зрелая инженерная организация всей программы.
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | EventExample.mq5 | Советник | Советник обработки событий |
| 2 | EventMACD.mq5 | Индикатор | Индикатор MACD с генерацией событий при пересечении линий |
| 3 | EventRSI.mq5 | Индикатор | Индикатор RSI с генерацией событий при пересечении уровней |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Особенности написания Пользовательских Индикаторов
Нейросети в трейдинге: Оценка риска по несогласованности представлений (Основные компоненты)
Разработка инструментария для анализа Price Action (Часть 37): Индикатор смещения настроений
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Работать в советнике с индикаторами без буферов - наверное, для примера пойдет.
В примере делается строгое обновление данных символа (хорошо бы использовать MQL_TESTER).
Но не делается проверка на актуальность вычисленного через события сигнала и тика. И это реальная проблема.
Ее можно ослабить через асинхронные OrderSend, но не решить. Поэтому даже в таком примере нужно дополнительно в ChartEvent-событии передавать данные тика, на котором произошла генерация события.