Модель продолжения движения - поиск на графике и статистика исполнения

Almat Kaldybay | 7 сентября, 2018

  1. Введение
  2. Описание модели - общие положения
  3. Принципы распознавания модели на графике
  4. Построение алгоритма и написание кода
    1. Входные параметры, функция OnInit() и первоначальное объявление переменных
    2. Определение общих параметров
    3. Актуализация данных в массивах
      1. Заполнение массивов при появлении нового бара
      2. Заполнение массивов данными 0-го бара
      3. Актуализация данных о фракталах
    4. Поиск экстремумов
      1. Поиск экстремумов для тренда вниз
      2. Поиск экстремумов для тренда вверх
      3. Приведение значений High/Low коррекционных волн к единым переменным
    5. Описание условий для распознавания модели
    6. Создание контролей
      1. Контроль формирования точки входа в зоне открытия позиции
      2. Контроль отката цены к зоне открытия позиции
      3. Исключение создания дублирующихся позиций в рамках одной модели
    7. Описание условий для точки входа
    8. Описание торговых условий
    9. Работа с торговыми операциями
      1. Установка позиций
      2. Установка take-profit
      3. Перевод позиции в безубыток
  5. Сбор статистических данных
  6. Заключение


1. Введение

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


2. Описание модели - общие положения

Модель продолжения движения, описываемая в данной статье, состоит из двух волн: основной волны и коррекционной. Схематически модель описана на рисунке 1. Где, AB - это основная волна, BC - коррекционная волна, CD - продолжение движения в сторону основной тенденции.

Схема модели продолжения движения

Рис. 1. Схема модели продолжения движения

На графике это выглядит так:

Модель продолжения движения на графике H4 пары AUDJPY

Рис. 2. Модель продолжения движения на графике H4 пары AUDJPY


3.Принципы распознавания модели на графике

Принципы распознавания модели описаны в таблице 1.

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

№ Принципы распознавания модели для тренда вниз №Принципы распознавания модели для тренда вверх
1Баром-экстремумом считается бар, High/Low которого выше/ниже двух High/Low предыдущих баров1Баром-экстремумом считается бар, High/Low которого выше/ниже двух High/Low предыдущих баров
2Коррекционная волна всегда должна заканчиваться наличием верхнего экстремума (точка С - см. рисунок 1 и рисунок 2)2Коррекционная волна всегда должна заканчиваться наличием нижнего экстремума (точка С - см. рисунок 1 и рисунок 2)
 3Длительность коррекционной волны не может быть продолжительной и должна ограничиваться несколькими барами 3Длительность коррекционной волны не может быть продолжительной и должна ограничиваться несколькими барами
 4High коррекционного движения (точка С - см. рисунок 1 и рисунок 2) должен быть ниже High основного движения (точка A - см. рисунок 1 и рисунок 2) 4Low коррекционного движения (точка С - см. рисунок 1 и рисунок 2) должен быть выше Low основного движения (точка A - см. рисунок 1 и рисунок 2)
 5Принцип своевременности точки входа - означает, что открывать позицию нужно только в определенный момент формирования точки входа 5Принцип своевременности точки входа - означает, что открывать позицию нужно только в определенный момент формирования точки входа


4. Построение алгоритма и написание кода

1. Входные параметры, функция OnInit() и первоначальное объявление переменных

Сначала нужно подключить класс CTrade для упрощенного доступа к торговым операциям:

//--- подключение файлов
#include <Trade\Trade.mqh> 
//--- объект для проведения торговых операций
CTrade  trade;

Далее описать входные параметры:

//--- входные параметры
input ENUM_TIMEFRAMES base_tf;  //таймфрейм базового периода
input ENUM_TIMEFRAMES work_tf;  //таймфрейм рабочего периода
input double SummRisk=100;      //сумма риска на сделку
input double sar_step=0.1;      //set parabolic step
input double maximum_step=0.11; //set parabolic maximum step
input bool TP_mode=true;        //разрешение на установку take-profit
input int M=2;                  //отношение прибыли к риску
input bool Breakeven_mode=true; //разрешение на перенос позиции в безубыток
input double breakeven=1;       //отношение размера прибыли к размеру stop-loss

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

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

Также советник будет иметь возможность устанавливать take-profit по указанному отношению прибыли к риску (параметр М), а также переводить позицию в безубыток по указанному отношению размера прибыли к размеру stop-loss (параметр breakeven).

После описания входных параметров нужно объявить переменные для хэндлов индикаторов и массивы для таймфреймов base_tf и work_tf:

//--- объявление переменных для хэндлов индикаторов
int Fractal_base_tf,Fractal_work_tf;             //хэндл индикатора iFractals
int Sar_base_tf,Sar_work_tf;                     //хэндл индикатора iSar
//--- объявление массивов для base_tf
double High_base_tf[],Low_base_tf[];             //массивы для хранения цен High и Low баров
double Close_base_tf[],Open_base_tf[];           //массивы для хранения цен Close и Open баров 
datetime Time_base_tf[];                         //массив для хранения времени открытия баров
double Sar_array_base_tf[];                      //массив для хранения цен индикатора iSar (Parabolic)
double FractalDown_base_tf[],FractalUp_base_tf[];//массив для хранения цен индикатора iFractals
//--- объявление массивов для work_tf
double High_work_tf[],Low_work_tf[];
double Close_work_tf[],Open_work_tf[];
datetime Time_work_tf[];
double Sar_array_work_tf[];
double FractalDown_work_tf[],FractalUp_work_tf[];;

Советником будут использоваться два индикатора: фракталы для определения части экстремумов и Parabolic для ведения trailing-stop позиции. Также планирую использовать Parabolic для определения точки входа на рабочем таймфрейме work_tf.

Далее в функции OnInit() нужно получить хэндлы индикаторов и заполнить массивы первоначальными данными.

int OnInit()
  {
//--- получаем хэндл индикатора iSar
   Sar_base_tf=iSAR(Symbol(),base_tf,sar_step,maximum_step);
   Sar_work_tf=iSAR(Symbol(),work_tf,sar_step,maximum_step);
//--- получаем хендлы индикатора iFractals
   Fractal_base_tf=iFractals(Symbol(),base_tf);
   Fractal_work_tf=iFractals(Symbol(),work_tf);
//--- приведение порядка массивов, как в таймсерии для base_tf
   ArraySetAsSeries(High_base_tf,true);
   ArraySetAsSeries(Low_base_tf,true);
   ArraySetAsSeries(Close_base_tf,true);
   ArraySetAsSeries(Open_base_tf,true);
   ArraySetAsSeries(Time_base_tf,true);;
   ArraySetAsSeries(Sar_array_base_tf,true);
   ArraySetAsSeries(FractalDown_base_tf,true);
   ArraySetAsSeries(FractalUp_base_tf,true);
//--- первоначальное заполнение массивов для base_tf
   CopyHigh(Symbol(),base_tf,0,1000,High_base_tf);
   CopyLow(Symbol(),base_tf,0,1000,Low_base_tf);
   CopyClose(Symbol(),base_tf,0,1000,Close_base_tf);
   CopyOpen(Symbol(),base_tf,0,1000,Open_base_tf);
   CopyTime(Symbol(),base_tf,0,1000,Time_base_tf);
   CopyBuffer(Sar_base_tf,0,TimeCurrent(),1000,Sar_array_base_tf);
   CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);
   CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf);
//--- приведение порядка массивов, как в таймсерии для work_tf
   ArraySetAsSeries(High_work_tf,true);
   ArraySetAsSeries(Low_work_tf,true);
   ArraySetAsSeries(Close_work_tf,true);
   ArraySetAsSeries(Open_work_tf,true);
   ArraySetAsSeries(Time_work_tf,true);
   ArraySetAsSeries(Sar_array_work_tf,true);
   ArraySetAsSeries(FractalDown_work_tf,true);
   ArraySetAsSeries(FractalUp_work_tf,true);
//--- первоначальное заполнение массивов для work_tf
   CopyHigh(Symbol(),work_tf,0,1000,High_work_tf);
   CopyLow(Symbol(),work_tf,0,1000,Low_work_tf);
   CopyClose(Symbol(),work_tf,0,1000,Close_work_tf);
   CopyOpen(Symbol(),work_tf,0,1000,Open_work_tf);
   CopyTime(Symbol(),work_tf,0,1000,Time_work_tf);
   CopyBuffer(Sar_work_tf,0,TimeCurrent(),1000,Sar_array_work_tf);
   CopyBuffer(Fractal_work_tf,0,TimeCurrent(),1000,FractalUp_work_tf);
   CopyBuffer(Fractal_work_tf,1,TimeCurrent(),1000,FractalDown_work_tf);

