English 中文 Español Deutsch 日本語 Português
preview
Построение модели для ограничения диапазона сигналов по тренду (Часть 9): Советник с несколькими стратегиями (II)

Построение модели для ограничения диапазона сигналов по тренду (Часть 9): Советник с несколькими стратегиями (II)

MetaTrader 5Примеры |
742 1
Clemence Benjamin
Clemence Benjamin

Содержание:


Введение

В 20 веке Ричард Дончиан, изучая финансовые рынки, разработал стратегию следования за трендом, которая впоследствии трансформировалась в каналы Дончиана. Мы кратко обсудили его работу в предыдущей статье, но сегодня мы сосредоточимся на реализации стратегий, связанных с его теорией. Каналы охватывают в своей структуре несколько стратегий. Обилие литературы по каналам Дончиана свидетельствует о сохраняющейся эффективности этой теории в современной торговле. Интегрируя стратегии канала Дончиана, мы стремимся расширить возможности нашего советника Trend Constraint, повысив как его прибыльность, так и его адаптивность к различным рыночным условиям.

Некоторые популярные стратегии, основанные на канале Дончиана, доступные в Интернете, включают в себя стратегию прорыва (Breakout Strategy), стратегию ползка (Crawl Strategy), стратегию возврата к среднему (Mean Reversion Strategy) и другие. Известные трейдеры, такие как Райнер Тео (Rayner Teo), также создали образовательный контент, направленный на обучение трейдеров эффективному использованию этих каналов.

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


Что такое канал Дончиана?

Канал Дончиана — это индикатор технического анализа, состоящий из трех линий, а именно верхней полосы, средней линии и нижней полосы, который используется для построения графиков более высоких максимумов и более низких минимумов во время движения цены. Индикатор создан Ричардом Дончианом, пионером в области торговли по тренду. Краткое описание трех линий:
  • Верхняя полоса - самый высокий максимум за указанный период (например, за последние 20 периодов).
  • Нижняя полоса - самый низкий минимум за тот же указанный период.
  • Средняя линия - часто рассчитывается как среднее значение верхней и нижней полос, иногда эта линия используется в качестве точки отсчета.
Канал Дончиана, применяемый в MetaTrader 5:


Канал Дончиана

Линии канала Дончиана


Доступ к каналу Дончиана в MetaTrader 5

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

Доступ к каналу Дончиана в MetaTrade 5

Навигатор MetaTrader 5

Вы можете перетащить канал Дончиана на график, где вы собираетесь его использовать, как показано на изображении ниже. В этом примере мы используем настройки канала по умолчанию для индекса Volatility 150 (1s).

Добавление канала Дончиана на график

Добавление канала Дончиана на график в MetaTrader 5

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


Доступ к исходному коду канала Дончиана в MetaEditor

Чтобы получить доступ к исходному файлу для редактирования в MetaEditor 5, откройте окно "Навигатор" и найдите индикатор на вкладке Free Indicators, как и в платформе MetaTrader 5. Ключевое отличие в том, что здесь мы работаем с исходным файлом, а не со скомпилированной версией. Дважды щелкните файл, чтобы открыть код.

Каналы Дончиана в MetaEditor

Доступ к исходному коду канала Дончиана в MetaEditor


Реализация стратегий канала Дончиана в советнике Trend Constraint

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

  1. Касание ценой нижней границы канала - ордера на покупку с более высокой вероятностью выигрыша
  2. Отскок от средней линии - средняя вероятность прибыли
  3. Прорыв верхней границы - низкая вероятность прибыли

Я представил три идеи на рисунке ниже.

Канал Дончиана от Benjc Trade Advisor

Donchian Channel Strategies

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


Предварительный просмотр исходного кода индикатора

Индикатор канала Дончиана по умолчанию доступен на платформе MetaTrader 5. Вы также можете получить к нему доступ напрямую в MetaEditor 5, используя ранее рассмотренные методы.

