English Deutsch 日本語
preview
Создание самооптимизирующихся советников на MQL5 (Часть 3): Динамическое следование за трендом и возврат к среднему значению

Создание самооптимизирующихся советников на MQL5 (Часть 3): Динамическое следование за трендом и возврат к среднему значению

MetaTrader 5Примеры |
88 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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

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

Эта динамическая стратегия, как мы надеемся, заменит традиционную парадигму, предполагающую наличие одной стратегии для каждого рыночного состояния. Предлагаемая стратегия основана на расчете торгового канала на базе переноса вперед (roll forward). Основная идея стратегии заключается в том, что существует граница, разделяющая трендовые движения и движения в диапазоне. И обращая внимание на то, где находятся уровни цен относительно нашей границы, мы можем совершать более обоснованные сделки. Верхняя и нижняя граница нашего канала рассчитывается путем прибавления (верхняя граница) и вычитания (нижняя граница) кратного значения ATR от среднего значения индикатора скользящей средней. Мы подробно обсудим эту стратегию в последующих разделах статьи.

Однако читатель должен понимать, что канал динамически рассчитывается ежедневно с использованием стандартных технических индикаторов, включенных в MetaTrader 5. Наша первоначальная стратегия представляла собой простую стратегию следования за трендом, которая предполагала открытие длинных позиций всякий раз, когда уровни цен закрывались выше 100-периодной скользящей средней, и коротких позиций в противном случае. После открытия сделок они впоследствии управлялись с использованием фиксированного стоп-лосса и тейк-профита. По результатам 4-летнего тестирования на истории данных M1 по EURUSD только 52% сделок, заключенных с использованием нашей первоначальной торговой стратегии, оказались прибыльными. Предложенные нами динамические правила на основе каналов увеличили долю выигрышных сделок до 86% за тот же 4-летний период на таймфрейме M1 без использования каких-либо методов подгонки кривых или ИИ.

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


Обзор торговой стратегии

Как мы объяснили во введении, торговые стратегии, основанные на скользящих средних, особенно популярны среди трейдеров, поскольку они позволяют нам оставаться в курсе долгосрочных рыночных тенденций. Довольно исключительный пример этого принципа в действии представлен на рис. 1 ниже. Скриншот сделан на основе дневного курса EURUSD и демонстрирует бычий тренд на рынке, который начался в конце ноября 2016 года и продолжался до апреля 2018 года. Впечатляющий результат по любым меркам. Обратите внимание, что индикатор скользящей средней также подтвердил, что уровни цен находятся в длительном восходящем тренде. 

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

Режим тренда

Рис. 1. Пример нашей системы следования за трендом, определяемой скользящей средней, в действии

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

Режим рынка с ограниченным диапазоном

Рис. 2. Пример длительного убыточного периода при использовании систем следования за трендом

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


Начало работы в MQL5

Наша торговая система состоит из ряда различных частей:

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

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

//+------------------------------------------------------------------+
//|                              Dynamic Moving Average Strategy.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PRICE    PRICE_CLOSE           //Moving average applied price
#define MA_MODE    MODE_EMA               //Moving average type
#define MA_SHIFT   0                      //Moving average shift
#define ATR_PERIOD 14                     //Period for our ATR
#define TF_1       PERIOD_D1              //Hihger order timeframe
#define TRADING_MA_PERIOD  100            //Moving average period
#define SL_WIDTH 1.5                      //How wide should the stop loss be?
#define CURRENT_VOL 0.1                   //Trading volume

Далее мы импортируем одну из наиболее часто используемых библиотек в API MQL5 — торговую библиотеку. Это необходимо для удобства управления нашими позициями.

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

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

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,trading_ma_handler,atr_handler;
double ma[],atr[],trading_ma[];
double ask,bid;

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }

Когда наш конечный пользователь удаляет торговое приложение с графика, вызывается обработчик OnDeinit(). Мы будем использовать этот обработчик для освобождения ресурсов системной памяти, которые мы больше не используем.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
IndicatorRelease(atr_handler);
IndicatorRelease(ma_handler);
IndicatorRelease(trading_ma_handler);
  }

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
   if(PositionsTotal() == 0)
      find_setup();
  }
//+------------------------------------------------------------------+

Наши правила открытия позиций по стратегии следования за трендом просты для понимания. Если уровни цен превышают 100-периодную скользящую среднюю, мы откроем длинные позиции с фиксированными стоп-лоссами. Otherwise, if price levels are beneath the moving average, we will open short positions.

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//Buy on rallies
   if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
      Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * SL_WIDTH)),(bid + (atr[0] * SL_WIDTH)),"");

   if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
      Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * SL_WIDTH)),(ask - (atr[0] * SL_WIDTH)),"");
  }

