
Построение модели для ограничения диапазона сигналов по тренду (Часть 9): Советник с несколькими стратегиями (II)
Содержание:
- Введение
- Что такое канал Дончиана?
- Доступ к каналу Дончиана в MetaTrader 5
- Доступ к исходному коду канала Дончиана в MetaEditor
- Реализация стратегий канала Дончиана в советнике Trend Constraint.
- Предварительный просмотр исходного кода индикатора
- Реализация в коде
- Включение в советник Trend Constraint
- Тестирование и результаты
- Заключение
Введение
В 20 веке Ричард Дончиан, изучая финансовые рынки, разработал стратегию следования за трендом, которая впоследствии трансформировалась в каналы Дончиана. Мы кратко обсудили его работу в предыдущей статье, но сегодня мы сосредоточимся на реализации стратегий, связанных с его теорией. Каналы охватывают в своей структуре несколько стратегий. Обилие литературы по каналам Дончиана свидетельствует о сохраняющейся эффективности этой теории в современной торговле. Интегрируя стратегии канала Дончиана, мы стремимся расширить возможности нашего советника Trend Constraint, повысив как его прибыльность, так и его адаптивность к различным рыночным условиям.
Некоторые популярные стратегии, основанные на канале Дончиана, доступные в Интернете, включают в себя стратегию прорыва (Breakout Strategy), стратегию ползка (Crawl Strategy), стратегию возврата к среднему (Mean Reversion Strategy) и другие. Известные трейдеры, такие как Райнер Тео (Rayner Teo), также создали образовательный контент, направленный на обучение трейдеров эффективному использованию этих каналов.
Каналы Дончиана доступны в качестве бесплатных индикаторов на платформе MetaTrader 5, что дает нам существенное преимущество для этого проекта. Этот доступ позволяет нам использовать исходный код индикатора и получить более глубокое представление о его структуре, что облегчает адаптацию к советнику Trend Constraint. Поскольку наш код продолжает усложняться, мы разработаем новую стратегию независимо, прежде чем интегрировать ее в основную программу. В следующих разделах мы углубим наше понимание теорий и еще больше усовершенствуем наш алгоритм.
Что такое канал Дончиана?
Канал Дончиана — это индикатор технического анализа, состоящий из трех линий, а именно верхней полосы, средней линии и нижней полосы, который используется для построения графиков более высоких максимумов и более низких минимумов во время движения цены. Индикатор создан Ричардом Дончианом, пионером в области торговли по тренду. Краткое описание трех линий: - Верхняя полоса - самый высокий максимум за указанный период (например, за последние 20 периодов).
- Нижняя полоса - самый низкий минимум за тот же указанный период.
- Средняя линия - часто рассчитывается как среднее значение верхней и нижней полос, иногда эта линия используется в качестве точки отсчета.
Линии канала Дончиана
Доступ к каналу Дончиана в MetaTrader 5
Обычно доступ к каналу осуществляется через окно "Навигатор" на вкладке "Индикаторы", как показано на рисунке ниже.
Навигатор MetaTrader 5
Вы можете перетащить канал Дончиана на график, где вы собираетесь его использовать, как показано на изображении ниже. В этом примере мы используем настройки канала по умолчанию для индекса Volatility 150 (1s).
Добавление канала Дончиана на график в MetaTrader 5
Причина, по которой мы сначала применяем индикатор к графику, заключается в том, чтобы изучить взаимосвязь между ценовым действием и каналом. Это помогает нам понять правила взаимодействия до начала разработки алгоритма. Далее рассмотрим, как получить доступ к исходному коду индикатора в MetaEditor 5.
Доступ к исходному коду канала Дончиана в MetaEditor
Чтобы получить доступ к исходному файлу для редактирования в MetaEditor 5, откройте окно "Навигатор" и найдите индикатор на вкладке Free Indicators, как и в платформе MetaTrader 5. Ключевое отличие в том, что здесь мы работаем с исходным файлом, а не со скомпилированной версией. Дважды щелкните файл, чтобы открыть код.
Доступ к исходному коду канала Дончиана в MetaEditor
Реализация стратегий канала Дончиана в советнике Trend Constraint
Важно выделить руководящие параметры, которые устанавливают правила взаимодействия при внедрении новых инструментов. С самого начала мы всегда определяем ограничивающие условия. Например, мы покупаем, только когда появляется бычья свеча D1, и продаем, только когда появляется медвежья свеча D1. Имея это в виду, мы сначала определим ограничивающие условия, прежде чем искать возможности для размещения ордеров, предоставляемые параметрами канала, которые соответствуют рыночному тренду. Например, в бычьем сценарии D1 мы сосредоточимся на следующих условиях: - Касание ценой нижней границы канала - ордера на покупку с более высокой вероятностью выигрыша
- Отскок от средней линии - средняя вероятность прибыли
- Прорыв верхней границы - низкая вероятность прибыли
Я представил три идеи на рисунке ниже.
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. Эта интеграция расширила функциональность советника, согласовав стратегию прорыва с настроениями рынка на более высоких таймфреймах, в частности с анализом свечей D1, что позволило сократить чрезмерное количество исполняемых сделок.
Каждая сделка, совершенная в тестере стратегий, была четко прокомментирована с указанием использованной стратегии, что обеспечило прозрачность и ясность в понимании лежащих в основе механизмов. Для эффективного решения сложной проблемы необходимо разбить ее на более мелкие, управляемые компоненты и решать каждый из них шаг за шагом. Так мы и поступили, разработав минисоветник перед тем, как объединить его с основным.
Хотя наша реализация демонстрирует потенциал, она все еще находится в стадии разработки и требует усовершенствования. Советник демонстрирует, как различные стратегии могут работать вместе для достижения лучших торговых результатов. Я рекомендую вам поэкспериментировать с предоставленными советниками и модифицировать их в соответствии с вашими торговыми стратегиями. Ваши отзывы очень важны, поскольку мы совместно изучаем возможности объединения нескольких стратегий в алгоритмической торговле.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16137
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ознакомьтесь с новой статьей: Построение модели ограничения свечного тренда (часть 9): Советник по нескольким стратегиям (II).
Автор: Клеменс Бенджамин