
Передовые алгоритмы исполнения ордеров на MQL5: TWAP, VWAP и ордера Iceberg
- Введение
- Ознакомление с алгоритмами исполнения
- Реализация средствами MQL5
- Реализация анализатора эффективности
- Сравнение эффективности алгоритмов
- Интеграция алгоритмов исполнения с торговыми стратегиями
- Примеры интеграции
- Интегрированная стратегия
- Результаты тестирования на истории
- Заключение
Введение
Представьте, что вы стоите на краю торгового зала и ваше сердце колотится, когда вы следите за изменениями цен в реальном времени. Одно неверное движение, один слишком большой ордер — и ваше преимущество испарится в мгновение ока. Добро пожаловать в мир, где качество исполнения — это не просто приятный бонус, а секретное оружие, отделяющее победителей от остальных.
На протяжении десятилетий институциональные тяжеловесы тихо использовали сложные алгоритмы, чтобы разрезать, фильтровать и скрытно развертывать свои ордера, чтобы избегать проскальзывания и сдерживать влияние рынка. Теперь, благодаря гибкости MQL5, этот же эффективный набор сценариев доступен каждому амбициозному розничному трейдеру.
В чем же дело?
Представьте себе: вы замечаете прекрасную возможность и решаете пойти по крупному пути. Вы размещаете рыночный ордер на полную сумму, но только для того, чтобы наблюдать, как цена падает под тяжестью вашей собственной сделки. За считанные секунды ваш идеальный вход становится шатким компромиссом. Это печально известный тормоз рыночного влияния, и он кусается даже на самых ликвидных площадках.
Алгоритмы исполнения — ваше противоядие. Разбивая большой заказ на последовательность более мелких, стратегически рассчитанных по времени частей, они сглаживают ваш след в книге ордеров. Каков результат? Меньше проскальзывания, более плотное исполнение и общее улучшение средней цены исполнения.
Из башен из слоновой кости на ваш рабочий стол
«Конечно», — можете вы пожать плечами, — «но я не перемещаю институциональные суммы». Вот в чем загвоздка: вам это делать не обязательно. Независимо от того, используете ли вы половину лота или несколько мини-лотов, волатильность все равно может исказить ваше исполнение. Эти инструменты помогут вам:
- Укрощение проскальзывания: Даже скромные ордера могут колебаться на нестабильных рынках.
- Отточите своё преимущество: Многоуровневые исполнения часто дают вам более выгодную среднюю цену, чем одноразовая игра.
- Оставайтесь в дзен-состоянии: Автоматизированные рабочие процессы исключают соблазн панически покупать или продавать.
- Масштабируйте плавно: По мере роста вашего счета исполнение остается четким, независимо от того, насколько объемными становятся ваши ордера.
-
Оставайтесь незамеченными: В частности, ордера Iceberg скрывают истинный размер вашего ордера, заставляя любопытные алгоритмы гадать.
Сегодняшняя демократизированная среда означает, что те же технологии исполнения, которые когда-то требовали многомиллионных бюджетов, теперь могут работать на вашей персональной торговой станции. Добавив в свою платформу отточенный код на MQL5 для стратегий TWAP, VWAP и Iceberg, вы вооружитесь институциональной огневой мощью, не покидая при этом сферу розничной торговли.
Приготовьтесь включить сценарий в свой процесс исполнения. Игра меняется и с этими алгоритмами в своём арсенале вы будете играть на победу.
Ознакомление с алгоритмами исполнения
Прежде чем углубляться в детали реализации, важно понять теорию, лежащую в основе каждого алгоритма исполнения, и понять, почему они эффективны в различных рыночных сценариях.
- Средневзвешенная по времени цена (Time-Weighted Average Price (TWAP): TWAP — это простой алгоритм исполнения, который делит большой ордер на равные части и отправляет их через фиксированные интервалы времени в течение заданного периода. Его цель — соответствовать средней цене инструмента за этот период.
-
Как это работает:
- Отправляет ордера через регулярные промежутки времени между началом и окончанием.
- Обычно использует заказы одинакового размера (хотя вы можете добавить размерам произвольность).
- Следует заранее определенной временной диаграмме, независимо от движения цен.
-
Равномерно распределяет влияние рынка во времени, сводя проскальзывание к минимуму.
-
Когда использовать:
- Вам нужна средняя цена исполнения за определенный таймфрейм.
- Ликвидность стабильна на протяжении всего торгового периода.
- У вас есть фиксированное время для выполнения своего ордера.
-
Вы предпочитаете простой и предсказуемый подход.
-
- Средневзвешенная цена по объёму Volume-Weighted Average Price (VWAP): VWAP улучшает TWAP за счет взвешивания размеров ордеров в соответствии с ожидаемым объемом. Вместо равных частей он отправляет более крупные сделки, когда объем стремится к росту.
-
Как это работает:
- Распределяет размер ордера пропорционально историческим паттернам объема.
- Анализирует прошлые объемы торговли для прогнозирования будущего распределения объемов.
- В некоторых реализациях может адаптироваться к изменениям объема в реальном времени.
-
Выполняется чаще в периоды пиковой нагрузки по объему для снижения воздействия.
-
Когда использовать:
- Ваша эффективность оценивается по VWAP.
- Объем следует прогнозируемому дневному паттерну.
- Вы торгуете на рынке, где ликвидность меняется в течение сессии.
-
Вы хотите соответствовать естественному течению рынка.
-
- Ордера Iceberg: Ордера типа Iceberg направлены на сокрытие истинного размера крупного ордера. В любой момент времени виден только небольшой «кончик»; как только он заполняется, появляется следующая порция.
-
Как это работает:
- Отображает только часть общего ордера.
- Выпускает новые видимые блоки после исполнения каждого «кончика».
- Можно зафиксировать или рандомизировать видимый размер, чтобы снизить вероятность обнаружения.
-
Часто размещаются как лимитные ордера для лучшего контроля цен.
-
Когда использовать:
- Необходимо скрыть полный размер вашего ордера.
- Рынок не очень ликвиден и крупные сделки могут повлиять на цены.
- Вам надо поддерживать исполнение на определенном ценовом уровне.
-
Вы обеспокоены тем, что другие трейдеры обнаружат ваш ордер и выполнят его раньше других.
-
Реализация средствами MQL5
Теперь, когда мы понимаем теорию, лежащую в основе этих алгоритмов исполнения, давайте реализуем их на MQL5. Мы создадим модульную объектно-ориентированный фреймворк, который позволит использовать эти алгоритмы по отдельности или объединять их в единую систему исполнения.
Базовый класс: CExecutionAlgorithm
Начнем с определения базового класса, обеспечивающего общую функциональность для всех алгоритмов исполнения:
//+------------------------------------------------------------------+ //| Base class for all execution algorithms | //+------------------------------------------------------------------+ class CExecutionAlgorithm { protected: string m_symbol; // Symbol to trade double m_totalVolume; // Total volume to execute double m_executedVolume; // Volume already executed double m_remainingVolume; // Volume remaining to execute datetime m_startTime; // Start time for execution datetime m_endTime; // End time for execution bool m_isActive; // Flag indicating if the algorithm is active int m_totalOrders; // Total number of orders placed int m_filledOrders; // Number of filled orders double m_avgExecutionPrice; // Average execution price double m_executionValue; // Total value of executed orders int m_slippage; // Allowed slippage in points public: // Constructor CExecutionAlgorithm(string symbol, double volume, datetime startTime, datetime endTime, int slippage = 3); // Destructor virtual ~CExecutionAlgorithm(); // Common methods virtual bool Initialize(); virtual bool Execute() = 0; virtual bool Update() = 0; virtual bool Terminate(); // Utility methods bool PlaceOrder(ENUM_ORDER_TYPE orderType, double volume, double price); bool CancelOrder(ulong ticket); void UpdateAverageExecutionPrice(double price, double volume); // Getters string GetSymbol() const { return m_symbol; } double GetTotalVolume() const { return m_totalVolume; } double GetExecutedVolume() const { return m_executedVolume; } double GetRemainingVolume() const { return m_remainingVolume; } datetime GetStartTime() const { return m_startTime; } datetime GetEndTime() const { return m_endTime; } bool IsActive() const { return m_isActive; } int GetTotalOrders() const { return m_totalOrders; } int GetFilledOrders() const { return m_filledOrders; } double GetAverageExecutionPrice() const { return m_avgExecutionPrice; } };
Метод PlaceOrder особенно важен, поскольку он обрабатывает фактическое исполнение ордера и обновляет отслеживание объема:
bool CExecutionAlgorithm::PlaceOrder(ENUM_ORDER_TYPE orderType, double volume, double price) { // Prepare the trade request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volume; request.type = orderType; request.price = price; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("OrderSend failed with code: ", result.retcode); return false; } // Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volume; m_remainingVolume -= volume; UpdateAverageExecutionPrice(price, volume); // Store the order ticket for future reference ulong ticket = result.order; return true; }
Эта функция создает и отправляет рыночный ордер путем нулевой инициализации MqlTradeRequest и MqlTradeResult, заполнения символа, объема, типа ордера, цены, проскальзывания и магического числа, а затем вызова OrderSend. Если отправка не удалась или код возврата брокера не TRADE_RETCODE_DONE, он регистрирует ошибку и возвращает значение false. В случае успеха обновляет внутренние счетчики (общее количество/количество заполнений, выполненный и оставшийся объем), пересчитывает среднюю цену, фиксирует идентификатор тикета и возвращает значение true.
Реализация TWAP
Алгоритм TWAP делит период исполнения на равные временные интервалы и размещает ордера одинакового (или случайного) размера на каждом интервале:
//+------------------------------------------------------------------+ //| Time-Weighted Average Price (TWAP) Algorithm | //+------------------------------------------------------------------+ class CTWAP : public CExecutionAlgorithm { private: int m_intervals; // Number of time intervals int m_currentInterval; // Current interval datetime m_nextExecutionTime; // Next execution time double m_intervalVolume; // Volume per interval bool m_useRandomization; // Whether to randomize order sizes double m_randomizationFactor; // Factor for randomization (0-1) ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) bool m_firstOrderPlaced; // Flag to track if first order has been placed int m_initialDelay; // Initial delay in seconds before first execution datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) public: // Constructor CTWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, bool useRandomization = false, double randomizationFactor = 0.2, int slippage = 3, int initialDelay = 10); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // TWAP specific methods void CalculateIntervalVolume(); datetime CalculateNextExecutionTime(); double GetRandomizedVolume(double baseVolume); bool IsTimeToExecute(); };
Ключевой метод: CalculateNextExecutionTime
Этот метод обеспечивает надлежащее распределение ордеров по времени с первоначальной задержкой для первого ордера:datetime CTWAP::CalculateNextExecutionTime() { // Calculate the duration of each interval int totalSeconds = (int)(m_endTime - m_startTime); int intervalSeconds = totalSeconds / m_intervals; // Calculate the next execution time datetime nextTime; if(m_currentInterval == 0) { // First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("TWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { // For subsequent intervals, ensure proper spacing from current time datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; // Make sure we don't exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("TWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } return nextTime; }
Этот метод разбивает окно от m_startTime до m_endTime на равные сегменты m_intervals и возвращает момент, когда должна сработать следующая сделка: при самом первом вызове это просто m_startTime + m_initialDelay, а при каждом последующем вызове это TimeCurrent() + один интервал в секундах (но никогда не больше m_endTime).
Метод Execute:Метод Execute проверяет, настало ли время разместить ордер, и обрабатывает фактическое размещение заказа:
bool CTWAP::Execute() { if(!m_isActive) return false; // Check if it's time to execute the next order if(!IsTimeToExecute()) return true; // Not time yet // Calculate the volume for this execution double volumeToExecute = m_useRandomization ? GetRandomizedVolume(m_intervalVolume) : m_intervalVolume; // Ensure we don't exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; // Get current market price double price = 0.0; if(m_orderType == ORDER_TYPE_BUY) price = SymbolInfoDouble(m_symbol, SYMBOL_ASK); else price = SymbolInfoDouble(m_symbol, SYMBOL_BID); Print("TWAP: Placing order for interval ", m_currentInterval, ", Volume: ", DoubleToString(volumeToExecute, 2), ", Price: ", DoubleToString(price, _Digits)); // Place the order using OrderSend directly for more control MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = price; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("TWAP: OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("TWAP: OrderSend failed with code: ", result.retcode); return false; } // Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volumeToExecute; m_remainingVolume -= volumeToExecute; // Update interval counter m_currentInterval++; m_firstOrderPlaced = true; // Calculate the time for the next execution if(m_currentInterval < m_intervals && m_remainingVolume > 0) m_nextExecutionTime = CalculateNextExecutionTime(); else m_isActive = false; // All intervals completed or no volume left Print("TWAP: Executed ", DoubleToString(volumeToExecute, 2), " at price ", DoubleToString(price, _Digits), ". Remaining: ", DoubleToString(m_remainingVolume, 2), ", Next execution: ", TimeToString(m_nextExecutionTime)); return true; }
Этот метод Execute управляет одним фрагментом работы вашего TWAP. Во-первых, он прерывается, если стратегия не активна или еще не настало время торговать. При этом он выбирает либо фиксированный, либо случайный фрагмент оставшегося объема (никогда не превышающий того, что осталось), а затем ищет текущий спрос (для покупки) или предложение (для продажи). Он регистрирует интервал, объем и цену, создает MqlTradeRequest с вашим символом, объемом, типом, ценой, проскальзыванием и магическим числом, а затем вызывает OrderSend. Если отправка не удалась или брокер возвращает что-либо, кроме TRADE_RETCODE_DONE, он выводит ошибку и возвращает значение false.
В случае успеха увеличивает счетчики ордеров, корректирует выполненный и оставшийся объем, увеличивает счетчик интервалов, отмечает, что первый ордер был отправлен, а затем либо планирует время следующего исполнения, либо деактивирует стратегию, если у вас закончились интервалы или объем. Наконец, он регистрирует произошедшее и возвращает значение true.
Реализация VWAP
Алгоритм VWAP похож на TWAP, но распределяет размеры ордеров на основе исторических паттернов объема://+------------------------------------------------------------------+ //| Volume-Weighted Average Price (VWAP) Algorithm | //+------------------------------------------------------------------+ class CVWAP : public CExecutionAlgorithm { private: int m_intervals; // Number of time intervals int m_currentInterval; // Current interval datetime m_nextExecutionTime; // Next execution time double m_volumeProfile[]; // Historical volume profile double m_intervalVolumes[]; // Volume per interval based on profile bool m_adaptiveMode; // Whether to adapt to real-time volume ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) int m_historyDays; // Number of days to analyze for volume profile bool m_profileLoaded; // Flag indicating if profile was loaded bool m_firstOrderPlaced; // Flag to track if first order has been placed int m_initialDelay; // Initial delay in seconds before first execution datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) public: // Constructor CVWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, int historyDays = 5, bool adaptiveMode = true, int slippage = 3, int initialDelay = 10); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // VWAP specific methods bool LoadVolumeProfile(); void CalculateIntervalVolumes(); void AdjustToRealTimeVolume(); datetime CalculateNextExecutionTime(); double GetCurrentVWAP(); bool IsTimeToExecute(); };Как и в случае с TWAP, VWAP также реализует метод CalculateNextExecutionTime для обеспечения надлежащего интервала между ордерами:
datetime CVWAP::CalculateNextExecutionTime() { // Calculate the duration of each interval int totalSeconds = (int)(m_endTime - m_startTime); int intervalSeconds = totalSeconds / m_intervals; // Calculate the next execution time datetime nextTime; if(m_currentInterval == 0) { // First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("VWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { // For subsequent intervals, ensure proper spacing from current time datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; // Make sure we don't exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("VWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } return nextTime; }
Реализация ордеров Iceberg
Ордера типа Iceberg скрывают истинный размер ордера, выставляя на рынок в любой определенный момент времени лишь небольшую его часть://+------------------------------------------------------------------+ //| Iceberg Order Implementation | //+------------------------------------------------------------------+ class CIcebergOrder : public CExecutionAlgorithm { private: double m_visibleVolume; // Visible portion of the order double m_minVisibleVolume; // Minimum visible volume double m_maxVisibleVolume; // Maximum visible volume bool m_useRandomVisibleVolume; // Whether to randomize visible volume int m_orderPlacementDelay; // Delay between order placements (ms) bool m_avoidRoundNumbers; // Whether to avoid round numbers in price double m_limitPrice; // Limit price for the orders ulong m_currentOrderTicket; // Current active order ticket ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) bool m_orderActive; // Flag indicating if an order is currently active int m_priceDeviation; // Price deviation to avoid round numbers (in points) datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) int m_maxOrderLifetime; // Maximum lifetime for an order in seconds datetime m_orderPlacementTime; // When the current order was placed public: // Constructor CIcebergOrder(string symbol, double volume, double limitPrice, ENUM_ORDER_TYPE orderType, double visibleVolume, double minVisibleVolume = 0.0, double maxVisibleVolume = 0.0, bool useRandomVisibleVolume = true, int orderPlacementDelay = 1000, bool avoidRoundNumbers = true, int priceDeviation = 2, int slippage = 3); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // Iceberg specific methods double GetRandomVisibleVolume(); double AdjustPriceToAvoidRoundNumbers(double price); bool CheckAndReplaceOrder(); bool IsOrderFilled(ulong ticket); bool IsOrderPartiallyFilled(ulong ticket, double &filledVolume); bool IsOrderCancelled(ulong ticket); bool IsOrderExpired(ulong ticket); bool IsOrderTimeout(); ulong GetCurrentOrderTicket() { return m_currentOrderTicket; } bool IsOrderActive() { return m_orderActive; } };Метод Execute размещает новую видимую часть ордера:
bool CIcebergOrder::Execute() { if(!m_isActive) { Print("Iceberg: Execute called but algorithm is not active"); return false; } // If an order is already active, check its status if(m_orderActive) { Print("Iceberg: Execute called with active order ", m_currentOrderTicket); return CheckAndReplaceOrder(); } // Calculate the volume for this execution double volumeToExecute = m_useRandomVisibleVolume ? GetRandomVisibleVolume() : m_visibleVolume; // Ensure we don't exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; Print("Iceberg: Placing order for ", DoubleToString(volumeToExecute, 2), " at price ", DoubleToString(m_limitPrice, _Digits)); // Place the order using OrderSend directly for more control MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_PENDING; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = m_limitPrice; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("Iceberg: OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("Iceberg: OrderSend failed with code: ", result.retcode); return false; } // Store the order ticket m_currentOrderTicket = result.order; m_orderActive = true; m_orderPlacementTime = TimeCurrent(); Print("Iceberg: Order placed successfully. Ticket: ", m_currentOrderTicket, ", Volume: ", DoubleToString(volumeToExecute, 2), ", Remaining: ", DoubleToString(m_remainingVolume, 2)); return true; }
При запуске Execute сначала проверяется активность алгоритма. Если уже есть активный дочерний ордер типа iceberg, он вызывает CheckAndReplaceOrder(), чтобы проверить, требуется ли его отмена или пополнение. В противном случае он выбирает видимый фрагмент (фиксированный или случайный), ограничивает его оставшейся суммой и регистрирует размер и цену.
Затем он создает запрос отложенного ордера (TRADE_ACTION_PENDING) с символом, объемом, предельной ценой, проскальзыванием и магическим числом и вызывает OrderSend. В случае ошибки или невыполнения кода возврата он регистрирует и возвращает значение false; в случае успеха он сохраняет новый тикет, отмечает заказ активным, записывает время размещения, регистрирует данные и возвращает значение true.
Метод Update включает обнаружение таймаута ордера, чтобы гарантировать, что ордера не останутся активными бесконечно:
bool CIcebergOrder::Update() { if(!m_isActive) { Print("Iceberg: Update called but algorithm is not active"); return false; } // Check if all volume has been executed if(m_remainingVolume <= 0) { Print("Iceberg: All volume executed. Terminating algorithm."); return Terminate(); } // Check if it's time to check order status datetime currentTime = TimeCurrent(); if(currentTime >= m_lastCheckTime + m_checkInterval) { m_lastCheckTime = currentTime; // Log current market conditions double currentBid = SymbolInfoDouble(m_symbol, SYMBOL_BID); double currentAsk = SymbolInfoDouble(m_symbol, SYMBOL_ASK); Print("Iceberg: Market update - Bid: ", DoubleToString(currentBid, _Digits), ", Ask: ", DoubleToString(currentAsk, _Digits), ", Limit Price: ", DoubleToString(m_limitPrice, _Digits)); // If an order is active, check its status if(m_orderActive) { // Check if the order has been active too long if(IsOrderTimeout()) { Print("Iceberg: Order ", m_currentOrderTicket, " has timed out. Replacing it."); // Cancel the current order if(!CancelOrder(m_currentOrderTicket)) { Print("Iceberg: Failed to cancel timed out order ", m_currentOrderTicket); } // Reset order tracking m_orderActive = false; m_currentOrderTicket = 0; // Place a new order after a delay Sleep(m_orderPlacementDelay); return Execute(); } return CheckAndReplaceOrder(); } else { // If no order is active, execute a new one Print("Iceberg: No active order, executing new order"); return Execute(); } } return true; }
Update периодически опрашивает рынок и статус ордеров с интервалами, определяемыми функцией m_checkInterval. Если весь объем исчерпан, он завершается. В противном случае, как только наступит время проверки, он регистрирует текущую цену спроса/предложения и лимитную цену. Если ордер активен, он проверяется на таймаут: если ордер истек, он отменяется, сбрасывает состояние, переходит в режим m_orderPlacementDelay и повторно выполняется для размещения нового фрагмента; если таймаут не истек, он откладывается в CheckAndReplaceOrder(). Если активного ордера нет, он просто вызывает Execute для отправки следующей видимой порции.
Реализация анализатора эффективности
Наш класс анализатора эффективности отслеживает эти показатели и предоставляет методы для анализа и сравнения работы алгоритмов://+------------------------------------------------------------------+ //| Performance Analyzer for Execution Algorithms | //+------------------------------------------------------------------+ class CPerformanceAnalyzer { private: string m_symbol; // Symbol being analyzed datetime m_startTime; // Analysis start time datetime m_endTime; // Analysis end time double m_decisionPrice; // Price at decision time double m_avgExecutionPrice; // Average execution price double m_totalVolume; // Total volume executed double m_implementationShortfall; // Implementation shortfall double m_marketImpact; // Estimated market impact double m_slippage; // Average slippage int m_executionTime; // Total execution time in seconds double m_priceImprovement; // Total price improvement public: // Constructor CPerformanceAnalyzer(string symbol, double decisionPrice); // Analysis methods void RecordExecution(datetime time, double price, double volume); void CalculateMetrics(); void CompareAlgorithms(CPerformanceAnalyzer &other); // Reporting methods void PrintReport(); void SaveReportToFile(string filename); // Getters double GetImplementationShortfall() const { return m_implementationShortfall; } double GetMarketImpact() const { return m_marketImpact; } double GetSlippage() const { return m_slippage; } int GetExecutionTime() const { return m_executionTime; } double GetPriceImprovement() const { return m_priceImprovement; } };
CPerformanceAnalyzer объединяет все ваши пост-торговые показатели в одном месте. При его построении вы присваиваете ему символ и контрольную цену на момент принятия решения; в качестве начала отсчета он отмечает текущее время. По мере наполнения каждого дочернего ордера вы вызываете RecordExecution(время, цена, объем), который обновляет текущие итоги — совокупный объем, средневзвешенную цену исполнения и временные метки. После завершения стратегии (или периодически) вы вызываете CalculateMetrics(), который вычисляет:
- Дефицит реализации (разница в прибылях и убытках между ценой вашего решения и фактическим исполнением),
- Среднее проскальзывание по сравнению с котируемыми ценами,
- Оценочное влияние вашего присутствия на рынке,
- Общее время выполнения (конец минус начало),
- Улучшение цен (если таковое имеется) по сравнению с эталонными показателями.
Вы даже можете сравнить два прогона с помощью CompareAlgorithms(otherAnalyzer), чтобы увидеть, какая стратегия показала лучшие результаты. Наконец, PrintReport() выводит ключевые статистические данные в лог для быстрого просмотра, а SaveReportToFile(filename) позволяет сохранить полный отчет во внешнем хранилище. Легкие методы-получатели предоставляют доступ к каждой метрике для пользовательских информационных панелей или для дальнейшего анализа.
Сравнение эффективности алгоритмов
Различные рыночные условия благоприятствуют различным алгоритмам исполнения. Вот общее сравнение, т.е. практическое правило:- TWAP:
- Наилучшим образом подходит для: Стабильных рынков с постоянной ликвидностью
- Преимущества: Простой, предсказуемый исполнительный паттерн
- Минусы: Не адаптируется к изменяющимся рыночным условиям
- VWAP:
- Наилучшим образом подходит для: Рынки с предсказуемыми паттернами объема
- Преимущества: Соответствует естественному ритму рынка, часто достигает более выгодных цен
- Минусы: Требуется объем исторических данных, более сложная реализация
- Ордера Iceberg:
- Наилучшим образом подходит для: Менее ликвидные рынки или когда чувствительность цен высокая
- Преимущества: Минимизирует влияние на рынок, сохраняет контроль над ценами
- Минусы: Время исполнения может быть непредсказуемым, риск частичного исполнения
Интеграция алгоритмов исполнения с торговыми стратегиями
Настоящая сила этих алгоритмов исполнения раскрывается при их интеграции с торговыми стратегиями. В данном разделе показано, как интегрировать наши алгоритмы исполнения в комплексные торговые системы.
Диспетчер
Чтобы упростить интеграцию, мы создадим класс диспетчер (Execution Manager), который будет служить фасадом для всех наших алгоритмов выполнения:
//+------------------------------------------------------------------+ //| Execution Manager - Facade for all execution algorithms | //+------------------------------------------------------------------+ class CExecutionManager { private: CExecutionAlgorithm* m_algorithm; // Current execution algorithm CPerformanceAnalyzer* m_analyzer; // Performance analyzer public: // Constructor CExecutionManager(); // Destructor ~CExecutionManager(); // Algorithm creation methods bool CreateTWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, bool useRandomization = false, double randomizationFactor = 0.2, int slippage = 3); bool CreateVWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, int historyDays = 5, bool adaptiveMode = true, int slippage = 3); bool CreateIcebergOrder(string symbol, double volume, double limitPrice, ENUM_ORDER_TYPE orderType, double visibleVolume, double minVisibleVolume = 0.0, double maxVisibleVolume = 0.0, bool useRandomVisibleVolume = true, int orderPlacementDelay = 1000, bool avoidRoundNumbers = true, int priceDeviation = 2, int slippage = 3); // Execution methods bool Initialize(); bool Execute(); bool Update(); bool Terminate(); // Performance analysis void EnablePerformanceAnalysis(double decisionPrice); void PrintPerformanceReport(); // Getters CExecutionAlgorithm* GetAlgorithm() { return m_algorithm; } };
CExecutionManager действует как простой фасад для любого из ваших алгоритмов исполнения и объединяет их в единый торговый процесс. Внутри он хранит указатель на текущий выбранный CExecutionAlgorithm (TWAP, VWAP или Iceberg), а также CPerformanceAnalyzer для отслеживания того, насколько хорошо выполняются ваши ордера.
Вы выбираете свою стратегию, вызывая один из методов Create…, передавая символ, общий объем, время начала/окончания (для TWAP/VWAP), количество интервалов или размеры фрагментов, тип ордера и любые специфичные для алгоритма параметры (рандомизация, окно истории, предельная цена и т. д.). После создания вы проводите его через обычный жизненный цикл:
- Initialize() настраивает любое необходимое вам состояние или данные.
- Execute() запускает следующий фрагмент или дочерний ордер.
- Update() опрашивает заполнения, рыночные данные или таймауты.
-
Terminate() выполняет очистку, как только вы все заполнили или хотите остановиться.
Если вы включите анализ работы с помощью EnablePerformanceAnalysis(), диспетчер будет регистрировать цены исполнения в сравнении с контрольным показателем при принятии решения и вы сможете вывести краткий отчет о прибылях/убытках и проскальзывании с помощью PrintPerformanceReport(). Вы всегда можете получить необработанный объект алгоритма с помощью GetAlgorithm() для пользовательского зондирования или измерения.
Примеры интеграции
Ниже приведены примеры того, как интегрировать наши алгоритмы исполнения с различными типами торговых стратегий:
-
Стратегия следования за трендом с исполнением TWAP.
//+------------------------------------------------------------------+ //| Trend-Following Strategy with TWAP Execution | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int maPeriodFast = 20; int maPeriodSlow = 50; double volume = 1.0; int executionIntervals = 5; // Calculate indicators double maFast = iMA(Symbol(), PERIOD_CURRENT, maPeriodFast, 0, MODE_SMA, PRICE_CLOSE, 0); double maSlow = iMA(Symbol(), PERIOD_CURRENT, maPeriodSlow, 0, MODE_SMA, PRICE_CLOSE, 0); // Check for entry conditions static bool inPosition = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: Fast MA crosses above Slow MA if(maFast > maSlow) { // Create TWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateTWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_BUY)) { executionManager.Initialize(); inPosition = true; Print("Buy signal detected. Starting TWAP execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("TWAP execution completed."); } } } }
-
Стратегия возврата к среднему с исполнением VWAP.
//+------------------------------------------------------------------+ //| Mean-Reversion Strategy with VWAP Execution | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int rsiPeriod = 14; int rsiOversold = 30; int rsiOverbought = 70; double volume = 1.0; int executionIntervals = 5; // Calculate indicators double rsi = iRSI(Symbol(), PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE, 0); // Check for entry conditions static bool inPosition = false; static bool isLong = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: RSI oversold if(rsi < rsiOversold) { // Create VWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_BUY)) { executionManager.Initialize(); inPosition = true; isLong = true; Print("Buy signal detected. Starting VWAP execution."); } } // Sell signal: RSI overbought else if(rsi > rsiOverbought) { // Create VWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_SELL)) { executionManager.Initialize(); inPosition = true; isLong = false; Print("Sell signal detected. Starting VWAP execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("VWAP execution completed."); } } // Check for exit conditions if(isLong && rsi > rsiOverbought) { executionManager.Terminate(); inPosition = false; Print("Exit signal detected. Terminating VWAP execution."); } else if(!isLong && rsi < rsiOversold) { executionManager.Terminate(); inPosition = false; Print("Exit signal detected. Terminating VWAP execution."); } } }
- Стратегия пробоя с помощью ордеров Iceberg.
//+------------------------------------------------------------------+ //| Breakout Strategy with Iceberg Orders | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int channelPeriod = 20; double volume = 1.0; double visibleVolume = 0.1; // Calculate indicators double upperChannel = iHigh(Symbol(), PERIOD_CURRENT, iHighest(Symbol(), PERIOD_CURRENT, MODE_HIGH, channelPeriod, 1)); double lowerChannel = iLow(Symbol(), PERIOD_CURRENT, iLowest(Symbol(), PERIOD_CURRENT, MODE_LOW, channelPeriod, 1)); double currentPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID); // Check for entry conditions static bool inPosition = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: Price breaks above upper channel if(currentPrice > upperChannel) { // Create Iceberg Order double limitPrice = upperChannel; // Place limit order at breakout level if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_BUY, visibleVolume, visibleVolume * 0.8, visibleVolume * 1.2, true, 1000, true, 2, 3)) { executionManager.Initialize(); inPosition = true; Print("Buy breakout detected. Starting Iceberg Order execution."); } } // Sell signal: Price breaks below lower channel else if(currentPrice < lowerChannel) { // Create Iceberg Order double limitPrice = lowerChannel; // Place limit order at breakout level if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_SELL, visibleVolume, visibleVolume * 0.8, visibleVolume * 1.2, true, 1000, true, 2, 3)) { executionManager.Initialize(); inPosition = true; Print("Sell breakout detected. Starting Iceberg Order execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("Iceberg Order execution completed."); } } } }
Во всех трех примерах интеграция следует одному и тому же паттерну высокого уровня:
-
Поддержание состояния
- Логическое значение inPosition отслеживает, заполняете ли вы в данный момент ордер.
-
Статический диспетчер CExecutionManager существует на протяжении тиков и управляет жизненным циклом выбранного вами алгоритма.
-
Логика входа
- После вашего входного сигнала (пересечение скользящих средних, пороговое значение RSI, прорыв канала) вызовите соответствующий метод создания в executionManager (TWAP, VWAP или Iceberg), передающий символ, общий объем, временное окно или лимитную цену, параметры фрагментов и тип ордера.
-
Если создание прошло успешно, немедленно вызовите executionManager.Initialize(), установите inPosition=true и зарегистрируйте начало работы.
-
Непрерывное исполнение
- Пока inPosition имеет значение true, каждый OnTick() вызывает executionManager.Update().
-
Внутри Update() диспетчер будет по мере необходимости вызывать Execute(), опрашивать заполнения, обрабатывать таймауты или обновления рынка, а также планировать следующий фрагмент (или отменять/заменять дочерние ордера для Iceberg).
-
Завершение и выход
- После каждого обновления проверяйте executionManager.GetAlgorithm()->IsActive(). Как только он вернет значение false (все интервалы выполнены или объем исчерпан), установите inPosition=false и запишите в лог, что выполнение завершено.
-
В примере возврата к среднему значению VWAP есть дополнительная проверка выхода: если цена разворачивается и выходит за пределы порогового значения RSI в середине исполнения, для ранней остановки вызывайте executionManager.Terminate() .
- Следование за трендом + TWAP
Вход: Пересечение быстрой скользящей средней над медленной скользящей средней активирует
Исполнение: Разделяет вашу покупку 1 лота на 5 равных частей в течение следующего часа - Возврат к среднему + VWAP
Вход: RSI < 30 для сделок на покупку, > 70 для сделок на продажу
Исполнение: Распределяет 1 лот против исторического объема на 5 частей в течение 1 часа.
Ранний выход: Если сигнал меняется (например, RSI > 70 во время сделки на покупку), executionManager.Terminate() прерывает оставшиеся части - Пробой + Iceberg
Вход: Цена пробивает максимум (покупка) или минимум (продажа) канала.
Исполнение: Устанавливает отложенный лимит на уровне цены прорыва, открывая только ~0,1 лота за раз и пополняя его до тех пор, пока не будет заполнен 1 лот целиком.
Заменив CreateTWAP, CreateVWAP или CreateIcebergOrder, вы подключаете любой алгоритм исполнения к своей логике сигнала, не дублируя шаблон управления ордерами.
Интегрированная стратегия
Полный код:
//+------------------------------------------------------------------+ //| IntegratedStrategy.mq5 | //| Copyright 2025, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Software Corp." #property link "https://www.metaquotes.net" #property version "1.00" #include "ExecutionAlgorithm.mqh" #include "TWAP.mqh" #include "VWAP.mqh" #include "IcebergOrder.mqh" #include "PerformanceAnalyzer.mqh" #include "ExecutionManager.mqh" // Input parameters input int FastMA = 20; // Fast moving average period input int SlowMA = 50; // Slow moving average period input double TradingVolume = 0.1; // Trading volume input bool UseAdaptiveExecution = true; // Use adaptive execution based on market conditions // Global variables CExecutionManager *g_executionManager = NULL; int g_maHandle1 = INVALID_HANDLE; int g_maHandle2 = INVALID_HANDLE; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize execution manager g_executionManager = new CExecutionManager(Symbol(), 3, UseAdaptiveExecution); // Initialize indicators g_maHandle1 = iMA(Symbol(), Period(), FastMA, 0, MODE_SMA, PRICE_CLOSE); g_maHandle2 = iMA(Symbol(), Period(), SlowMA, 0, MODE_SMA, PRICE_CLOSE); if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE) { Print("Failed to create indicator handles"); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up if(g_executionManager != NULL) { delete g_executionManager; g_executionManager = NULL; } // Release indicator handles IndicatorRelease(g_maHandle1); IndicatorRelease(g_maHandle2); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update execution algorithms if(g_executionManager != NULL) g_executionManager.UpdateAlgorithms(); // Only process at bar open if(iVolume(_Symbol, PERIOD_CURRENT, 0) > 1) return; // Get indicator values double fastMA[2], slowMA[2]; if(CopyBuffer(g_maHandle1, 0, 0, 2, fastMA) <= 0 || CopyBuffer(g_maHandle2, 0, 0, 2, slowMA) <= 0) { Print("Failed to copy indicator buffers"); return; } // Check for trend signals bool buySignal = (fastMA[0] > slowMA[0]) && (fastMA[1] <= slowMA[1]); bool sellSignal = (fastMA[0] < slowMA[0]) && (fastMA[1] >= slowMA[1]); // Execute signals using the execution manager if(buySignal) { Print("Buy signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume); } else { // Manually create a TWAP algorithm datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_BUY, true); } } else if(sellSignal) { Print("Sell signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume); } else { // Manually create a TWAP algorithm datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_SELL, true); } } } //+------------------------------------------------------------------+
Советник IntegratedStrategy.mq5 начинается с объявления своих метаданных (авторское право, ссылка, версия) и включения заголовков для всех наших классов алгоритма исполнения и анализатора работы. Затем он определяет четыре настраиваемых пользователем входа: периоды быстрой и медленной простых скользящих средних, общий объем торговли на сигнал и логический флаг для переключения на «адаптивное» исполнение (где диспетчер решает, использовать ли TWAP, VWAP или Iceberg). Также объявлены глобальный указатель на CExecutionManager и два хэндла индикатора, чтобы они сохранялись между тиками.
В OnInit() мы инстанцируем диспетчер исполнения, передавая ему текущий символ, максимум из трёх параллельных алгоритмов и наш адаптивный флаг, а затем создаем два хэндла индикатора SMA. Если какой-либо из хэндлов дает сбой, инициализация прерывается. OnDeinit() просто очищает данные, удаляя диспетчера и освобождая хэндлы индикаторов, гарантируя отсутствие утечек памяти или хэндлов при удалении советника или завершении работы платформы.
Основная логика находится в OnTick(). Сначала мы вызываем UpdateAlgorithms() в диспетчере исполнения, чтобы любые существующие дочерние ордера (фрагменты TWAP, интервалы VWAP или ветви Iceberg) обрабатывались, отменялись или пополнялись по мере необходимости. Затем ждем новый бар (пропуская его, если тиковый объем еще нарастает). После открытия бара мы вытаскиваем последние два значения SMA для быстрого и медленного периодов. Пересечение снизу вверх вызывает сигнал на покупку; обратное — на продажу.
Если адаптивное выполнение включено, мы передаем сигнал и объём в g_executionManager.ExecuteSignal(), чтобы он выбрал подходящий алгоритм. В противном случае мы вручную запускаем экземпляр TWAP на один час и шесть фрагментов. Этот шаблон четко разделяет логику входа (обнаружение тренда) от логики управления ордерами, позволяя одному и тому же фасаду управлять множеством стилей исполнения без дублирования шаблона.
После включения Take Profit код меняется на:
//+------------------------------------------------------------------+ //| IntegratedStrategy.mq5 | //| Copyright 2025, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Software Corp." #property link "https://www.metaquotes.net" #property version "1.00" #include "ExecutionAlgorithm.mqh" #include "TWAP.mqh" #include "VWAP.mqh" #include "IcebergOrder.mqh" #include "PerformanceAnalyzer.mqh" #include "ExecutionManager.mqh" #include <Trade\Trade.mqh> // Input parameters input int FastMA = 20; // Fast moving average period input int SlowMA = 50; // Slow moving average period input double TradingVolume = 0.1; // Trading volume input bool UseAdaptiveExecution = true; // Use adaptive execution based on market conditions input double EquityTPPercent = 10.0; // Equity Take Profit in percent input double EquitySLPercent = 5.0; // Equity Stop Loss in percent // Global variables CExecutionManager *g_executionManager = NULL; int g_maHandle1 = INVALID_HANDLE; int g_maHandle2 = INVALID_HANDLE; double g_initialEquity = 0.0; CTrade trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Record initial equity g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY); // Initialize execution manager g_executionManager = new CExecutionManager(Symbol(), 3, UseAdaptiveExecution); // Initialize indicators g_maHandle1 = iMA(Symbol(), Period(), FastMA, 0, MODE_SMA, PRICE_CLOSE); g_maHandle2 = iMA(Symbol(), Period(), SlowMA, 0, MODE_SMA, PRICE_CLOSE); if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE) { Print("Failed to create indicator handles"); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up if(g_executionManager != NULL) { delete g_executionManager; g_executionManager = NULL; } // Release indicator handles IndicatorRelease(g_maHandle1); IndicatorRelease(g_maHandle2); } //+------------------------------------------------------------------+ //| Check equity-based TP and SL, then reset baseline | //+------------------------------------------------------------------+ void CheckEquityTPandSL() { double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); double tpEquity = g_initialEquity * (1.0 + EquityTPPercent / 100.0); double slEquity = g_initialEquity * (1.0 - EquitySLPercent / 100.0); if(currentEquity >= tpEquity) { Print("Equity Take Profit reached: ", currentEquity); CloseAllPositions(); g_initialEquity = currentEquity; Print("Equity baseline reset to: ", g_initialEquity); } else if(currentEquity <= slEquity) { Print("Equity Stop Loss reached: ", currentEquity); CloseAllPositions(); g_initialEquity = currentEquity; Print("Equity baseline reset to: ", g_initialEquity); } } //+------------------------------------------------------------------+ //| Close all open positions | //+------------------------------------------------------------------+ void CloseAllPositions() { CPositionInfo m_position; CTrade m_trade; for(int i = PositionsTotal() - 1; i >= 0; i--) // loop all Open Positions if(m_position.SelectByIndex(i)) { // select a position m_trade.PositionClose(m_position.Ticket()); // then delete it --period } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Check and reset equity thresholds CheckEquityTPandSL(); // Update execution algorithms if(g_executionManager != NULL) g_executionManager.UpdateAlgorithms(); // Only process at bar open if(iVolume(_Symbol, PERIOD_CURRENT, 0) > 1) return; // Get indicator values double fastMA[2], slowMA[2]; if(CopyBuffer(g_maHandle1, 0, 0, 2, fastMA) <= 0 || CopyBuffer(g_maHandle2, 0, 0, 2, slowMA) <= 0) { Print("Failed to copy indicator buffers"); return; } // Check for trend signals bool buySignal = (fastMA[0] > slowMA[0]) && (fastMA[1] <= slowMA[1]); bool sellSignal = (fastMA[0] < slowMA[0]) && (fastMA[1] >= slowMA[1]); // Execute signals using the execution manager if(buySignal) { Print("Buy signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume); } else { datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_BUY, true); } } else if(sellSignal) { Print("Sell signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume); } else { datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_SELL, true); } } } //+------------------------------------------------------------------+
Результаты тестирования на истории
1. Кривые капитала и баланса
Зеленая лесенка «Balance» отображает балансовый капитал вашего счета всякий раз, когда советник закрывает позицию; синяя линия «Equity» сглаживает нереализованные прибыли и убытки между сделками. Мы можем наблюдать четкую тенденцию к росту с января по начало марта с несколькими откатами — каждый откат достигает максимума около 10–16%, прежде чем следующая серия выигрышей восстановит прирост. Этот паттерн предполагает, что система процветает в трендовых условиях, но все же испытывает терпимые падения капитала.
2. Объем и использование риска
Внизу треугольники «Нагрузка на депозит» (Deposit Load) постепенно уменьшаются с течением времени — это размер вашей позиции в процентах от капитала. Он начинается примерно с 10% от вашего баланса и уменьшается по мере роста вашего капитала (при фиксированном размере объема), то есть наш риск на сделку фактически уменьшается по мере роста счета. Вот почему просадки остаются пропорционально схожими, даже если ваш долларовый капитал растет.
3. Ключевые показатели прибыльности
-
Начальный депозит: $1 000
-
Чистая прибыль: + $703 (доходность 70% за ~2 месяца)
-
Коэффициент прибыльности: 2,34 (вы зарабатываете 2,34 доллара за каждый потраченный 1 доллар)
-
Ожидаемая прибыль: в среднем 2,34 доллара за сделку
-
Коэффициент Шарпа: 5,47 (очень высокий — высокая доходность с поправкой на риск)
Эти цифры говорят нам, что стратегия не только прибыльна, но и обеспечивает существенный буфер над собственной волатильностью.
4. Просадка и восстановление
-
Максимальная просадка баланса: 156 пунктов или 9.99%
-
Максимальная просадка капитала: 228 пунктов или 15.89%
-
Фактор восстановления: 3.08 (чистая прибыль ÷ максимальная просадка)
Фактор восстановления выше 2 обычно считается хорошим, так что при 3,08 вы получаете прибыль, в три раза превышающую ваш наибольший убыток.
5. Распределение сделок
-
Всего сделок: 300 (600 сделок, поэтому каждый вход+выход считается за два)
-
Коэффициент выигрыша: 76% (228 выигрышей против 72 проигрышей)
-
Средний выигрыш: $5,39
-
Средний убыток: – $7,31
Несмотря на то, что ваш процент выигрышных сделок и коэффициент прибыли высоки, обратите внимание, что в среднем ваши убыточные сделки больше выигрышных — на это стоит обратить внимание, если рыночные условия изменятся.
6. Серии и согласованность
-
Максимальное количество прибыльных сделок подряд: 87 сделок, + $302
-
Максимальное количество убыточных сделок подряд: 23 сделок, – $156
-
Средняя прибыльная серия: 57 сделок
-
Средняя убыточная серия: 18 сделок
Длительные прибыльные серии ведут к росту, в то время как самая длительная серия проигрышей по-прежнему стоит всего около 15% капитала.
Заключение
Представьте, что вы — индивидуальный трейдер на переполненной рыночной арене — каждый тик имеет значение, каждая цена исполнения предсказывает прибыль или убыток. Включив в свой инструментарий ордера TWAP, VWAP и Iceberg, вы больше не просто реагируете на колебания цен; вы ими дирижируете. Эти некогда элитные алгоритмы институционального уровня теперь у вас под рукой, разрезая ликвидность словно лазер и превращая хаотичные стаканы цен в возможности.
TWAP станет вашим постоянным метрономом, равномерно регулируя размер в заданном интервале — идеально, когда течение спокойное и вам просто нужна плавная поездка. VWAP превращает вас в опытного трейдера, отслеживающего самые напряженные торговые события дня и чувствующего пульс рынка. А когда вам нужно скрыть свои намерения, ордера Iceberg скрывают ваши истинные размеры под поверхностью, открывая ровно столько, сколько нужно для выполнения, не отпугивая при этом крупных игроков.
Но это не просто отдельные приемы. С помощью нашей модульной платформы MQL5 вы можете интегрировать их в любую стратегию — будь то следование за трендом, возврат к среднему значению или охота за пробоями — с легкостью переключаясь на новый подход. Единый фасад ExecutionManager позволяет вам менять, объединять или даже накладывать алгоритмы во время торговли, в то время как PerformanceAnalyzer ведет счет как ястреб, измеряя проскальзывание, дефицит и влияние на рынок с точностью до последнего пипса.
Что будет дальше? Подумайте об исполнении как о живом существе, которое адаптируется. Позвольте вашему TWAP учиться на скачках волатильности. Направьте фрагменты VWAP в самые глубокие водоёмы. Научите свой Iceberg чувствовать, где затаились хищники и прятаться глубже. И зачем останавливаться на достигнутом? Внедрите машинное обучение, чтобы предсказать идеальную микросекунду для срабатывания, или смешайте типы ордеров в индивидуальные гибриды, которые соответствуют вашему уникальному преимуществу.
Мир трейдинга никогда не стоит на месте и исполнение ваших ордеров тоже не должно останавливаться. Погружайтесь, смело экспериментируйте и превратите каждую часть, каждое выполнение в рассчитанное преимущество. Ваша сильная сторона ждет в коде.
Для удобства приводим краткий обзор файлов, включенных в эту статью:
Название файла | Описание |
---|---|
ExecutionAlgorithm.mqh | Базовый класс для всех алгоритмов исполнения |
TWAP.mqh | Реализация средневзвешенной по времени цены |
VWAP.mqh | Реализация средневзвешенной цены по объему |
IcebergOrder.mqh | Реализация ордеров Iceberg |
PerformanceAnalyzer.mqh | Инструменты для анализа эффективности исполнения |
ExecutionManager.mqh | Фасад для легкой интеграции с торговыми стратегиями |
IntegratedStrategy.mq5 | Пример советника, демонстрирующий интеграцию с торговой стратегией |
IntegratedStrategy - Take Profit.mq5 | Пример советника, демонстрирующий интеграцию с торговой стратегией с тейк-профитом и стоп-лоссом в процентах от баланса счета |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17934





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Тестировал ваш алгоритм.
В файле ExecutionAlgorithm.mqh добавил эту строку request.type_filling = ORDER_FILLING_IOC; при выставлении ордера, чтобы исправить проблему с выставлением ордера.
Протестировал на M5, открыта только 1 сделка за 2 месяца, частичных ордеров не открывалось.
Протестировал на H1, не применил ни SL, ни TP, все сделки закрылись в убыток.
также при компиляции выдает предупреждения
подскажите, как протестировать algo,
временные рамки. и любые другие рекомендации.
также при компиляции выдает предупреждения
я изменил строку кода
m_volumeProfile[intervalIndex] += rates[i].tick_volu
на
Это устранило предупреждения
Теперь нужна ваша помощь в отношении других моих запросов, как
Таймфрейм
А также
почему все сделки во время бэктеста приводят к убыткам
как проверить эту большую работу от вас.
подскажите, как протестировать algo,
временные рамки. и любые другие рекомендации.
Предупреждения не являются проблемой, но их можно быстро исправить. Но да, было бы здорово, если бы автор пошагово показал, какие настройки и входы он использовал для бэктеста.
Я согласен с Домиником, поскольку предупреждения - это всего лишь предупреждения. Результаты I_Virgo, вероятно, связаны с тем, что он использовал неправильный таймфрейм и валютную пару. Судя по отчету Back Test из почти 2000 баров, это должен был быть либо M1, либо M5 в качестве таймфрейма с неизвестной парой.
Было бы неплохо, если бы MQ добавил в отчет Back Test таймфрейм и валютную пару или пары, а также более подробно разделил результаты по парам, чтобы мы могли более точно воспроизвести результаты обратного тестирования автора, а также определить его применимость к парам Форекс. Также было бы очень полезно, если бы советник мог выводить текст на график во время работы.
Я также считаю, что это отличная статья, и планирую тщательно изучить ее в ожидании адаптации его методов к другим советникам
CapeCoddah