Функция, вызываемая обработчиком OnInit() для настройки наших технических индикаторов.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler =  iATR(Symbol(),TF_1,ATR_PERIOD);
   trading_ma_handler  =  iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

Наша функция Update() будет вызываться обработчиком OnTick() для обновления наших глобальных переменных.

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(trading_ma_handler,0,0,1,trading_ma);
     }
  }
//+------------------------------------------------------------------+

Ниже показано текущее состояние нашего торгового приложения в его нынешнем виде.

//+------------------------------------------------------------------+
//|                              Dynamic Moving Average Strategy.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PRICE    PRICE_CLOSE           //Moving average applied price
#define MA_MODE    MODE_EMA               //Moving average type
#define MA_SHIFT   0                      //Moving average shift
#define ATR_PERIOD 14                     //Period for our ATR
#define TF_1       PERIOD_D1              //Hihger order timeframe
#define TRADING_MA_PERIOD  100            //Moving average period
#define SL_WIDTH 1.5                      //How wide should the stop loss be?
#define CURRENT_VOL 0.1                   //Trading volume

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,trading_ma_handler,atr_handler;
double ma[],atr[],trading_ma[];
double ask,bid;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
IndicatorRelease(atr_handler);
IndicatorRelease(ma_handler);
IndicatorRelease(trading_ma_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
   if(PositionsTotal() == 0)
      find_setup();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//Buy on rallies
   if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
      Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * SL_WIDTH)),(bid + (atr[0] * SL_WIDTH)),"");

   if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
      Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * SL_WIDTH)),(ask - (atr[0] * SL_WIDTH)));
  }

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler =  iATR(Symbol(),TF_1,ATR_PERIOD);
   trading_ma_handler  =  iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(trading_ma_handler,0,0,1,trading_ma);
     }
  }
//+------------------------------------------------------------------+


Эталонное значение

Давайте посмотрим, насколько хорошо работает наше приложение для следования за трендом на исторических данных M1, начиная со среды 1 января 2020 года по понедельник 6 января 2025 года. Мы используем пару EURUSD для тестирования на истории. Обратите внимание, что мы не настраиваем никакие параметры стратегии, поэтому настройка "Форвард" установлена на "Нет". 

Рис. 3. Даты нашего тестирования на истории

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

Рис. 4. Условия нашего тестирования на истории

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

Рис. 5. Наша кривая эквити, полученная из первоначальной версии нашей стратегии

Если проанализировать подробные результаты нашего тестирования на истории, то можно увидеть, что наша стратегия была прибыльной в течение 5-летнего периода, что побуждает нас попытаться улучшить стратегию, однако соотношение прибыльных и убыточных сделок составляет почти 50/50. Это нежелательно. Мы хотим отфильтровать убыточные сделки, чтобы иметь более высокий процент прибыльных сделок. В среднем у нас 2 последовательных выигрыша и 2 последовательных проигрыша. Это подтверждает наше замечание о том, что наша система с одинаковой вероятностью может принести нам прибыль либо потерять наш капитал. Такой системе нельзя доверять торговлю без надзора.

Рис. 6. Подробный отчет о нашем тестировании на истории


Обзор предлагаемого решения

Рассмотрев результаты первоначального тестирования на истории позволяют нам подробно обсудить предлагаемую систему. Мы знаем, что наша система будет прибыльной, когда рынки следуют тренду, но она будет терять деньги, когда на рынках нет тренда. Итак, как мы можем разработать нашу систему, которая будет самостоятельно решать, будет ли рынок следовать тренду или нет?

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

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

Итак, теперь остался только один вопрос - как мы можем определить, в каком режиме находится рынок? Предлагаемая нами стратегия заключается в разделении уровней цен на 4 дискретные зоны. Основная идея нашей стратегии интуитивна:

  1. Режим тренда - рынок может по-настоящему двигаться в тренде только в Зоне 1 или Зоне 4.
  2. Режим диапазона: рынок может по-настоящему находиться в диапазоне только в Зоне 2 или Зоне 3.
Визуализация 4 зон представлена ниже на рис. 7.

Рис. 7. Простой пример нашей зональной торговой системы

Давайте рассмотрим каждую зону по очереди, начиная с Зоны 1. Когда уровни цен находятся в Зоне 1, мы воспринимаем это как бычье настроение рынка и будем искать возможности для покупки всякий раз, когда уровни цен закрываются выше 100-периодной скользящей средней. Ширина наших тейк-профита и стоп-лосса будет сохранена с нашего первоначального тестирования на истории. Обратите внимание: мы будем искать только долгосрочные возможности следовать за бычьими трендами, пока находимся в Зоне 1. Мы не будем занимать короткие позиции, пока уровни цен находятся в Зоне 1!

