Автоматизация торговых стратегий на MQL5 (Часть 20): Мультисимвольная стратегия с использованием CCI и AO
Введение
В предыдущей статье (Часть 19) мы рассмотрели стратегию скальпинга на коррекции на основе конвертов, а именно - исполнение сделок и управление рисками, закончив ее автоматизацию на языке MQL5. В части 20 мы представим мультисимвольную торговую стратегию, использующую Commodity Channel Index (CCI) и Awesome Oscillator (AO) для фиксации разворотов тренда по нескольким валютным парам. Мы рассмотрим следующие темы:
- Стратегическая дорожная карта и архитектура
- Реализация средствами MQL5
- Тестирование на истории и оптимизация
- Заключение
В финале у вас будет надежная торговая система на 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Машинное обучение и Data Science (Часть 43): Поиск скрытых паттернов в индикаторах с помощью моделей латентных гауссовых смесей LGMM
Нейросети в трейдинге: Оптимизация Cross-Attention для анализа длинных последовательностей рынка (STCA)
Преодоление ограничений машинного обучения (Часть 5): Краткий обзор кросс-валидации временных рядов
Неопределенность как модель (Часть 1): Случайные величины — язык неопределенности
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования