English 中文 Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 20): Мультисимвольная стратегия с использованием CCI и AO

Автоматизация торговых стратегий на MQL5 (Часть 20): Мультисимвольная стратегия с использованием CCI и AO

MetaTrader 5Трейдинг |
95 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В предыдущей статье (Часть 19) мы рассмотрели стратегию скальпинга на коррекции на основе конвертов, а именно - исполнение сделок и управление рисками, закончив ее автоматизацию на языке MQL5. В части 20 мы представим мультисимвольную торговую стратегию, использующую Commodity Channel Index (CCI) и Awesome Oscillator (AO) для фиксации разворотов тренда по нескольким валютным парам. Мы рассмотрим следующие темы:

  1. Стратегическая дорожная карта и архитектура
  2. Реализация средствами MQL5
  3. Тестирование на истории и оптимизация
  4. Заключение

В финале у вас будет надежная торговая система на MQL5 для мультисимвольной торговли, готовая к оптимизации и внедрению.


Стратегическая дорожная карта и архитектура

В Части 19 мы создали стратегии скальпинга на коррекции на основе конвертов (Envelopes Trend Bounce Scalping Strategy), в частности сфокусировавшись на исполнении сделок и управлении рисками для обнаружения торговых сигналов с использованием взаимодействия цены с конвертами, подтвержденного трендовыми фильтрами. Теперь, в части 20, мы переходим к многосимвольной торговой стратегии, которая использует индекс товарного канала (Commodity Channel Index, CCI) и осциллятор Awesome (AO) для выявления разворотов тренда по нескольким валютным парам на двух таймфреймах (M5 для сигналов и H1 для подтверждения тренда). В плане статьи - разработка масштабируемой системы, которая эффективно обрабатывает сигналы, совершает сделки и управляет рисками по нескольким символам.

В архитектурном плане особое внимание уделяется модульности и надежности, для организации компонентов стратегии используется классовая структура в MQL5. Наша цель — создать общий класс, который будет обрабатывать расчеты индикаторов (CCI и AO), генерацию сигналов на основе предопределенных пороговых значений и исполнение сделок с настройками стоп-лосса и тейк-профита, гарантируя при этом отсутствие открытых ордеров для данного символа перед размещением новых сделок. Проект включает в себя такие меры защиты, как проверка спреда, и поддерживает торговлю на новых барах или потиковыми данными, обеспечивая гибкость для различных рыночных условий и, в конечном итоге, создавая целостную мультисимвольную торговую систему. Ниже схематично показано, чего мы стремимся достичь.

ПЛАН-СТРАТЕГИЯ


Реализация средствами MQL5

Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в "Навигатор", выберите папку Indicators, кликните "Создать" и следуйте инструкциям для создания файла. После создания объекта в среде программирования мы начнем с объявления некоторых структур и классов, которые будем использовать, поскольку хотим применить объектно-ориентированный (Object Oriented Programming, OOP) подход.

//+------------------------------------------------------------------+
//|                                          MultiSymbolCCIAO_EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict
#property description "Multi-symbol trading strategy using CCI and AO indicators"

#include <Trade/Trade.mqh> //--- Include the Trade library for trading operations

//+------------------------------------------------------------------+
//| Input Parameters Structure                                       | //--- Define a structure for trading parameters
//+------------------------------------------------------------------+
struct TradingParameters {
   string            symbols;                  //--- Store comma-separated symbol list
   int               per_signal_cci;           //--- Set period for CCI signal
   int               per_trend_cci;            //--- Set period for CCI trend
   ENUM_APPLIED_PRICE price_cci;               //--- Specify applied price for CCI
   int               cci_signal_buy_value;     //--- Define CCI buy signal threshold
   int               cci_signal_sell_value;    //--- Define CCI sell signal threshold
   ENUM_TIMEFRAMES   tf_signal;                //--- Set timeframe for signal
   ENUM_TIMEFRAMES   tf_trend;                 //--- Set timeframe for trend
   bool              use_ao_for_trend;         //--- Enable AO for trend confirmation
   int               take;                     //--- Set take-profit in points
   int               stop;                     //--- Set stop-loss in points
   double            lots;                     //--- Specify trade lot size
   int               slip;                     //--- Set maximum slippage
   int               max_spread;               //--- Define maximum allowed spread
   int               magic;                    //--- Set magic number for trades
   bool              trade_anytime;            //--- Allow trading at any time
   string            comment;                  //--- Store trade comment
   int               tester_max_balance;       //--- Set tester balance limit
   bool              debug_mode;               //--- Enable debug mode
};