Рис. 8. Объяснение значимости Зоны 1

Если уровни цен падают из Зоны 1 в Зону 2, то наши рыночные настроения меняются. Мы больше не считаем, что будут наблюдаться какие-либо истинные тренды до тех пор, пока уровни цен остаются в Зоне 2. Скорее, мы считаем, что в Зоне 2 уровни цен имеют тенденцию колебаться вокруг среднего диапазона, разделяющего Зону 2 и Зону 3. Мы будем искать возможности для продажи исключительно в Зоне 2, поскольку считаем, что уровни цен продемонстрируют тенденцию к снижению до средней полосы.

Если уровень цен поднимется выше 100-периодной скользящей средней, пока мы находимся в Зоне 2, мы будем продавать, размещение нашего стоп-лосса будет сохранено из нашей первоначальной торговой стратегии. Однако положение нашего тейк-профита должно быть изменено. Мы разместим наш тейк-профит на средней полосе, разделяющей Зону 2 и Зону 3, поскольку предполагаем, что именно там будут сохраняться уровни цен до тех пор, пока мы находимся в пределах Зоны 2. Обратите внимание: мы не будем занимать длинные позиции, пока находимся в Зоне 2. Каждая зона допускает только 1 тип позиции.

Рис. 9. Понимание того, как наша торговая стратегия развивается по мере прохождения через четыре определенные нами рыночные зоны

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

А где мы разместим наш тейк-профит? Я надеюсь, читатель теперь интуитивно понимает, что когда мы находимся в Зоне 2 или 3, наш тейк-профит будет размещен на средней полосе, разделяющей Зону 2 и Зону 3.

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

Рис. 10. Зоны 2 и 3 считаются зонами возврата к среднему значению

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

Рис. 11. Зона 4 — это место, где, по нашему мнению, сформируются медвежьи тренды. Поэтому мы не будем занимать длинные позиции в Зоне 4

Чтобы реализовать стратегию четырех зон, нам придется внести изменения в изначальную стратегию следования за трендом:

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

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


Реализация решения на MQL5

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

#define MA_PERIOD  20                     //Moving Average period for calculating the mid point of our range zone

Далее нам необходимо определить глобальные переменные, которые будут отслеживать канал. В частности, наша цель — узнать, где пролегает граница Зоны 2 (верхняя граница) и Зоны 3 (нижняя граница). Кроме того, мы хотим точно определить, где проходит граница между Зоной 2 и Зоной 3 (средняя граница). Зона 1 определяется верхней границей и не имеет верхнего предела. Аналогично, Зона 4 начинается там, где заканчивается Зона 3, и не имеет нижнего предела.

double range_zone_mid,range_zone_ub,range_zone_lb;
int    active_zone;

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

//+------------------------------------------------------------------+
//| User inputs                                                      |
//+------------------------------------------------------------------+
input group "Technical Analysis"
input double atr_multiple = 
2 ;            //ATR Multiple
input int    bars_used = 30;              //How Many Bars should we use to calculate the channel?

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

//+------------------------------------------------------------------+
//| Get our current active zone                                      |
//+------------------------------------------------------------------+
void get_active_zone(void)
  {
   if(iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_ub)
     {
      active_zone = 1;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_ub) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_mid))
     {
      active_zone = 2;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_mid) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_lb))
     {
      active_zone = 3;
      return;
     }

   if(iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_lb)
     {
      active_zone = 4;
      return;
     }
  }
//+------------------------------------------------------------------+

Нам также необходимо обновить нашу функцию настройки. Проигнорируем те части функции, которые не изменились. Мы представляем только 1 новый технический индикатор. Первый технический индикатор, который мы применили, представлял собой 100-периодную скользящую среднюю на таймфрейме M1. Наш новый индикатор представляет собой 20-периодную скользящую среднюю, применяемую к дневному таймфрейму.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   //We have omitted parts of the code that have not changed
   ma_handler  =  iMA(Symbol(),TF_1,MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

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

Затем мы берем среднее значение 20-периодной дневной скользящей средней, и это будет средняя полоса, разделяющая Зону 2 и Зону 3. Границы Зоны 2 и Зоны 3 будут рассчитаны путем прибавления (Зона 2) и вычитания (Зона 3) кратного значения показания ATR от средней точки, рассчитанной с использованием 20-периодной скользящей средней.

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      //Omitted parts of the function that remained unchanged
      vector ma_average = vector::Zeros(1);
      ma_average.CopyIndicatorBuffer(ma_handler,0,1,bars_used);
      range_zone_mid = ma_average.Mean();
      range_zone_ub = (range_zone_mid + (atr[0] * atr_multiple));
      range_zone_lb = (range_zone_mid - (atr[0] * atr_multiple));
      get_active_zone();
      Comment("Zone: ",active_zone);
      ObjectDelete(0,"RANGE HIGH");
      ObjectDelete(0,"RANGE LOW");
      ObjectDelete(0,"RANGE MID");
      ObjectCreate(0,"RANGE MID",OBJ_HLINE,0,0,range_zone_mid);
      ObjectCreate(0,"RANGE LOW",OBJ_HLINE,0,0,range_zone_lb);
      ObjectCreate(0,"RANGE HIGH",OBJ_HLINE,0,0,range_zone_ub);
     }
  }

