Разворотные паттерны: Тестируем паттерн "Голова-Плечи"

Dmitriy Gizlyk | 20 ноября, 2018

Содержание

Введение

В статье "Разворотные паттерны: Тестируем паттерн "Двойная вершина/дно" мы рассмотрели и протестировали стратегию торговли по разворотному паттерну "Двойная вершина". Данная статья продолжает начатую тему, и теперь я предлагаю рассмотреть еще один разворотный паттерн графического анализа под названием "Голова-Плечи".


1. Теоретические аспекты формирования паттерна

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

Паттерн "Голова-Плечи"

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


2. Стратегия торговли по паттерну

Как и в случае с паттерном "Двойная вершина/дно", для торговли по паттерну "Голова-Плечи" существуют различные стратегии. И во многом они напоминают стратегии торговли по предыдущему паттерну.

2.1. Сценарий 1

Первая стратегия основана на пробитии линии шеи. В этом случае ордер открывается после пробития линии ценой Close анализируемого таймфрейма. Стоп-лосс в этом случае выставляется на уровне экстремума головы паттерна или с небольшим отступом. При этом следует учитывать спред инструмента.

Стратегия 1

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

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

2.2. Сценарий 2

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

Сценарий 2

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

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

Тейк-профит


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

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

Большая часть подготовительной работы нами уже проведена в статье [1]. Для поиска паттернов создадим класс CHS_Pattern. И, чтобы воспользоваться наработками из предыдущей статьи, сделаем его наследником класса CPattern. Таким образом нам будут доступны все методы родительского класса. Здесь же мы добавим только недостающие элементы и перепишем методы инициализации класса, поиска паттерна и точки входа.

class CHS_Pattern : public CPattern
  {
protected:
   s_Extremum     s_HeadExtremum;         //Точка экстремума головы
   s_Extremum     s_StartRShoulder;       //Точка начала правого плеча

public:
                     CHS_Pattern();
                    ~CHS_Pattern();
//--- Инициализация класса
   virtual bool      Create(CTrends *trends, double min_correction, double max_correction);
//--- Методы поиска паттерна и точки входа
   virtual bool      Search(datetime start_time);
   virtual bool      CheckSignal(int &signal, double &sl, double &tp1, double &tp2);
//---
   s_Extremum        HeadExtremum(void)      const {  return s_HeadExtremum;     }
   s_Extremum        StartRShoulder(void)    const {  return s_StartRShoulder;   }
  };

В методе инициализации класса сначала проведем инициализацию родительского класса, а потом подготовим добавленные структуры.

bool CHS_Pattern::Create(CTrends *trends,double min_correction,double max_correction)
  {
   if(!CPattern::Create(trends,min_correction,max_correction))
      return false;
//---
   s_HeadExtremum.Clear();
   s_StartRShoulder.Clear();
//---
   return true;
  }

В методе поиска паттернов сначала проверим достаточное количество найденных экстремумов для определения паттерна.

bool CHS_Pattern::Search(datetime start_time)
  {
   if(CheckPointer(C_Trends)==POINTER_INVALID || C_Trends.Total()<6)
      return false;

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

   int start=C_Trends.ExtremumByTime(start_time);
   if(start<0)
      return false;

Далее в цикле загружаем данные о последних 6-ти экстремумах, и проверяем ценовые движения на соответствие искомому паттерну. При нахождении паттерна выходим из метода с результатом true. Если паттерн не найден, смещаемся на один паттерн в сторону текущего момента и повторяем цикл. Если после перебора всех экстремумов паттерн не найден, выходим из метода с результатом false.

   b_found=false; 
   for(int i=start;i>=0;i--)
     {
      if((i+5)>=C_Trends.Total())
         continue;
      if(!C_Trends.Extremum(s_StartTrend,i+5) || !C_Trends.Extremum(s_StartCorrection,i+4) ||
         !C_Trends.Extremum(s_EndCorrection,i+3)|| !C_Trends.Extremum(s_HeadExtremum,i+2) ||
         !C_Trends.Extremum(s_StartRShoulder,i+1) || !C_Trends.Extremum(s_EndTrend,i))
         continue;
//---
      double trend=MathAbs(s_StartCorrection.Price-s_StartTrend.Price);
      double correction=MathAbs(s_StartCorrection.Price-s_EndCorrection.Price);
      double header=MathAbs(s_HeadExtremum.Price-s_EndCorrection.Price);
      double revers=MathAbs(s_HeadExtremum.Price-s_StartRShoulder.Price);
      double r_shoulder=MathAbs(s_EndTrend.Price-s_StartRShoulder.Price);
      if((correction/trend)<d_MinCorrection || header>(trend-correction)   ||
         (1-fmin(header,revers)/fmax(header,revers))>=d_MaxCorrection      ||
         (1-r_shoulder/revers)<d_MinCorrection || (1-correction/header)<d_MinCorrection)
         continue;
      b_found= true; 
//---
      break;
     }
//---
   return b_found;
  }

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