//+------------------------------------------------------------------+
//| Symbol Data Structure                                            | //--- Define a structure for symbol-specific data
//+------------------------------------------------------------------+
struct SymbolData {
   string            name;                     //--- Store symbol name
   datetime          last_bar_time;            //--- Track last bar timestamp
   int               cci_signal_handle;        //--- Hold CCI signal indicator handle
   int               cci_trend_handle;         //--- Hold CCI trend indicator handle
   int               ao_signal_handle;         //--- Hold AO signal indicator handle
   int               ao_trend_handle;          //--- Hold AO trend indicator handle
   double            cci_signal_data[];        //--- Store CCI signal data
   double            cci_trend_data[];         //--- Store CCI trend data
   double            ao_signal_data[];         //--- Store AO signal data
   double            ao_trend_data[];          //--- Store AO trend data
};

Начнем реализацию мультисимвольной торговой стратегии на основе CCI и AO на MQL5, настроив базовые компоненты для надежного исполнения сделок. Добавим библиотеку Trade.mqh, чтобы обеспечить возможность проведения торговых операций через класс CTrade, который предоставляет методы для открытия и закрытия позиций. Это дополнение гарантирует нам доступ к основным торговым функциям, необходимым для исполнения ордеров на покупку и продажу по нескольким символам. 

Далее мы создаем структуру TradingParameters для организации всех входных параметров для стратегии. Эта структура содержит важные переменные, такие как symbols для списка валютных пар, разделенных запятыми, per_signal_cci и per_trend_cci для периодов индикатора CCI, а также price_cci для применяемого типа цены. Мы также определяем переменные cci_signal_buy_value и cci_signal_sell_value для установки пороговых значений сигналов, tf_signal и tf_trend для таймфреймов, а также переменные управления рисками, такие как take, stop, lots и max_spread. Кроме того, magic обеспечивает уникальную идентификацию сделок, trade_anytime контролирует время их совершения, а debug_mode включает диагностическое логирование, обеспечивая централизованную конфигурацию стратегии.

Наконец, мы создаем структуру SymbolData для управления данными по каждому символу в торговой системе. Эта структура содержит поле name для идентификатора символа, поле last_bar_time для отслеживания метки времени последнего бара, а также хэндлы, такие как cci_signal_handle и ao_trend_handle для индикаторов CCI и AO как на сигнальных, так и на трендовых таймфреймах. Мы также включаем массивы, такие как cci_signal_data и ao_trend_data, для хранения значений индикаторов, что обеспечивает эффективное управление данными при обработке нескольких символов. Эти структуры закладывают основу для модульной и масштабируемой торговой системы. Следующим шагом нам необходимо объявить торговый класс для управляющей логики.

//+------------------------------------------------------------------+
//| Trading Strategy Class                                           | //--- Implement a class for trading strategy logic
//+------------------------------------------------------------------+
class CTradingStrategy {
private:
   CTrade            m_trade;                  //--- Initialize trade object for trading operations
   TradingParameters m_params;                 //--- Store trading parameters
   SymbolData        m_symbols[];              //--- Store array of symbol data
   int               m_array_size;             //--- Track number of symbols
   datetime          m_last_day;               //--- Store last day timestamp
   bool              m_is_new_day;             //--- Indicate new day detection
   int               m_candle_shift;           //--- Set candle shift for signal calculation
   const int         CCI_TREND_BUY_VALUE;      //--- Define constant for CCI trend buy threshold
   const int         CCI_TREND_SELL_VALUE;     //--- Define constant for CCI trend sell threshold
}

Здесь мы реализуем основную логику стратегии, создавая класс CTradingStrategy, которая объединяет весь функционал торговых операций в модульном и организованном виде. Определяем приватные переменные-члены для управления состоянием и операциями стратегии, начиная с m_trade, экземпляра класса CTrade из библиотеки Trade, для обработки задач исполнения сделок, таких как открытие и закрытие позиций. Далее добавляем m_params, экземпляр структуры TradingParameters, для хранения всех параметров конфигурации, таких как списки символов, периоды индикаторов и параметры риска, обеспечивая централизованный доступ к определяемым пользователем входным данным.

Мы также объявляем m_symbols, массив структуры SymbolData, для хранения данных по каждому символу, включая хэндлы индикаторов и буферы данных, что облегчает обработку нескольких символов. Для отслеживания количества символов мы используем m_array_size, а параметры m_last_day и m_is_new_day отвечают за ежедневное обновление временных меток для определения новых торговых дней. Кроме того, мы устанавливаем параметр m_candle_shift, который определяет, генерируются ли сигналы на текущем или предыдущем баре, в зависимости от параметра trade_anytime. Наконец, мы определяем константы CCI_TREND_BUY_VALUE и CCI_TREND_SELL_VALUE, чтобы установить фиксированные пороговые значения CCI для подтверждения тренда, обеспечивая согласованную логику сигналов на протяжении всей стратегии. Теперь, с модификатором приватного доступа, мы можем добавить больше методов, как показано ниже, для повышения их полезности.