//+------------------------------------------------------------------+
//|                                             Donchian Channel.mq5 |
//|                              Copyright 2009-2024, MetaQuotes Ltd |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "2009-2024, MetaQuotes Ltd"
#property link        "http://www.mql5.com"
#property description "Donchian Channel"
//---
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots   3
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed
//--- labels
#property indicator_label1  "Upper Donchian"
#property indicator_label2  "Middle Donchian"
#property indicator_label3  "Lower Donchian"

//--- input parameter
input int  InpDonchianPeriod=20;    // period of the channel
input bool InpShowLabel     =true;  // show price of the level

//--- indicator buffers
double    ExtUpBuffer[];
double    ExtMdBuffer[];
double    ExtDnBuffer[];

//--- unique prefix to identify indicator objects
string ExtPrefixUniq;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- define buffers
   SetIndexBuffer(0, ExtUpBuffer);
   SetIndexBuffer(1, ExtMdBuffer);
   SetIndexBuffer(2, ExtDnBuffer);

//--- set a 1-bar offset for each line
   PlotIndexSetInteger(0, PLOT_SHIFT, 1);
   PlotIndexSetInteger(1, PLOT_SHIFT, 1);
   PlotIndexSetInteger(2, PLOT_SHIFT, 1);

//--- indicator name
   IndicatorSetString(INDICATOR_SHORTNAME, "Donchian Channel");
//--- number of digits of indicator value
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- prepare prefix for objects
   string number=StringFormat("%I64d", GetTickCount64());
   ExtPrefixUniq=StringSubstr(number, StringLen(number)-4);
   ExtPrefixUniq=ExtPrefixUniq+"_DN";
   Print("Indicator \"Donchian Channels\" started, prefix=", ExtPrefixUniq);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- if the indicator has previously been calculated, start from the bar preceding the last one
   int start=prev_calculated-1;

//--- if this is the first calculation of the indicator, then move by InpDonchianPeriod bars form the beginning
   if(prev_calculated==0)
      start=InpDonchianPeriod+1;

//--- calculate levels for all bars in a loop
   for(int i=start; i<rates_total; i++)
     {
      //--- get max/min values for the last InpDonchianPeriod bars
      int    highest_bar_index=ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
      int    lowest_bar_index=ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);;
      double highest=high[highest_bar_index];
      double lowest=low[lowest_bar_index];

      //--- write values into buffers
      ExtUpBuffer[i]=highest;
      ExtDnBuffer[i]=lowest;
      ExtMdBuffer[i]=(highest+lowest)/2;
     }

//--- draw labels on levels
   if(InpShowLabel)
     {
      ShowPriceLevels(time[rates_total-1], rates_total-1);
      ChartRedraw();
     }

//--- succesfully calculated
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete all our graphical objects after use
   Print("Indicator \"Donchian Channels\" stopped, delete all objects with prefix=", ExtPrefixUniq);
   ObjectsDeleteAll(0, ExtPrefixUniq, 0, OBJ_ARROW_RIGHT_PRICE);
   ChartRedraw(0);
  }
//+------------------------------------------------------------------+
//|  Show prices' levels                                             |
//+------------------------------------------------------------------+
void ShowPriceLevels(datetime time, int last_index)
  {
   ShowRightPrice(ExtPrefixUniq+"_UP", time, ExtUpBuffer[last_index], clrBlue);
   ShowRightPrice(ExtPrefixUniq+"_MD", time, ExtMdBuffer[last_index], clrGray);
   ShowRightPrice(ExtPrefixUniq+"_Dn", time, ExtDnBuffer[last_index], clrRed);
  }
//+------------------------------------------------------------------+
//| Create or Update "Right Price Label" object                      |
//+------------------------------------------------------------------+
bool ShowRightPrice(const string name, datetime time, double price, color clr)
  {
   if(!ObjectCreate(0, name, OBJ_ARROW_RIGHT_PRICE, 0, time, price))
     {
      ObjectMove(0, name, 0, time, price);
      return(false);
     }

//--- make the label size adaptive
   long scale=2;
   if(!ChartGetInteger(0, CHART_SCALE, 0, scale))
     {
      //--- output an error message to the Experts journal
      Print(__FUNCTION__+", ChartGetInteger(CHART_SCALE) failed, error = ", GetLastError());
     }
   int width=scale>1 ? 2:1;  // if chart scale > 1, then label size = 2

   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);

   return(true);
  }