bool CHS_Pattern::CheckSignal(int &signal, double &sl, double &tp1, double &tp2)
  {
   if(!b_found)
      return false;

Затем проверим сколько было сформировано баров после формирования паттерна. Если паттерн только сформировался, выходим из метода с результатом false.

   string symbol=C_Trends.Symbol();
   if(symbol=="Not Initilized")
      return false;
   datetime start_time=s_EndTrend.TimeStartBar+PeriodSeconds(C_Trends.Timeframe());
   int shift=iBarShift(symbol,e_ConfirmationTF,start_time);
   if(shift<0)
      return false;

После этого загружаем необходимую историю котировок и подготавливаем вспомогательные переменные.

   MqlRates rates[];
   int total=CopyRates(symbol,e_ConfirmationTF,0,shift+1,rates);
   if(total<=0)
      return false;
//---
   signal=0;
   sl=tp1=tp2=-1;
   bool up_trend=C_Trends.IsHigh(s_EndTrend);
   int shift1=iBarShift(symbol,e_ConfirmationTF,s_EndCorrection.TimeStartBar,true);
   int shift2=iBarShift(symbol,e_ConfirmationTF,s_StartRShoulder.TimeStartBar,true);
   if(shift1<=0 || shift2<=0)
      return false;
   double koef=(s_StartRShoulder.Price-s_EndCorrection.Price)/(shift1-shift2);
   bool break_neck=false;

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

   for(int i=0;i<total;i++)
     {
      if(up_trend)
        {
         if((tp1>0 && rates[i].low<=tp1) || rates[i].high>s_HeadExtremum.Price)
            return false;
         double neck=koef*(shift2-shift-i)+s_StartRShoulder.Price;
         if(!break_neck)
           {
            if(rates[i].close>neck)
               continue;
            break_neck=true;
            tp1=neck-(s_HeadExtremum.Price-neck)*0.9;
            tp2=neck-(neck-s_StartTrend.Price)*0.9;
            tp1=fmax(tp1,tp2);
            continue;
           }
         if(rates[i].high>neck)
           {
            if(sl==-1)
               sl=rates[i].high;
            else
               sl=fmax(sl,rates[i].high);
           }
         if(rates[i].close>neck || sl==-1)
            continue;
         if((total-i)>2)
            return false;
//---
         signal=-1;
         break;
        }
      else
        {
         if((tp1>0 && rates[i].high>=tp1) || rates[i].low<s_HeadExtremum.Price)
            return false;
         double neck=koef*(shift2-shift-i)+s_StartRShoulder.Price;
         if(!break_neck)
           {
            if(rates[i].close<neck)
               continue;
            break_neck=true;
            tp1=neck+(neck-s_HeadExtremum.Price)*0.9;
            tp2=neck+(s_StartTrend.Price-neck)*0.9;
            tp1=fmin(tp1,tp2);
            continue;
           }
         if(rates[i].low<neck)
           {
            if(sl==-1)
               sl=rates[i].low;
            else
               sl=fmin(sl,rates[i].low);
           }
         if(rates[i].close<neck || sl==-1)
            continue;
         if((total-i)>2)
            return false;
//---
         signal=1;
         break;
        }
     }   
//---
   return true;
  }

С полным кодом всех методов и функций можно ознакомиться во вложении.

Код советника для тестирование стратеги взяты из статьи [1] практически без изменений. Изменения были внесены только в части замены класса для работы с паттерном. С полным кодом советника можно ознакомиться во вложении.

По результатам работы было проведено тестирование работы советника за 10 месяцев 2018 года. Параметры тестирования приведены на скриншотах ниже.

Параметры тестирования Параметры тестирования

По результатам тестирования советник продемонстрировал способность генерировать прибыль на анализируемом временном интервале. Более 57% сделок было закрыто с прибылью и профит-фактор составил 2.17. Но огорчило количество сделок, за 10 месяцев было совершено всего 14 трейдов.

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


4. Объединяем два паттерна в один советник

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

Для начала идентифицируем классы. В базовом классе CObject есть виртуальный метод Type. Добавим этот метод в описание наших классов. В классе CPattern данный метод будет возвращать значение 101, а в классе CHS_Pattern — 102. В данный момент мы используем только 2 паттерна, поэтому мы вполне можем ограничиться числовыми константами. При увеличении числа паттернов, я бы рекомендовал воспользоваться перечислениями, что позволит повысить читабельность кода.

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

int CPattern::Compare(const CPattern *node,const int mode=0) const
  {
   if(Type()>node.Type())
      return -1;
   else
      if(Type()<node.Type())
         return 1;
//---
   if(s_StartTrend.TimeStartBar>node.StartTrend().TimeStartBar)
      return -1;
   else
      if(s_StartTrend.TimeStartBar<node.StartTrend().TimeStartBar)
         return 1;
//---
   if(s_StartCorrection.TimeStartBar>node.StartCorrection().TimeStartBar)
      return -1;
   else
      if(s_StartCorrection.TimeStartBar<node.StartCorrection().TimeStartBar)
         return 1;
//---
   if(s_EndCorrection.TimeStartBar>node.EndCorrection().TimeStartBar)
      return -1;
   else
      if(s_EndCorrection.TimeStartBar<node.EndCorrection().TimeStartBar)
         return 1;
//---
   return 0;
  }

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

CPattern *NewClass(int type)
  {
   switch(type)
     {
      case 0:
        return new CPattern();
        break;
      case 1:
        return new CHS_Pattern();
        break;
     }
//---
   return NULL;
  }

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

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

void OnTick()
  {
//---
.........................
.........................
.........................
//---
   for(int pat=0;pat<2;pat++)
     {
      Pattern=NewClass(pat);
      if(CheckPointer(Pattern)==POINTER_INVALID)
         return;
      if(!Pattern.Create(ar_Objects.At(1),d_MinCorrection,d_MaxCorrection))
        {
         delete Pattern;
         continue;
        }
//---
      datetime ss=start_search;
      while(!IsStopped() && Pattern.Search(ss))
        {
         ss=fmax(ss,Pattern.EndTrendTime()+PeriodSeconds(e_TimeFrame));
         bool found=false;
         for(int i=2;i<ar_Objects.Total();i++)
           {
            CPattern *temp=ar_Objects.At(i);
            if(Pattern.Compare(temp,0)==0)
              {
               found=true;
               break;
              }
           }
         if(found)
            continue;
         if(!CheckPattern(Pattern))
            continue;
         if(!ar_Objects.Add(Pattern))
            continue;
         Pattern=NewClass(pat);
         if(CheckPointer(Pattern)==POINTER_INVALID)
            break;
         if(!Pattern.Create(ar_Objects.At(1),d_MinCorrection,d_MaxCorrection))
           {
            delete Pattern;
            break;
           }
        }
      if(CheckPointer(Pattern)!=POINTER_INVALID)
         delete Pattern;
     }
//---
   return;
  }

На этом изменения кода советника завершены. Полный код нового советника можно найти во вложении, файл TwoPatterns.mq5.

После внесения необходимых изменений проведем тестирование с прежними параметрами.

Параметры тестирования Параметры тестирования

По результатам тестирования видно, как стратегии дополняют друг друга. За тестируемый период советник совершил 128 трейдов, более 60% которых были закрыты с прибылью. В результате, профит-фактор работы советника составил 1.94, и фактор восстановления составил 3.85. Полные результаты тестирования приведены на скриншотах ниже.

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

5. Тестирование торговой системы

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

ИнструментПрибыльПрофит-фактор
EURUSD743.571.94
EURJPY125.131.47
GBPJPY
33.931.04
EURGBP-191.7
0.82
GBPUSD-371.050.60
USDJPY
-657.380.31

Как видно из приведенных в таблице результатов, на половине из протестированных инструментов советник получил прибыль, а по остальным убыток. Данный факт еще раз подтверждает, что ценовые графики каждого инструмента индивидуальны, и перед использованием советника необходимо провести его оптимизацию для конкретных условий. К примеру, оптимизация по параметру "Stop Loss Backstep" позволило увеличить прибыль по 4 инструментам и снизить убытки по 2 инструментам. Что в целом увеличило доходность по всей корзине инструментов. Результаты приведены в таблице ниже.

ИнструментПрибыльПрофит-факторStop Loss Backstep
EURUSD1020.281.78350
EURJPY532.541.52400
GBPJPY208.691.17300
EURGBP91.451.05450
GBPUSD-315.870.55100
USDJPY-453.080.33100


6. Разворачиваем торговые сигналы на убыточных инструментах

По результатам тестирования нашего советника в предыдущей главе выбиваются два инструмента, которые не удалось вывести в положительный результат. Это GBPUSD и USDJPY. Что может свидетельствовать о недостаточной силе рассматриваемых паттернов для разворота тренда указанных инструментов. Упорное движение в сторону снижения баланса наводит на мысль о развороте сделок при поступлении сигнала.

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

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

Такой подход дает нам возможность развернуть сделки, внеся изменения только в код советника, без изменения кода классов поиска паттернов.

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

input bool  ReverseTrade   =  true; //Reverse Deals

Однако добавлением одного параметра нам не изменить логику открытия сделок. Внесем изменения в функцию CheckPattern. В блоке объявления локальных переменных добавим переменную temp, которую будем использовать в качестве временного хранилища при обмене цен стоп-лосса и тейк-профита 1.

bool CheckPattern(CPattern *pattern)
  {
   int signal=0;
   double sl=-1, tp1=-1, tp2=-1;
   if(!pattern.CheckSignal(signal,sl,tp1,tp2))
      return false;
//---
   double price=0;
   double to_close=100;
   double temp=0;
//---

Далее в теле переключателя switch добавим проверку состояния флага ReverseTrade. Если флаг установлен в состояние false, используем старую логику. При использовании реверсивной торговли поменяем значение стоп-лосса и тейк-профита 1. Затем пересчитаем значения тейк-профитов в пунктах инструмента и передадим полученные значения в класс CLimitTakeProfit. После успешного прохождения всех итераций открываем сделку, противоположную полученному сигналу.

//---
   switch(signal)
     {
      case 1:
        CLimitTakeProfit::Clear();
        if(!ReverseTrade)
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
           if((tp1-price)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(CLimitTakeProfit::AddTakeProfit((uint)((tp1-price)/_Point),(fabs(tp1-tp2)>=_Point ? 50 : 100)))
                 to_close-=(fabs(tp1-tp2)>=_Point ? 50 : 100);
           if(to_close>0 && (tp2-price)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(!CLimitTakeProfit::AddTakeProfit((uint)((tp2-price)/_Point),to_close))
                 return false;
           if(Trade.Buy(d_Lot,_Symbol,price,sl-i_SL*_Point,0,NULL))
              return false;
          }
        else
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
           temp=tp1;
           tp1=sl-i_SL*_Point;
           sl=temp;
           tp1=(price-tp1)/_Point;
           tp2=(tp2-price)/_Point;
           if(tp1>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(CLimitTakeProfit::AddTakeProfit((uint)(tp1),((tp2-tp1)>=1? 50 : 100)))
                 to_close-=((tp2-tp1)>=1 ? 50 : 100);
           if(to_close>0 && tp2>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(!CLimitTakeProfit::AddTakeProfit((uint)(tp2),to_close))
                 return false;
           if(Trade.Sell(d_Lot,_Symbol,price,sl,0,NULL))
              return false;
          }
        break;
      case -1:
        CLimitTakeProfit::Clear();
        if(!ReverseTrade)
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
           if((price-tp1)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(CLimitTakeProfit::AddTakeProfit((uint)((price-tp1)/_Point),(fabs(tp1-tp2)>=_Point ? 50 : 100)))
                 to_close-=(fabs(tp1-tp2)>=_Point ? 50 : 100);
           if(to_close>0 && (price-tp2)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
              if(!CLimitTakeProfit::AddTakeProfit((uint)((price-tp2)/_Point),to_close))
                 return false;
           if(Trade.Sell(d_Lot,_Symbol,price,sl+i_SL*_Point,0,NULL))
              return false;
          }
        else
          {
           price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
           temp=tp1;
           tp1=sl+i_SL*_Point;
           sl=temp;
           tp1=(tp1-price)/_Point;
           tp2=(price-tp2)/_Point;
           if(tp1>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(CLimitTakeProfit::AddTakeProfit((uint)(tp1),((tp2-tp1)>=1 ? 50 : 100)))
                 to_close-=((tp2-tp1)>=1 ? 50 : 100);
           if(to_close>0 && tp2>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
              if(!CLimitTakeProfit::AddTakeProfit((uint)(tp2),to_close))
                 return false;
           if(Trade.Buy(d_Lot,_Symbol,price,sl,0,NULL))
              return false;
          }
        break;
     }
//---
   return true;
  }

На этом дополнение кода советника завершено. С полным кодом всех классов и функции можно ознакомиться во вложении.

Проведем тестирование функции реверсивной торговли. Первое тестирование проведем на инструменте GBPUSD с использованием того же временного интервала и тайм-фрейма. Параметры советника приведены на скриншотах ниже.

GBPUSD реверсивный тестGBPUSD реверсивный тест

Результаты тестирования показали успешность такого решения. На тестируемом временном интервале советник с включенной функцией реверсивной торговли показал прибыль. Из 127 совершенных сделок, 95 (75,59%) закрылись с прибылью. Профит-фактор составил 2,35. Полные результаты тестирования приведены на скриншотах ниже.

GBPUSD результат реверсивного тестаGBPUSD результата реверсивного теста

Для закрепления полученного результата проведем аналогичное тестирование на инструменте USDJPY. Параметры тестирования приведены на скриншотах.

USDJPY реверсивный тестUSDJPY реверсивный тест

Второе тестирование реверсивной функции также оказалось успешным. Из 108 сделок, 66 (61,11%) было закрыто с прибылью, профит фактор составил 2,45. Полные результаты тестирования приведены на скриншотах.

USDJPY результат реверсивного тестаUSDJPY результат реверсивного теста

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


Заключение

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

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

Надеюсь, мой опыт будет полезен читателю в построении его собственной торговой стратегии.

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

Ссылки

  1. Разворотные паттерны: Тестируем паттерн "Двойная вершина/дно"
  2. Реализация Take Profit в виде лимитных ордеров без изменения оригинального кода советника

Программы, используемые в статье:

# Имя Тип Описание
1 ZigZag.mqh Библиотека класса Касс индикатора Zig Zag
2 Trends.mqh Библиотека класса Класс поиска тенденций
3 Pattern.mqh Библиотека класса Класс работы с паттернами "Двойная вершина / дно"
4 HS_Pattern Библиотека класса Класс работы с паттернами "Голова-Плечи"
5 LimitTakeProfit.mqh Библиотека класса Класс подмены тейк-профита ордеров на лимитные ордера
6 Header.mqh Библиотека Файл заголовков советника
7 Head-Shoulders.mq5 Советник Советник по стратегии "Голова-Плечи"
8 TwoPatterns.mq5 Советник Советник с отработкой двух паттернов