void PrintDebug(string text) {              //--- Define method to print debug messages
   if(m_params.debug_mode && !MQLInfoInteger(MQL_OPTIMIZATION)) { //--- Check debug mode and optimization status
      Print(text);                          //--- Output debug message
   }
}

void PrintMessage(string text) {            //--- Define method to print informational messages
   if(!MQLInfoInteger(MQL_OPTIMIZATION)) {  //--- Check if not in optimization mode
      Print(text);                          //--- Output message
   }
}

void PrepareSymbolsList() {                 //--- Define method to prepare symbol list
   string symbols_array[];                  //--- Initialize temporary array for symbols
   ushort sep = StringGetCharacter(",", 0); //--- Get comma separator character
   m_array_size = StringSplit(m_params.symbols, sep, symbols_array); //--- Split symbols string into array
   ArrayResize(m_symbols, m_array_size);    //--- Resize symbol data array
   for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
      m_symbols[i].name = symbols_array[i]; //--- Set symbol name
      m_symbols[i].last_bar_time = 0;       //--- Initialize last bar time
      SymbolSelect(m_symbols[i].name, true); //--- Ensure symbol is in market watch
   }
}

В классе CTradingStrategy реализованы вспомогательные методы для отладки и подготовки символов для мультисимвольной стратегии CCI и AO. Создадим функцию PrintDebug для вывода диагностических сообщений, когда включен m_params.debug_mode, а советник не находится в режиме оптимизации, используя Print при записи параметра text для устранения неполадок. Аналогичным образом определим функцию PrintMessage для записи информационных сообщений, обеспечивая вывод информации только вне режима оптимизации путем проверки MQLInfoInteger(MQL_OPTIMIZATION) и использования Print для входного параметра text.

Введем функцию PrepareSymbolsList для инициализации массива данных символа. Объявим временный symbols_array для хранения разделенных символов, используем StringGetCharacter, чтобы получить разделитель-запятую для sep, и применим StringSplit для преобразования m_params.symbols в symbols_array. Меняем размер m_symbols, используя ArrayResize на основе m_array_size и проводим итерацию, чтобы установить каждый m_symbols[i].name в символ, инициализируем m_symbols[i].last_bar_time в ноль и вызываем SymbolSelect, чтобы убедиться в наличии каждого символа на рынке для совершения сделок. Далее нам необходимо инициализировать и обновить значения индикаторов.

