
Автоматизация торговых стратегий с помощью MQL5 (Часть 2): Система прорыва Кумо с Ichimoku и Awesome Oscillator
Введение
В предыдущей статье (первой в серии) мы рассмотрели автоматизацию системы Profitunity (Торговый хаос Билла Вильямса). В этой статье мы продемонстрируем, как превратить стратегию прорыва Кумо (Kumo Breakout Strategy) в полнофункциональный советник на MetaQuotes Language 5 (MQL5). Стратегия прорыва Кумо использует индикатор Ichimoku Kinko Hyo indicator для определения потенциальных разворотов рынка и продолжения тренда с учетом движения цен относительно Кумо (облака) — динамической зоны поддержки и сопротивления, образованной линиями Senkou Span A и Senkou Span B. Использование индикатора Awesome Oscillator в качестве инструмента подтверждения тренда позволяет отсортировать ложные сигналы и повысить точность входов и выходов из сделок. Эту стратегию широко используют трейдеры, желающие извлечь выгоду из сильных импульсных движений рынка.
Мы рассмотрим процесс кодирования логики стратегии, управления сделками и улучшения контроля рисков с помощью трейлинг-стопов. К концу этой статьи у вас будет четкое понимание того, как автоматизировать стратегию, проверить ее эффективность с помощью тестера стратегий MQL5 и доработать ее для достижения оптимальных результатов. Для облегчения понимания мы разделили процесс на следующие разделы.
- Обзор стратегии прорыва Кумо
- Реализация стратегии прорыва Kumo на MQL5
- Тестирование и оптимизация стратегии
- Заключение
Обзор стратегии прорыва Кумо
Стратегия прорыва Кумо — это подход следования за трендом, направленный на извлечение выгоды из движения цен за пределами облака Kumo. Кумо, также называемое облаком Кумо, представляет собой затененную область между линиями Senkou Span A и Senkou Span B индикатора Ichimoku Kinko Hyo, которая действует как динамические уровни поддержки и сопротивления. Когда цена прорывается выше Кумо, это сигнализирует о возможном бычьем тренде, тогда как прорыв ниже указывает на возможный медвежий тренд. Что касается индикатора, то для его настройки используются следующие параметры: Tenkan-sen = 8, Kijun-sen = 29 и Senkou-span B = 34. Настройки:
Чтобы отфильтровать ложные сигналы, стратегия также интегрирует индикатор Awesome Oscillator для предоставления дополнительного подтверждения для входа. Awesome Oscillator определяет сдвиги импульса, измеряя разницу между 34-периодной и 5-периодной простой скользящей средней, построенной по медианной цене. Сигналы на покупку подтверждаются, когда осциллятор переходит из отрицательного состояния в положительное, а сигналы на продажу — когда он переходит из положительного состояния в отрицательное. Объединяя прорывы Кумо с подтверждением импульса от Awesome Oscillator, стратегия стремится сократить количество ложных сигналов и повысить вероятность успешных сделок.
В собранном виде это выглядит так, как показано на графике ниже.
Для выхода из позиций мы используем логику смены импульса. Когда осциллятор переходит из положительного значения в отрицательное, это указывает на смену бычьего импульса, и мы закрываем существующие позиции на покупку. Аналогично, когда осциллятор переходит из отрицательного значения в положительное, мы закрываем существующие позиции на продажу.
Такой подход особенно эффективен на трендовых рынках, где динамика сильна. Однако в периоды консолидации стратегия может генерировать ложные сигналы из-за изменчивого характера движения цены в пределах Кумо и осциллятора. В результате мы можем применять дополнительные фильтры или методы управления рисками, такие как трейлинг-стопы, для смягчения потенциальных просадок. Понимание этих основных принципов необходимо для успешной реализации стратегии в качестве автоматизированного советника.
Реализация стратегии прорыва Kumo на MQL5
С теорией покончено. Давайте создадим советника на MQL5 для MetaTrader 5 .
В терминале MetaTrader 5 выберите "Сервис" > "Редактор MetaQuotes Language" или просто нажмите F4. Также вы можете щелкнуть по иконке IDE (интегрированная среда разработки) на панели инструментов. Откроется среда разработки на MQL5, которая позволяет писать торговых роботов, технические индикаторы, скрипты и библиотеки функций. На панели инструментов выберите "Файл" - "Новый файл" или нажмите CTRL + N, чтобы создать новый документ. Также вы можете нажать на иконку "Создать" в панели инструментов. Откроется окно Мастера MQL.
В открывшемся Мастере выберите Советник (шаблон) и нажмите Далее. В общих свойствах укажите имя файла вашего советника. Чтобы указать или создать папку, если она не существует, используйте обратную косую черту перед именем советника. По умолчанию указана папка Experts\. Это значит, что наш советник будет создан в папке Experts. Остальные разделы довольно просты, но вы можете перейти по ссылке в нижней части Мастера, чтобы узнать детали.
После указания имени файла советника нажмите "Далее" > "Далее" > "Готово". Мы готовы к воплощению стратегии в коде.
Начнем с определения некоторых метаданных о советнике (EA). Сюда входит название советника, информация об авторских правах и ссылка на сайт MetaQuotes. Мы также указываем версию советника. Сейчас это 1.00.
//+------------------------------------------------------------------+ //| 1. Kumo Breakout EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00"
Это позволит отобразить метаданные системы при загрузке программы. Затем мы можем перейти к добавлению некоторых глобальных переменных, которые будем использовать в программе. Прежде всего используем #include в начале исходного кода. Он даст нам доступ к классу CTrade, который мы будем использовать для создания торгового объекта. Доступ нужен для открытия сделок.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
Препроцессор заменит строку #include<Trade/Trade.mqh> содержимым файла Trade.mqh. Угловые скобки указывают, что файл Trade.mqh будет взят из стандартного каталога (обычно это каталог_установки_терминала\MQL5\Include). Текущий каталог не включен в поиск. Строку можно разместить в любом месте программы, но обычно все включения размещаются в начале исходного кода для лучшей структуры кода и удобства ссылок. Благодаря разработчикам MQL5 объявление объекта obj_Trade класса CTrade предоставит нам легкий доступ к методам, содержащимся в этом классе.
После этого нам необходимо объявить несколько важных хендлов индикаторов, которые мы будем использовать в торговой системе.
int handle_Kumo = INVALID_HANDLE; //--- Initialize the Kumo indicator handle to an invalid state int handle_AO = INVALID_HANDLE; //--- Initialize the Awesome Oscillator handle to an invalid state
Здесь мы объявляем две целые переменные, handle_Kumo и handle_AO, которые мы используем для хранения хэндлов индикатора Kumo (Ichimoku) и Awesome Oscillator (AO) соответственно. Мы инициализируем обе переменные значением INVALID_HANDLE, предопределенной константой в MQL5, представляющей недействительный или неинициализированный хэндл. Это важно, поскольку при создании индикатора система возвращает хэндл, позволяющий нам взаимодействовать с индикатором. Если хэндл равен INVALID_HANDLE, создание индикатора не удалось или он не был правильно инициализирован. Изначально устанавливая хэндлы на INVALID_HANDLE, мы гарантируем, что в дальнейшем сможем проверить наличие проблем инициализации и обработать ошибки соответствующим образом.
Далее нам необходимо инициализировать массивы, в которых будут храниться полученные значения.
double senkouSpan_A[]; //--- Array to store Senkou Span A values double senkouSpan_B[]; //--- Array to store Senkou Span B values double awesome_Oscillator[]; //--- Array to store Awesome Oscillator values
Опять же, в глобальной области, мы охватываем три массива: senkouSpan_A, senkouSpan_B и awesome_Oscillator, которые мы используем для хранения значений Senkou Span A, Senkou Span B и Awesome Oscillator соответственно. Мы определяем эти массивы как типы double, то есть они будут содержать значения с плавающей точкой, что подходит для хранения результатов расчетов индикаторов. Мы используем массивы senkouSpan_A и senkouSpan_B для хранения значений компонентов Senkou Span A и B индикатора Ишимоку. Напротив, массив awesome_Oscillator хранит значения, рассчитанные Awesome Oscillator. Объявляя эти массивы, мы готовимся к сохранению значений индикаторов, чтобы впоследствии иметь к ним доступ и использовать их в нашей торговой логике.
Это все переменные, которые нам нужны в глобальной области. Теперь мы можем инициализировать хэндлы индикатора в обработчике событий OnInit, представляющем собой функцию, обрабатывающую цикл инициализации.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- return(INIT_SUCCEEDED); //--- Return successful initialization }
Это обработчик событий, который вызывается всякий раз, когда по какой-либо причине инициализируется индикатор. Внутри него мы инициализируем хэндлы индикаторов. Начнем с хэндла Kumo.
//--- Initialize the Ichimoku Kumo indicator handle_Kumo = iIchimoku(_Symbol,_Period,8,29,34); if (handle_Kumo == INVALID_HANDLE){ //--- Check if Kumo indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE KUMO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure }
Здесь мы инициализируем handle_Kumo, вызывая функцию iIchimoku, которая создает экземпляр индикатора Ишимоку Кумо для текущего символа (_Symbol) и периода (_Period). Мы используем конкретные параметры для индикатора Ишимоку: 8, 29 и 34 периода для Tenkan-sen, Kijun-sen и Senkou Span B соответственно, как было показано ранее.
После вызова iIchimoku функция возвращает хэндл, который мы сохраняем в handle_Kumo. Затем проверяем, равен ли handle_Kumo INVALID_HANDLE, что будет означать, что инициализация индикатора не удалась. Если хэндл недействителен, выводим сообщение об ошибке с помощью функции Print, которая указывает причину сбоя и возвращает константу INIT_FAILED, сигнализирующую о том, что процесс инициализации не удался. Аналогично инициализируем индикатор осциллятора.
//--- Initialize the Awesome Oscillator handle_AO = iAO(_Symbol,_Period); if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure }
Чтобы инициализировать осциллятор, мы вызываем функцию iAO и передаем в качестве параметров по умолчанию только символ и период. Затем мы продолжаем остальную логику инициализации, используя тот же формат, что и для хэндла Kumo. После завершения инициализации мы можем перейти к настройке массивов хранения как временных рядов.
ArraySetAsSeries(senkouSpan_A,true); //--- Set Senkou Span A array as a time series ArraySetAsSeries(senkouSpan_B,true); //--- Set Senkou Span B array as a time series ArraySetAsSeries(awesome_Oscillator,true); //--- Set Awesome Oscillator array as a time series
Мы используем функцию ArraySetAsSeries для установки массивов senkouSpan_A, senkouSpan_B и awesome_Oscillator в качестве массивов временных рядов. Устанавливая эти массивы как временные ряды, мы гарантируем, что самые последние значения будут храниться в начале массива, а более старые значения будут перемещаться в конец. Это важно, поскольку в MQL5 данные временных рядов обычно организованы таким образом, что доступ к последним значениям осуществляется в первую очередь (по индексу 0), что упрощает извлечение самых последних данных для принятия торговых решений.
Мы вызываем ArraySetAsSeries для каждого массива, передавая true в качестве второго аргумента, чтобы включить это поведение временного ряда. Это позволяет нам работать с данными таким образом, который соответствует типичным торговым стратегиям, где нам часто сначала нужно получить доступ к самым последним значениям. Наконец, когда все инициализации пройдены, мы можем напечатать сообщение в журнале, указывающее на готовность.
Print("SUCCESS. ",__FILE__," HAS BEEN INITIALIZED."); //--- Log successful initialization
После успешной инициализации мы используем функцию Print для регистрации сообщения, указывающего на то, что процесс инициализации прошел успешно. Сообщение включает строку SUCCESS., за которой следует специальная предопределенная переменная __FILE__, которая представляет имя текущего файла исходного кода. Используя __FILE__, мы можем динамически вставлять имя файла в сообщение журнала, что может помочь отладить или отслеживать процесс инициализации в более крупных проектах с несколькими файлами. Сообщение будет выведено на терминал или в файл журнала, подтверждая, что инициализация была успешно завершена. Этот шаг помогает обеспечить получение надлежащей обратной связи о состоянии процесса инициализации, что упрощает выявление потенциальных проблем в коде.
Полный фрагмент кода инициализации выглядит следующим образом:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Initialize the Ichimoku Kumo indicator handle_Kumo = iIchimoku(_Symbol,_Period,8,29,34); if (handle_Kumo == INVALID_HANDLE){ //--- Check if Kumo indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE KUMO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure } //--- Initialize the Awesome Oscillator handle_AO = iAO(_Symbol,_Period); if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure } ArraySetAsSeries(senkouSpan_A,true); //--- Set Senkou Span A array as a time series ArraySetAsSeries(senkouSpan_B,true); //--- Set Senkou Span B array as a time series ArraySetAsSeries(awesome_Oscillator,true); //--- Set Awesome Oscillator array as a time series Print("SUCCESS. ",__FILE__," HAS BEEN INITIALIZED."); //--- Log successful initialization //--- return(INIT_SUCCEEDED); //--- Return successful initialization }
Это дает следующий результат.
Поскольку мы инициализировали массивы и хэндлы хранения данных, мы не хотим сохранять их после деинициализации программы, поскольку в этом случае мы займем ненужные ресурсы. Обработка происходит в обработчике событий OnDeinit, который вызывается всякий раз, когда программа деинициализируется, независимо от причины.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- Free memory allocated for Senkou Span A and B arrays ArrayFree(senkouSpan_A); ArrayFree(senkouSpan_B); //--- Free memory allocated for the Awesome Oscillator array ArrayFree(awesome_Oscillator); }
Внутри функции OnDeinit мы выполняем очистку, чтобы освободить всю память, выделенную в процессе инициализации. В частности, мы используем функцию ArrayFree, чтобы освободить память для массивов senkouSpan_A, senkouSpan_B и awesome_Oscillator. Ранее эти массивы использовались для хранения значений индикатора Ichimoku Kumo и Awesome Oscillator, а теперь, когда они больше не нужны, мы освобождаем память, чтобы предотвратить утечки ресурсов. Благодаря этому мы гарантируем, что программа эффективно управляет системными ресурсами и избегает ненужного использования памяти после того, как советник перестает быть активным.
Теперь нам остается только заняться торговой логикой: извлекать значения индикаторов и анализировать их для принятия торговых решений. Мы занимаемся этим в обработчике событий OnTick, который вызывается всякий раз, когда появляется новый тик или просто изменяется цена. Первый шаг, который нам необходимо сделать, — это извлечь точки данных из хэндлов индикатора и сохранить их для дальнейшего анализа.
//--- Copy data for Senkou Span A from the Kumo indicator if (CopyBuffer(handle_Kumo,SENKOUSPANA_LINE,0,2,senkouSpan_A) < 2){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM SENKOUSPAN A LINE. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails } //--- Copy data for Senkou Span B from the Kumo indicator if (CopyBuffer(handle_Kumo,SENKOUSPANB_LINE,0,2,senkouSpan_B) < 2){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM SENKOUSPAN B LINE. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails }
Здесь мы используем функцию CopyBuffer для копирования данных из линий Senkou Span A и Senkou Span B в массивы senkouSpan_A и senkouSpan_B соответственно. Первый аргумент, передаваемый CopyBuffer, — это хэндл индикатора handle_Kumo, который ссылается на инициализированный индикатор Kumo. Второй аргумент указывает, какую строку данных копировать: SENKOUSPANA_LINE для Senkou Span A и SENKOUSPANB_LINE для Senkou Span B. Третий аргумент — начальный индекс, с которого начинается копирование; он равен 0, чтобы начать с самых последних данных. Четвертый аргумент указывает количество копируемых точек данных, в данном случае - 2. Последний аргумент — массив, в котором будут сохранены данные: senkouSpan_A или senkouSpan_B.
После вызова CopyBuffer мы проверяем, возвращает ли функция значение меньше 2, что указывает на то, что запрошенные данные не были успешно скопированы. Если это происходит, мы регистрируем сообщение об ошибке с помощью функции Print, указывающей, что данные не могут быть скопированы из соответствующей линии Senkou Span, а затем мы выходим из функции, используя return. Это гарантирует, что в случае сбоя копирования данных мы корректно обработаем ошибку, зарегистрировав ее и остановив дальнейшее выполнение функции.
Мы используем ту же логику для извлечения значений осциллятора.
//--- Copy data from the Awesome Oscillator if (CopyBuffer(handle_AO,0,0,3,awesome_Oscillator) < 3){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM AWESOME OSCILLATOR. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails }
Используем функцию CopyBuffer для копирования данных из индикатора Awesome Oscillator (AO) в массив awesome_Oscillator. Первый аргумент, передаваемый функции CopyBuffer, — это хэндл индикатора handle_AO, который ссылается на инициализированный Awesome Oscillator. Второй аргумент указывает строку данных или индекс буфера для копирования, в данном случае это 0, поскольку Awesome Oscillator имеет один буфер данных. Третий аргумент — начальный индекс, равный 0, чтобы начать копирование с самых последних данных. Четвертый аргумент указывает количество копируемых точек данных, в данном случае равное 3, что означает, что мы хотим скопировать три последних значения. Последний аргумент — массив awesome_Oscillator, в котором будут сохранены скопированные данные. Если извлеченных данных меньше запрошенных, мы регистрируем сообщение об ошибке и возвращаем его.
Если у нас есть все необходимые данные, мы можем продолжить их обработку. Первое, что нам нужно сделать, — это определить логику, которую мы будем использовать, чтобы убедиться, что мы анализируем данные один раз при каждой генерации нового полного бара, а не на каждом тике. Добавим эту логику в функцию.
//+------------------------------------------------------------------+ //| IS NEW BAR FUNCTION | //+------------------------------------------------------------------+ bool isNewBar(){ static int prevBars = 0; //--- Store previous bar count int currBars = iBars(_Symbol,_Period); //--- Get current bar count for the symbol and period if (prevBars == currBars) return (false); //--- If bars haven't changed, return false prevBars = currBars; //--- Update previous bar count return (true); //--- Return true if new bar is detected }
Определим логическую функцию isNewBar, которая используется для определения появления нового бара на графике для указанного символа и периода. Внутри этой функции мы объявляем статическую переменную prevBars, которая хранит количество баров из предыдущей проверки. Ключевое слово static гарантирует, что переменная сохраняет свое значение между вызовами функций.
Затем используем функцию iBars для получения текущего количества баров на графике для заданного символа (_Symbol) и периода (_Period). Результат сохраняется в переменной currBars. Если количество баров не изменилось (то есть prevBars равно currBars), возвращаем false, что указывает на то, что новый бар не появился. Если количество баров изменилось, обновляем prevBars текущим количеством баров и возвращаем true, сигнализируя об обнаружении нового бара. Мы можем вызвать эту функцию внутри обработчика событий тика и проанализировать его.
//--- Check if a new bar has formed if (isNewBar()){ //--- Determine if the AO has crossed above or below zero bool isAO_Above = awesome_Oscillator[1] > 0 && awesome_Oscillator[2] < 0; bool isAO_Below = awesome_Oscillator[1] < 0 && awesome_Oscillator[2] > 0; //--- }
Здесь мы проверяем, образовался ли новый бар, вызывая функцию isNewBar. Если обнаружен новый бар (т.е. isNewBar возвращает true), мы переходим к определению поведения Awesome Oscillator (AO).
Определяем две логические переменные: isAO_Above и isAO_Below. Переменная isAO_Above устанавливается в значение true, если предыдущее значение Awesome Oscillator (awesome_Oscillator[1]) больше нуля, а предыдущее значение (awesome_Oscillator[2]) меньше нуля. Это условие проверяет, пересек ли АО уровень выше нуля, что указывает на потенциальный бычий сигнал. Аналогично, isAO_Below устанавливается в значение true, если предыдущее значение AO (awesome_Oscillator[1]) меньше нуля, а предыдущее значение (awesome_Oscillator[2]) больше нуля, что указывает на то, что AO пересекло линию ниже нуля, что может сигнализировать о медвежьем движении. Затем мы можем использовать тот же метод для настройки другой логики.
//--- Determine if the Kumo is bullish or bearish bool isKumo_Above = senkouSpan_A[1] > senkouSpan_B[1]; bool isKumo_Below = senkouSpan_A[1] < senkouSpan_B[1]; //--- Determine buy and sell signals based on conditions bool isBuy_Signal = isAO_Above && isKumo_Below && getClosePrice(1) > senkouSpan_A[1] && getClosePrice(1) > senkouSpan_B[1]; bool isSell_Signal = isAO_Below && isKumo_Above && getClosePrice(1) < senkouSpan_A[1] && getClosePrice(1) < senkouSpan_B[1];
Здесь мы определяем условия бычьего или медвежьего Кумо (Ишимоку). Сначала определяем две логические переменные - isKumo_Above и isKumo_Below. Переменная isKumo_Above устанавливается в true, если предыдущее значение Senkou Span A (senkouSpan_A[1]) больше предыдущего значения Senkou Span B (senkouSpan_B[1]), что указывает на бычий Kumo (бычьи настроения рынка). С другой стороны, isKumo_Below устанавливается в true, если Senkou Span A меньше Senkou Span B, что указывает на медвежье Кумо (медвежьи настроения рынка).
Далее мы определяем условия потенциальных сигналов покупки и продажи. Сигнал на покупку (isBuy_Signal) устанавливается в true, если выполняются следующие условия: Awesome Oscillator пересек линию выше нуля (isAO_Above), Kumo медвежий (isKumo_Below) и цена закрытия предыдущего бара находится выше как Senkou Span A, так и Senkou Span B. Это предполагает потенциальное восходящее движение цены, несмотря на медвежий Kumo. Сигнал на продажу (isSell_Signal) равен true, если Awesome Oscillator пересек линию ниже нуля (isAO_Below), Kumo является бычьим (isKumo_Above), а цена закрытия предыдущего бара находится ниже как Senkou Span A, так и Senkou Span B. Это указывает на потенциальное нисходящее движение цены, несмотря на бычий Kumo.
Вы могли заметить, что мы использовали новую функцию для получения цен закрытия. Вот логика всех функций, которые нам понадобятся.
//+------------------------------------------------------------------+ //| FUNCTION TO GET CLOSE PRICES | //+------------------------------------------------------------------+ double getClosePrice(int bar_index){ return (iClose(_Symbol, _Period, bar_index)); //--- Retrieve the close price of the specified bar } //+------------------------------------------------------------------+ //| FUNCTION TO GET ASK PRICES | //+------------------------------------------------------------------+ double getAsk(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits)); //--- Get and normalize the Ask price } //+------------------------------------------------------------------+ //| FUNCTION TO GET BID PRICES | //+------------------------------------------------------------------+ double getBid(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); //--- Get and normalize the Bid price }
Здесь мы определяем три функции для извлечения различных типов данных о ценах:
- Функция getClosePrice извлекает цену закрытия указанного бара. Он принимает параметр bar_index, который представляет собой индекс бара, для которого мы хотим получить цену закрытия. Функция вызывает встроенную функцию iClose, передающую символ (_Symbol), период (_Period) и индекс бара, чтобы получить цену закрытия указанного бара. Найденная цена возвращается в виде значения типа double.
- Функция getAsk извлекает текущую цену Ask для заданного символа. Она использует функцию SymbolInfoDouble с константой SYMBOL_ASK для получения цены Ask. Затем результат нормализуется с помощью функции NormalizeDouble, обеспечивающей округление цены до правильного количества знаков после запятой в зависимости от свойства символа _Digits. Эта функция возвращает нормализованную цену Ask в виде значения типа double.
- Функция getBid извлекает текущую цену Bid для заданного символа. Подобно функции getAsk, она использует SymbolInfoDouble с константой SYMBOL_BID для получения цены Bid, а затем нормализует ее с помощью функции NormalizeDouble, чтобы гарантировать ее соответствие правильной точности, определенной свойством символа _Digits. Эта функция возвращает нормализованную цену Bid в виде значения типа double.
Эти функции обеспечивают простой способ извлечения и нормализации соответствующих цен для принятия торговых решений в программе. Затем мы можем использовать рассчитанные торговые сигналы и открывать соответствующие позиции по имеющимся сигналам.
if (isBuy_Signal){ //--- If buy signal is generated Print("BUY SIGNAL GENERATED @ ",iTime(_Symbol,_Period,1),", PRICE: ",getAsk()); //--- Log buy signal obj_Trade.Buy(0.01,_Symbol,getAsk()); //--- Execute a buy trade } else if (isSell_Signal){ //--- If sell signal is generated Print("SELL SIGNAL GENERATED @ ",iTime(_Symbol,_Period,1),", PRICE: ",getBid()); //--- Log sell signal obj_Trade.Sell(0.01,_Symbol,getBid()); //--- Execute a sell trade }
Мы проверяем, сгенерирован ли сигнал на покупку или продажу, и совершаем соответствующую сделку. Если isBuy_Signal равен true, указывая на сигнал на покупку, мы сначала регистрируем событие с помощью функции Print. Мы включаем временную метку предыдущего бара, которая извлекается с помощью функции iTime, и текущую цену Ask, полученную с помощью функции getAsk. Запись фиксирует сигнал на покупку и цену, на которой он был получен. После регистрации мы совершаем сделку на покупку, вызывая obj_Trade.Buy(0.01, _Symbol, getAsk()), который размещает ордер на покупку объемом 0,01 лота по текущей цене Ask.
Аналогично, если isSell_Signal равен true, что указывает на сигнал на продажу, мы регистрируем событие с помощью функции Print, которая включает в себя временную метку предыдущего бара и текущую цену Bid из функции getBid. После регистрации совершаем сделку на продажу с помощью obj_Trade.Sell(0.01, _Symbol, getBid()), который размещает ордер на продажу объемом 0,01 лота по текущей цене Bid. Это гарантирует, что сделки будут совершаться всякий раз, когда выполняются условия сигналов на покупку или продажу, и мы ведем учет этих действий.
Наконец, нам просто нужно проверить изменения импульса и закрыть соответствующие позиции. Логика представлена ниже.
if (isAO_Above || isAO_Below){ //--- If AO crossover occurs if (PositionsTotal() > 0){ //--- If there are open positions for (int i=PositionsTotal()-1; i>=0; i--){ //--- Loop through open positions ulong posTicket = PositionGetTicket(i); //--- Get the position ticket if (posTicket > 0){ //--- If ticket is valid if (PositionSelectByTicket(posTicket)){ //--- Select position by ticket ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get position type if (posType == POSITION_TYPE_BUY){ //--- If position is a buy if (isAO_Below){ //--- If AO indicates bearish crossover Print("CLOSING THE BUY POSITION WITH #",posTicket); //--- Log position closure obj_Trade.PositionClose(posTicket); //--- Close the buy position } } else if (posType == POSITION_TYPE_SELL){ //--- If position is a sell if (isAO_Above){ //--- If AO indicates bullish crossover Print("CLOSING THE SELL POSITION WITH #",posTicket); //--- Log position closure obj_Trade.PositionClose(posTicket); //--- Close the sell position } } } } } } }
Здесь мы проверяем наличие пересечения Awesome Oscillator (AO) (выше или ниже нуля) и соответствующим образом управляем открытыми позициями. Если либо isAO_Above, либо isAO_Below имеют значение true, что указывает на то, что произошло пересечение АО, мы продолжаем проверять наличие открытых позиций, вызывая функцию PositionsTotal. Если есть открытые позиции (то есть PositionsTotal возвращает значение больше 0), мы проходим по всем открытым позициям, начиная с самой последней (PositionsTotal()-1) и двигаясь в обратном направлении.
В цикле мы извлекаем тикет позиции, используя функцию PositionGetTicket. Если тикет позиции действителен (т.е. больше 0), мы выбираем позицию с помощью функции PositionSelectByTicket. Затем мы определяем тип позиции, вызывая PositionGetInteger. При позиции на покупку (POSITION_TYPE_BUY) проверяем, является ли isAO_Below равным true, что указывает на медвежье пересечение. При true мы регистрируем закрытие позиции на покупку с помощью функции Print и закрываем позицию с помощью obj_Trade.PositionClose(posTicket).
Аналогично, при позиции на продажу (POSITION_TYPE_SELL) проверяем, является ли isAO_Above равным true, что указывает на бычье пересечение. При true мы регистрируем закрытие позиции на продажу и закрываем ее с помощью obj_Trade.PositionClose(posTicket). Это гарантирует, что мы эффективно управляем открытыми позициями, закрывая их, когда условия пересечения АО сигнализируют об изменении рыночного импульса. После запуска программы мы получим следующий результат.
Подтверждение позиции на продажу.
Подтверждение выхода из позиции на продажу при изменении рыночного импульса:
Из приведенных выше иллюстраций мы можем быть уверены, что мы достигли желаемых целей. Теперь мы можем приступить к тестированию и оптимизации программы. Займемся этим в следующем разделе.
Тестирование и оптимизация стратегии
В этом разделе мы протестируем стратегию и оптимизируем ее для лучшей работы в различных рыночных условиях. Изменение, которое мы внесем, касается сектора управления рисками, где мы можем добавить трейлинг-стоп для фиксации прибыли, когда мы уже в прибыли, вместо того, чтобы ждать, пока рынок примет окончательное решение об изменении рыночного импульса. Для большей эффективности построим динамическую функцию для обработки логики трейлинг-стопа.
//+------------------------------------------------------------------+ //| FUNCTION TO APPLY TRAILING STOP | //+------------------------------------------------------------------+ void applyTrailingSTOP(double slPoints, CTrade &trade_object,int magicNo=0){ double buySL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID)-slPoints,_Digits); //--- Calculate SL for buy positions double sellSL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK)+slPoints,_Digits); //--- Calculate SL for sell positions for (int i = PositionsTotal() - 1; i >= 0; i--){ //--- Iterate through all open positions ulong ticket = PositionGetTicket(i); //--- Get position ticket if (ticket > 0){ //--- If ticket is valid if (PositionGetString(POSITION_SYMBOL) == _Symbol && (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)){ //--- Check symbol and magic number if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && buySL > PositionGetDouble(POSITION_PRICE_OPEN) && (buySL > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)){ //--- Modify SL for buy position if conditions are met trade_object.PositionModify(ticket,buySL,PositionGetDouble(POSITION_TP)); } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && sellSL < PositionGetDouble(POSITION_PRICE_OPEN) && (sellSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)){ //--- Modify SL for sell position if conditions are met trade_object.PositionModify(ticket,sellSL,PositionGetDouble(POSITION_TP)); } } } } }
Здесь мы реализуем функцию применения трейлинг-стопа к открытым позициям. Функция называется applyTrailingSTOP и принимает три параметра: slPoints - количество пунктов, устанавливаемых для стоп-лосса; trade_object - ссылка на торговый объект, используемый для изменения позиций; и необязательный magicNo - для идентификации конкретных позиций по их магическому номеру. Сначала мы рассчитываем уровни стоп-лосса (SL) для позиций на покупку и продажу. Для позиций на покупку стоп-лосс устанавливается на уровне цены Bid за вычетом указанных slPoints, а для позиций на продажу стоп-лосс устанавливается на уровне цены Ask плюс указанные slPoints. Оба значения SL нормализованы с использованием функции NormalizeDouble для сопоставления десятичной точности символа, которая определяется переменной _Digits.
Далее мы просматриваем все открытые позиции, используя функцию PositionsTotal, итерирующую от самой последней позиции к самой старой. Для каждой позиции мы извлекаем тикет с помощью функции PositionGetTicket и убеждаемся в его действительности. Затем мы проверяем, совпадает ли символ позиции с текущим символом (_Symbol), а магическое число символа - с предоставленным magicNo, если только магическое число не установлено равным 0, в этом случае рассматриваются все позиции.
В случае позиции на покупку (POSITION_TYPE_BUY), проверяем, превышает ли рассчитанный стоп-лосс на покупку (buySL) цену открытия позиции (POSITION_PRICE_OPEN) и текущий стоп-лосс (POSITION_SL) или текущий стоп-длсс не установлен (POSITION_SL == 0). Если эти условия соблюдены, мы обновляем стоп-лосс позиции, вызывая trade_object.PositionModify(ticket, buySL, PositionGetDouble(POSITION_TP)), который изменяет стоп-лосс позиции, сохраняя тейк-профит (POSITION_TP) неизменным.
При позиции на продажу (POSITION_TYPE_SELL) применяем такую же логику. Проверяем, находится ли рассчитанный стоп-лосс на продажу (sellSL) ниже цены открытия позиции (POSITION_PRICE_OPEN) и текущего стоп-лосса (POSITION_SL) или текущий стоп-лосс не установлен. Если эти условия соблюдены, обновляем стоп-лосс позиции с помощью trade_object.PositionModify(ticket, sellSL, PositionGetDouble(POSITION_TP)).
После определения функции нам просто нужно вызвать ее в функции тика для выполнения. Сделаем это, вызвав ее и передав соответствующие параметры следующим образом.
if (PositionsTotal() > 0){ //--- If there are open positions applyTrailingSTOP(3000*_Point,obj_Trade,0); //--- Apply a trailing stop }
Если имеются какие-либо открытые позиции, вызываем функцию applyTrailingSTOP, чтобы применить трейлинг-стоп к открытым позициям. Функция вызывается с тремя аргументами:
- Trailing Stop Points - расстояние стоп-лосса рассчитывается как "3000 * _Point", где _Point представляет собой наименьшее возможное изменение цены для текущего символа. Это означает, что стоп-лосс устанавливается на расстоянии 3000 пунктов от текущей рыночной цены.
- Trade Object - передает obj_Trade, который является экземпляром объекта торговли, используемого для изменения уровней стоп-лосса и тейк-профита позиции.
- Magic Number - третий аргумент равен 0, что означает, что функция применит трейлинг-стоп ко всем открытым позициям, независимо от их магического числа.
После применения трейлинг-стопа получаем следующий результат.
Из визуализации мы видим, что вместо того, чтобы ждать смены рыночного импульса, мы фиксируем прибыль и максимизируем ее, перемещая уровень стоп-лосса каждый раз, когда рынок движется в нашем направлении. Окончательные результаты тестера стратегий приведены ниже.
График тестера:
Заключение
Мы рассмотрели разработку MQL5-советника на основе системы прорыва Кумо. Интегрировав индикаторы Ichimoku Kumo и Awesome Oscillator (AO), мы создали структуру для обнаружения изменений рыночного импульса и сигналов прорыва. Ключевые этапы включали настройку хэндлов индикаторов, извлечение ключевых значений и автоматизацию исполнения сделок с использованием трейлинг-стопов и управления позициями, что привело к созданию стратегического советника с надежной торговой логикой.
Отказ от ответственности: Статья представляет собой обучающее руководство по разработке советников MQL5 на основе торговых сигналов, управляемых индикаторами. Хотя система прорыва Кумо является популярной стратегией, ее эффективность не гарантирована во всех рыночных условиях. Трейдинг сопряжен с финансовым риском, и прошлые результаты не гарантируют будущих. Перед началом реальной торговли необходимо тщательное тестирование и правильное управление рисками.
Следуя этому руководству, вы сможете улучшить свои навыки MQL5-разработки и создавать более сложные торговые системы. Продемонстрированные здесь концепции интеграции индикаторов, логики сигналов и автоматизации торговли могут быть применены к другим стратегиям, стимулируя дальнейшие исследования и инновации в алгоритмической торговле. Удачной разработки и успешной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16657





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Отлично!
Спасибо большое. Я очень ценю ваш добрый отзыв.