MQL5 Bootstrap (I): Набор переиспользуемых функций для работы с позициями и ордерами
Содержание
- Введение
- Проверка наличия позиций
- Счётчики позиций
- Закрытие позиций
- Получение информации о последней позиции
- Получение информации о самой старой позиции
- Пример разворотной стратегии на пересечении SMA
- Заключение
Введение
Случалось ли вам во время разработки снова и снова переписывать одни и те же функции? Если вы создали больше нескольких советников, скриптов или индикаторов, скорее всего, такое уже бывало.
В разных торговых системах одни и те же шаблонные задачи повторяются постоянно. Например, почти каждому советнику нужны мониторинг и управление торговыми операциями, трейлинг-стопы, перевод в безубыток, частичное закрытие, подсчёт позиций, проверка наличия ордеров, анализ истории сделок и надёжные методы закрытия позиций или удаления ордеров. Это не особенности конкретной стратегии, а структурная необходимость.

Хотя у каждого разработчика свой подход к написанию кода, повторное создание таких шаблонных функций в каждой новой программе неэффективно и контрпродуктивно по нескольким причинам:
- Повышенный риск ошибок. Каждый раз, когда вы переписываете одну и ту же логику, появляется риск внести новые несоответствия или трудноуловимые ошибки.
Например, трейлинг-стоп, который идеально работал в одном советнике, может немного иначе вести себя в другом из-за небольшой недоработки. Процедура закрытия позиции может работать у одного брокера, но давать сбой у другого из-за особенностей обработки режима исполнения. Повторная реализация одной и той же логики увеличивает вероятность мелких, но дорогостоящих ошибок. -
Несогласованное поведение в разных проектах.
Без общей базовой библиотеки каждый советник может немного по-своему обрабатывать проверку наличия позиций, отмену ордеров, расчёт рисков, логику закрытия позиций и другие операции. Со временем такая неоднородность усложняет отладку и делает сопровождение кода ещё более хаотичным.
-
Потеря времени на разработку. «Время, потраченное на переписывание шаблонного кода, — это время, не потраченное на улучшение логики стратегии, оптимизацию производительности или поиск новых решений».
Как разработчики, мы должны концентрироваться на проектировании стратегии и надёжности системы, а не на многократной реализации фильтров позиций или базовых торговых утилит.
-
Более сложное сопровождение. Если вы обнаружили ошибку в логике закрытия позиций и ранее скопировали этот код в 10 разных советников, теперь придётся исправлять 10 отдельных файлов.
Централизованный переиспользуемый модуль позволяет исправить проблему один раз и сразу применить это исправление везде.
-
Плохая масштабируемость. По мере роста и усложнения проектов дублированный код становится всё сложнее поддерживать.
- Снижение читаемости кода. Логика стратегии может оказаться скрытой под повторяющимся служебным кодом. Возможно, вы уже видели советник, настолько компактный и перегруженный, что в нём трудно найти саму торговую логику!
Когда базовые утилиты вынесены в переиспользуемые библиотеки и модули в папке include, наши MQL5-программы, например советники, становятся чище, понятнее и сосредоточены именно на торговой логике.
Переиспользуемый слой кода способствует более чистой архитектуре, разделению ответственности, лучшей организации проекта и более удобной совместной работе. Именно это я называю Bootstrap.
Идея вдохновлена Bootstrap (ранее Twitter Bootstrap). Это фронтенд-фреймворк для веб-разработчиков. Он получил огромную популярность благодаря идее предоставить набор готовых CSS- и JavaScript-компонентов, избавляющих от необходимости проектировать типовые элементы интерфейса с нуля.
В первой статье этой серии мы напишем и соберём переиспользуемые функции и код для работы с позициями и ордерами.
Проверка наличия позиций
В стратегиях, основанных на одном сигнале или на торговле только в одном направлении за раз, такие функции полезны: они помогают проверить наличие позиции перед открытием новой или предотвратить открытие дополнительных позиций.
//+------------------------------------------------------------------+ //| Returns true if a position exists filtered by | //| symbol, magic, type, or ticket | //+------------------------------------------------------------------+ bool PositionExists(const string symbol="", const long magic=LONG_MAX, const int type=-1, const long ticket=-1) { bool use_symbol = (symbol != ""); bool use_magic = (magic != LONG_MAX); bool use_type = (type != -1); bool use_ticket = (ticket != -1); CPositionInfo pos; for(int i = PositionsTotal()-1; i >= 0; --i) { if(!pos.SelectByIndex(i)) continue; if(use_symbol && pos.Symbol() != symbol) continue; if(use_magic && pos.Magic() != magic) continue; if(use_type && (int)pos.PositionType() != type) continue; if(use_ticket && pos.Ticket() != ticket) continue; //--- passed all active filters return true; } return false; }
Чтобы эту функцию можно было использовать в разных ситуациях, мы задаём четыре необязательных параметра: символ, magic-номер, тип позиции и тикет. Это позволяет фильтровать и проверять позиции по конкретным параметрам.
Все аргументы имеют значения по умолчанию, поэтому они являются необязательными.
Если все активные фильтры выполняются, функция возвращает true; в противном случае она возвращает false.
Чтобы упростить работу, создадим функции с той же задачей, но с более конкретными именами.
A: Проверка наличия позиции по magic-номеру
Magic-номера помогают различать торговые объекты (ордера и позиции), открытые другими советниками или стратегиями. Очень часто нужно проверить, существуют ли позиции с определённым magic-номером.
//+------------------------------------------------------------------+ //| Returns true if a position exists with a specified ticket number| //+------------------------------------------------------------------+ bool PositionExistsByMagic(int magic) { return PositionExists("", magic); }
B: Проверка наличия позиции по конкретному инструменту
Для операций, привязанных к символу, удобна следующая функция. Она проверяет, существует ли позиция по заданному символу (инструменту).
//+------------------------------------------------------------------+ //| Returns true if a position exists with a specified symbol | //+------------------------------------------------------------------+ bool PositionExistsBySymbol(string symbol) { return PositionExists(symbol); }
C: Проверка наличия позиции заданного типа
//+------------------------------------------------------------------+ //| Returns true if a position exists with a specified type | //+------------------------------------------------------------------+ bool PositionExistsByType(ENUM_POSITION_TYPE type) { return PositionExists("", LONG_MAX, type); }
Эта функция принимает аргумент типа позиции и использует его, чтобы проверить, существует ли позиция заданного типа (true) или нет (false).
D: Проверка наличия позиции с заданным тикетом
Хотя по сравнению с предыдущими вариантами эта функция используется реже, она полезна в торговых панелях для ручного закрытия сделок с конкретным тикетом.
//+------------------------------------------------------------------+ //| Returns true if a position exists with a specified ticket number| //+------------------------------------------------------------------+ bool PositionExistsByTicket(long ticket) { return PositionExists("", LONG_MAX, -1, ticket); }
Счётчики позиций
Счётчики позиций полезны почти в каждом советнике: они не только показывают количество активных позиций на рынке, но и могут использоваться для определения наличия позиции, как и рассмотренные выше методы.
//+------------------------------------------------------------------+ //| Counts positions filtered by symbol, magic, type, or ticket | //+------------------------------------------------------------------+ int PositionCount(const string symbol="", const long magic=LONG_MAX, const int type=-1, const long ticket=-1) { bool use_symbol = (symbol != ""); bool use_magic = (magic != LONG_MAX); bool use_type = (type != -1); bool use_ticket = (ticket != -1); CPositionInfo pos; int count = 0; for(int i = PositionsTotal()-1; i >= 0; --i) { if(!pos.SelectByIndex(i)) continue; if(use_symbol && pos.Symbol() != symbol) continue; if(use_magic && pos.Magic() != magic) continue; if(use_type && (int)pos.PositionType() != type) continue; if(use_ticket && pos.Ticket() != ticket) continue; //--- passed all active filters count++; } return count; }
В целом логика похожа. Однако вместо возврата true при обнаружении позиции функция подсчитывает позиции, увеличивая переменную count, а в конце возвращает итоговое значение.
Как и в предыдущих методах, введём несколько вариантов подсчёта позиций по конкретным атрибутам.
A: Подсчёт позиций с определённым magic-номером
//+------------------------------------------------------------------+ //| Counts positions with a specified magic number | //+------------------------------------------------------------------+ int PositionCountByMagic(int magic) { return PositionCount("", magic); }
B: Подсчёт позиций по конкретному инструменту
//+------------------------------------------------------------------+ //| Counts positions with a specified symbol | //+------------------------------------------------------------------+ int PositionCountBySymbol(string symbol) { return PositionCount(symbol); }
C: Подсчёт позиций конкретного типа
//+------------------------------------------------------------------+ //| Counts positions with a specified type | //+------------------------------------------------------------------+ int PositionCountByType(int type) { return PositionCount("", LONG_MAX, type); }
Закрытие позиций
Функция для немедленного закрытия позиций полезна, но не все позиции закрываются только по Stop Loss и Take Profit. Иногда по той или иной причине нужно закрыть одну или несколько позиций и выйти из рынка.
Примеры ситуаций, когда необходимо закрывать позиции:
- Перед новостями на валютных рынках, чтобы избежать высокой волатильности, возникающей во время выхода новости.
- При достижении лимитов риска, то есть когда значения баланса счёта, маржи или средств (equity) опускаются ниже определённого порога или допустимых значений.
//+------------------------------------------------------------------+ //| Closes positions filtered by symbol, magic, type, or ticket | //+------------------------------------------------------------------+ void PositionClose(const long deviation_points=LONG_MAX, const string symbol="", const long magic=LONG_MAX, const int type=-1, const long ticket=-1) { bool use_symbol = (symbol != ""); bool use_magic = (magic != LONG_MAX); bool use_type = (type != -1); bool use_ticket = (ticket != -1); CPositionInfo pos; CTrade trade; for(int i = PositionsTotal()-1; i >= 0; --i) { if(!pos.SelectByIndex(i)) continue; if(use_symbol && pos.Symbol() != symbol) continue; if(use_magic && pos.Magic() != magic) continue; if(use_type && (int)pos.PositionType() != type) continue; if(use_ticket && pos.Ticket() != (ulong)ticket) continue; const string sym = pos.Symbol(); //--- trade.SetExpertMagicNumber(magic); trade.SetTypeFillingBySymbol(pos.Symbol()); if (!trade.PositionClose(pos.Ticket(), deviation_points)) printf("Failed to close position #%I64u", pos.Ticket()); } }
Все аргументы необязательны: если фильтры не переданы, функция закрывает все позиции, доступные в терминале MetaTrader 5.
Чтобы упростить работу, нам нужны специализированные варианты этой функции.
| Функция | Описание |
|---|---|
void ClosePositionsBySymbol(string symbol, long deviation_points=LONG_MAX) | Закрывает позицию, используя символ в качестве фильтра. |
void ClosePositionsByMagic(int magic, long deviation_points=LONG_MAX) | Закрывает позицию с указанным magic-номером. |
void ClosePositionsByType(ENUM_POSITION_TYPE type, long deviation_points=LONG_MAX) | Закрывает позицию заданного типа. |
Дополнительно можно добавить функции для закрытия прибыльных и убыточных сделок соответственно.
| Функция | Описание |
|---|---|
void CloseProfitablePositions(const long deviation_points=LONG_MAX, const string symbol="", const long magic=LONG_MAX, const int type=-1, const long ticket=-1) | Закрывает все прибыльные позиции с заданными свойствами. |
void CloseLosingPositions(const long deviation_points=LONG_MAX, const string symbol="", const long magic=LONG_MAX, const int type=-1, const long ticket=-1) | Закрывает все убыточные позиции с заданными свойствами. |
Получение информации о последней позиции
Информация о последней позиции полезна для торговых систем и стратегий, которые опираются на данные о самой свежей сделке, чтобы определить, где и как следует разместить новую позицию или ордер.
Функции для таких задач применимы в следующих случаях:
- Пирамидинг или сеточные торговые стратегии. Как известно, в таких стратегиях расчёт цены открытия следующей позиции зависит от того, где была размещена предыдущая позиция.
- Стратегии, основанные на времени. Возможно, вы встречали или тестировали стратегии, которые закрывают сделки после того, как с момента последнего открытия позиции прошло определённое время не так ли?
- В управлении капиталом и расчёте лота для сеточных или полусеточных торговых стратегий.
//+------------------------------------------------------------------+ //| Returns the most recently opened position filtered by | //| symbol, magic, or type | //+------------------------------------------------------------------+ bool GetRecentPosition(CPositionInfo &pos_info, const string symbol="", const long magic=LONG_MAX, const int type=-1) { bool use_symbol = (symbol != ""); bool use_magic = (magic != LONG_MAX); bool use_type = (type != -1); ulong recent_ticket = 0; ulong last_time_msc = 0; CPositionInfo pos; for(int i = PositionsTotal()-1; i >= 0; --i) { if(!pos.SelectByIndex(i)) continue; if(use_symbol && pos.Symbol() != symbol) continue; if(use_magic && pos.Magic() != magic) continue; if(use_type && (int)pos.PositionType() != type) continue; //--- passed all active filters ulong t = pos.TimeMsc(); if (t > last_time_msc) { last_time_msc = t; recent_ticket = pos.Ticket(); } } if(recent_ticket == 0) return false; return pos_info.SelectByTicket(recent_ticket); //--- select the latest position using it's ticket }
В отличие от предыдущих функций, эта принимает объект CPositionInfo по ссылке. Этот объект заполняется данными о самой последней позиции.
Функция находит позицию с самой поздней временной меткой и сохраняет её тикет для использования после цикла. Используя тикет самой последней позиции, метод SelectByTicket заполняет переменную info.
Ниже приведена таблица с вариантами этой функции.
| Функция | Описание |
|---|---|
| GetRecentPositionBySymbol | Получает наиболее недавно открытую позицию, используя только её символ в качестве фильтра. |
| GetRecentPositionByMagic | Получает наиболее недавно открытую позицию, используя в качестве фильтра только её magic-номер. |
| GetRecentPositionByType | Получает наиболее недавно открытую позицию из терминала, используя только её тип в качестве фильтра. |
Получение информации о самой старой позиции
Хотя, на мой взгляд, это менее практично, чем получение самой последней позиции, всё же полезно иметь функцию, выполняющую обратную задачу.
Логика противоположна предыдущему варианту:
//+-------------------------------------------------------------------+ //|Returns the oldest open position filtered by symbol, magic, or type| //+-------------------------------------------------------------------+ bool GetOldestPosition(CPositionInfo &pos_info, const string symbol="", const long magic=LONG_MAX, const int type=-1) { bool use_symbol = (symbol != ""); bool use_magic = (magic != LONG_MAX); bool use_type = (type != -1); ulong oldest_ticket = 0; ulong oldest_time_msc = ULONG_MAX; CPositionInfo pos; for(int i = PositionsTotal()-1; i >= 0; --i) { if(!pos.SelectByIndex(i)) continue; if(use_symbol && pos.Symbol() != symbol) continue; if(use_magic && pos.Magic() != magic) continue; if(use_type && (int)pos.PositionType() != type) continue; //--- passed all active filters ulong t = pos.TimeMsc(); if (t < oldest_time_msc) { oldest_time_msc = t; oldest_ticket = pos.Ticket(); } } if(oldest_ticket == 0) return false; return pos_info.SelectByTicket(oldest_ticket); //--- select the oldest position using it's ticket }
Функция находит позицию с самым ранним временем открытия в миллисекундах. После нахождения она выбирается и присваивается переменной info, переданной по ссылке.
Ниже приведена таблица с вариантами этой функции.
| Функция | Описание |
|---|---|
| GetOldestPositionBySymbol | Получает самую старую позицию, используя только её символ в качестве фильтра. |
| GetOldestPositionByMagic | Получает самую старую позицию, используя в качестве фильтра только её magic-номер. |
| GetOldestPositionByType | Получает самую старую позицию из терминала, используя только её тип в качестве фильтра. |
Создадим простой советник и проверим на практике некоторые из рассмотренных функций.
Пример разворотной стратегии на пересечении SMA
В основе примера — две простые скользящие средние (SMA) с периодами 10 и 20. Когда происходит бычье пересечение, то есть SMA(10) пересекает SMA(20) снизу вверх, это бычий сигнал: следует открыть позицию на покупку, а позицию в противоположном направлении (позицию на продажу) закрыть.
И наоборот, когда SMA(10) пересекает SMA(20) сверху вниз, должна выполняться обратная операция.
Открывается позиция на продажу и закрывается противоположная позиция.
SMA crossover EA.mq5
#include <Bootstrap\positions.mqh> #include <Bootstrap\orders.mqh> #include <Trade\Trade.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; //+------------------------------------------------------------------+ input int magic_number = 14022026; input uint slippage = 100; input uint short_sma_period = 10; input uint long_sma_period = 20; //+------------------------------------------------------------------+ int short_handle, long_handle; double long_sma_buff[], short_sma_buff[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if(!m_symbol.Name(Symbol())) { printf("Failed to get symbolinfo, error = %d", GetLastError()); return INIT_FAILED; } //--- m_trade.SetExpertMagicNumber(magic_number); m_trade.SetDeviationInPoints(slippage); m_trade.SetTypeFillingBySymbol(Symbol()); //--- short_handle = iMA(Symbol(), Period(), short_sma_period, 0, MODE_SMA, PRICE_CLOSE); long_handle = iMA(Symbol(), Period(), long_sma_period, 0, MODE_SMA, PRICE_CLOSE); TesterHideIndicators(false); //ChartIndicatorAdd(0, 0, short_handle); //ChartIndicatorAdd(0, 0, long_handle); //--- ArraySetAsSeries(long_sma_buff, true); ArraySetAsSeries(short_sma_buff, true); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(!m_symbol.RefreshRates()) { printf("Failed to fetch latest tick information, error = %d", GetLastError()); return; } if(CopyBuffer(long_handle, 0, 0, 2, long_sma_buff) < 0) return; if(CopyBuffer(short_handle, 0, 0, 2, short_sma_buff) < 0) return; double curr_long_sma = long_sma_buff[0], prev_long_sma = long_sma_buff[1], curr_short_sma = short_sma_buff[0], prev_short_sma = short_sma_buff[1]; //--- long signal if(curr_short_sma > curr_long_sma && prev_short_sma < prev_long_sma) if(!PositionExists(Symbol(), magic_number, POSITION_TYPE_BUY)) { ClosePositions(slippage, Symbol(), magic_number, POSITION_TYPE_SELL); //close an opposite trade m_trade.Buy(m_symbol.LotsMin(), Symbol(), m_symbol.Ask()); } //--- short signal if(curr_short_sma < curr_long_sma && prev_short_sma > prev_long_sma) if(!PositionExists(Symbol(), magic_number, POSITION_TYPE_SELL)) { ClosePositions(slippage, Symbol(), magic_number, POSITION_TYPE_BUY); //close an opposite trade m_trade.Sell(m_symbol.LotsMin(), Symbol(), m_symbol.Bid()); } }
Результаты.

Заключение
Функции, показанные в этой статье, взяты из Bootstrap\positions.mqh(ссылка приведена во вложениях). Они покрывают самые распространённые задачи для открытых позиций: проверку наличия, счётчики, вспомогательные функции для группового закрытия позиций и утилиты для получения наиболее недавно открытой или самой старой позиции на основе времени открытия.
Поскольку названия и назначение этих помощников достаточно очевидны, я не стал расширять статью эквивалентами для отложенных ордеров . Тем не менее в проект входит отдельный модуль — Bootstrap\orders.mqh— который предоставляет переиспользуемые обёртки в том же стиле для работы с ордерами (отложенными ордерами), включая:
- CancelOrders, CancelOrdersByMagic, CancelOrdersBySymbol, CancelOrdersByTicket, CancelOrdersByType
- OldestOrder, RecentOrder
- OrderCount, OrderCountByMagic, OrderCountBySymbol, OrderCountByTicket, OrderCountByType
- OrderExists, OrderExistsByMagic, OrderExistsBySymbol, OrderExistsByTicket, OrderExistsByType
На практике разделение помощников на positions.mqh и orders.mqh соответствует тому, как MetaTrader 5 разделяет торговые объекты:
- Позиции представляют текущую чистую экспозицию по символу по символу (а на неттинговых счетах обычно одну позицию на символ).
- Ордера представляют инструкции (особенно отложенные ордера), которые могут исполниться и превратиться в сделки/позиции, а могут и не исполниться.
- Поэтому закрытие и отмена — разные операции: позиции закрываются торговыми запросами, а отложенные ордера удаляются/отменяются.
Эта библиотека намеренно не является заменой стандартной библиотеке MQL5. Многие задачи уже реализованы во встроенных торговых классах MetaQuotes (например, CTrade, CPositionInfo, COrderInfo, помощниках CHistory* и т. д.), и их дублирование увеличило бы затраты на сопровождение без дополнительной пользы. Вместо этого цель — предоставить тонкий переиспользуемый слой, который стандартизирует повторяющиеся части, которые приходится постоянно переписывать: фильтрацию по символу, magic-номеру, типу позиции, тикету, а также единообразное выполнение массовых операций.
Главная мысль в том, что эти помощники — это архитектурный шаблон и базовый фундамент — слой bootstrap. Его стоит расширять утилитами, специфичными для конкретного проекта, такими как:
- лимиты риска и экспозиции (максимум позиций на символ/magic-номер, максимальный объём, правила максимальной просадки),
- процедуры частичного закрытия,
- механизмы безубытка и трейлинг-стопа,
- фильтры времени нахождения в сделке,
- защита по спреду/проскальзыванию и обработка режима исполнения,
- аналитика истории/сделок для более взвешенных решений по исполнению.
Включённый файл "Experts\SMA crossover EA.mq5" — это минимальный тестовый советник, который демонстрирует, как помощники позволяют коду стратегии сосредоточиться на сигналах (пересечении SMA), а библиотека берёт на себя повторяющуюся повторяющуюся торговую обвязку: проверку, подсчёт, закрытие и выбор позиций.
С наилучшими пожеланиями.
Таблица вложений
| Название файла | Описание и использование |
|---|---|
| Bootstrap\orders.mqh | Содержит служебные функции для работы с отложенными ордерами. |
| Bootstrap\positions.mqh | Содержит служебные функции для работы с позициями. |
| Experts\SMA crossover EA.mq5 | Советник для тестирования функций, рассмотренных в статье, на простой торговой стратегии пересечения скользящих средних. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21398
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Алгоритм бактериальной эволюции — Bacterial Evolutionary Algorithm (BEA)
Создание пользовательских индикаторов в MQL5 (Часть 5): Доработка WaveTrend Crossover с помощью Canvas — градиентный туман, сигнальные пузырьки и управление рисками
Неопределённость как модель (Часть 7): Стохастические регрессоры
Моделирование рынка: Position View (VIII)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ознакомьтесь с новой статьей: «MQL5 Bootstrap (I): многократно используемые функции для работы с позициями и ордерами».
Автор: Омега Дж. Мсигва