bool InitializeIndicators(int index) {      //--- Define method to initialize indicators
   m_symbols[index].cci_signal_handle = iCCI(m_symbols[index].name, m_params.tf_signal, m_params.per_signal_cci, m_params.price_cci); //--- Create CCI signal indicator
   if(m_symbols[index].cci_signal_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF CCI SIGNAL FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].cci_trend_handle = iCCI(m_symbols[index].name, m_params.tf_trend, m_params.per_trend_cci, m_params.price_cci); //--- Create CCI trend indicator
   if(m_symbols[index].cci_trend_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF CCI TREND FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].ao_signal_handle = iAO(m_symbols[index].name, m_params.tf_signal); //--- Create AO signal indicator
   if(m_symbols[index].ao_signal_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF AO SIGNAL FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].ao_trend_handle = iAO(m_symbols[index].name, m_params.tf_trend); //--- Create AO trend indicator
   if(m_symbols[index].ao_trend_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF AO TREND FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   ArraySetAsSeries(m_symbols[index].cci_signal_data, true); //--- Set CCI signal data as series
   ArraySetAsSeries(m_symbols[index].cci_trend_data, true); //--- Set CCI trend data as series
   ArraySetAsSeries(m_symbols[index].ao_signal_data, true); //--- Set AO signal data as series
   ArraySetAsSeries(m_symbols[index].ao_trend_data, true); //--- Set AO trend data as series
   return true;                             //--- Return success
}
   
bool UpdateIndicatorData(int index) {       //--- Define method to update indicator data
   if(CopyBuffer(m_symbols[index].cci_signal_handle, 0, 0, 3, m_symbols[index].cci_signal_data) < 3) { //--- Copy CCI signal data
      Print("UNABLE TO COPY CCI SIGNAL DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].cci_trend_handle, 0, 0, 3, m_symbols[index].cci_trend_data) < 3) { //--- Copy CCI trend data
      Print("UNABLE TO COPY CCI TREND DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].ao_signal_handle, 0, 0, 3, m_symbols[index].ao_signal_data) < 3) { //--- Copy AO signal data
      Print("UNABLE TO COPY AO SIGNAL DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].ao_trend_handle, 0, 0, 3, m_symbols[index].ao_trend_data) < 3) { //--- Copy AO trend data
      Print("UNABLE TO COPY AO TREND DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   return true;                             //--- Return success
}

Здесь мы реализуем настройку и обновление индикаторов в классе CTradingStrategy. Создадим функцию InitializeIndicators для установки m_symbols[index].cci_signal_handle и m_symbols[index].cci_trend_handle, используя функцию iCCI, а также m_symbols[index].ao_signal_handle и m_symbols[index].ao_trend_handle, используя функцию iAO для символа на index и выводя ошибки с помощью Print, если хэндлы равны INVALID_HANDLE. Настроим массивы данных как временные ряды с помощью ArraySetAsSeries.

Введем функцию UpdateIndicatorData для копирования трех точек данных в m_symbols[index].cci_signal_data, m_symbols[index].cci_trend_data, m_symbols[index].ao_signal_data и m_symbols[index].ao_trend_data, используя функцию CopyBuffer, выводя ошибки с помощью Print, если получено недостаточно данных. Далее нам нужно подсчитать количество ордеров, чтобы отслеживать пороговое значение позиции.

int CountOrders(string symbol, int magic, ENUM_POSITION_TYPE type) { //--- Define method to count orders
   int count = 0;                           //--- Initialize order counter
   for(int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);   //--- Get position ticket
      if(PositionSelectByTicket(ticket)) {   //--- Select position by ticket
         if(PositionGetInteger(POSITION_MAGIC) == magic && //--- Check magic number
            PositionGetString(POSITION_SYMBOL) == symbol && //--- Check symbol
            PositionGetInteger(POSITION_TYPE) == type) { //--- Check position type
            count++;                        //--- Increment counter
         }
      }
   }
   return count;                            //--- Return order count
}

long OpenOrder(string symbol, ENUM_ORDER_TYPE type, double price, double sl, double tp, double lots, int magic, string comment) { //--- Define method to open orders
   long ticket = m_trade.PositionOpen(symbol, type, lots, price, sl, tp, comment); //--- Execute position open
   if(ticket < 0) {                         //--- Check for order failure
      PrintMessage(StringFormat("Info - OrderSend %s %d_%s_%.5f error %.5f_%.5f_%.5f_#%d", //--- Log error
         comment, type, symbol, price, price, sl, tp, GetLastError()));
   } else {                                 //--- Handle successful order
      PrintMessage(StringFormat("Info - OrderSend done. Comment:%s, Type:%d, Sym:%s, Price:%.5f, SL:%.5f, TP:%.5f", //--- Log success
         comment, type, symbol, price, sl, tp));
   }
   return ticket;                           //--- Return order ticket
}

Здесь мы реализуем функции управления сделками в рамках класса CTradingStrategy для обработки подсчета и исполнения ордеров. Создадим функцию CountOrders для подсчета открытых позиций для заданного значения symbol и magic типа ENUM_POSITION_TYPE. Инициализируем count нулем, перебираем позиции с помощью PositionsTotal и PositionGetTicket, а также используем PositionSelectByTicket, чтобы убедиться в том, что PositionGetInteger(POSITION_MAGIC), PositionGetString(POSITION_SYMBOL) и PositionGetInteger(POSITION_TYPE) соответствуют входным значениям, увеличивая count для совпадений перед возвратом результата.

Вводим функцию OpenOrder для исполнения сделок с указанными symbol и type с параметрами price, sl (stop-loss), tp (take-profit), lots, magic и comment. Вызовем m_trade.PositionOpen для открытия позиции, хранения результата в ticket и использования PrintMessage с StringFormat для вывода ошибок, если ticket имеет отрицательное значение, включая GetLastError, или сообщение об успешной установке ордера со значением ticket для отслеживания. После этого мы можем определить публичный класс, из которого будем инициализировать члены.

public:
   CTradingStrategy() : CCI_TREND_BUY_VALUE(-114), CCI_TREND_SELL_VALUE(134) { //--- Initialize constructor with constants
      m_last_day = 0;                          //--- Set initial last day
      m_is_new_day = true;                     //--- Set new day flag
      m_array_size = 0;                        //--- Set initial array size
   }
   
   bool Init() {                               //--- Define initialization method
      m_params.symbols = "EURUSDm,GBPUSDm,AUDUSDm"; //--- Set default symbols
      m_params.per_signal_cci = 20;            //--- Set CCI signal period
      m_params.per_trend_cci = 24;             //--- Set CCI trend period
      m_params.price_cci = PRICE_TYPICAL;      //--- Set CCI applied price
      m_params.cci_signal_buy_value = -90;     //--- Set CCI buy signal threshold
      m_params.cci_signal_sell_value = 130;    //--- Set CCI sell signal threshold
      m_params.tf_signal = PERIOD_M5;          //--- Set signal timeframe
      m_params.tf_trend = PERIOD_H1;           //--- Set trend timeframe
      m_params.use_ao_for_trend = false;       //--- Disable AO trend by default
      m_params.take = 200;                     //--- Set take-profit
      m_params.stop = 300;                     //--- Set stop-loss
      m_params.lots = 0.01;                    //--- Set lot size
      m_params.slip = 5;                       //--- Set slippage
      m_params.max_spread = 20;                //--- Set maximum spread
      m_params.magic = 123456789;              //--- Set magic number
      m_params.trade_anytime = false;          //--- Disable trade anytime
      m_params.comment = "EA_AO_BP";           //--- Set trade comment
      m_params.tester_max_balance = 0;         //--- Set tester balance limit
      m_params.debug_mode = false;             //--- Disable debug mode
      
      m_candle_shift = m_params.trade_anytime ? 0 : 1; //--- Set candle shift based on trade mode
      m_trade.SetExpertMagicNumber(m_params.magic); //--- Set magic number for trade object
      
      PrepareSymbolsList();                    //--- Prepare symbol list
      for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
         if(!InitializeIndicators(i)) {         //--- Initialize indicators
            return false;                      //--- Return failure on error
         }
      }
      PrintMessage("Current Spread on " + Symbol() + ": " + //--- Log current spread
         IntegerToString((int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD)));
      return true;                             //--- Return success
   }

Реализуем логику инициализации в модификаторе публичного доступа. Определим конструктор CTradingStrategy для инициализации CCI_TREND_BUY_VALUE и CCI_TREND_SELL_VALUE в качестве констант, установленных на -114 и 134, соответственно, установим m_last_day на ноль, m_is_new_day - на true, а m_array_size - на ноль для управления начальным состоянием.

Также создаем функцию Init для настройки параметров и ресурсов стратегии. Мы присваиваем значения по умолчанию членам m_params, включая m_params.symbols для валютных пар, m_params.per_signal_cci и m_params.per_trend_cci для периодов CCI, m_params.price_cci как PRICE_TYPICAL, а также пороговые значения, такие как m_params.cci_signal_buy_value и m_params.cci_signal_sell_value.

Устанавливаем m_params.tf_signal на PERIOD_M5, m_params.tf_trend - на PERIOD_H1, а также параметры риска, такие как m_params.take, m_params.stop и m_params.lots. Настраиваем параметр m_candle_shift на основе параметра m_params.trade_anytime, вызываем метод m_trade.SetExpertMagicNumber с параметром m_params.magic и используем метод PrepareSymbolsList для настройки символов. Перебираем значения в массиве m_array_size, чтобы вызвать InitializeIndicators для каждого символа, возвращая false в случае неудачи, и регистрируем текущий спред с помощью функции PrintMessage, прежде чем вернуть true в случае успеха.

Наконец, после инициализации всего необходимого, мы можем определить обработчик событий OnTick по-прежнему внутри нашего класса, чтобы вызывать метод только внутри самого обработчика событий. Поэтому здесь он должен быть публичным. Мы также можете установить его как виртуальный, но пока будем придерживаться простого подхода.

void OnTick() {                             //--- Define tick handling method
   datetime new_day = iTime(Symbol(), PERIOD_D1, 0); //--- Get current day timestamp
   m_is_new_day = (m_last_day != new_day);  //--- Check for new day
   if(m_is_new_day) {                       //--- Handle new day
      m_last_day = new_day;                 //--- Update last day
   }
   
   for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
      bool is_new_bar = false;              //--- Initialize new bar flag
      bool buy_signal = false, sell_signal = false; //--- Initialize signal flags
      bool buy_trend = false, sell_trend = false; //--- Initialize trend flags
      
      datetime new_time = iTime(m_symbols[i].name, m_params.tf_signal, 0); //--- Get current bar time
      if(!m_params.trade_anytime && m_symbols[i].last_bar_time != new_time) { //--- Check for new bar
         is_new_bar = true;                 //--- Set new bar flag
         m_symbols[i].last_bar_time = new_time; //--- Update last bar time
      }
      
      if(!UpdateIndicatorData(i)) continue; //--- Update indicators, skip on failure
      
      double ask = SymbolInfoDouble(m_symbols[i].name, SYMBOL_ASK); //--- Get ask price
      double bid = SymbolInfoDouble(m_symbols[i].name, SYMBOL_BID); //--- Get bid price
      double point = SymbolInfoDouble(m_symbols[i].name, SYMBOL_POINT); //--- Get point value
      long spread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
      
      int total_orders = CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_BUY) + //--- Count buy orders
                        CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_SELL); //--- Count sell orders
      
      // Generate signals
      buy_signal = m_symbols[i].cci_signal_data[m_candle_shift+1] < m_params.cci_signal_buy_value && //--- Check CCI buy signal condition
                  m_symbols[i].cci_signal_data[m_candle_shift] > m_params.cci_signal_buy_value && //--- Confirm CCI buy signal
                  m_symbols[i].ao_signal_data[m_candle_shift+1] < m_symbols[i].ao_signal_data[m_candle_shift]; //--- Confirm AO buy signal
                  
      sell_signal = m_symbols[i].cci_signal_data[m_candle_shift+1] > m_params.cci_signal_sell_value && //--- Check CCI sell signal condition
                   m_symbols[i].cci_signal_data[m_candle_shift] < m_params.cci_signal_sell_value && //--- Confirm CCI sell signal
                   m_symbols[i].ao_signal_data[m_candle_shift+1] > m_symbols[i].ao_signal_data[m_candle_shift]; //--- Confirm AO sell signal
      
      buy_trend = m_symbols[i].cci_trend_data[m_candle_shift+1] < m_symbols[i].cci_signal_data[m_candle_shift] && //--- Check CCI trend buy condition
                 m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && //--- Confirm CCI trend buy threshold
                 m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && //--- Confirm CCI trend sell threshold
                 (!m_params.use_ao_for_trend || //--- Check AO trend condition
                  (m_symbols[i].ao_trend_data[m_candle_shift+1] < m_symbols[i].ao_trend_data[m_candle_shift])); //--- Confirm AO trend buy
                  
      sell_trend = m_symbols[i].cci_trend_data[m_candle_shift+1] > m_symbols[i].cci_signal_data[m_candle_shift] && //--- Check CCI trend sell condition
                  m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && //--- Confirm CCI trend buy threshold
                  m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && //--- Confirm CCI trend sell threshold
                  (!m_params.use_ao_for_trend || //--- Check AO trend condition
                   (m_symbols[i].ao_trend_data[m_candle_shift+1] > m_symbols[i].ao_trend_data[m_candle_shift])); //--- Confirm AO trend sell
      
      // Execute trades
      if(spread < m_params.max_spread && total_orders == 0 && //--- Check spread and open orders
         (m_params.trade_anytime || is_new_bar)) { //--- Check trade timing
         if(buy_signal && buy_trend) {         //--- Check buy conditions
            double sl = m_params.stop == 0 ? 0 : ask - m_params.stop * point; //--- Calculate stop-loss
            double tp = m_params.take == 0 ? 0 : ask + m_params.take * point; //--- Calculate take-profit
            OpenOrder(m_symbols[i].name, ORDER_TYPE_BUY, ask, sl, tp, m_params.lots, //--- Open buy order
                     m_params.magic, "Open BUY " + m_params.comment);
         }
         if(sell_signal && sell_trend) {       //--- Check sell conditions
            double sl = m_params.stop == 0 ? 0 : bid + m_params.stop * point; //--- Calculate stop-loss
            double tp = m_params.take == 0 ? 0 : bid - m_params.take * point; //--- Calculate take-profit
            OpenOrder(m_symbols[i].name, ORDER_TYPE_SELL, bid, sl, tp, m_params.lots, //--- Open sell order
                     m_params.magic, "Open SELL " + m_params.comment);
         }
      }
      
      // Debug output
      if((m_params.trade_anytime || is_new_bar) && (buy_signal || sell_signal)) { //--- Check debug conditions
         PrintDebug(StringFormat("Debug - IsNewBar: %b - candle_shift: %d - buy_signal: %b - " //--- Log debug information
                               "sell_signal: %b - buy_trend: %b - sell_trend: %b",
                               is_new_bar, m_candle_shift, buy_signal, sell_signal, 
                               buy_trend, sell_trend));
      }
   }
}

Здесь мы реализуем торговую логику для мультисимвольной стратегии CCI и AO, создавая функцию OnTick в классе CTradingStrategy. Мы получаем метку времени текущего дня с помощью iTime в new_day и обновляем m_is_new_day и m_last_day, чтобы отслеживать ежедневные изменения. Для каждого символа в m_array_size мы инициализируем флаги is_new_bar, buy_signal, sell_signal, buy_trend и sell_trend, а также используем iTime для обнаружения новых баров, обновляя m_symbols[i].last_bar_time, если m_params.trade_anytime равен false.

Вызываем UpdateIndicatorData для обновления индикаторов, пропуская вызовы при сбоях, и получаем значения ask, bid, point и spread с SymbolInfoDouble и SymbolInfoInteger. Мы вычисляем total_orders, используя CountOrders для позиций на покупку и продажу. Мы устанавливаем buy_signal, сверяя m_symbols[i].cci_signal_data с m_params.cci_signal_buy_value и m_symbols[i].ao_signal_data для подтверждения, а sell_signal — с m_params.cci_signal_sell_value. Мы определяем buy_trend и sell_trend, используя m_symbols[i].cci_trend_data, CCI_TREND_BUY_VALUE, CCI_TREND_SELL_VALUE и, при необходимости, m_symbols[i].ao_trend_data.

Если spread меньше m_params.max_spread, total_orders равно нулю и торговля разрешена, мы вычисляем sl и tp с помощью m_params.stop и m_params.take, а затем вызываем функцию OpenOrder для совершения сделок на покупку или продажу. Регистрируем состояния сигналов с помощью функции PrintDebug и StringFormat, когда запускается отладка, что обеспечивает эффективный мониторинг торговли. Для закрытия позиций мы реализуем следующую функцию.

bool CloseAllTrades() {                     //--- Define method to close all trades
   for(int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);   //--- Get position ticket
      if(PositionSelectByTicket(ticket) &&   //--- Select position
         PositionGetInteger(POSITION_MAGIC) == m_params.magic) { //--- Check magic number
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get position type
         if(type == POSITION_TYPE_BUY || type == POSITION_TYPE_SELL) { //--- Check position type
            m_trade.PositionClose(ticket);   //--- Close position
            PrintMessage("Position close " + IntegerToString(ticket)); //--- Log closure
         }
      }
   }
   for(int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders
      ulong ticket = OrderGetTicket(i);      //--- Get order ticket
      if(OrderSelect(ticket) && OrderGetInteger(ORDER_MAGIC) == m_params.magic) { //--- Select order and check magic
         ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); //--- Get order type
         if(type >= ORDER_TYPE_BUY_STOP && type <= ORDER_TYPE_SELL_LIMIT) { //--- Check order type
            m_trade.OrderDelete(ticket);   //--- Delete order
            PrintMessage("Order delete " + IntegerToString(ticket)); //--- Log deletion
         }
      }
   }
   return true;                             //--- Return success
}