//---
   return(INIT_SUCCEEDED);
  }

Сначала получили хэндл инидкаторов, затем задали порядок массивов, как в таймсерии и заполнили массив данными. Я посчитал, что для работы данного советника данные о 1000 барах более чем достаточно.

2. Определение общих параметров

С этого этапа перехожу к работе с функцией OnTick().

В раздел "Общие параметры" я обычно записываю рыночную информацию, а также объявляю переменные, которые будут использоваться для установки позиций.

//+------------------------------------------------------------------+
//| 1. Общие параметры (начало)                                      |
//+------------------------------------------------------------------+
//--- рыночная информация
//кол-во знаков после запятой в цене инструмента
   int Digit=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
//определение разрядности цены текущего символа
   double f=1;
   if(Digit==5) {f=100000;}
   if(Digit==4) {f=10000;}
   if(Digit==3) {f=1000;}
   if(Digit==2) {f=100;}
   if(Digit==1) {f=10;}
//---
   double spread=SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/f;//приведение спреда к дробному значению с учетом разрядности цены
   double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);//информация о цене Bid
   double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);//информация о цене Ask
   double CostOfPoint=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_VALUE);//информация о стоимости тика
//--- переменные для расчета лота для установки позиции
   double RiskSize_points;//переменная для хранения размера stop-loss текущей позиции
   double CostOfPoint_position;//переменная для хранения стоимости пункта текущей позиции с учетом риска на сделку
   double Lot;//переменная для храннения размера лота для открытия позиции
   double SLPrice_sell,SLPrice_buy;//переменные для хранения ценовых уровней stop-loss
//--- переменные для хранения информации о количестве баров
   int bars_base_tf=Bars(Symbol(),base_tf);
   int bars_work_tf=Bars(Symbol(),work_tf);
//--- переменные для хранения информации о позиции
   string P_symbol; //символ позиции
   int P_type,P_ticket,P_opentime;//тип, тикет и время открытия позиции
//+------------------------------------------------------------------+
//| 1. Общие параметры (конец)                                       |
//+------------------------------------------------------------------+ 

3. Актуализация данных в массивах

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

Для этого можно использовать следующую конструкцию:

   static datetime LastBar_base_tf=0;//переменная для определения появления нового бара
   datetime ThisBar_base_tf=(datetime)SeriesInfoInteger(_Symbol,base_tf,SERIES_LASTBAR_DATE);//время текущего бара
   if(LastBar_base_tf!=ThisBar_base_tf)//если время не совпадает, значит появился новый бар
     {
         //тут заполнять массивы
     }

Но при таком подходе теряются данные нулевого бара, поэтому для данных бара с индексом 0 я ввел отдельные массивы.

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

Далее будут приведены примеры заполнения массивов.

1. Заполнение массивов при появлении нового бара

Сначала заполняем массивы при появлении нового бара:

//+------------------------------------------------------------------+
//| 2.1 Заполнение массивов при появлении нового бара(начало)        |
//+------------------------------------------------------------------+
//--- для base_tf
//--- приведение порядка массивов, как в таймсерии
   ArraySetAsSeries(High_base_tf,true);
   ArraySetAsSeries(Low_base_tf,true);
   ArraySetAsSeries(Close_base_tf,true);
   ArraySetAsSeries(Open_base_tf,true);
   ArraySetAsSeries(Time_base_tf,true);
   ArraySetAsSeries(Sar_array_base_tf,true);
   ArraySetAsSeries(FractalDown_base_tf,true);
   ArraySetAsSeries(FractalUp_base_tf,true);
//--- заполнение массивов
   static datetime LastBar_base_tf=0;//переменная для определения появления нового бара
   datetime ThisBar_base_tf=(datetime)SeriesInfoInteger(_Symbol,base_tf,SERIES_LASTBAR_DATE);//время открытия текущего бара
   if(LastBar_base_tf!=ThisBar_base_tf)//если время не совпадает, значит появился новый бар
     {
      CopyHigh(Symbol(),base_tf,0,1000,High_base_tf);
      CopyLow(Symbol(),base_tf,0,1000,Low_base_tf);
      CopyClose(Symbol(),base_tf,0,1000,Close_base_tf);
      CopyOpen(Symbol(),base_tf,0,1000,Open_base_tf);
      CopyTime(Symbol(),base_tf,0,1000,Time_base_tf);
      CopyBuffer(Sar_base_tf,0,TimeCurrent(),1000,Sar_array_base_tf);
      CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);
      CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf);
      LastBar_base_tf=ThisBar_base_tf;
     }
//--- для work_tf
//--- приведение порядка массивов, как в таймсерии
   ArraySetAsSeries(High_work_tf,true);
   ArraySetAsSeries(Low_work_tf,true);
   ArraySetAsSeries(Close_work_tf,true);
   ArraySetAsSeries(Open_work_tf,true);
   ArraySetAsSeries(Time_work_tf,true);
   ArraySetAsSeries(Sar_array_work_tf,true);
   ArraySetAsSeries(FractalDown_work_tf,true);
   ArraySetAsSeries(FractalUp_work_tf,true);
//--- заполнение массивов
   static datetime LastBar_work_tf=0;//переменная для определения появления нового бара
   datetime ThisBar_work_tf=(datetime)SeriesInfoInteger(_Symbol,work_tf,SERIES_LASTBAR_DATE);//время открытия текущего бара
   if(LastBar_work_tf!=ThisBar_work_tf)//если время не совпадает, значит появился новый бар
     {
      CopyHigh(Symbol(),work_tf,0,1000,High_work_tf);
      CopyLow(Symbol(),work_tf,0,1000,Low_work_tf);
      CopyClose(Symbol(),work_tf,0,1000,Close_work_tf);
      CopyOpen(Symbol(),work_tf,0,1000,Open_work_tf);
      CopyTime(Symbol(),work_tf,0,1000,Time_work_tf);
      CopyBuffer(Sar_work_tf,0,TimeCurrent(),1000,Sar_array_work_tf);
      CopyBuffer(Fractal_work_tf,0,TimeCurrent(),1000,FractalUp_work_tf);
      CopyBuffer(Fractal_work_tf,1,TimeCurrent(),1000,FractalDown_work_tf);
      LastBar_work_tf=ThisBar_work_tf;
     }
//+------------------------------------------------------------------+
//| 2.1 Заполнение массивов при появлении нового бара(конец)         |
//+------------------------------------------------------------------+

2. Заполнение массивов данными 0-го бара

Информация о барах с индексом 1 и выше теперь постоянно будет актуальной, тогда как данные о баре с индексом 0 все еще остаются неактуальными. Для хранения информации о нулевых барах я ввел отдельные массивы:

//+------------------------------------------------------------------+
//| 2.2 Заполнение массивов данными 0-го бара(начало)                |
//+------------------------------------------------------------------+
//--- для base_tf
//--- объявление массивов
   double High_base_tf_0[],Low_base_tf_0[];
   double Close_base_tf_0[],Open_base_tf_0[];
   datetime Time_base_tf_0[];
   double Sar_array_base_tf_0[];
//--- приведение порядка массивов, как в таймсерии
   ArraySetAsSeries(High_base_tf_0,true);
   ArraySetAsSeries(Low_base_tf_0,true);
   ArraySetAsSeries(Close_base_tf_0,true);
   ArraySetAsSeries(Open_base_tf_0,true);
   ArraySetAsSeries(Time_base_tf_0,true);
   ArraySetAsSeries(Sar_array_base_tf_0,true);
//--- заполнение массивов
   CopyHigh(Symbol(),base_tf,0,1,High_base_tf_0);
   CopyLow(Symbol(),base_tf,0,1,Low_base_tf_0);
   CopyClose(Symbol(),base_tf,0,1,Close_base_tf_0);
   CopyOpen(Symbol(),base_tf,0,1,Open_base_tf_0);
   CopyTime(Symbol(),base_tf,0,1,Time_base_tf_0);
   CopyBuffer(Sar_base_tf,0,TimeCurrent(),1,Sar_array_base_tf_0);
