English Deutsch 日本語
preview
Создание советника на MQL5 на основе стратегии PIRANHA с использованием Полос Боллинджера

Создание советника на MQL5 на основе стратегии PIRANHA с использованием Полос Боллинджера

MetaTrader 5Торговые системы | 22 мая 2025, 15:03
260 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В настоящей статье мы рассмотрим, как создать советника (EA) в MetaQuotes Language 5 (MQL5) на основе стратегии PIRANHA, сосредоточив внимание на интеграции Полос Боллинджера. Поскольку трейдеры ищут эффективные решения для автоматической торговли, стратегия PIRANHA появилась в качестве системного подхода, извлекающего выгоду из колебаний рынка, что делает ее привлекательным выбором для большого количества энтузиастов Форекс.

Мы начнем с изложения фундаментальных принципов стратегии PIRANHA, обеспечивающих прочную основу для ее реализации в автоматической торговле. Далее рассмотрим Полосы Боллинджера, популярный технический индикатор, помогающий определить потенциальные точки входа и выхода, измеряя волатильность рынка.

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

  1. Обзор стратегии PIRANHA
  2. Знакомсво с Полосами Боллинджера
  3. План стратегии
  4. Реализация средствами MQL5
  5. Тестирование
  6. Заключение

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


Обзор стратегии PIRANHA

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

Одним из компонентов стратегии PIRANHA, который включен, является применение Полос Боллинджера, которые являются распространенными индикаторами, помогающими трейдерам видеть волатильность на их рынке. Для нашего метода мы будем использовать 12-периодную Полосу Боллинджера просто потому, что это отличный инструмент, который со временем приобретает топологию, позволяющую нам лучше понять поведение цены. Мы также установим двухстандартное отклонение, что, по сути, означает фиксацию основных изменений цены при одновременной фильтрации шума от незначительных колебаний. Эти каналы создают потолок и дно, отражающие потенциальную перекупленность или перепроданность на рынке. Если цена опускается ниже нижней полосы, это считается отличной возможностью для покупки, в то время как рост выше верхней полосы указывает на то, что, возможно, стоит продавать. Ниже показана иллюстрация:

ОБЗОР СТРАТЕГИИ

Управление рисками является еще одним важным элементом стратегии PIRANHA. Это подчеркивает важность защиты капитала с помощью четко определенных уровней стоп-лосса и тейк-профита. Для нашей стратегии мы установим стоп-лосс на 100 пунктов ниже цены входа для сделок на покупку, а также установим уровень тейк-профита на 50 пунктов выше цены входа. Такой дисциплинированный подход гарантирует, что мы сможем снизить потенциальные убытки при одновременном обеспечении прибыли, способствуя более устойчивой торговой методологии.

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


Знакомство с Полосами Боллинджера

Трейдеры могут использовать Полосы Боллинджера, надежный инструмент технического анализа, для определения потенциальных изменений цен на волатильном рынке. Джон Боллинджер (John Bollinger) разработал этот индикатор в 1980-х годах. Он состоит из трех компонентов: скользящей средней, верхней полосы и нижней полосы. Расчеты, которые выполняют трейдеры, позволяют им оценить, далека ли цена от своего среднего значения в любом направлении.

Для начала мы вычисляем среднюю полосу (скользящая средняя), которая обычно представляет собой простую скользящую среднюю за 20 периодов, или SMA. Формула SMA выглядит следующим образом:

SMA FORMULA