Здесь мы реализуем функционал закрытия сделок для мультисимвольной стратегии CCI и AO, создав функцию CloseAllTrades внутри класса CTradingStrategy. Перебираем все открытые позиции с помощью PositionsTotal и получаем тикет каждой позиции с помощью PositionGetTicket в ticket. Для каждой позиции, выбранной с помощью PositionSelectByTicket, проверяем, совпадает ли PositionGetInteger(POSITION_MAGIC) с m_params.magic. Если PositionGetInteger(POSITION_TYPE) равен POSITION_TYPE_BUY или POSITION_TYPE_SELL, вызываем m_trade.PositionClose для закрытия и регистрируем действие с помощью PrintMessage и IntegerToString.

Перебираем отложенные ордера с помощью OrdersTotal, получая тикет каждого ордера с OrderGetTicket в ticket. Если OrderSelect успешно выполнен и OrderGetInteger(ORDER_MAGIC) равен m_params.magic, проверяем находится ли OrderGetInteger(ORDER_TYPE) между ORDER_TYPE_BUY_STOP и ORDER_TYPE_SELL_LIMIT, затем используем m_trade.OrderDelete для удаления ордера и отобразим это с помощью PrintMessage и IntegerToString. Вернем true, что означает успешное закрытие всех соответствующих сделок и ордеров. На этом всё! Теперь нам осталось только использовать эти классы.