//+------------------------------------------------------------------+

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

Для простоты понимания давайте разберем приведенные выше фрагменты кода и определим наши буферы для дальнейшей разработки в следующем разделе.

Объявление буфера:

Объявлены три буфера - ExtUpBuffer[], ExtMdBuffer[] и ExtDnBuffer[], которые хранят верхнее, среднее и нижнее значения канала Дончиана соответственно.

double ExtUpBuffer[];
double ExtMdBuffer[];
double ExtDnBuffer[];

Настройка буфера в OnInit:

Функция SetIndexBuffer связывает графики (линии) с буферами, позволяя рисовать и обновлять их на графике.

SetIndexBuffer(0, ExtUpBuffer);
SetIndexBuffer(1, ExtMdBuffer);
SetIndexBuffer(2, ExtDnBuffer);

Расчет значений буфера в OnCalculate:

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

for(int i=start; i<rates_total; i++)
{
   //--- calculate highest and lowest for the Donchian period
   int highest_bar_index = ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
   int lowest_bar_index  = ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);
   double highest = high[highest_bar_index];
   double lowest  = low[lowest_bar_index];

   //--- assign values to buffers
   ExtUpBuffer[i] = highest;
   ExtDnBuffer[i] = lowest;
   ExtMdBuffer[i] = (highest + lowest) / 2;
}

Для генерации сигнала на покупку стратегия использует верхний буфер (ExtUpBuffer), активируя покупку, когда цена закрывается выше верхней линии Дончиана. И наоборот, сигнал на продажу срабатывает, когда цена закрывается ниже нижней линии Дончиана, определяемой нижним буфером (ExtDnBuffer). Кроме того, средний канал (ExtMdBuffer) может действовать как фильтр, уточняя стратегию, ограничивая сделки на покупку случаями, когда цена находится выше среднего канала, что указывает на более сильный восходящий тренд. Теперь мы можем приступить к разработке нашего советника.


Реализация в коде

Наличие канала Дончиана в качестве встроенного индикатора упрощает нашу задачу, поскольку мы можем разработать советник, который фокусируется на буферах индикатора для генерации сигналов для совершения сделок. Как упоминалось ранее, для ясности мы сначала разработаем советник на основе канала Дончиана, а затем интегрируем его с нашим советником Trend Constraint. Сегодня мы сосредоточимся на стратегии прорыва с использованием канала Дончиана. Условие прорыва простое: оно происходит, когда цена закрывается за пределами крайних полос канала. Вы можете обратиться к предыдущему изображению, где мы подробно объяснили различные стратегии.

Для начала создадим новый файл в MetaEditor 5, как показано на иллюстрации ниже. Я назвал его BreakoutEA, поскольку наше основное внимание будет сосредоточено на стратегии прорыва.

Новый советник

Начнем создание нового советника в MetaEditor

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

В этом базовом шаблоне вы найдете основные свойства, такие как директива #property strict. Эта директива гарантирует, что компилятор будет применять правильное использование типов данных, помогая предотвратить потенциальные ошибки программирования, вызванные несоответствием типов. Другим важным аспектом является включение торговой библиотеки, которая предоставляет необходимые инструменты для эффективного управления торговыми операциями. Эти шаги закладывают прочную основу для процесса разработки.

//+------------------------------------------------------------------+
//|                                                   BreakoutEA.mq5 |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/en/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

1. Инициализация советника

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

// Input parameters
input int InpDonchianPeriod = 20;      // Period for Donchian Channel
input double RiskRewardRatio = 1.5;    // Risk-to-reward ratio
input double LotSize = 0.1;            // Default lot size for trading
input double pipsToStopLoss = 15;      // Stop loss in pips
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;

