
Создание советника на MQL5 на основе стратегии PIRANHA с использованием Полос Боллинджера
Введение
В настоящей статье мы рассмотрим, как создать советника (EA) в MetaQuotes Language 5 (MQL5) на основе стратегии PIRANHA, сосредоточив внимание на интеграции Полос Боллинджера. Поскольку трейдеры ищут эффективные решения для автоматической торговли, стратегия PIRANHA появилась в качестве системного подхода, извлекающего выгоду из колебаний рынка, что делает ее привлекательным выбором для большого количества энтузиастов Форекс.
Мы начнем с изложения фундаментальных принципов стратегии PIRANHA, обеспечивающих прочную основу для ее реализации в автоматической торговле. Далее рассмотрим Полосы Боллинджера, популярный технический индикатор, помогающий определить потенциальные точки входа и выхода, измеряя волатильность рынка.
Далее расскажем о процессе написания кода на MQL5, выделив основные функции и логику, лежащие в основе стратегии. Кроме того, обсудим тестирование работы советника, оптимизацию параметров и рекомендации по его использованию в реальной торговой среде. Темы, которые мы рассмотрим в настоящей статье:
- Обзор стратегии PIRANHA
- Знакомсво с Полосами Боллинджера
- План стратегии
- Реализация средствами MQL5
- Тестирование
- Заключение
К концу настоящей статьи вы будете вооружены знаниями, необходимыми для разработки советника на MQL5, эффективно использующего стратегию PIRANHA и Полос Боллинджера, улучшая ваш подход к торговле. Итак, начнем.
Обзор стратегии PIRANHA
Стратегия PIRANHA - это динамичная торговая система, которая может извлекать выгоду из колебаний цен на валютном рынке. Эта стратегия определяется быстрым и оппортунистическим типом трейдинга, получившим название в честь проворной хищной рыбы, на которую этот вид ловли может походить из-за своей скорости и точности. Набор навыков: Стратегия PIRANHA - это стратегия, основанная на волатильности, разработанная для того, чтобы помочь трейдерам точно определять точки входа и выхода с рынка с помощью своего весьма относительного подхода.
Одним из компонентов стратегии PIRANHA, который включен, является применение Полос Боллинджера, которые являются распространенными индикаторами, помогающими трейдерам видеть волатильность на их рынке. Для нашего метода мы будем использовать 12-периодную Полосу Боллинджера просто потому, что это отличный инструмент, который со временем приобретает топологию, позволяющую нам лучше понять поведение цены. Мы также установим двухстандартное отклонение, что, по сути, означает фиксацию основных изменений цены при одновременной фильтрации шума от незначительных колебаний. Эти каналы создают потолок и дно, отражающие потенциальную перекупленность или перепроданность на рынке. Если цена опускается ниже нижней полосы, это считается отличной возможностью для покупки, в то время как рост выше верхней полосы указывает на то, что, возможно, стоит продавать. Ниже показана иллюстрация:
Управление рисками является еще одним важным элементом стратегии PIRANHA. Это подчеркивает важность защиты капитала с помощью четко определенных уровней стоп-лосса и тейк-профита. Для нашей стратегии мы установим стоп-лосс на 100 пунктов ниже цены входа для сделок на покупку, а также установим уровень тейк-профита на 50 пунктов выше цены входа. Такой дисциплинированный подход гарантирует, что мы сможем снизить потенциальные убытки при одновременном обеспечении прибыли, способствуя более устойчивой торговой методологии.
Таким образом, стратегия PIRANHA сочетает в себе технический анализ с акцентом на волатильность и управление рисками. Понимая эти принципы и настройки, трейдеры могут более эффективно ориентироваться на рынке Форекс, принимая обоснованные решения, соответствующие их торговым целям. По мере продвижения вперед мы рассмотрим, как реализовать эту стратегию на MQL5, воплощая стратегию PIRANHA в жизнь в автоматизированной торговой системе.
Знакомство с Полосами Боллинджера
Трейдеры могут использовать Полосы Боллинджера, надежный инструмент технического анализа, для определения потенциальных изменений цен на волатильном рынке. Джон Боллинджер (John Bollinger) разработал этот индикатор в 1980-х годах. Он состоит из трех компонентов: скользящей средней, верхней полосы и нижней полосы. Расчеты, которые выполняют трейдеры, позволяют им оценить, далека ли цена от своего среднего значения в любом направлении.
Для начала мы вычисляем среднюю полосу (скользящая средняя), которая обычно представляет собой простую скользящую среднюю за 20 периодов, или SMA. Формула SMA выглядит следующим образом:
Где 𝑃𝑖 представляет собой цену закрытия каждого периода, а 𝑛 - количество периодов (в данном случае 20). Например, если у нас есть следующие цены закрытия за последние 20 периодов:
Период | Цена закрытия (𝑃𝑖) |
---|---|
1 | 1.1050 |
2 | 1.1070 |
3 | 1.1030 |
4 | 1.1080 |
5 | 1.1040 |
6 | 1.1100 |
7 | 1.1120 |
8 | 1.1150 |
9 | 1.1090 |
10 | 1.1060 |
11 | 1.1085 |
12 | 1.1105 |
13 | 1.1130 |
14 | 1.1110 |
15 | 1.1075 |
16 | 1.1055 |
17 | 1.1080 |
18 | 1.1095 |
19 | 1.1115 |
20 | 1.1120 |
Суммируем эти цены и делим на 20:
Далее вычисляем стандартное отклонение, которое измеряет разброс цен закрытия от SMA. Формула стандартного отклонения (𝜎):
Используя рассчитанную нами SMA, равную 1,1080, мы вычисляем квадрат разности для каждой цены закрытия, затем берем их среднее значение и, наконец, извлекаем квадратный корень. Например, первые несколько квадратов разности:
Вычислив все 20 квадратов разности, мы находим:
- Верхняя Полоса = SMA + (k × σ)
- Нижняя Полоса = SMA − (k × σ)
Здесь мы обычно устанавливаем k=2 (что соответствует двум стандартным отклонениям). Подставляем наши значения:
- Верхняя Полоса = 1.1080 + (2 × 0.0030) = 1.1140
- Нижняя Полоса = 1.1080 − (2 × 0.0030) = 1.1020
Результирующие полосы Боллинджера выглядят следующим образом:
- Средняя Полоса (SMA): 1.1080
- Верхняя Полоса: 1.1140
- Нижняя Полоса: 1.1020
Эти три полосы выглядят следующим образом:
Расстояние между этими полосами меняется в зависимости от рыночных условий. Когда они расширяются, это указывает на растущую волатильность, а движущиеся рынки имеют тенденцию к волатильности. Когда полосы сужаются, это говорит о консолидации рынка. Трейдерам нравится искать взаимосвязи между ценами и полосами для генерации торговых сигналов. Они могут быть склонны интерпретировать взаимодействие цены с верхней полосой как перекупленность рынка, а взаимодействие цены с нижней полосой - как перепроданность рынка.
Подводя итог, можно сказать, что расчеты для Полос Боллинджера включают в себя разворот SMA, стандартное отклонение, а также верхнюю и нижнюю полосы. Понимание этих расчетов - это не просто упражнение в количественном анализе, но и предоставление трейдерам необходимых знаний для принятия обоснованных торговых решений, в частности, при применении стратегии PIRANHA в своей торговой деятельности.
План стратегии
Схема Верхней Полосы: Условие продажи
Когда цена пересекает верхнюю Полосу Боллинджера и закрывается выше нее, это сигнализирует о том, что рынок может быть перекуплен. Это условие говорит о том, что цены чрезмерно выросли и, вероятно, будут скорректированы в сторону понижения. В результате мы рассматриваем этот сценарий как сигнал на продажу. Таким образом, мы открываем позицию на продажу, когда цена закрытия текущего бара остается выше верхней полосы. Цель состоит в том, чтобы извлечь выгоду из потенциального разворота или отката назад.
Схема Нижней Полосы: Условие покупки
И наоборот, когда цена пересекает нижнюю Полосу Боллинджера и закрывается ниже нее, это указывает на то, что рынок может быть перепродан. Этот сценарий предполагает, что цены значительно упали и, возможно, готовы к восстановлению. Соответственно, это считается сигналом к покупке. Таким образом, мы открываем позицию на покупку, когда цена закрытия текущего бара находится ниже нижней полосы, ожидая возможного разворота вверх.
Эти наглядные изображения схемы стратегии будут полезны при реализации нами этих торговых условий на MQL5 и послужат ориентиром для написания точных правил входа и выхода.
Реализация средствами MQL5
После изучения всех теорий о торговой стратегии Piranha, давайте автоматизируем теорию и создадим советника (EA) на MetaQuotes Language 5 (MQL5) для MetaTrader 5.
Для создания советника (EA), в вашем терминале MetaTrader 5 перейдите на вкладку «Инструменты» и выберите языковой редактор MetaQuotes или просто нажмите клавишу F4 на клавиатуре. Кроме того, вы можете щелкнуть иконку IDE (интегрированная среда разработки) на панели инструментов. Откроется среда разработки на MetaQuotes Language Editor, которая позволяет писать торговых роботов, технические индикаторы, скрипты и библиотеки функций.
После открытия MetaEditor, на панели инструментов выберите "Файл" - "Новый файл" или нажмите CTRL + N, чтобы создать новый документ. Также вы можете нажать на иконку "Создать" в панели инструментов. Откроется окно Мастера MQL.
В открывшемся Мастере выберите Советник (шаблон) и нажмите Далее.
В общих свойствах укажите имя файла вашего советника. Чтобы указать или создать папку, если она не существует, используйте обратную косую черту перед именем советника. Например, по умолчанию указана папка «Experts\». Это значит, что наш советник будет создан в папке Experts. Остальные разделы довольно просты, но вы можете перейти по ссылке в нижней части Мастера, чтобы узнать детали.
После указания имени файла советника нажмите "Далее" > "Далее" > "Готово". Теперь мы готовы к воплощению стратегии в коде.
Во-первых, начнем с определения некоторых метаданных о советнике (EA). Сюда относятся: название советника, информация об авторских правах и ссылка на веб-сайт MetaQuotes. Мы также указываем версию советника, которая установлена на "1.00".
//+------------------------------------------------------------------+ //| PIRANHA.mq5 | //| Allan Munene Mutiiria, Forex Algo-Trader. | //| https://forexalgo-trader.com | //+------------------------------------------------------------------+ //--- Properties to define metadata about the Expert Advisor (EA) #property copyright "Allan Munene Mutiiria, Forex Algo-Trader." //--- Copyright information #property link "https://forexalgo-trader.com" //--- Link to the creator's website #property version "1.00" //--- Version number of the EA
При загрузке программы отображается информация, которая соответствует приведенной ниже.
Во-первых, мы включаем торговый экземпляр, используя #include в начале исходного кода. Он даст нам доступ к классу CTrade, который мы будем использовать для создания торгового объекта. Это очень важно, так как он необходимо нам для открытия сделок.
//--- Including the MQL5 trading library #include <Trade/Trade.mqh> //--- Import trading functionalities CTrade obj_Trade; //--- Creating an object of the CTrade class to handle trading operations
Препроцессор заменит строку #include <Trade/Trade.mqh> содержимым файла Trade.mqh. Угловые скобки указывают, что файл Trade.mqh будет взят из стандартного каталога (обычно это terminal_installation_directory\MQL5\Include). Текущий каталог не включен в поиск. Строку можно разместить в любом месте программы, но обычно все включения размещаются в начале исходного кода для лучшей структуры кода и удобства ссылок. Благодаря разработчикам MQL5 объявление объекта obj_Trade класса CTrade предоставит нам легкий доступ к методам, содержащимся в этом классе.
Нам понадобится создать хэндлы индикаторов, чтобы мы могли включить необходимые индикаторы в стратегию.
//--- Defining variables for Bollinger Bands indicator and price arrays int handleBB = INVALID_HANDLE; //--- Store Bollinger Bands handle; initialized as invalid double bb_upper[], bb_lower[]; //--- Arrays to store upper and lower Bollinger Bands values
Здесь мы объявляем и инициализируем единую целочисленную переменную «handleBB», которая будет служить в качестве хэндла индикатора Полосы Боллинджера в нашем советнике. В MQL5 хэндл - это уникальный идентификатор, присваиваемый индикатору, что упрощает использование этого индикатора во всем коде. Установив "handleBB" в INVALID_HANDLE изначально, мы гарантируем, что программа не будет ссылаться на недопустимый хэндл индикатора до надлежащего создания, тем самым предотвращая непредвиденные ошибки. Наряду с хэндлом мы также определяем два динамических массива, "bb_upper" и "bb_lower", в которых будут храниться значения верхней и нижней Полос Боллинджера соответственно. Эти массивы помогут нам фиксировать и анализировать текущее состояние индикатора, обеспечивая надежную основу для реализации нашей торговой стратегии, основанной на условиях Полосы Боллинджера. Опять же, нам нужно будет убедиться, что мы открываем только одну позицию в одном направлении.
//--- Flags to track if the last trade was a buy or sell bool isPrevTradeBuy = false, isPrevTradeSell = false; //--- Prevent consecutive trades in the same direction
Здесь мы объявляем и инициализируем два логических флага, "isPrevTradeBuy" и "isPrevTradeSell", чтобы отслеживать направление последней совершенной сделки. Изначально оба параметра имеют значение false, что указывает на то, что сделки еще не совершались. Эти флаги будут играть решающую роль в управлении нашей торговой логикой, гарантируя, что советник не будет открывать последовательные сделки в одном и том же направлении. Например, если предыдущая сделка была на покупку, для параметра "isPrevTradeBuy" будет установлено значение true, что предотвратит повторную сделку на покупку до тех пор, пока не произойдет сделка на продажу. Этот механизм поможет избежать излишних сделок и поддерживать сбалансированную торговую стратегию.
Далее, нам потребуется обработчик событий OnInit . Обработчик важен, поскольку он автоматически вызывается при инициализации советника (EA) на графике. Данная функция отвечает за настройку советника, включая создание необходимых хэндлов индикаторов, инициализацию переменных и подготовку ресурсов. Другими словами, OnInit - это встроенная функция, гарантирующая, что все корректно настроено до того, как советник начнет обрабатывать рыночные данные. Она выглядит следующим образом.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ // OnInit is called when the EA is initialized on the chart //... }
В обработчике событий OnInit нам нужно инициализировать хэндл индикатора, чтобы ему были присвоены значения данных.
//--- Create Bollinger Bands indicator handle with a period of 12, no shift, and a deviation of 2 handleBB = iBands(_Symbol, _Period, 12, 0, 2, PRICE_CLOSE);
Здесь мы создаем хэндл для индикатора Полосы Боллинджера, вызывая функцию iBands, которая генерирует индикатор на основе заданных параметров. Мы передаем этой функции несколько аргументов: _Symbol обозначает валютную пару, которую мы анализируем, а _Period обозначает таймфрейм для индикатора, который может быть любым - от минут до часов или дней. Параметры Полос Боллинджера включают период в 12, указывающий количество баров, используемых для расчета индикатора, сдвиг в 0, что означает, что к полосам не применяется корректировка, а также стандартное отклонение в 2, определяющее, насколько далеко полосы будут находиться от скользящей средней. Использование параметра PRICE_CLOSE указывает на то, что мы будем основывать наши расчеты на ценах закрытия баров. После успешного выполнения наша переменная handle "handleBB" сохранит действительный идентификатор индикатора Полосы Боллинджера, что позволит нам ссылаться на него для поиска и анализа данных. Таким образом прежде чем продолжить процедуру, необходимо проверить, успешно ли создан хэндл.
//--- Check if the Bollinger Bands handle was created successfully if (handleBB == INVALID_HANDLE){ Print("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING"); //--- Print error if handle creation fails return (INIT_FAILED); //--- Return initialization failed }
Здесь мы проверяем, успешно ли создан хэндл для индикатора Полосы Боллинджера, проверяя, равен ли он INVALID_HANDLE. Если обнаружится, что любой из этих хэндлов неверен, выводим сообщение об ошибке ("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING,», что помогает выявить любые проблемы в процессе инициализации. Затем мы возвращаем INIT_FAILED, указывая, что советнику не удалось должным образом инициализироваться. Если это пройдет успешно, мы продолжим использовать массивы данных в качестве временных рядов.
//--- Set the arrays for the Bollinger Bands to be time-series based (most recent data at index 0) ArraySetAsSeries(bb_upper, true); //--- Set upper band array as series ArraySetAsSeries(bb_lower, true); //--- Set lower band array as series return(INIT_SUCCEEDED); //--- Initialization successful
Здесь мы настраиваем массивы для Полос Боллинджера "bb_upper" и "bb_lower", чтобы обрабатывать их как данные временных рядов, вызывая функцию ArraySetAsSeries и устанавливая для второго параметра значение true. Это гарантирует, что самые свежие данные хранятся с индексом 0, что облегчает доступ к последним значениям при анализе рыночных условий. Организуя массивы таким образом, мы приводим нашу структуру данных в соответствие с типичным использованием в торговых алгоритмах, где самая свежая информация часто является наиболее актуальной. Наконец, мы возвращаем INIT_SUCCEEDED, указывая, что процесс инициализации успешно завершен, что позволяет советнику продолжить работу.
До этого момента все в разделе инициализации работало корректно. Полный исходный код, отвечающий за инициализацию программы, выглядит следующим образом:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create Bollinger Bands indicator handle with a period of 12, no shift, and a deviation of 2 handleBB = iBands(_Symbol, _Period, 12, 0, 2, PRICE_CLOSE); //--- Check if the Bollinger Bands handle was created successfully if (handleBB == INVALID_HANDLE){ Print("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING"); //--- Print error if handle creation fails return (INIT_FAILED); //--- Return initialization failed } //--- Set the arrays for the Bollinger Bands to be time-series based (most recent data at index 0) ArraySetAsSeries(bb_upper, true); //--- Set upper band array as series ArraySetAsSeries(bb_lower, true); //--- Set lower band array as series return(INIT_SUCCEEDED); //--- Initialization successful }
Далее мы переходим к обработчику событий OnDeinit , который представляет собой функцию, вызываемую при деинициализации программы.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ // OnDeinit is called when the EA is removed from the chart or terminated //... }
Функция OnDeinit запускается при удалении советника (EA) с графика или при выключении терминала. Мы должны использовать этот обработчик событий для обеспечения корректного обслуживания и управления ресурсами. Когда советник завершает работу, мы должны освободить все хэндлы для индикаторов, которые мы создали на этапе инициализации. Если бы мы этого не сделали, то могли бы игнорировать те области памяти, которые использовали что было бы неэффективно. Мы, конечно, не хотели рисковать, игнорируя какие-либо ресурсы, которые нам не нужны. Вот почему функция OnDeinit важна и почему этапы очистки имеют решающее значение в любой среде программирования.
IndicatorRelease(handleBB); //--- Release the indicator handle
Здесь мы просто вызываем функцию "IndicatorRelease" с аргументом "handleBB", чтобы освободить ранее созданный нами хэндл индикатора Полосы Боллинджера. Очистка имеет решающее значение для поддержания производительности платформы, особенно если вы используете множество советников или используете платформу в течение длительного времени. Таким образом, полный исходный код для освобождения ресурсов выглядит следующим образом:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Function to handle cleanup when the EA is removed from the chart IndicatorRelease(handleBB); //--- Release the indicator handle }
Далее, нам нужно проверять наличие торговых возможностей всякий раз при обновлении цен. Это достигается с помощью обработчика событий OnTick .
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ // OnTick is called whenever there is a new market tick (price update) //... }
Функция обработчика событий OnTick исполняет и обрабатывает свежую информацию о ценах каждый раз, когда появляется новый тик или изменение рыночных условий. Это важная часть работы нашего советника (EA), потому что именно здесь мы реализуем свою торговую логику, торговые условия которой, как мы надеемся, структурированы таким образом, чтобы приносить прибыльные сделки. Когда рыночные данные меняются, мы оцениваем текущее состояние рынка и принимаем решения относительно того, открывать или закрывать позицию. Эта функция запускается так часто, как только меняются рыночные условия, гарантируя, что наша стратегия работает в режиме реального времени и реагирует на текущие цены и изменения значений наших рыночных индикаторов.
Чтобы быть в курсе текущих рыночных условий, нам необходимо получать значения текущих ценовых котировок.
//--- Get current Ask and Bid prices double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Normalize Ask price to correct digits double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Normalize Bid price to correct digits
Здесь мы получаем самые актуальные цены спроса и предложения на торгуемый инструмент. Для получения этих цен мы пользуемся функцией SymbolInfoDouble . Для получения цены предложения указываем SYMBOL_ASK, а для получения цены спроса указываем SYMBOL_BID. После получения цен используем функцию NormalizeDouble , чтобы округлить цены до количества знаков после запятой, определенного параметром _Digits. Данный шаг имеет решающее значение, поскольку он гарантирует, что наши торговые операции будут осуществляться с использованием цен, которые как стандартизированы, так и точны. Если бы мы не округлили цены, неточности с плавающей запятой могли бы привести к ошибочным результатам при расчете цены операции. Затем мы копируем значения индикатора для использования в анализе и торговых операциях.
//--- Retrieve the most recent Bollinger Bands values (3 data points) if (CopyBuffer(handleBB, UPPER_BAND, 0, 3, bb_upper) < 3){ Print("UNABLE TO GET UPPER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; } if (CopyBuffer(handleBB, LOWER_BAND, 0, 3, bb_lower) < 3){ Print("UNABLE TO GET LOWER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; }
Здесь мы используем функцию CopyBuffer для получения самых последних значений Полос Боллинджера, а именно трех точек данных как для верхней, так и для нижней полос. Первый вызов в CopyBuffer запрашивает данные из верхней полосы, начиная с индекса 0, и сохраняет их в массиве "bb_upper". Если функция возвращает значение меньше 3, это указывает на то, что извлечение данных не удалось, и нам предлагается вывести сообщение об ошибке: "UNABLE TO GET UPPER BAND REQUESTED DATA. REVERTING NOW!" Затем мы выходим из функции, чтобы предотвратить дальнейшее выполнение. Аналогичный процесс выполняется и для нижней полосы, гарантируя, что мы также обработаем любые ошибки при извлечении из неё данных. Обратите внимание, что при обращении к буферным индексам мы используем идентификаторы линий индикатора, допустимые при копировании значений индикатора Полосы Боллинджера, вместо номеров буферов. Это самый простой способ выполнения, чтобы избежать путаницы, но логика остается. Вот наглядное представление номеров буферов.
Поскольку нам нужно будет провести сравнение между значениями индикатора и ценами, следует получить актуальные для нас цены баров, в данном случае высокие и низкие цены.
//--- Get the low and high prices of the current bar double low0 = iLow(_Symbol, _Period, 0); //--- Lowest price of the current bar double high0 = iHigh(_Symbol, _Period, 0); //--- Highest price of the current bar
Здесь мы получаем низкие и высокие цены текущего бара, вызывая функции iLow и iHigh. Функция iLow извлекает самую низкую цену для текущего бара (индекс 0) для указанного символа (_Symbol) и таймфрейма (_Period), сохраняя это значение в переменной "low0". Аналогично, iHigh выбирает самую высокую цену текущего бара и присваивает ее переменной "high0". Всё же нужно убедиться, что мы выполняем один сигнал в одном баре. Вот используемая логика.
//--- Get the timestamp of the current bar datetime currTimeBar0 = iTime(_Symbol, _Period, 0); //--- Time of the current bar static datetime signalTime = currTimeBar0; //--- Static variable to store the signal time
Здесь мы получаем временную метку текущего бара с помощью функции iTime, которая возвращает время указанного бар (индекс 0) на заданный символ (_Symbol) и таймфрейм (_Period). Эта временная метка хранится в переменной "currTimeBar0". Кроме того, мы объявляем статическую переменную под названием "signalTime" и инициализируем ее со значением "currTimeBar0". Делая "signalTime" статичным, мы гарантируем, что его значение сохраняется между вызовами функции, что позволяет нам отслеживать, когда в последний раз был сгенерирован торговый сигнал. Это имеет решающее значение для нашей стратегии, поскольку помогает нам предотвратить срабатывание нескольких сигналов на одном и том же баре, гарантируя, что мы будем действовать только по одному сигналу за период. Сделав все это, теперь мы можем приступить к проверке сигналов. Первое, что мы делаем, - это проверяем наличие сигнала на покупку.
//--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0]){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time }
Здесь мы проверяем наличие потенциального сигнала на покупку, оценивая, является ли самая низкая цена текущего бара, сохраненная в переменной "low0", ниже значения самой последней нижней Полосы Боллинджера, которая хранится в массиве "bb_lower" с индексом 0. Если "low0" меньше, чем "bb_lower[0]", это указывает на то, что цена пересекла нижнюю полосу, предполагая потенциальную перепроданность и вероятную возможность покупки. Когда это условие выполняется, программа регистрирует сообщение, используя функцию Print, чтобы отобразить "BUY SIGNAL @" вместе с текущим временем, полученным с помощью функции TimeCurrent. Это оповещение помогает нам отслеживать появление сигналов на покупку, обеспечивая прозрачность и прослеживаемость процесса принятия решений советником. Во время работы мы получаем следующий результат.
Из предоставленных выходных данных видно, что мы выводим сигналы на каждом тике, когда выполняются бычьи условия. Нам надо выводить сигнал один раз на каждый бар в каждом случае, когда у нас выполняются условия. Для этого мы используем следующую логику.
//--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0] && signalTime != currTimeBar0){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades }
Здесь мы уточняем условия нашего сигнала на покупку, добавляя дополнительную проверку, чтобы убедиться, что мы не генерируем повторяющиеся сделки в пределах одного и того же бара. Изначально мы проверяли, была ли самая низкая цена текущего бара, сохраненная в переменной "low0", ниже последнего нижнего значения Полосы Боллинджера ("bb_lower[0]"). Теперь мы вводим дополнительное условие: "signalTime != currTimeBar0", которое гарантирует, что временная метка текущего бара ("currTimeBar0") отличается от времени последнего записанного сигнала ("signalTime"). Затем мы обновляем значение "signalTime", чтобы оно соответствовало значению "currTimeBar0", чтобы подтвердить, что учитывается только один сигнал на покупку на каждом баре, даже если цена пересекается ниже полосы несколько раз. После запуска обновления мы получаем следующий результат.
Операция прошла успешно. Теперь мы видим, что выводим сигналы один раз на каждый бар. Затем мы можем продолжить действовать по сгенерированным сигналам, открывая позиции на покупку.
if (PositionsTotal() == 0 && !isPrevTradeBuy){ obj_Trade.Buy(0.01, _Symbol, Ask, Ask - 100 * _Point, Ask + 50 * _Point); //--- Open a buy position with predefined parameters isPrevTradeBuy = true; isPrevTradeSell = false; //--- Update trade flags }
Здесь мы добавляем условия, гарантирующие, что сделка на покупку будет совершена только при определенных обстоятельствах. Сначала мы проверяем, равно ли общее количество открытых позиций нулю, используя функцию PositionsTotal, которая гарантирует, что в данный момент другие сделки не активны. Затем мы проверяем, что последняя совершенная сделка не была покупкой, оценивая "!isPrevTradeBuy". Это предотвращает последовательные ордера на покупку и гарантирует, что наш советник не откроет новую позицию на покупку, если предыдущая сделка уже была на сделкой на покупку.
Если оба условия выполнены, переходим к открытию позиции на покупку, используя "obj_Trade.Buy". Указываем объем ордера как "0,01" лота, с текущим торговым символом (_Symbol) и ценой "Ask". Уровни стоп-лосса и тейк-профита устанавливаются на 100 и 50 пунктов ниже и выше запрашиваемой цены соответственно, что определяет наши правила управления рисками. После успешного открытия сделки на покупку мы обновляем торговые флаги: для "isPrevTradeBuy" устанавливается значение "true", а для "isPrevTradeSell" - значение "false", что указывает на то, что последняя сделка была на покупку, и предотвращает повторную покупку до тех пор, пока не сработает сигнал на продажу. Для логики продажи аналогичный подход используется следующим образом.
//--- Check for a sell signal when price crosses above the upper Bollinger Band else if (high0 > bb_upper[0] && signalTime != currTimeBar0){ Print("SELL SIGNAL @ ", TimeCurrent()); //--- Log the sell signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeSell){ obj_Trade.Sell(0.01, _Symbol, Bid, Bid + 100 * _Point, Bid - 50 * _Point); //--- Open a sell position with predefined parameters isPrevTradeBuy = false; isPrevTradeSell = true; //--- Update trade flags } }
После компиляции и запуска программы получим следующий результат.
Мы видим, что успешно выполнили позицию на покупку. Завершив реализацию, мы интегрировали стратегию PIRANHA с использованием Полос Боллинджера и настроили программу таким образом, чтобы она реагировала на сигналы покупки и продажи на основе определенных условий. В следующем разделе мы сосредоточимся на тестировании программы, чтобы оценить ее работу и точно настроить параметры для достижения оптимальных результатов.
Тестирование
После завершения реализации следующим важным шагом является тщательное тестирование советника (EA), чтобы оценить его работу и оптимизировать параметры. Эффективное тестирование гарантирует, что стратегия работает должным образом в различных рыночных условиях, сводя к минимуму риск возникновения непредвиденных проблем во время торговли. Здесь мы будем использовать Тестер Стратегий MetaTrader 5 для проведения бэк-тестирования и оптимизации, чтобы найти наилучшие возможные входные значения для нашей стратегии.
Начнем с настройки наших начальных входных параметров для значений стоп-лосса (SL) и тейк-профита (TP), которые существенно влияют на управление рисками стратегии. В первоначальной реализации SL и TP были определены с использованием фиксированных значений в пипсах. Однако, чтобы дать стратегии достаточно пространства для маневра и лучше улавливать движения рынка, мы изменим входные параметры, сделав их более гибкими и оптимизированными во время тестирования. Обновим код следующим образом:
//--- INPUTS input int sl_points = 500; input int tp_points = 250; //--- //--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0] && signalTime != currTimeBar0){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeBuy){ obj_Trade.Buy(0.01, _Symbol, Ask, Ask - sl_points * _Point, Ask + tp_points * _Point); //--- Open a buy position with predefined parameters isPrevTradeBuy = true; isPrevTradeSell = false; //--- Update trade flags } } //--- Check for a sell signal when price crosses above the upper Bollinger Band else if (high0 > bb_upper[0] && signalTime != currTimeBar0){ Print("SELL SIGNAL @ ", TimeCurrent()); //--- Log the sell signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeSell){ obj_Trade.Sell(0.01, _Symbol, Bid, Bid + sl_points * _Point, Bid - tp_points * _Point); //--- Open a sell position with predefined parameters isPrevTradeBuy = false; isPrevTradeSell = true; //--- Update trade flags } }
Входные данные позволяют нам проводить динамическую оптимизацию для различных символов и торговых товаров. После запуска получаем следующий результат.
Операция прошла успешно! Можем сделать вывод, что программа сработала так, как ожидалось. Окончательный фрагмент исходного кода, ответственный за создание и реализацию стратегии Piranha, выглядит следующим образом:
//+------------------------------------------------------------------+ //| PIRANHA.mq5 | //| Allan Munene Mutiiria, Forex Algo-Trader. | //| https://forexalgo-trader.com | //+------------------------------------------------------------------+ //--- Properties to define metadata about the Expert Advisor (EA) #property copyright "Allan Munene Mutiiria, Forex Algo-Trader." //--- Copyright information #property link "https://forexalgo-trader.com" //--- Link to the creator's website #property version "1.00" //--- Version number of the EA //--- Including the MQL5 trading library #include <Trade/Trade.mqh> //--- Import trading functionalities CTrade obj_Trade; //--- Creating an object of the CTrade class to handle trading operations input int sl_points = 500; input int tp_points = 250; //--- Defining variables for Bollinger Bands indicator and price arrays int handleBB = INVALID_HANDLE; //--- Store Bollinger Bands handle; initialized as invalid double bb_upper[], bb_lower[]; //--- Arrays to store upper and lower Bollinger Bands values //--- Flags to track if the last trade was a buy or sell bool isPrevTradeBuy = false, isPrevTradeSell = false; //--- Prevent consecutive trades in the same direction //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create Bollinger Bands indicator handle with a period of 12, no shift, and a deviation of 2 handleBB = iBands(_Symbol, _Period, 12, 0, 2, PRICE_CLOSE); //--- Check if the Bollinger Bands handle was created successfully if (handleBB == INVALID_HANDLE){ Print("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING"); //--- Print error if handle creation fails return (INIT_FAILED); //--- Return initialization failed } //--- Set the arrays for the Bollinger Bands to be time-series based (most recent data at index 0) ArraySetAsSeries(bb_upper, true); //--- Set upper band array as series ArraySetAsSeries(bb_lower, true); //--- Set lower band array as series return(INIT_SUCCEEDED); //--- Initialization successful } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Function to handle cleanup when the EA is removed from the chart IndicatorRelease(handleBB); //--- Release the indicator handle } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Retrieve the most recent Bollinger Bands values (3 data points) if (CopyBuffer(handleBB, UPPER_BAND, 0, 3, bb_upper) < 3){ Print("UNABLE TO GET UPPER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; } if (CopyBuffer(handleBB, LOWER_BAND, 0, 3, bb_lower) < 3){ Print("UNABLE TO GET LOWER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; } //--- Get current Ask and Bid prices double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Normalize Ask price to correct digits double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Normalize Bid price to correct digits //--- Get the low and high prices of the current bar double low0 = iLow(_Symbol, _Period, 0); //--- Lowest price of the current bar double high0 = iHigh(_Symbol, _Period, 0); //--- Highest price of the current bar //--- Get the timestamp of the current bar datetime currTimeBar0 = iTime(_Symbol, _Period, 0); //--- Time of the current bar static datetime signalTime = currTimeBar0; //--- Static variable to store the signal time //--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0] && signalTime != currTimeBar0){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeBuy){ obj_Trade.Buy(0.01, _Symbol, Ask, Ask - sl_points * _Point, Ask + tp_points * _Point); //--- Open a buy position with predefined parameters isPrevTradeBuy = true; isPrevTradeSell = false; //--- Update trade flags } } //--- Check for a sell signal when price crosses above the upper Bollinger Band else if (high0 > bb_upper[0] && signalTime != currTimeBar0){ Print("SELL SIGNAL @ ", TimeCurrent()); //--- Log the sell signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeSell){ obj_Trade.Sell(0.01, _Symbol, Bid, Bid + sl_points * _Point, Bid - tp_points * _Point); //--- Open a sell position with predefined parameters isPrevTradeBuy = false; isPrevTradeSell = true; //--- Update trade flags } } } //+------------------------------------------------------------------+
Результаты тестирования на истории:
График бэк-тестирования:
На этом этапе тестирования мы оптимизировали входные параметры и проверили эффективность стратегии с помощью тестера стратегий. Корректировки, внесенные нами в значения стоп-лосса и тейк-профита, придали стратегии PIRANHA большую гибкость. Теперь она может справляться с колебаниями рынка. Мы подтвердили, что стратегия работает так, как задумано, и дает положительные результаты после ее бэк-тестирования и оптимизации.
Заключение
В настоящей статье мы рассмотрели разработку советника MetaQuotes Language 5 (MQL5) на основе стратегии PIRANHA, использующего Полосы Боллинджера для определения потенциальных сигналов на покупку и продажу. Мы начали с понимания основ стратегии PIRANHA, за которой последовал подробный обзор Полос Боллинджера, подчеркнули их роль в выявлении волатильности рынка и настройке входов в сделки и выходов из них.
На протяжении всего процесса реализации мы иллюстрировали пошаговый процесс кодирования, настраивали хэндлы индикаторов и реализовывали торговую логику. Для обеспечения оптимальной работы мы скорректировали критические входные данные и протестировали программу с помощью Тестера Стратегий MetaTrader 5, подтвердив эффективность стратегии в различных рыночных условиях.
Отказ от ответственности: Информация, содержащаяся в статье, приведена исключительно в образовательных целях. Она предназначена для того, чтобы дать представление о создании советника (EA) на основе стратегии PIRANHA и должна служить основой для разработки более продвинутых систем с дальнейшей оптимизацией и тестированием. Описанные стратегии и методы не гарантируют каких-либо результатов торговли. Вы используете этот контент на свой страх и риск. Прежде чем применять какое-либо автоматическое торговое решение, проводите тщательное тестирование и учитывайте потенциальные рыночные условия.
В целом, настоящая статья служит руководством по автоматизации стратегии PIRANHA и настройке ее в соответствии с вашим стилем торговли. Надеемся, что она даст ценную информацию и послужит стимулом для дальнейшего изучения возможностей создания сложных торговых систем на MQL5. Удачного программирования и успешной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16034





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