//+------------------------------------------------------------------+
//| Global Variables and Functions                                   | //--- Define global variables and functions
//+------------------------------------------------------------------+
CTradingStrategy g_strategy;                   //--- Initialize global strategy object

//+------------------------------------------------------------------+
//| Expert Advisor Functions                                         | //--- Define EA core functions
//+------------------------------------------------------------------+
int OnInit() {                                 //--- Define initialization function
   return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED; //--- Initialize strategy and return status
}

Мы завершаем реализацию мультисимвольной стратегии CCI и AO, определяя глобальные и основные функции советника. Объявим g_strategy как глобальный экземпляр класса CTradingStrategy для управления всеми торговыми операциями по различным символам, обеспечивая централизованный доступ к логике и состоянию стратегии на протяжении всего жизненного цикла советника.

Мы также создаем функцию OnInit для обработки инициализации советника. Вызываем функцию Init класса g_strategy для установки параметров, символов и индикаторов и возвращаем INIT_SUCCEEDED при успехе или INIT_FAILED в случае ошибки, обеспечивая корректный статус инициализации для платформы MetaTrader 5. При инициализации мы получаем следующее.

ИНИЦИАЛИЗАЦИЯ СОВЕТНИКА

Как видим, у нас есть все данные для трех выбранных нами символов. Теперь мы можем перейти к обработчику событий OnTick, который возьмет на себя основную работу.