// Expert initialization function
int OnInit() {
    // Create a unique key for the indicator based on the symbol and period
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod);
    
    // Load the Donchian Channel indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    
    // Check if the indicator loaded successfully
    if (handle == INVALID_HANDLE) {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

На основе торгового символа и определенного периода создается уникальный ключ, что гарантирует возможность дифференциации каждого экземпляра индикатора. Функция iCustom() используется для загрузки индикатора канала Дончиана с указанием его пути в каталоге MetaTrader (Free Indicators\\Donchian Channel). Если загрузка не удалась (INVALID_HANDLE), выводится сообщение об ошибке, и инициализация завершается неудачей, что делает невозможным дальнейшее выполнение без требуемых данных индикатора. Важно указать место хранения, так как индикатор хранится не в корневой папке индикатора. В большинстве случаев советник не запустится без загрузки индикатора.

//Typical Journal log when the EA fails to locate an indicator in the root indicators storage.
2024.10.20 08:49:04.117 2022.01.01 00:00:00   cannot load custom indicator 'Donchian Channel' [4802]
2024.10.20 08:49:04.118 2022.01.01 00:00:00   indicator create error in 'DonchianEA.mq5' (1,1)
2024.10.20 08:49:04.118 OnInit critical error
2024.10.20 08:49:04.118 tester stopped because OnInit failed

2. Очистка и деинициализация

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

// Expert deinitialization function
void OnDeinit(const int reason) {
    // Release the indicator handle to free up resources
    IndicatorRelease(handle);
}

3. Основная логика выполнения

Основная логика выполнения находится в функции OnTick(), которая срабатывает при каждом движении рынка или изменении цены. В этой функции выполняется проверка наличия открытых позиций с использованием функции PositionsTotal(). Если открытых позиций нет, программа переходит к оценке торговых условий, вызывая отдельную функцию. Такая структура предотвращает одновременное открытие нескольких сделок, что может привести к слишком частым сделкам.

// Main execution function with block-based control
void OnTick() {
    // Check if any positions are currently open
    if (PositionsTotal() == 0) {
        CheckTradingConditions();
    }
}

4. Оценка торговых условий

В этом сегменте советник проверяет рыночные условия по верхней и нижней полосам канала Дончиана. Размеры буферов индикатора меняются для размещения последних данных. Функция CopyBuffer() извлекает самые последние значения из канала Дончиана.

// Check trading conditions based on indicator buffers
void CheckTradingConditions() {
    double ExtUpBuffer[], ExtDnBuffer[];  // Buffers for upper and lower Donchian bands

    // Resize buffers to hold the latest data

    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0) {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1]) {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1]) {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

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

5. Функции размещения ордеров

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

// Open a buy order
void OpenBuy(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a buy order
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order")) {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

// Open a sell order
void OpenSell(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a sell order
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order")) {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

Наш советник на основе прорыва канала Дончиана теперь полностью готов.

//+----------------------------------------------------------------------+
//|                                                       BreakoutEA.mq5 |
//|                                    Copyright 2024, Clemence Benjamin |
//|                 https://www.mql5.com/en/users/billionaire2024/seller |
//+----------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.00"
#property strict
#include <Trade\Trade.mqh> // Include the trade library

// Input parameters
input int InpDonchianPeriod = 20;      // Period for Donchian Channel
input double RiskRewardRatio = 1.5;    // Risk-to-reward ratio
input double LotSize = 0.1;            // Default lot size for trading
input double pipsToStopLoss = 15;      // Stop loss in pips
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;
double ExtUpBuffer[];  // Upper Donchian buffer
double ExtDnBuffer[];  // Lower Donchian buffer

// Trade instance
CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod); // Create a unique key for the indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release the indicator handle
    IndicatorRelease(handle);
}

//+------------------------------------------------------------------+
//| Main execution function with block-based control                 |
//+------------------------------------------------------------------+
void OnTick()
{
    // Check if any positions are currently open
    if (PositionsTotal() == 0)
    {
        CheckTradingConditions();
    }
}

//+------------------------------------------------------------------+
//| Check trading conditions based on indicator buffers              |
//+------------------------------------------------------------------+
void CheckTradingConditions()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1])
    {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1])
    {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

//+------------------------------------------------------------------+
//| Open a buy order                                                 |
//+------------------------------------------------------------------+
void OpenBuy(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order"))
    {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order                                                |
//+------------------------------------------------------------------+
void OpenSell(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order"))
    {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

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

Давайте протестируем советник BreakoutEA, прежде чем включать его в основной код Trend Constraint. Это не окончательная версия, поскольку нам еще предстоит реализовать ограничивающую логику, соответствующую нашим целям.

Добавление BreakoutEA на график

Щелкните правой кнопкой мыши по списку советников и выберите "Тестировать", чтобы открыть окно тестера. Отсюда вы можете выбрать и настроить BreakoutEA для тестирования.

Тестер

Работа BreakoutEA в тестере стратегий

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


Включение в советник Trend Constraint

Объединение двух кодов советников подразумевает объединение функций с обеих сторон, при этом общие функции становятся основными в конечном советнике. Кроме того, уникальные функции каждого советника расширят общий размер и возможности объединенного кода. Например, есть свойства, которые существуют как в советнике Trend Constraint, так и в Breakout EA, и мы объединим их в одну программу. Эти общие свойства выделены во фрагменте кода ниже.

// We merge it to one
#property strict
#include <Trade\Trade.mqh>  // Include the trade library

При объединении двух или более стратегий в один советник, основные функции, которые остаются центральными для общей торговой логики, включают OnInit(), OnTick() и функции управления позициями (такие как OpenBuy() и OpenSell()). Эти функции выступают в качестве ядра советника, отвечая за инициализацию индикаторов, анализ рынка в реальном времени и размещение ордеров соответственно.

Между тем, функции стратегии прорыва канала Дончиана и стратегии следования за трендом RSI со скользящей средней в советнике Trend Constraint становятся расширениями существующей программы, включенными в качестве отдельных условий в функции OnTick(). Советник одновременно оценивает как сигналы прорыва канала Дончиана, так и трендовые сигналы от RSI и скользящих средних, что позволяет ему более комплексно реагировать на рыночные условия.

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

1. Функции инициализации

Функция инициализации (OnInit()) имеет решающее значение, поскольку она устанавливает необходимые индикаторы для обеих торговых стратегий, которые будет использовать советник. Эта функция вызывается при первой загрузке советника и обеспечивает готовность всех основных компонентов до начала любых торговых операций. Она инициализирует RSI (индекс относительной силы) и индикатор канала Дончиана. Если какой-либо из этих индикаторов не инициализируется должным образом, функция вернет ошибку, что предотвратит запуск торговой системы и позволит избежать потенциальных рыночных рисков.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // Initialize RSI handle
    rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
    if (rsi_handle == INVALID_HANDLE)
    {
        Print("Failed to create RSI indicator handle");
        return INIT_FAILED;
    }

    // Create a handle for the Donchian Channel
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the Donchian Channel indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

2. Основная логика выполнения

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Execute both strategies independently on each tick
    CheckTrendConstraintTrading();
    CheckBreakoutTrading();
    CheckOrderExpiration(); // Check for expired Trend Following orders
}

3. Модульные функции условий торговли

Стратегия следования за трендом:

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

//+------------------------------------------------------------------+
//| Check and execute Trend Constraint EA trading logic              |
//+------------------------------------------------------------------+
void CheckTrendConstraintTrading()
{
    // Check if there are any positions open
    if (PositionsTotal() == 0)
    {
        // Get RSI value
        double rsi_value;
        double rsi_values[];
        if (CopyBuffer(rsi_handle, 0, 0, 1, rsi_values) <= 0)
        {
            Print("Failed to get RSI value");
            return;
        }
        rsi_value = rsi_values[0];

        // Calculate moving averages
        double ma_short = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
        double ma_long = iMA(_Symbol, PERIOD_CURRENT, 200, 0, MODE_EMA, PRICE_CLOSE);

        // Determine trend direction
        bool is_uptrend = ma_short > ma_long;
        bool is_downtrend = ma_short < ma_long;

        // Check for buy conditions
        if (is_uptrend && rsi_value < RSI_Oversold)
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double stopLossPrice = currentPrice - StopLoss * _Point;
            double takeProfitPrice = currentPrice + TakeProfit * _Point;

            // Attempt to open a Buy order
            if (trade.Buy(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Buy") > 0)
            {
                Print("Trend Following Buy order placed.");
            }
            else
            {
                Print("Error placing Trend Following Buy order: ", GetLastError());
            }
        }
        // Check for sell conditions
        else if (is_downtrend && rsi_value > RSI_Overbought)
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double stopLossPrice = currentPrice + StopLoss * _Point;
            double takeProfitPrice = currentPrice - TakeProfit * _Point;

            // Attempt to open a Sell order
            if (trade.Sell(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Sell") > 0)
            {
                Print("Trend Following Sell order placed.");
            }
            else
            {
                Print("Error placing Trend Following Sell order: ", GetLastError());
            }
        }
    }
    else
    {
        // Implement Trailing Stop for open positions
        TrailingStopLogic();
    }
}

Функция стратегии прорыва: 

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

/+-------------------------------------------------------------------+
//| Check and execute Breakout EA trading logic                      |
//+------------------------------------------------------------------+
void CheckBreakoutTrading()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading Donchian Channel buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);
    
    // Get the daily open and close for the previous day
    double lastOpen = iOpen(Symbol(), PERIOD_D1, 1);
    double lastClose = iClose(Symbol(), PERIOD_D1, 1);

    // Determine if the last day was bullish or bearish
    bool isBullishDay = lastClose > lastOpen; // Bullish if close > open
    bool isBearishDay = lastClose < lastOpen; // Bearish if close < open

    // Check if there are any open positions before executing breakout strategy
    if (PositionsTotal() == 0) // Only proceed if no positions are open
    {
        // Buy condition: Closing price is above the upper Donchian band on a bullish day
        if (closePrice > ExtUpBuffer[1] && isBullishDay)
        {
            double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutBuyOrder(stopLoss, takeProfit);
        }

        // Sell condition: Closing price is below the lower Donchian band on a bearish day
        if (closePrice < ExtDnBuffer[1] && isBearishDay)
        {
            double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutSellOrder(stopLoss, takeProfit);
        }
    }
}


 Проверка торговли на прорыв:

Функция извлекает последние значения из индикатора канала Дончиана для анализа рыночных условий. Она определяет цену закрытия текущей свечи и дневные цены открытия и закрытия предыдущего дня. На основе сравнения этих цен определяется, был ли предыдущий день бычьим или медвежьим. Затем функция проверяет наличие открытых позиций перед выполнением стратегии прорыва. Если открытых позиций нет, она проверяет условия для открытия ордера на покупку (если цена закрытия выше верхней полосы в бычий день) или ордера на продажу (если цена закрытия ниже нижней полосы в медвежий день). Функция рассчитывает уровни стоп-лосса и прибыли для каждой сделки перед попыткой разместить ордер.

//+------------------------------------------------------------------+
//| Open a buy order for the Breakout strategy                       |
//+------------------------------------------------------------------+
void OpenBreakoutBuyOrder(double stopLoss, double takeProfit)
{
    if (trade.Buy(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Buy"))
    {
        Print("Breakout Buy order placed.");
    }
    else
    {
        Print("Error placing Breakout Buy order: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order for the Breakout strategy                      |
//+------------------------------------------------------------------+
void OpenBreakoutSellOrder(double stopLoss, double takeProfit)
{
    if (trade.Sell(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Sell"))
    {
        Print("Breakout Sell order placed.");
    }
    else
    {
        Print("Error placing Breakout Sell order: ", GetLastError());
    }
}

4. Проверка срока действия ордера

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

//+------------------------------------------------------------------+
//| Check for expired Trend Following orders                         |
//+------------------------------------------------------------------+
void CheckOrderExpiration()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            long magicNumber = PositionGetInteger(POSITION_MAGIC);

            // Check if it's a Trend Following position
            if (magicNumber == MagicNumber)
            {
                datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
                if (TimeCurrent() - openTime >= OrderLifetime)
                {
                    // Attempt to close the position
                    if (trade.PositionClose(ticket))
                    {
                        Print("Trend Following position closed due to expiration.");
                    }
                    else
                    {
                        Print("Error closing position due to expiration: ", GetLastError());
                    }
                }
            }
        }
    }
}

5. Логика трейлинг-стопа

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

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void TrailingStopLogic()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double stopLoss = PositionGetDouble(POSITION_SL);

            // Update stop loss for long positions
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
                if (currentPrice - stopLoss > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice - TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
            // Update stop loss for short positions
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                if (stopLoss - currentPrice > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice + TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}

6. Функция очистки

Функция OnDeinit() действует как процедура очистки при удалении советника с графика. Функция управляет освобождением любых выделенных ресурсов или хэндлов индикаторов, гарантируя отсутствие утечек памяти или висячих ссылок. Она подтверждает, что советник был правильно деинициализирован, и регистрирует это действие.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release indicators and handles
    IndicatorRelease(rsi_handle);
    IndicatorRelease(handle);
    Print("Expert deinitialized.");
}


Тестирование и результаты

Ниже представлено тестирование стратегий с новыми функциями.

Советник Trend Constraint. Прорыв Дончиана и следование тренду

Советник Trend Constraint: Стратегии прорыва канала Дончиана и следования за трендом


Заключение

Мы рассмотрели проблему адаптации к изменяющимся рыночным условиям путем объединения нескольких стратегий в одном советнике. Первоначально мы разработали мини-советник по прорыву для эффективного управления прорывом, прежде чем интегрировать его в наш основной советник Trend Constraint. Эта интеграция расширила функциональность советника, согласовав стратегию прорыва с настроениями рынка на более высоких таймфреймах, в частности с анализом свечей D1, что позволило сократить чрезмерное количество исполняемых сделок.

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

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

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

Прикрепленные файлы |
BreakoutEA.mq5 (5.07 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
ramontds
ramontds | 31 окт. 2024 в 10:26
Узнал здесь много нового, большое спасибо!
Майнинг данных CFTC на Python и ИИ модель на их основе Майнинг данных CFTC на Python и ИИ модель на их основе
Попробуем смайнить даные CFTC, загрузить отчеты COT и TFF через Python, соединить это с котировками MetaTrader 5 и моделью ИИ и получить прогнозы. Что такое отчеты COT на рынке Форекс? Как использовать отчеты COT и TFF для прогнозирования?
От начального до среднего уровня: Рекурсия От начального до среднего уровня: Рекурсия
В этой статье мы рассмотрим очень интересную и довольно интересную концепцию программирования, хотя к ней следует относиться с большой осторожностью, поскольку неправильное её использование или непонимание превращает относительно простые программы в нечто неоправданно сложное. Но правильное использование и идеальная адаптация в одинаково подходящих ситуациях делают рекурсию отличным союзником в решении вопросов, которые в другом случае были бы гораздо более трудоемкими и длительными. Представленные здесь материалы предназначены только для изучения. Ни в коем случае нельзя рассматривать это приложение как окончательное, цели которого будут иные, кроме изучения представленных концепций.
От начального до среднего уровня: Определения (I) От начального до среднего уровня: Определения (I)
В этой статье мы будем делать такие вещи, которые многим покажутся странными и совершенно вырванными из контекста, но которые при правильном применении сделают ваше обучение гораздо более увлекательным и интересным: мы сможем построить довольно интересные вещи на основе показанного здесь, что позволит лучше усвоить синтаксис языка MQL5. Представленные здесь материалы предназначены только для обучения. Ни в коем случае не рассматривайте его как окончательное приложение, целью которого не является изучение представленных концепций.
Майнинг данных балансов центробанков и получение картины мировой ликвидности Майнинг данных балансов центробанков и получение картины мировой ликвидности
Майнинг данных балансов центробанков позволяет получить картину мировой ликвидности рынка Форекс и ключевых валют. Мы объединяем данные ФРС, ЕЦБ, BOJ и PBoC в композитный индекс и применяем машинное обучение для выявления скрытых закономерностей. Такой подход превращает сырой поток данных в реальные торговые сигналы, соединяя фундаментальный и технический анализ.