Последнее изменение, которое нам необходимо сделать, будет касаться того, как мы будем находить наши настройки. Торговая логика, лежащая в основе размещения наших позиций, уже подробно обсуждалась, поэтому этот фрагмент кода должен быть понятен читателю. Подводя итог основной идеи, мы будем отслеживать тенденцию только в зонах 1 и 4. Это значит, что если цена закроется выше 100-периодной скользящей средней в Зоне 1, мы будем покупать. В противном случае, если мы находимся в Зоне 2 или 3, мы пойдем против тренда, то есть, если цена закроется выше 100-периодной скользящей средней в Зоне 2, мы будем продавать вместо того, чтобы покупать, как мы это делали в Зоне 1.

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {

// Follow the trend
   if(active_zone == 1)
     {
      //Buy on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),(bid + (atr[0] * SL_WIDTH)),"");
     }

// Go against the trend
   if(active_zone == 2)
     {
      //Sell on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * 1.5)),range_zone_mid);
     }

// Go against the trend
   if(active_zone == 3)
     {
      //Buy the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),range_zone_mid,"");
     }

// Follow the trend
   if(active_zone == 4)
     {
      //Sell the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * atr_multiple)),(ask - (atr[0] * SL_WIDTH)));
     }
  }

Если собрать все воедино, то вот как выглядит обновленная версия нашей торговой стратегии.

//+------------------------------------------------------------------+
//|                              Dynamic Moving Average Strategy.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PRICE    PRICE_CLOSE           //Moving average shift
#define MA_MODE    MODE_EMA               //Moving average shift
#define MA_SHIFT   0                      //Moving average shift
#define ATR_PERIOD 14                     //Period for our ATR
#define TF_1       PERIOD_D1              //Hihger order timeframe
#define MA_PERIOD  20                     //Moving Average period for calculating the mid point of our range zone
#define TRADING_MA_PERIOD  100            //Moving average period
#define SL_WIDTH   1.5                    //How wide should the stop loss be?
#define CURRENT_VOL 0.1                   //Trading volume

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,trading_ma_handler,atr_handler;
double ma[],atr[],trading_ma[];
double range_zone_mid,range_zone_ub,range_zone_lb;
double ask,bid;
int    active_zone;

//+------------------------------------------------------------------+
//| User inputs                                                      |
//+------------------------------------------------------------------+
input group "Technical Analysis"
input double atr_multiple = 1;            //ATR Multiple
input int    bars_used = 30;              //How Many Bars should we use to calculate the channel?

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_handler);
   IndicatorRelease(atr_handler);
   IndicatorRelease(trading_ma_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
   if(PositionsTotal() == 0)
      find_setup();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {

// Follow the trend
   if(active_zone == 1)
     {
      //Buy on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),(bid + (atr[0] * SL_WIDTH)),"");
     }

// Go against the trend
   if(active_zone == 2)
     {
      //Sell on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * 1.5)),range_zone_mid);
     }

// Go against the trend
   if(active_zone == 3)
     {
      //Buy the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),range_zone_mid,"");
     }

// Follow the trend
   if(active_zone == 4)
     {
      //Sell the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * atr_multiple)),(ask - (atr[0] * SL_WIDTH)));
     }
  }

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler =  iATR(Symbol(),TF_1,ATR_PERIOD);
   trading_ma_handler  =  iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
   ma_handler  =  iMA(Symbol(),TF_1,MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_handler,0,0,1,ma);
      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(trading_ma_handler,0,0,1,trading_ma);
      vector ma_average = vector::Zeros(1);
      ma_average.CopyIndicatorBuffer(ma_handler,0,1,bars_used);
      range_zone_mid = ma_average.Mean();
      range_zone_ub = (range_zone_mid + (atr[0] * atr_multiple));
      range_zone_lb = (range_zone_mid - (atr[0] * atr_multiple));
      get_active_zone();
      Comment("Zone: ",active_zone);
      ObjectDelete(0,"RANGE HIGH");
      ObjectDelete(0,"RANGE LOW");
      ObjectDelete(0,"RANGE MID");
      ObjectCreate(0,"RANGE MID",OBJ_HLINE,0,0,range_zone_mid);
      ObjectCreate(0,"RANGE LOW",OBJ_HLINE,0,0,range_zone_lb);
      ObjectCreate(0,"RANGE HIGH",OBJ_HLINE,0,0,range_zone_ub);
     }
  }