//+------------------------------------------------------------------+
//| Expert Advisor Functions                                         | //--- Define EA core functions
//+------------------------------------------------------------------+
int OnInit() {                                                         //--- Define initialization function
   return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED;            //--- Initialize strategy and return status
}

//+------------------------------------------------------------------+

Для инициализации торговых параметров, списков символов и индикаторов мы просто вызываем функцию Init глобального объекта g_strategy, являющегося экземпляром класса CTradingStrategy. Возвращаем INIT_SUCCEEDED, если инициализация прошла успешно, или INIT_FAILED при ошибке. Таким образом платформа MetaTrader 5 получает соответствующий статус для продолжения или остановки исполнения ордера. В результате компиляции мы получаем следующее.

ОКОНЧАТЕЛЬНЫЙ РЕЗУЛЬТАТ СДЕЛОК

Из изображения видно, что мы можем открывать сделки на основе подтвержденных сигналов для соответствующих символов и управлять ими индивидуально. Остается только протестировать программу на истории.


Тестирование на истории и оптимизация

После тщательного тестирования на истории мы получили следующие результаты.

График тестирования на истории:

График

Отчет о тестировании на истории:

ОТЧЕТ


Заключение

Мы разработали программу на MQL5, которая автоматизирует мультисимвольную стратегию CCI и AO, совершая сделки по нескольким валютным парам с использованием класса CTradingStrategy для генерации сигналов на основе индикаторов CCI и AO, а также надежное управление сделками с помощью библиотеки CTrade. Благодаря модульным компонентам и средствам контроля рисков, таким как проверка спреда и настройка стоп-лосса, эта система предлагает масштабируемую структуру, которую можно настраивать, изменяя параметры или интегрируя дополнительные фильтры.