Где 𝑃𝑖 представляет собой цену закрытия каждого периода, а 𝑛 - количество периодов (в данном случае 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 CALCULATION

Далее вычисляем стандартное отклонение, которое измеряет разброс цен закрытия от SMA. Формула стандартного отклонения (𝜎):

STD DEV FORMULA

Используя рассчитанную нами SMA, равную 1,1080, мы вычисляем квадрат разности для каждой цены закрытия, затем берем их среднее значение и, наконец, извлекаем квадратный корень. Например, первые несколько квадратов разности:

FIRST SQUARED DIFFERENCES

Вычислив все 20 квадратов разности, мы находим:

ALL DIFFERENCES

Рассчитав среднюю полосу (SMA) и стандартное отклонение, мы теперь можем определить верхнюю и нижнюю полосы. Формулы выглядят следующим образом:

  • Верхняя Полоса = SMA + (k × σ)
  • Нижняя Полоса = SMA − (k × σ)

Здесь мы обычно устанавливаем k=2 (что соответствует двум стандартным отклонениям). Подставляем наши значения:

  1. Верхняя Полоса = 1.1080 + (2 × 0.0030) = 1.1140
  2. Нижняя Полоса = 1.1080 − (2 × 0.0030) = 1.1020

Результирующие полосы Боллинджера выглядят следующим образом:

  • Средняя Полоса (SMA): 1.1080
  • Верхняя Полоса: 1.1140
  • Нижняя Полоса: 1.1020

Эти три полосы выглядят следующим образом:

3 BANDS

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

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


План стратегии

Схема Верхней Полосы: Условие продажи

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

UPPER BAND BLUEPRINT

Схема Нижней Полосы: Условие покупки

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

LOWER BAND BLUEPRINT

Эти наглядные изображения схемы стратегии будут полезны при реализации нами этих торговых условий на MQL5 и послужат ориентиром для написания точных правил входа и выхода.


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

После изучения всех теорий о торговой стратегии Piranha, давайте автоматизируем теорию и создадим советника (EA) на MetaQuotes Language 5 (MQL5) для MetaTrader 5.

Для создания советника (EA), в вашем терминале MetaTrader 5 перейдите на вкладку «Инструменты» и выберите языковой редактор MetaQuotes или просто нажмите клавишу F4 на клавиатуре. Кроме того, вы можете щелкнуть иконку IDE (интегрированная среда разработки) на панели инструментов. Откроется среда разработки на MetaQuotes Language Editor, которая позволяет писать торговых роботов, технические индикаторы, скрипты и библиотеки функций.

Открыть MetaEditor

После открытия MetaEditor, на панели инструментов выберите "Файл" - "Новый файл" или нажмите CTRL + N, чтобы создать новый документ. Также вы можете нажать на иконку "Создать" в панели инструментов. Откроется окно Мастера MQL.

CREATE NEW EA

В открывшемся Мастере выберите Советник (шаблон) и нажмите Далее.

Мастер MQL

В общих свойствах укажите имя файла вашего советника. Чтобы указать или создать папку, если она не существует, используйте обратную косую черту перед именем советника. Например, по умолчанию указана папка «Experts\». Это значит, что наш советник будет создан в папке Experts. Остальные разделы довольно просты, но вы можете перейти по ссылке в нижней части Мастера, чтобы узнать детали.

NEW EA NAME

После указания имени файла советника нажмите "Далее" > "Далее" > "Готово". Теперь мы готовы к воплощению стратегии в коде.

Во-первых, начнем с определения некоторых метаданных о советнике (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

При загрузке программы отображается информация, которая соответствует приведенной ниже.

METADATA

Во-первых, мы включаем торговый экземпляр, используя #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  предоставит нам легкий доступ к методам, содержащимся в этом классе.

Класс 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!" Затем мы выходим из функции, чтобы предотвратить дальнейшее выполнение. Аналогичный процесс выполняется и для нижней полосы, гарантируя, что мы также обработаем любые ошибки при извлечении из неё данных. Обратите внимание, что при обращении к буферным индексам мы используем идентификаторы линий индикатора, допустимые при копировании значений индикатора Полосы Боллинджера, вместо номеров буферов. Это самый простой способ выполнения, чтобы избежать путаницы, но логика остается. Вот наглядное представление номеров буферов.

BUFFER VISUALIZATION

Поскольку нам нужно будет провести сравнение между значениями индикатора и ценами, следует получить актуальные для нас цены баров, в данном случае высокие и низкие цены.

   //--- 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. Это оповещение помогает нам отслеживать появление сигналов на покупку, обеспечивая прозрачность и прослеживаемость процесса принятия решений советником. Во время работы мы получаем следующий результат.

BUY SIGNAL 1

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

   //--- 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", чтобы подтвердить, что учитывается только один сигнал на покупку на каждом баре, даже если цена пересекается ниже полосы несколько раз. После запуска обновления мы получаем следующий результат.

BUY SINGLE BAR SIGNAL

Операция прошла успешно. Теперь мы видим, что выводим сигналы один раз на каждый бар. Затем мы можем продолжить действовать по сгенерированным сигналам, открывая позиции на покупку.

      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
      }
   }

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

BUY POSITION

Мы видим, что успешно выполнили позицию на покупку. Завершив реализацию, мы интегрировали стратегию 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
      }
   }

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

FINAL TESTING