//+------------------------------------------------------------------+
//| Get our current active zone                                      |
//+------------------------------------------------------------------+
void get_active_zone(void)
  {
   if(iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_ub)
     {
      active_zone = 1;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_ub) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_mid))
     {
      active_zone = 2;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_mid) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_lb))
     {
      active_zone = 3;
      return;
     }

   if(iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_lb)
     {
      active_zone = 4;
      return;
     }
  }
//+------------------------------------------------------------------+

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

Рис. 12. Период нашего тестирования на истории совпадает с использованным первоначальным периодом

Аналогичным образом мы исправим условия обратного тестирования, чтобы они соответствовали нашему предыдущему тесту.

Рис. 13. Убедитесь, что обе стратегии тестируются в одинаковых условиях

Наша новая стратегия имеет 2 входа. Первый параметр управляет шириной канала, а второй — количеством полос, используемых для расчета канала. 

Рис. 14. Элементы управления, которые может настраивать наш конечный пользователь

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

Рис. 15. Кривая эквити, полученная с помощью нашей новой торговой стратегии

Анализируя подробные результаты нашей новой торговой стратегии, мы видим, что доля наших выигрышных сделок выросла с 52% до 86%, а доля убыточных сделок снизилась с 47% до 13%. Наша средняя прибыль меньше, чем наши средние убытки, но имейте в виду, что наши стоп-лосс и тейк-профит фиксированы; эту проблему можно решить, если сохранять стоп-лосс фиксированным, если мы терпим убытки, и позволять ему изменяться, если мы получаем прибыль. Кроме того, наше среднее количество последовательных прибылей возросло с 2 до 9, а среднее количество последовательных убытков, наоборот, сократилось с 2 до 1. Наша первоначальная стратегия совершила 300 сделок. Наша новая стратегия совершила в общей сложности 1301 сделку. То есть наша новая стратегия совершает больше сделок и чаще выигрывает. 

Рис. 16. Подробный обзор эффективности нашей новой стратегии


Заключение

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

Прикрепленный файл Описание
Эталонная стратегия скользящей средней Первоначальная реализация нашей стратегии следования за трендом
Стратегия динамической скользящей средней Предложенная новая динамическая стратегия.

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

От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (VI) — Стратегия отложенных ордеров для торговли на новостях От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (VI) — Стратегия отложенных ордеров для торговли на новостях
В настоящей статье мы сосредоточим внимание на интеграции логики исполнения ордеров, основанной на новостях, что позволит советнику действовать, а не просто информировать. Присоединяйтесь к нам, и мы рассмотрим, как реализовать автоматическое исполнение сделок на MQL5 и превратить советник «Заголовки новостей» в полностью адаптивную торговую систему. Советники предлагают значительные преимущества разработчикам алгоритмов благодаря широкому спектру поддерживаемых ими функций. До сих пор мы сосредоточились на создании инструмента для представления новостей и событий календаря, оснащенного встроенными полосами аналитики с использованием ИИ и техническими индикаторами.
Упрощаем торговлю на новостях (Часть 6): Совершаем сделки (III) Упрощаем торговлю на новостях (Часть 6): Совершаем сделки (III)
В этой статье будет реализована сортировка новостей для отдельных новостных событий на основе их идентификаторов. Кроме того, предыдущие запросы SQL будут улучшены для предоставления дополнительной информации или сокращения времени выполнения запроса. Код, созданный в предыдущих статьях, станет работоспособным.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (V) — Система напоминаний о событиях От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (V) — Система напоминаний о событиях
В этом обсуждении мы рассмотрим дополнительные усовершенствования, поскольку интегрируем усовершенствованную логику оповещения о событиях в экономическом календаре, отображаемых советником «Заголовки новостей». Это усовершенствование имеет решающее значение — оно гарантирует, что пользователи будут получать своевременные уведомления за короткое время до ключевых предстоящих событий. Присоединяйтесь к этой дискуссии, чтобы узнать больше.