//--- для work_tf
//--- объявление массивов
   double High_work_tf_0[],Low_work_tf_0[];
   double Close_work_tf_0[],Open_work_tf_0[];
   datetime Time_work_tf_0[];
   double Sar_array_work_tf_0[];
//--- приведение порядка массивов, как в таймсерии
   ArraySetAsSeries(High_work_tf_0,true);
   ArraySetAsSeries(Low_work_tf_0,true);
   ArraySetAsSeries(Close_work_tf_0,true);
   ArraySetAsSeries(Open_work_tf_0,true);
   ArraySetAsSeries(Time_work_tf_0,true);
   ArraySetAsSeries(Sar_array_work_tf_0,true);
//--- заполнение массивов
   CopyHigh(Symbol(),work_tf,0,1,High_work_tf_0);
   CopyLow(Symbol(),work_tf,0,1,Low_work_tf_0);
   CopyClose(Symbol(),work_tf,0,1,Close_work_tf_0);
   CopyOpen(Symbol(),work_tf,0,1,Open_work_tf_0);
   CopyTime(Symbol(),work_tf,0,1,Time_work_tf_0);
   CopyBuffer(Sar_work_tf,0,TimeCurrent(),1,Sar_array_work_tf_0);
//+------------------------------------------------------------------+
//| 2.2 Заполнение массивов данными 0-го бара(конец)                 |
//+------------------------------------------------------------------+

3. Актуализация данных о фракталах

Массивы с данными фракталов также нужно актуализировать. Каждый раз, когда экстремумы 0-го бара выше или ниже двух предыдущих, должно происходить перезаполнение массивов:

//+------------------------------------------------------------------+
//| 2.3 Актуализация данных о фракталах (начало)                     |
//+------------------------------------------------------------------+
//--- для base_tf
   if(High_base_tf_0[0]>High_base_tf[1] && High_base_tf_0[0]>High_base_tf[2])
     {
      CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);
     }
   if(Low_base_tf_0[0]<Low_base_tf[1] && Low_base_tf_0[0]<Low_base_tf[2])
     {
      CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf);
     }
//--- для work_tf
   if(High_work_tf_0[0]>High_work_tf[1] && High_work_tf_0[0]>High_work_tf[2])
     {
      CopyBuffer(Fractal_work_tf,0,TimeCurrent(),1000,FractalUp_work_tf);
     }
   if(Low_work_tf_0[0]<Low_work_tf[1] && Low_work_tf_0[0]<Low_work_tf[2])
     {
      CopyBuffer(Fractal_work_tf,1,TimeCurrent(),1000,FractalDown_work_tf);
     }
//+------------------------------------------------------------------+
//| 2.3 Актуализация данных о фракталах (конец)                      |
//+------------------------------------------------------------------+

4. Поиск экстремумов

Вернемся снова к модели продолжения движения. Для этого нужно снова вернуться к рисунку 2.

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

В Таблице 2 показана последовательность определения экстремумов.

Таблица 2. Последовательность определения экстремумов

№ п/пДля тренда внизДля тренда вверх
1Найти high коррекционного движения (точка С)Найти low коррекционного движения (точка С)
2От high коррекционного движения найти следующий верхний экстремум (точка А)От low коррекционного движения найти следующий нижний экстремум (точка А)
3Между точками С и А найти точку В - low коррекционного движенияМежду точками С и А найти точку В - high коррекционного движения
1. Поиск экстремумов для тренда вниз
//+------------------------------------------------------------------+
//| 3.1 Поиск экстремумов для тренда вниз (начало)                   |
//+------------------------------------------------------------------+
//--- объявление переменных
   int High_Corr_wave_downtrend_base_tf;//для бара high коррекционного движения (точка С)
   int UpperFractal_downtrend_base_tf;  //для бара - следующего верхнего экстремума (точка А)
   int Low_Corr_wave_downtrend_base_tf; //для бара low коррекционного движения (точка B)
//--- 
//--- Найти high коррекционного движения (точка С)
   if(High_base_tf_0[0]>High_base_tf[1] && High_base_tf_0[0]>High_base_tf[2])
     {
      High_Corr_wave_downtrend_base_tf=0;
     }
   else
     {
      for(n=0; n<(bars_base_tf);n++)
        {
         if(High_base_tf[n]>High_base_tf[n+1] && High_base_tf[n]>High_base_tf[n+2])
            break;
        }
      High_Corr_wave_downtrend_base_tf=n;
     }
//--- 
//--- От high коррекционного движения найти следующий верхний экстремум (точка А)
   for(n=High_Corr_wave_downtrend_base_tf+1; n<(bars_base_tf);n++)
     {
      // --- if a non-empty value, terminate the loop
      if(FractalUp_base_tf[n]!=EMPTY_VALUE)
         break;
     }
   UpperFractal_downtrend_base_tf=n;
//---
//--- Между точками С и А найти точку В - low коррекционного движения
   int CountToFind_arrmin=UpperFractal_downtrend_base_tf-High_Corr_wave_downtrend_base_tf;
   Low_Corr_wave_downtrend_base_tf=ArrayMinimum(Low_base_tf,High_Corr_wave_downtrend_base_tf,CountToFind_arrmin);
//+------------------------------------------------------------------+
//| 3.1 Поиск экстремумов для тренда вниз (конец)                    |
//+------------------------------------------------------------------+

2. Поиск экстремумов для тренда вверх

//+------------------------------------------------------------------+
//| 3.2 Поиск экстремумов для тренда вверх (начало)                  |
//+------------------------------------------------------------------+
//--- объявление переменных
   int Low_Corr_wave_uptrend_base_tf;//для бара Low коррекционного движения (точка С)
   int LowerFractal_uptrend_base_tf;  //для бара - следующего нижнего экстремума (точка А)
   int High_Corr_wave_uptrend_base_tf; //для бара High коррекционного движения (точка B)
//--- 
//--- Найти Low коррекционного движения (точка С)
   if(Low_base_tf_0[0]<Low_base_tf[1] && Low_base_tf_0[0]<Low_base_tf[2])
     {
      Low_Corr_wave_uptrend_base_tf=0;
     }
   else
     {
      //ищем лоу отката
      for(n=0; n<(bars_base_tf);n++)
        {
         if(Low_base_tf[n]<Low_base_tf[n+1] && Low_base_tf[n]<Low_base_tf[n+2])
            break;
        }
      Low_Corr_wave_uptrend_base_tf=n;
     }
//---
//--- От low коррекционного движения найти следующий нижний экстремум (точка А)
   for(n=Low_Corr_wave_uptrend_base_tf+1; n<(bars_base_tf);n++)
     {
      if(FractalDown_base_tf[n]!=EMPTY_VALUE)
         break;
     }
   LowerFractal_uptrend_base_tf=n;
//---
//--- Между точками С и А найти точку В - high коррекционного движения
int CountToFind_arrmax=LowerFractal_uptrend_base_tf-Low_Corr_wave_uptrend_base_tf;
High_Corr_wave_uptrend_base_tf=ArrayMaximum(High_base_tf,Low_Corr_wave_uptrend_base_tf,CountToFind_arrmax);
//+------------------------------------------------------------------+
//| 3.2 Поиск экстремумов для тренда вверх (конец)                   |
//+------------------------------------------------------------------+

3. Приведение значений High/Low коррекционных волн к единым переменным

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

//+----------------------------------------------------------------------------------+
//| 3.3 Приведение значений High/Low коррекционных волн к единым переменным (начало) |
//+----------------------------------------------------------------------------------+
//--- объявление переменных
   double High_Corr_wave_downtrend_base_tf_double,Low_Corr_wave_uptrend_base_tf_double;
   datetime High_Corr_wave_downtrend_base_tf_time,Low_Corr_wave_uptrend_base_tf_time;
//--- для High_Corr_wave_downtrend_base_tf
   if(High_Corr_wave_downtrend_base_tf==0)
     {
      High_Corr_wave_downtrend_base_tf_double=High_base_tf_0[High_Corr_wave_downtrend_base_tf];
      High_Corr_wave_downtrend_base_tf_time=Time_base_tf_0[High_Corr_wave_downtrend_base_tf];
     }
   else
     {
      High_Corr_wave_downtrend_base_tf_double=High_base_tf[High_Corr_wave_downtrend_base_tf];
      High_Corr_wave_downtrend_base_tf_time=Time_base_tf[High_Corr_wave_downtrend_base_tf];
     }