Операция прошла успешно! Можем сделать вывод, что программа сработала так, как ожидалось. Окончательный фрагмент исходного кода, ответственный за создание и реализацию стратегии 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
      }
   }
  }
//+------------------------------------------------------------------+

Результаты тестирования на истории:

РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ НА ИСТОРИИ

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

BACKTEST GRAPH

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


Заключение

В настоящей статье мы рассмотрели разработку советника MetaQuotes Language 5 (MQL5) на основе стратегии PIRANHA, использующего Полосы Боллинджера для определения потенциальных сигналов на покупку и продажу. Мы начали с понимания основ стратегии PIRANHA, за которой последовал подробный обзор Полос Боллинджера, подчеркнули их роль в выявлении волатильности рынка и настройке входов в сделки и выходов из них.

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

Отказ от ответственности: Информация, содержащаяся в статье, приведена исключительно в образовательных целях. Она предназначена для того, чтобы дать представление о создании советника (EA) на основе стратегии PIRANHA и должна служить основой для разработки более продвинутых систем с дальнейшей оптимизацией и тестированием. Описанные стратегии и методы не гарантируют каких-либо результатов торговли. Вы используете этот контент на свой страх и риск. Прежде чем применять какое-либо автоматическое торговое решение, проводите тщательное тестирование и учитывайте потенциальные рыночные условия.

В целом, настоящая статья служит руководством по автоматизации стратегии PIRANHA и настройке ее в соответствии с вашим стилем торговли. Надеемся, что она даст ценную информацию и послужит стимулом для дальнейшего изучения возможностей создания сложных торговых систем на MQL5. Удачного программирования и успешной торговли!

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

Прикрепленные файлы |
PIRANHA.mq5 (5.57 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Roman Shiredchenko
Roman Shiredchenko | 23 мая 2025 в 12:21

спасибо большое - очень интересная статья и подробно разобраны условия. Возьму за основу написания и тестирования своих роботов на пользовательских символах.

Allan Munene Mutiiria
Allan Munene Mutiiria | 23 мая 2025 в 13:28
Roman Shiredchenko пользовательских персонажах.

Конечно, очень приятно.

Как интегрировать концепцию Smart Money (OB) в сочетании с индикатором Фибоначчи для оптимального входа в сделку Как интегрировать концепцию Smart Money (OB) в сочетании с индикатором Фибоначчи для оптимального входа в сделку
SMC (Order Block) — это ключевые области, где институциональные трейдеры совершают значительные покупки или продажи. После значительного движения цены уровни Фибоначчи помогают определить потенциальный откат от недавнего максимума колебания (swing high) к минимуму колебания (swing low) для определения оптимальной точки входа в сделку.
Нейросети в трейдинге: Обобщение временных рядов без привязки к данным (Окончание) Нейросети в трейдинге: Обобщение временных рядов без привязки к данным (Окончание)
Эта статья позволит вам увидеть, как Mamba4Cast превращает теорию в рабочий торговый алгоритм и подготовить почву для собственных экспериментов. Не упустите возможность получить полный спектр знаний и вдохновения для развития собственной стратегии.
Упрощаем торговлю на новостях (Часть 4): Повышаем производительность Упрощаем торговлю на новостях (Часть 4): Повышаем производительность
В этой статье будут рассмотрены методы улучшения работы советника в тестере стратегий, будет написан код для разделения времени новостных событий на почасовые категории. Доступ к этим новостным событиям будет осуществляться в течение указанного для них часа. Это гарантирует, что советник может эффективно управлять сделками на основе событий как в условиях высокой, так и низкой волатильности.
Создание торговой панели администратора на MQL5 (Часть IV): Безопасность входа в систему Создание торговой панели администратора на MQL5 (Часть IV): Безопасность входа в систему
Представьте себе, что злоумышленник проник в систему управления торговли и получил доступ к компьютерам и панели администратора, используемым для передачи ценных сведений миллионам трейдеров по всему миру. Это может привести к катастрофическим последствиям, таким как несанкционированная отправка вводящих в заблуждение сообщений или случайные нажатия на кнопки, запускающие непреднамеренные действия. В этой статье мы рассмотрим меры безопасности в MQL5 и новые функции безопасности, которые мы реализовали в нашей панели администратора для защиты от этих угроз. Совершенствуя наши протоколы безопасности, мы стремимся защитить наши каналы связи и сохранить доверие членов нашего торгового сообщества.