Предупреждение: Статья предназначена исключительно для образовательных целей. Торговля сопряжена со значительными финансовыми рисками, а волатильность рынка может привести к убыткам. Тщательное тестирование на исторических данных и внимательное управление рисками имеют решающее значение перед внедрением этой программы на реальных рынках.

Используя представленные идеи, вы можете усовершенствовать эту мультисимвольную торговую систему или адаптировать ее архитектуру для создания новых стратегий, тем самым повысив свой уровень знаний в области алгоритмической торговли на MQL5.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18604

Прикрепленные файлы |
Машинное обучение и Data Science (Часть 43): Поиск скрытых паттернов в индикаторах с помощью моделей латентных гауссовых смесей LGMM Машинное обучение и Data Science (Часть 43): Поиск скрытых паттернов в индикаторах с помощью моделей латентных гауссовых смесей LGMM
У вас когда-нибудь возникало ощущение, что за графиком скрывается что-то большее, какая-то закономерность? Какой-то секретный код, расшифровав который, вы могли бы точно понять, куда движутся цены? Представляю вашему вниманию LGMM — детектор скрытых закономерностей на рынке. Эта модель машинного обучения помогает выявлять такие скрытые закономерности на рынке.
Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (STCA) Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (STCA)
Статья показывает, как применить STCA к рынку: цель формируется сценарием, история задаётся эмбеддингами, а внимание вычисляется через Single-query Target-to-history Cross-Attention. Интеграция с FlashAttention на OpenCL переносит проекции на запросы и избегает формирования K/V для всей истории. Практический эффект — линейная сложность, экономия памяти и ускорение при анализе тысяч баров.
Преодоление ограничений машинного обучения (Часть 5): Краткий обзор кросс-валидации временных рядов Преодоление ограничений машинного обучения (Часть 5): Краткий обзор кросс-валидации временных рядов
В этой серии статей мы рассмотрим проблемы, с которыми сталкиваются алгоритмические трейдеры при внедрении торговых стратегий, основанных на машинном обучении. Некоторые проблемы в нашем сообществе остаются незамеченными, поскольку требуют более глубокого технического понимания. Сегодняшнее обсуждение служит отправной точкой для изучения "белых пятен" кросс-валидации в машинном обучении. Несмотря на то, что этот шаг часто рассматривается как рутинный, при небрежном обращении он может легко привести к вводящим в заблуждение или недостаточно оптимальным результатам. В этой статье кратко рассматриваются основы кросс-валидации временных рядов, чтобы подготовить нас к более глубокому пониманию скрытых слепых зон.
Неопределенность как модель (Часть 1): Случайные величины — язык неопределенности Неопределенность как модель (Часть 1): Случайные величины — язык неопределенности
В статье системно излагается теория случайных величин, служащая базой для анализа и моделирования неопределенности на финансовых рынках. Рассматриваются определения и свойства одномерных величин, функции распределения (CDF) и плотности (PDF), а также различия между дискретными, непрерывными и смешанными моделями. Теоретический материал опирается на интуитивные аналогии с массой и плотностью. Приложение к статье содержит практические примеры использования стандартной библиотеки MQL5 для расчета вероятностей, квантилей и моментов распределений. Также в нем демонстрируются графические возможности платформы MetaTrader 5 для визуального анализа данных через построение кривых PDF, CDF и графиков QQ-Plot.