//-- для Low_Corr_wave_uptrend_base_tf
   if(Low_Corr_wave_uptrend_base_tf==0)
     {
      Low_Corr_wave_uptrend_base_tf_double=Low_base_tf_0[Low_Corr_wave_uptrend_base_tf];
      Low_Corr_wave_uptrend_base_tf_time=Time_base_tf_0[Low_Corr_wave_uptrend_base_tf];
     }
   else
     {
      Low_Corr_wave_uptrend_base_tf_double=Low_base_tf[Low_Corr_wave_uptrend_base_tf];
      Low_Corr_wave_uptrend_base_tf_time=Time_base_tf[Low_Corr_wave_uptrend_base_tf];
     }
//+---------------------------------------------------------------------------------+
//| 3.3 Приведение значений High/Low коррекционных волн к единым переменным (конец) |
//+---------------------------------------------------------------------------------+

Таким образом, ценовые и временные значения high/low коррекционных волн записываются в переменные и обращаться каждый раз к разным массивам надобности нет.

Если подытожить работу по поиску экстремумов, то получается, что найдены точки А, В и С согласно концепту распознавания модели (см. таблицы 4 и 5).

Таблица 4. Значения точек А, В и С для тренда вниз

ПоказательЗначения точки АЗначения точки ВЗначения точки С
Индекс бараUpperFractal_downtrend_base_tfLow_Corr_wave_downtrend_base_tfHigh_Corr_wave_downtrend_base_tf
Временное значениеTime_base_tf[UpperFractal_downtrend_base_tf]Time_base_tf[Low_Corr_wave_downtrend_base_tf]High_Corr_wave_downtrend_base_tf_time
Ценовое значениеHigh_base_tf[UpperFractal_downtrend_base_tf]Low_base_tf[Low_Corr_wave_downtrend_base_tf]High_Corr_wave_downtrend_base_tf_double

Таблица 5. Значения точек А, В и С для тренда вверх

ПоказательЗначения точки АЗначения точки ВЗначения точки С
Индекс бараLowerFractal_uptrend_base_tfHigh_Corr_wave_uptrend_base_tfLow_Corr_wave_uptrend_base_tf
Временное значениеTime_base_tf[LowerFractal_uptrend_base_tf]Time_base_tf[High_Corr_wave_uptrend_base_tf]Low_Corr_wave_uptrend_base_tf_time
Ценовое значениеLow_base_tf[LowerFractal_uptrend_base_tf]High_base_tf[High_Corr_wave_uptrend_base_tf]Low_Corr_wave_uptrend_base_tf_double


5. Описание условий для распознавания модели

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

Таблица 6. Минимальный набор условий для распознавания модели продолжения движения

№ п/пУсловия для тренда внизУсловия для тренда вверх
1High коррекционной волны (точка C) ниже high следующего за ним экстремума (точка А)Low коррекционной волны (точка С) выше low следующего за ним экстремума (точка А)
2Индекс low коррекционной волны (точка В) больше индекса high коррекционной (точка С)Индекс high коррекционной волны (точка В) больше индекса low коррекционной (точка С)
3Длительность коррекционного движения от 2 до 6 баров (кол-во баров от точки В)Длительность коррекционного движения от 2 до 6 баров(кол-во баров от точки В)

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

//+------------------------------------------------------------------+
//| 4. Описание условий для распознавания модели (начало)            |
//+------------------------------------------------------------------+
//--- для тренда вниз
/*1. High коррекционной волны (точка C) ниже high следующего за ним экстремума (точка А)*/
/*2. Индекс low коррекционной волны (точка В) больше индекса high коррекционной (точка С)*/
/*3. Длительность коррекционного движения от 2 до 6 баров (кол-во баров от точки В)*/
   bool Model_downtrend_base_tf=(
                                 /*1.*/High_Corr_wave_downtrend_base_tf_double<High_base_tf[UpperFractal_downtrend_base_tf] && 
                                 /*2.*/Low_Corr_wave_downtrend_base_tf>High_Corr_wave_downtrend_base_tf && 
                                 /*3.*/Low_Corr_wave_downtrend_base_tf>=1 && Low_Corr_wave_downtrend_base_tf<=6
                                 );
//--- для тренда вверх
/*1. Low коррекционной волны (точка С) выше low следующего за ним экстремума (точка А)*/
/*2. Индекс high коррекционной волны (точка В) больше индекса low коррекционной (точка С)*/
/*3. Длительность коррекционного движения от 2 до 6 баров(кол-во баров от точки В)*/
   bool Model_uptrend_base_tf=(
                               /*1.*/Low_Corr_wave_uptrend_base_tf_double>Low_base_tf[LowerFractal_uptrend_base_tf] && 
                               /*2.*/High_Corr_wave_uptrend_base_tf>Low_Corr_wave_uptrend_base_tf && 
                               /*3.*/High_Corr_wave_uptrend_base_tf>=1 && High_Corr_wave_uptrend_base_tf<=6
                               );
//+------------------------------------------------------------------+
//| 4. Описание условий для распознавания модели (конец)             |
//+------------------------------------------------------------------+

6. Создание контролей

Советник, как минимум, должен производить три проверки.

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

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

Модель продолжения движения

Рис. 3. Модель продолжения движения на графике H4 пары AUDJPY

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

1. Контроль формирования точки входа в зоне открытия позиции

Тут все просто: для модели на продажу цена bid должна быть выше low коррекционного движения (точка В). Для модели на покупку цена bid должна быть ниже high коррекционного движения (точка В).

//+------------------------------------------------------------------------+
//| 5.1 Контроль формирования точки входа в зоне открытия позиции (начало) |
//+------------------------------------------------------------------------+
//--- для тренда вниз
bool First_downtrend_control_bool=(bid>=Low_base_tf[Low_Corr_wave_downtrend_base_tf]);
//--- для тренда вверх
bool First_uptrend_control_bool=(bid<=High_base_tf[High_Corr_wave_uptrend_base_tf]);
//+------------------------------------------------------------------------+
//| 5.1 Контроль формирования точки входа в зоне открытия позиции (конец)  |
//+------------------------------------------------------------------------+

2. Контроль отката цены к зоне открытия позиции

Чтобы реализовать данный контроль, нужно определить бар с наименьшим значением low (для продаж) или бар с наибольшим значением high (для покупок), начиная от текущего индекса и до бара high/low коррекционного движения (точка В). Для этого используется функция ArrayMinimum() для модели на продажу и ArrayMaximum() для модели на покупку.

Далее сопоставляются индексы — индекс low/high коррекционного движения (точка В) и индексы, полученные функциями ArrayMinimum() и ArrayMaximum(). Если они совпадают, значит пробоя low/high коррекционного движения не было и можно ситуацию рассматривать как торговую. Если индексы не совпадают, значит, движение началось ранее и открывать позицию уже поздно.

//+------------------------------------------------------------------+
//| 5.2 Контроль отката цены к зоне открытия позиции (начало)        |
//+------------------------------------------------------------------+
//--- для тренда вниз
//находим бар с самой низкой ценой между 0 баром и low коррекционного движения
   int Second_downtrend_control_int=ArrayMinimum(Low_base_tf,0,Low_Corr_wave_downtrend_base_tf+1);
//если low текущего бара ниже low коррекционного движения
   if(Low_base_tf_0[0]<Low_base_tf[Second_downtrend_control_int])
     {
      Second_downtrend_control_int=0; //значит минимум на 0 баре
     }
//если бар с самой низкой ценой и low коррекционного движения совпадают, то есть это один и тот же бар
//значит не было выхода цены из зоны открытия позиции
   bool Second_downtrend_control_bool=(Second_downtrend_control_int==Low_Corr_wave_downtrend_base_tf);
//---
//--- для тренда вверх
//находим бар с самой высокой ценой между 0 баров и high коррекционного движения
   int Second_uptrend_control_int=ArrayMaximum(High_base_tf,0,High_Corr_wave_uptrend_base_tf+1);
   //если high текущего бара выше high коррекционного движения
   if(High_base_tf_0[0]>High_base_tf[Second_uptrend_control_int])
     {
      Second_uptrend_control_int=0;//значит максимум на 0 баре
     }
//если бар с самой высокой ценой и high коррекционного движения совпадают, то есть это один и тот же бар
//значит не было выхода цены из зоны открытия позиции
   bool Second_uptrend_control_bool=(Second_uptrend_control_int==High_Corr_wave_uptrend_base_tf);
//+------------------------------------------------------------------+
//| 5.2 Контроль отката цены к зоне открытия позиции (конец)         |
//+------------------------------------------------------------------+

3. Исключение создания дублирующихся позиций в рамках одной модели

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

Далее время найденного бара — high/low коррекционного движения (точка С от точки входа), сопоставляется со временем текущего high/low коррекционного движения (текущая точка С). Если они совпадают, то открытия позиции происходить не должно, так как позиция по данной модели уже существует.

Создание контроля для продаж:

//+---------------------------------------------------------------------------+
//| 5.3.1 Для продаж (начало)                                                 |
//+---------------------------------------------------------------------------+
//--- объявление переменных
   int Bar_sell_base_tf,High_Corr_wave_downtrend_base_tf_sell;
   bool Third_downtrend_control_bool=false;
//--- перебор открытых позиций
   if(PositionsTotal()>0)
     {
      for(i=0;i<=PositionsTotal();i++)
        {
         if(PositionGetTicket(i))
           {
            //--- определяем символ, тип и время открытия позиции
            P_symbol=string(PositionGetString(POSITION_SYMBOL));
            P_type=int(PositionGetInteger(POSITION_TYPE));
            P_opentime=int(PositionGetInteger(POSITION_TIME));
            //--- если символ позиции совпадает с текущим графиком и тип сделки "sell"
            if(P_symbol==Symbol() && P_type==1)
              {
               //--- находим бар, на котором была открыта позиция
               Bar_sell_base_tf=iBarShift(Symbol(),base_tf,P_opentime);
               //--- от бара, на котором была открыта позиция производим поиск high коррекционного движения
               //если позиция была открыта на текущем баре
               if(Bar_sell_base_tf==0)
                 {
                  //то при условии, что текущий бар является экстремумом
                  if(High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf+1] && High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf+2])
                    {
                     High_Corr_wave_downtrend_base_tf_sell=Bar_sell_base_tf;//high коррекционного движения будет равен текущему бару
                    }
                  else
                    {
                     //если текущий бар не является экстремумом, то запускаем цикл на поиск экстремума
                     for(n=Bar_sell_base_tf; n<(bars_base_tf);n++)
                       {
                        if(High_base_tf[n]>High_base_tf[n+1] && High_base_tf[n]>High_base_tf[n+2])//если экстремум найден
                           break;//прерываем цикл
                       }
                     High_Corr_wave_downtrend_base_tf_sell=n;
                    }
                  //--- описываем условия для контроля
                  Third_downtrend_control_bool=(
                                                /*1. Время high коррекционного движения, найденного от открытия позиции
                                                 совпадает со временем текущего high коррекционного движения*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time
                                                );
                 }
               //--- если позиция была открыта не на текущем баре
               if(Bar_sell_base_tf!=0 && Bar_sell_base_tf!=1000)
                 {
                  //--- запускаем цикл по поиску бара-экстремума
                  for(n=Bar_sell_base_tf; n<(bars_base_tf);n++)
                    {
                     //--- если экстремум найден
                     if(High_base_tf[n]>High_base_tf[n+1] && High_base_tf[n]>High_base_tf[n+2])
                        break;//прерываем цикл
                    }
                  High_Corr_wave_downtrend_base_tf_sell=n;
                 }
               Third_downtrend_control_bool=(
                                             /*1. Время high коррекционного движения, найденного от открытия позиции
                                                 совпадает со временем текущего high коррекционного движения*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time
                                             );
              }
           }
        }
     }
//+---------------------------------------------------------------------------+
//| 5.3.1 Для продаж (конец)                                                  |
//+---------------------------------------------------------------------------+
Создание контроля для покупок:
//+---------------------------------------------------------------------------+
//| 5.3.2 Для покупок (начало)                                                |
//+---------------------------------------------------------------------------+
//--- объявление переменных
   int Bar_buy_base_tf,Low_Corr_wave_uptrend_base_tf_buy;
   bool Third_uptrend_control_bool=false;
//--- перебор открытых позиций
   if(PositionsTotal()>0)
     {
      for(i=0;i<=PositionsTotal();i++)
        {
         if(PositionGetTicket(i))
           {
            //определяем символ, тип и время открытия позиции
            P_symbol=string(PositionGetString(POSITION_SYMBOL));
            P_type=int(PositionGetInteger(POSITION_TYPE));
            P_opentime=int(PositionGetInteger(POSITION_TIME));
            //если символ позиции совпадает с текущим графиком и тип сделки "buy"
            if(P_symbol==Symbol() && P_type==0)
              {
               //находим бар, на котором была открыта позиция
               Bar_buy_base_tf=iBarShift(Symbol(),base_tf,P_opentime);
               //от бара, на котором была открыта позиция производим поиск low коррекционного движения
               //если позиция была открыта на текущем баре
               if(Bar_buy_base_tf==0)
                 {
                 //то при условии, что текущий бар является экстремумом
                  if(Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf+1] && Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf+2])
                    {
                     Low_Corr_wave_uptrend_base_tf_buy=Bar_buy_base_tf;
                    }
                  else
                    {
                    //если текущий бар не является экстремумом, то запускаем цикл на поиск экстремума
                     for(n=Bar_buy_base_tf; n<(bars_base_tf);n++)
                       {
                        if(Low_base_tf[n]<Low_base_tf[n+1] && Low_base_tf[n]<Low_base_tf[n+2])//если экстремум найден
                           break;//прерываем цикл
                       }
                     Low_Corr_wave_uptrend_base_tf_buy=n;
                    }
                  //--- описываем условия для контроля  
                  Third_uptrend_control_bool=(
                                               /*1. Время low коррекционного движения, найденного от открытия позиции
                                                 совпадает со временем текущего low коррекционного движения*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time
                                               );
                 }
               //--- если позиция была открыта не на текущем баре
               if(Bar_buy_base_tf!=0 && Bar_buy_base_tf!=1000)
                 {
                  //--- запускаем цикл по поиску бара-экстремума
                  for(n=Bar_buy_base_tf; n<(bars_base_tf);n++)
                    {
                     //--- если экстремум найден
                     if(Low_base_tf[n]<Low_base_tf[n+1] && Low_base_tf[n]<Low_base_tf[n+2])
                        break;//прерываем цикл
                    }
                  Low_Corr_wave_uptrend_base_tf_buy=n;
                 }
                 //--- описываем условия для контроля  
               Third_uptrend_control_bool=(
                                            /*1. Время low коррекционного движения, найденного от открытия позиции
                                                 совпадает со временем текущего low коррекционного движения*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time
                                            );
              }
           }
        }
     }
//+---------------------------------------------------------------------------+
//| 5.3.2 Для покупок (конец)                                                 |
//+---------------------------------------------------------------------------+

7. Описание условий для точки входа

Точка входа должна определяться на рабочем периоде — work_tf. Это нужно для своевременного входа в рынок и, по возможности, уменьшения размера риска в пунктах. В качестве сигнала используются показания индикатора Parabolic: если на текущем баре значение индикатора выше high текущего бара, а на предыдущем баре значение индикатора ниже low этого же бара, значит можно продавать. Для покупок — наоборот.

//+------------------------------------------------------------------+
//| 6. Описание условий для точки входа (начало)                     |
//+------------------------------------------------------------------+
//--- для продаж
   bool PointSell_work_tf_bool=(
                                /*1. Low бара 1 выше iSar[1]*/Low_work_tf[1]>Sar_array_work_tf[1] && 
                                /*2. High бара 0 ниже iSar[0]*/High_work_tf_0[0]<Sar_array_work_tf_0[0]
                                );
//--- для покупок
   bool PointBuy_work_tf_bool=(
                               /*1. High бара 1 ниже iSar*/High_work_tf[1]<Sar_array_work_tf[1] && 
                               /*2. Low бара 0 выше iSar[0]*/Low_work_tf_0[0]>Sar_array_work_tf_0[0]
                               );
//+------------------------------------------------------------------+
//| 6. Описание условий для точки входа (конец)                      |
//+------------------------------------------------------------------+

8. Описание торговых условий

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

//+------------------------------------------------------------------+
//| 7. Описание торговых условий (начало)                            |
//+------------------------------------------------------------------+
//--- для продаж
   bool OpenSell=(
                  /*1. сформировалась модель*/Model_downtrend_base_tf==true && 
                  /*2. 1 контроль позволяет открыть позицию*/First_downtrend_control_bool==true && 
                  /*3. 2 контроль позволяет открыть позицию*/Second_downtrend_control_bool==true && 
                  /*4. 3 контроль позволяет открыть позицию*/Third_downtrend_control_bool==false && 
                  /*5. Точка входа на work_tf*/PointSell_work_tf_bool==true
                  );
//--- для покупок
   bool OpenBuy=(
                 /*1. сформировалась модель*/Model_uptrend_base_tf==true && 
                 /*2. 1 контроль позволяет открыть позицию*/First_uptrend_control_bool==true && 
                 /*3. 2 контроль позволяет открыть позицию*/Second_uptrend_control_bool==true && 
                 /*4. 3 контроль позволяет открыть позицию*/Third_uptrend_control_bool==false && 
                 /*5. Точка входа на work_tf*/PointBuy_work_tf_bool==true
                 );
//+------------------------------------------------------------------+
//| 7. Описание торговых условий (конец)                             |
//+------------------------------------------------------------------+

9. Работа с торговыми операциями

Работа с торговыми операциями делится на:

1. Установка позиций

//+------------------------------------------------------------------+
//| 8. Работа с торговыми операциями (начало)                        |
//+------------------------------------------------------------------+
//--- определение уровней stop-loss
   SLPrice_sell=High_Corr_wave_downtrend_base_tf_double+spread;
   SLPrice_buy=Low_Corr_wave_uptrend_base_tf_double-spread;
//+------------------------------------------------------------------+
//| 8.1 Установка позиций (начало)                                   |
//+------------------------------------------------------------------+
//--- на продажу
   if(OpenSell==true)
     {
      RiskSize_points=(SLPrice_sell-bid)*f;//определяем размер sl в пунктах и приводим его в целое значение
      if(RiskSize_points==0)//контроль деления на 0
        {
         RiskSize_points=1;
        }
      CostOfPoint_position=SummRisk/RiskSize_points;//определяем стоимость пунктах позиции с учетом размера sl
      Lot=CostOfPoint_position/CostOfPoint;//вычисляем лот для открытия позиции
      //открываем позицию
      trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(Lot,2),bid,NormalizeDouble(SLPrice_sell,5),0,"");
     }
//--- на покупку
   if(OpenBuy==true)
     {
      RiskSize_points=(bid-SLPrice_buy)*f;//определяем размер sl в пунктах и приводим его в целое значение
      if(RiskSize_points==0)//контроль деления на 0
        {
         RiskSize_points=1;
        }
      CostOfPoint_position=SummRisk/RiskSize_points;//определяем стоимость пунктах позиции с учетом размера sl
      Lot=CostOfPoint_position/CostOfPoint;//вычисляем лот для открытия позиции
      //открываем позицию
      trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(Lot,2),ask,NormalizeDouble(SLPrice_buy,5),0,"");
     }
//+------------------------------------------------------------------+
//| 8.1 Установка позиций (конец)                                    |
//+------------------------------------------------------------------+

2. Установка take-profit

//+------------------------------------------------------------------+
//| 8.2 Установка take-profit (начало)                               |
//+------------------------------------------------------------------+
   if(TP_mode==true)
     {
      if(PositionsTotal()>0)
        {
         for(i=0;i<=PositionsTotal();i++)
           {
            if(PositionGetTicket(i))
              {
              //получаем значения позиции
               SL_double=double (PositionGetDouble(POSITION_SL));
               OP_double=double (PositionGetDouble(POSITION_PRICE_OPEN));
               TP_double=double (PositionGetDouble(POSITION_TP));
               P_symbol=string(PositionGetString(POSITION_SYMBOL));
               P_type=int(PositionGetInteger(POSITION_TYPE));
               P_profit=double (PositionGetDouble(POSITION_PROFIT));
               P_ticket=int (PositionGetInteger(POSITION_TICKET));
               P_opentime=int(PositionGetInteger(POSITION_TIME));
               if(P_symbol==Symbol())
                 {
                  if(P_type==0 && TP_double==0)
                    {
                     double SL_size_buy=OP_double-SL_double;//опеределяем размер sl в пунктах
                     double TP_size_buy=SL_size_buy*M;//умножаем размер sl на соотношение заданного во входных параметрах
                     double TP_price_buy=OP_double+TP_size_buy;//определяем уровень tp
                     //модифицируем позицию
                     trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_buy,5));
                    }
                  if(P_type==1 && TP_double==0)
                    {
                     double SL_size_sell=SL_double-OP_double;//опеределяем размер sl в пунктах
                     double TP_size_sell=SL_size_sell*M;//умножаем раз мер sl на соотношение заданного во входных параметрах
                     double TP_price_sell=OP_double-TP_size_sell;//определяем уровень tp
                     //модифицируем позицию
                     trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_sell,5));
                    }
                 }
              }
           }
        }
     }
//+------------------------------------------------------------------+
//| 8.2 Установка take-profit (конец)                                |
//+------------------------------------------------------------------+

3. Перевод позиции в безубыток

//+------------------------------------------------------------------+
//| 8.3 Перевод позиции в безубыток (начало)                         |
//+------------------------------------------------------------------+
   double Size_Summ=breakeven*SummRisk;//определяем уровень прибыли, по достижению которой нужно позицию переводить в б/у
   if(Breakeven_mode==true && breakeven!=0)
     {
      if(PositionsTotal()>0)
        {
         for(i=0;i<=PositionsTotal();i++)
           {
            if(PositionGetTicket(i))
              {
              //получаем значения позиции
               SL_double=double (PositionGetDouble(POSITION_SL));
               OP_double=double (PositionGetDouble(POSITION_PRICE_OPEN));
               TP_double=double (PositionGetDouble(POSITION_TP));
               P_symbol=string(PositionGetString(POSITION_SYMBOL));
               P_type=int(PositionGetInteger(POSITION_TYPE));
               P_profit=double (PositionGetDouble(POSITION_PROFIT));
               P_ticket=int (PositionGetInteger(POSITION_TICKET));
               P_opentime=int(PositionGetInteger(POSITION_TIME));
               if(P_symbol==Symbol())
                 {
                  if(P_type==0 && P_profit>=Size_Summ && SL_double<OP_double)
                    {
                     trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double);
                    }
                  if(P_type==1 && P_profit>=Size_Summ && SL_double>OP_double)
                    {
                     trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double);
                    }
                 }
              }
           }
        }
     }
//+------------------------------------------------------------------+
//| 8.3 Перевод позиции в безубыток (конец)                          |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 8. Работа с торговыми операциями (конец)                         |
//+------------------------------------------------------------------+

5. Сбор статистических данных

Сначала нужно определиться с набором показателей для статистики:

  1. Символ инструмента;
  2. Тип сделки;
  3. Время входа;
  4. Цена открытия;
  5. Уровень stop-loss;
  6. Размер stop-loss;
  7. Уровень максимальной прибыли;
  8. Размер максимальной прибыли;
  9. Длительность сделки.

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

Для начала нужно протестировать работу советника в тестере стратегий. Для тестирования я выбрал пару AUDJPY за период 01.01.2018—29.08.2018. В качестве базового периода выбрал D1, в качестве рабочего периода H6. Риск на сделку — $100. Перевод позиции в безубыток 1/2, установка take-profit — 1/3 (риск/прибыль).

Входные параметры советника

Рис. 4. Входные параметры советника

После тестирования сохраняем отчет в файл формата CSV. В локальной папке терминала создаем новый файл report.csv. В него копируем данные отчета. Но копировать нужно не всё, а часть из раздела Ордера. При этом нужно удалить строки, которые относятся к закрытию позиций, как показано на рисунке 5:

Удаление строк из отчета, которые относятся к закрытию позиций

Рис 5. Удаление строк из отчета, которые относятся к закрытию позиций

Копировать нужно столбцы:

  1. Время открытия;
  2. Символ;
  3. Тип;
  4. Цена;
  5. S/L.

В итоге файл report.csv будет выглядеть так:

Содержимое файла report.csv

Рис. 6. Содержимое файла report.csv

Теперь нужно создать скрипт, который будет считывать данные из файла report.csv и создавать новый файл file_stat.csv с дополнительной статистической информацией:

  1. Размер SL;
  2. Уровень максимальной прибыли;
  3. Размер максимальной прибыли;
  4. Длительность сделки в барах.

Для данной задачи я использовал готовое решение из статьи Основы программирования на mql5: Файлы в разделе Чтение файла с разделителями в массив. От себя я добавил массивы и их заполнение для хранения значений столбцов в файле file_stat.csv.

Создаем новый скрипт, под функцией OnStart(), записываем код функции чтения файлов в массив:

//+------------------------------------------------------------------+
//| Функция чтения в массив (начало)                                 |
//+------------------------------------------------------------------+
bool ReadFileToArrayCSV(string FileName,SLine  &Lines[])
  {
   ResetLastError();
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");
   if(h==INVALID_HANDLE)
     {
      int ErrNum=GetLastError();
      printf("Ошибка открытия файла %s # %i",FileName,ErrNum);
      return(false);
     }
   int lcnt=0; // переменная для подсчета строк 
   int fcnt=0; // переменная для подсчета полей строки    
   while(!FileIsEnding(h))
     {
      string str=FileReadString(h);
      // новая строка (новый элемент массива структур)
      if(lcnt>=ArraySize(Lines))
        { // массив структур полностью заполнен
         ArrayResize(Lines,ArraySize(Lines)+1024); // увеличиваем размер массива на 1024 элемента
        }
      ArrayResize(Lines[lcnt].field,64);// изменяем размер массива в структуре
      Lines[lcnt].field[0]=str; // присваиваем значение первого поля
                                // начинаем читать остальные поля в строке
      fcnt=1; // пока занят один элемент в массиве полей
      while(!FileIsLineEnding(h))
        { // читаем остальные поля в строке
         str=FileReadString(h);
         if(fcnt>=ArraySize(Lines[lcnt].field))
           { // массив полей полностью заполнен
            ArrayResize(Lines[lcnt].field,ArraySize(Lines[lcnt].field)+64); // увеличиваем размер массива на 64 элемента
           }
         Lines[lcnt].field[fcnt]=str; // присваиваем значение очередного поля
         fcnt++; // увеличиваем счетчик полей
        }
      ArrayResize(Lines[lcnt].field,fcnt); // изменяем размер массива полей в соответствии с фактическим количеством полей
      lcnt++; // увеличиваем счетчик строк
     }
   ArrayResize(Lines,lcnt); // изменяем массив структур (строк) в соответствии с фактическим количеством строк
   FileClose(h);
   return(true);
  }
//+------------------------------------------------------------------+
//| Функция чтения в массив (конец)                                  |
//+------------------------------------------------------------------+

Далее указываем входные параметры:

#property script_show_inputs 
//--- входные параметры
input ENUM_TIMEFRAMES base_tf;  //таймфрейм базового периода
input double sar_step=0.1;      //set parabolic step
input double maximum_step=0.11; //set parabolic maximum step
//--- объявление переменных для хэндлов индикаторов
int Fractal_base_tf;             //хэндл индикатора iFractal
//--- объявление массивов для base_tf
double High_base_tf[],Low_base_tf[];             //массивы для хранения цен High и Low баров
double FractalDown_base_tf[],FractalUp_base_tf[];//массив для хранения цен индикатора iFractall
//--- структура массива
struct SLine
  {
   string            field[];
  };

Внутри функции OnStart() получаем хэндл индикатора iFractals, а также объявляем и заполняем массивы High/Low цен . Также нужно ввести переменную bars_base_tf, которая нужна будет для использования ее в цикле for, и еще нужно ввести переменную f, которая будет хранить разрядность цены в зависимости от количество знаков после запятой в цене инструмента. Эта переменная будет использоваться для приведения в целое значение показателей размера stop-loss и размера максимальной прибыли.

//--- получаем хендлы индикатора iFractal
   Fractal_base_tf=iFractals(Symbol(),base_tf);
//--- приведение порядка массивов, как в таймсерии для base_tf
   ArraySetAsSeries(High_base_tf,true);
   ArraySetAsSeries(Low_base_tf,true);
   ArraySetAsSeries(FractalDown_base_tf,true);
   ArraySetAsSeries(FractalUp_base_tf,true);
//--- первоначальное заполнение массивов для base_tf
   CopyHigh(Symbol(),base_tf,0,1000,High_base_tf);
   CopyLow(Symbol(),base_tf,0,1000,Low_base_tf);
   CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);
   CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf);
//--- переменные для хранения информации о количестве баров
   int bars_base_tf=Bars(Symbol(),base_tf);
//кол-во знаков после запятой в цене инструмента
   int Digit=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
//определение разрядности цены текущего символа
   double f=1;
   if(Digit==5) {f=100000;}
   if(Digit==4) {f=10000;}
   if(Digit==3) {f=1000;}
   if(Digit==2) {f=100;}
   if(Digit==1) {f=10;}

Далее нужно объявить массивы и переменные:

//--- объявление переменных и массивов
   int i,j,n; //переменные для циклов
   datetime opentime[];//массив для хранения времени установки позиций
   string symbol[];//массив для хранения символов
   string type[];////массив для хранения типов сделок
   string openprice[];//массив для хранения цен открытия
   string  sl_price[];//массив для хранения уровня stop loss
   int index[];//массив для хранения индекса бара, на котором произошла установка позици
   int down_fractal[];//массив для хранения индексов нижних фракталов 
   int up_fractal[];//массив для хранения индексов верхних фракталов
   double sl_size_points[];//массив для хранения размеров stop-loss в пунктах
   string maxprofit_price[];//массив для хранения уровней максимальной прибыли
   double maxprofit_size_points[];//массив для хранения размера максимальной прибыли
   int duration[];//массив для хранения информации о длительности волны в барах
   bool maxprofit_bool[];//массив для определения того, что позицию не выбьет по sl
   int maxprofit_int[];//массив для определения наименьшего/наибольшего бара. Будет использоваться с maxprofit_bool[]

После этого переходим к чтению данных из файла в массивы:

   SLine lines[];
   int size=0;
   if(!ReadFileToArrayCSV("report.csv",lines))
     {
      Alert("Ошибка, подробности см. во вкладе \"Эксперты\"");
     }
   else
     {
      size=ArraySize(lines);
      ArrayResize(opentime,ArraySize(lines));
      ArrayResize(symbol,ArraySize(lines));
      ArrayResize(type,ArraySize(lines));
      ArrayResize(openprice,ArraySize(lines));
      ArrayResize(sl_price,ArraySize(lines));
      ArrayResize(index,ArraySize(lines));
      ArrayResize(down_fractal,ArraySize(lines));
      ArrayResize(up_fractal,ArraySize(lines));
      ArrayResize(sl_size_points,ArraySize(lines));
      ArrayResize(maxprofit_price,ArraySize(lines));
      ArrayResize(maxprofit_size_points,ArraySize(lines));
      ArrayResize(duration,ArraySize(lines));
      ArrayResize(maxprofit_bool,ArraySize(lines));
      ArrayResize(maxprofit_int,ArraySize(lines));
      for(i=0;i<size;i++)
        {
         for(j=0;j<ArraySize(lines[i].field);j=j+5)//нужно отобрать поля по столбцу времени открытия позиции
           {
            opentime[i]=(datetime)(lines[i].field[j]);//записываем данные в массив
           }
         for(j=1;j<ArraySize(lines[i].field);j=j+4)//нужно отобрать поля по столбцу символа
           {
            symbol[i]=(lines[i].field[j]);//записываем данные в массив
           }
         for(j=2;j<ArraySize(lines[i].field);j=j+3)//нужно отобрать поля по столбцу тип сделки
           {
            type[i]=(lines[i].field[j]);//записываем данные в массив
           }
         for(j=3;j<ArraySize(lines[i].field);j=j+2)//нужно отобрать поля по столбцу цены открытия
           {
            openprice[i]=(lines[i].field[j]);//записываем данные в массив
           }
         for(j=4;j<ArraySize(lines[i].field);j=j+1)//нужно отобрать поля по столбцу stop-loss
           {
            sl_price[i]=(lines[i].field[j]);//записываем данные в массив
           }
        }
     }
//-----------------------------------------------------

Массивы openrpice[] и sl_price[] имеют строковый тип данных и чтобы их использовать в расчетах, их нужно будет преобразовать в тип double с помощью функции StringToDouble(). Но при этом знаки после запятых будут потеряны. Чтобы этого не происходило, заменим знаки запятых на точки с помощью функции StringReplace():

   for(i=0;i<size;i++)
     {
      StringReplace(openprice[i],",",".");
      StringReplace(sl_price[i],",",".");
     }

Далее нужно определить индексы баров, на которых производилась установка позиций:

//--- определение индексов баров на которых открывались позиции
   for(i=0;i<size;i++)
     {
      index[i]=iBarShift(Symbol(),PERIOD_D1,opentime[i]);//записываем данные в массив
     }

После этого находим нижние и верхние фракталы, ближайшие к установке позиций:

//--- поиск фраклала вниз для продаж
   for(i=0;i<size;i++)
     {
      if(type[i]=="sell")
        {
         for(n=index[i];n>0;n--)
           {
            if(FractalDown_base_tf[n]!=EMPTY_VALUE)
               break;
           }
         down_fractal[i]=n;
        }
     }
//--- поиск фраклала вверх для покупок
   for(i=0;i<size;i++)
     {
      if(type[i]=="buy")
        {
         for(n=index[i];n>0;n--)
           {
            if(FractalUp_base_tf[n]!=EMPTY_VALUE)
               break;
           }
         up_fractal[i]=n;
        }
     }

Далее определим размер stop-loss в пунктах и приведем количество пунктов в целое значение:

//--- размер stop-loss в пунктах
   for(i=0;i<size;i++)
     {
      if(type[i]=="sell")
        {
         sl_size_points[i]=(StringToDouble(sl_price[i])-StringToDouble(openprice[i]))*f;
        }
      if(type[i]=="buy")
        {
         sl_size_points[i]=(StringToDouble(openprice[i])-StringToDouble(sl_price[i]))*f;
        }
     }

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

//--- проверка на предмет того, что позиция не закроется по sl ранее чем достигнет уровня максимальной прибыли
//--- для сделок sell
   for(i=0;i<size;i++)
     {
      if(type[i]=="sell")
        {
         for(n=index[i];n>down_fractal[i];n--)
           {
            if(High_base_tf[n]>=StringToDouble(sl_price[i]))
               break;
           }
         maxprofit_int[i]=n;
         maxprofit_bool[i]=(n==down_fractal[i]);
        }
     }
//--- для сделок buy
   for(i=0;i<size;i++)
     {
      if(type[i]=="buy")
        {
         for(n=index[i];n>up_fractal[i];n--)
           {
            if(Low_base_tf[n]<=StringToDouble(sl_price[i]))
               break;
           }
         maxprofit_int[i]=n;
         maxprofit_bool[i]=(n==up_fractal[i]);
        }
     }

Теперь можно написать код определения уровня максимальной прибыли:

//--- уровень максимальной прибыли
   for(i=0;i<size;i++)
     {
      if(type[i]=="sell" && maxprofit_bool[i]==true)
        {
         maxprofit_price[i]=(string)Low_base_tf[down_fractal[i]];
        }
      if(type[i]=="sell" && maxprofit_bool[i]==false)
        {
         maxprofit_price[i]="";
        }
      if(type[i]=="buy" && maxprofit_bool[i]==true)
        {
         maxprofit_price[i]=(string)High_base_tf[up_fractal[i]];
        }
      if(type[i]=="buy" && maxprofit_bool[i]==false)
        {
         maxprofit_price[i]="";
        }
     }

Далее можно определить размер максимальной прибыли. Размер прибыли будет отрицательным на размер stop-loss, если срабатывает контроль:

   for(i=0;i<size;i++)
     {
      if(type[i]=="sell" && maxprofit_bool[i]==true)
        {
         maxprofit_size_points[i]=(StringToDouble(openprice[i])-Low_base_tf[down_fractal[i]])*f;
        }
      if(type[i]=="sell" && maxprofit_bool[i]==false)
        {
         maxprofit_size_points[i]=sl_size_points[i]*-1;
        }
      if(type[i]=="buy" && maxprofit_bool[i]==true)
        {
         maxprofit_size_points[i]=(High_base_tf[up_fractal[i]]-StringToDouble(openprice[i]))*f;
        }
      if(type[i]=="buy" && maxprofit_bool[i]==false)
        {
         maxprofit_size_points[i]=sl_size_points[i]*-1;
        }
     }

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

//--- расчет длительности сделки в барах
   for(i=0;i<size;i++)
     {
      if(type[i]=="sell" && maxprofit_bool[i]==true)
        {
         duration[i]=index[i]-(int)down_fractal[i];
        }
      if(type[i]=="sell" && maxprofit_bool[i]==false)
        {
         duration[i]=index[i]-maxprofit_int[i];
        }
      if(type[i]=="buy" && maxprofit_bool[i]==true)
        {
         duration[i]=index[i]-(int)up_fractal[i];
        }
      if(type[i]=="buy" && maxprofit_bool[i]==false)
        {
         duration[i]=index[i]-maxprofit_int[i];
        }
     }

После этого для нормального отображения показателей в отчете заменим обратно точки на запятые:

   for(i=0;i<size;i++)
     {
      StringReplace(openprice[i],".",",");
      StringReplace(sl_price[i],".",",");
      StringReplace(maxprofit_price[i],".",",");
     }

В конце остается только записать полученные данные в файл file_stat.csv:

//--- записываем данные в новый файл статистики
   int h=FileOpen("file_stat.csv",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_CSV,";");
//--- проверка открытия
   if(h==INVALID_HANDLE)
     {
      Alert("Ошибка открытия файла1");
      return;
     }
   else
     {
      FileWrite(h,
                /*1 символ*/"Символ",
                /*2 тип сделки*/"Тип сделки",
                /*3 время входа*/"Время открытия",
                /*4 цена открытия*/"Цена открытия",
                /*5 уровень sl*/"SL",
                /*6 размер sl*/"Размер SL",
                /*7 уровень макс прибыли*/"Уровень макс прибыли",
                /*8 размер макс прибыли*/"Размер макс прибыли",
                /*9 длительность*/"Длительность в барах");
      //--- перемещение в конец
      FileSeek(h,0,SEEK_END);
      for(i=0;i<size;i++)
        {
         FileWrite(h,
                   /*1 символ*/symbol[i],
                   /*2 тип сделки*/type[i],
                   /*3 время входа*/TimeToString(opentime[i]),
                   /*4 цена открытия*/openprice[i],
                   /*5 уровень sl*/sl_price[i],
                   /*6 размер sl*/NormalizeDouble(sl_size_points[i],2),
                   /*7 уровень макс прибыли*/maxprofit_price[i],
                   /*8 размер макс прибыли*/NormalizeDouble(maxprofit_size_points[i],2),
                   /*9 длительность*/duration[i]);
        }
     }
   FileClose(h);
   Alert("Файл file_stat.csv создан");

Проверяем: устанавливаем скрипт на график, не забывая при этом установить период базового таймфрейма во входных параметрах — в моем случае — D1, после чего в локальной папке терминала появится новый файл file_stat.csv со следующим набором показателей:

Содержимое файла file_stat.csv 


Рис.7 . Содержимое файла file_stat.csv

6. Заключение

Статья описывает способ программного определения одной из моделей продолжения движения. Ключевая идея данного способа — это поиск экстремума high/low коррекционного движения без применения каких-либо индикаторов. Далее уже от найденного экстремума производится поиск следующих точек модели.

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

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

В конце привожу изображения с примерами распознавания модели продолжения движения:

Пример распознавания модели

Рис. 8. Пример распознавания модели продолжения движения

Пример распознавания модели

Рис. 9. Пример распознавания модели продолжения движения

Пример распознавания модели продолжения тенденции

Рис. 10. Пример распознавания модели продолжения движения

Пример распознавания модели продолжения тенденции

Рис. 11. Пример распознавания модели продолжения движения

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

#ИмяТипОписание
1Trade.mqhБиблиотека классаКласс торговых операций
2MQL5_POST_finalСоветникСоветник, определяющий модель продолжения движения
3Report_readСкриптСкрипт для сбора статистических данных