MTF-индикаторы как инструмент технического анализа

Alexander Lasygin | 26 марта, 2019

Введение

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

Как быть дальше? Оставаться в неведении того, что творится на старших ТФ, либо продолжать «скакать» между окнами и периодами? Хорошо если мы работаем на таймфрейме Н1 и выше, тогда у нас есть время для тщательной оценки. А если это М1-М15? Но нам данная информация нужна, а иногда жизненно необходима. И не там, где-то на другой закладке или после нажатия очередной клавиши, а здесь и сейчас. Особенно это касается MTF стратегий, основанных на одновременной оценке разных TF, таких, как «Волны Вульфа» или «Три экрана Элдера».


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

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

Особенности алгоритма

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

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

Классификация мультитаймфреймовых индикаторов

Они представлены во всех стандартных классах, многие из них комплексные, то есть совмещают в себе расчетно-справочную информацию с графическими элементами. Можно выделить следующие группы:

1. Информационные – выводят на экран данные и дополнительную информацию без сигналов и графических построений. Классическим примером данного типа  можно считать индикатор MultiTimeFrame. Он отражает время закрытия свечи каждого таймфрейма Ask, Bid по выбранным валютным парам, состояние самой свечи (UP, DOWN, DOJI) и объем. Экран индикаторов этого типа наполнен большим объемом полезной информацией, но для торговли мало пригоден – только для просмотра.

Рис. 1. Информационные индикаторы

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


Рис. 2. Сигналы информационных индикаторов



Рис. 3. Сигналы информационных индикаторов

2. Графические выводят на экран построения одного и того же инструмента, но на разных ТФ. Вот так выглядит стандартный конверт МА(13) с разных ТФ.

Рис. 4. Графические индикаторы

Еще один тип графического построения представляет группу графиков с разным периодом расчета. Данный подход реализуется из простой математики. То есть Стохастик (5.3.3) на М5 будет иметь параметры(15.3.9) с М15, а с М30 уже другие — (30.3.18).

Рис. 5. Графические индикаторы с разными периодами расчета

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

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

Рис. 6. Сигнальные индикаторы

Также мы можем выделить группу, которую можно условна назвать «Окно в окне». Данная группа характеризуется тем, что отражает в одном окне с основным графики других таймфреймов или индикаторов.


Рис. 7. Индикатор типа "Окно в окне"


Рис. 7.1. Индикатор типа "Окно в окне"

Еще один пример решения All_Woodies CCI.


Рис. 7.1. Индикатор типа "Окно в окне" All_Woodies CCI


Отдельно нужно отметить МТФ индикаторы волатильности. К ним можно отнести MTF Candles.

Рис. 8. Индикатор волатильности MTF Candles

Способы реализации

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

Мультипериодные индикаторы. На примере МА рассмотрим задачу: создать вариант с изменением периода расчета для отображения трех разных ТФ. Зададим основные параметры и наши переменные:

//---- indicator settings
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots   3
#property indicator_type1   DRAW_LINE
#property indicator_type2   DRAW_LINE
#property indicator_type3   DRAW_LINE
#property indicator_color1  Blue
#property indicator_color2  Red
#property indicator_color3  Lime
#property indicator_width1  1
#property indicator_width2  1
#property indicator_width3  1
//---- input parameters
input ENUM_TIMEFRAMES    tf1             = 1;              // Time Frame (1)
input ENUM_TIMEFRAMES    tf2             = 5;              // Time Frame (2)
input ENUM_TIMEFRAMES    tf3             = 15;             // Time Frame (3)
input int                maPeriod        = 13;             // MA period
input int                Shift           = 0;              // Shift
input ENUM_MA_METHOD     InpMAMethod     = MODE_SMA;      // Moving average method
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE;   // Applied price
//---- indicator buffers
double ExtBuf1[];
double ExtBuf2[];
double ExtBuf3[];
//---- handles for moving averages
int    ExtHandle1;
int    ExtHandle2;
int    ExtHandle3;
//--- bars minimum for calculation
int    ExtBarsMinimum;
//---
int period1=0;
int period2=0;
int period3=0;

Теперь инициализируем данные массивов с условием, что ТФ на котором он расположен <= заданных в переменных.

void OnInit()
  {
   int timeframe;
//---- indicator buffers mapping
   SetIndexBuffer(0,ExtBuf1,INDICATOR_DATA);
   SetIndexBuffer(1,ExtBuf2,INDICATOR_DATA);
   SetIndexBuffer(2,ExtBuf3,INDICATOR_DATA);
//---
   timeframe =_Period;
//---
   if(tf1>=timeframe)
   {
   period1=maPeriod*(int)MathFloor(tf1/timeframe);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,period1-1);             //sets first bar from what index will be drawn
   PlotIndexSetInteger(0,PLOT_SHIFT,Shift);                      //line shifts when drawing
   PlotIndexSetString(0,PLOT_LABEL,"MA("+string(period1)+")");   //name for DataWindow
   ExtHandle1=iMA(NULL,0,period1,0,InpMAMethod,InpAppliedPrice); //get MA's handles
   }
//---- 
   if(tf2>=timeframe)
   {
   period2=maPeriod*(int)MathFloor(tf2/timeframe);
   PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,period2-1);             //sets first bar from what index will be drawn
   PlotIndexSetInteger(1,PLOT_SHIFT,Shift);                      //line shifts when drawing
   PlotIndexSetString(1,PLOT_LABEL,"MA("+string(period2)+")");   //name for DataWindow 
   ExtHandle2=iMA(NULL,0,period2,0,InpMAMethod,InpAppliedPrice); //get MA's handles
   }
//---- 
   if(tf3>=timeframe)
   {
   period3=maPeriod*(int)MathFloor(tf3/timeframe);
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,period3-1);             //sets first bar from what index will be drawn
   PlotIndexSetInteger(2,PLOT_SHIFT,Shift);                      //line shifts when drawing
   PlotIndexSetString(2,PLOT_LABEL,"MA("+string(period3)+")");   //name for DataWindow 
   ExtHandle3=iMA(NULL,0,period3,0,InpMAMethod,InpAppliedPrice); //get MA's handles
   }
//--- set accuracy
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//--- bars minimum for calculation
   int per=MathMax(period3,MathMax(period1,period2));
   ExtBarsMinimum=per+Shift;
//--- initialization done
  }

Основной цикл проверки инициализации и расчета:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- check for rates total
   if(rates_total<ExtBarsMinimum)
      return(0); // not enough bars for calculation
//--- not all data may be calculated
   int calculated=BarsCalculated(ExtHandle1);
   if(calculated<rates_total&&period1!=0)
     {
      Print("Not all data of ExtHandle1 is calculated (",calculated,"bars ). Error",GetLastError());
      return(0);
     }
   calculated=BarsCalculated(ExtHandle2);
   if(calculated<rates_total&&period2!=0)
     {
      Print("Not all data of ExtHandle2 is calculated (",calculated,"bars ). Error",GetLastError());
      return(0);
     }
   calculated=BarsCalculated(ExtHandle3);
   if(calculated<rates_total&&period3!=0)
     {
      Print("Not all data of ExtHandle3 is calculated (",calculated,"bars ). Error",GetLastError());
      return(0);
     }
//--- we can copy not all data
   int to_copy;
   if(prev_calculated>rates_total || prev_calculated<0) to_copy=rates_total;
   else
     {
      to_copy=rates_total-prev_calculated;
      if(prev_calculated>0) to_copy++;
     }
//---- get ma buffers
   if(IsStopped()) return(0); //Checking for stop flag
   if(period1!=0)
   if(CopyBuffer(ExtHandle1,0,0,to_copy,ExtBuf1)<=0)
     {
      Print("getting ExtHandle1 is failed! Error",GetLastError());
      return(0);
     }
   if(IsStopped()) return(0); //Checking for stop flag
   if(period2!=0)
   if(CopyBuffer(ExtHandle2,0,0,to_copy,ExtBuf2)<=0)
     {
      Print("getting ExtHandle2 is failed! Error",GetLastError());
      return(0);
     }
   if(IsStopped()) return(0); //Checking for stop flag
   if(period3!=0)
   if(CopyBuffer(ExtHandle3,0,0,to_copy,ExtBuf3)<=0)
     {
      Print("getting ExtHandle3 is failed! Error",GetLastError());
      return(0);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Посмотрим на полученный нами результат.

Рис. 9. Реализация MTF-индикатора

Мы рассмотрели пример, когда нам необходимо использовать один индикатор на разных ТФ, используя увеличение периода расчета. При незначительной переделке мы можем использовать его и с возможностью задавать периоды для каждой линии самостоятельно. Данный способ написания может показаться не целесообразным. Нет ничего проще чем рассчитать период самому и накинуть несколько индикаторов одновременно. Но возникают случаи, когда данный вариант при всех своих недостатках является оптимальным. К ним можно отнести случай, когда необходимо наблюдать одновременно два (три) ненормированных осциллятора в одном окне. Из-за разброса своей амплитуды данные осцилляторы смещаются относительно центральной линии, что вносит сложность в их интерпретацию. Данный вариант позволяет исключить этот недостаток.


Мультитаймфреймовые индикаторы

В место хорошо нам знакомых по MQL4 функций iClose(), iHigh(), iLow(), iOpen(), iTime(), iVolume() в MQL5 пришли CopyTime(), CopyClose(), CopyHigh(), CopyLow(), CopyOpen(), CopyTime(), CopyVolume(), а функции iCustom, iMA, iCCI, iMACD и т.д. реализуются через CopyBuffer(). Каждая из них имеет свои достоинства и недостатки. В нашем случае мы коснемся только MQL5. Для написания нам может понадобится весь список фреймов от М1 до MN1, это 26 вариантов. А если мы используем несколько торговых символов или инструментов, то это число увеличивается многократно. В большинстве случаев нет необходимости копировать всю историю. Для информационных индикаторов, в большинстве своем, количество баров ограничивается двумя. Поэтому, чтобы не раздувать текст кода до безграничных размеров целесообразно записывать данные команды отдельными функциями и вызывать их многократно.

Для функции тайм серии CopyClose() функция будет иметь вид:

//+------------------------------------------------------------------+
double _iClose(string symbol,int tf,int index)
{
   if(index < 0) return(-1);

   double buf[];
   ENUM_TIMEFRAMES timeframe=TFMigrate(tf);
   if(CopyClose(symbol,timeframe, index, 1, buf)>0) 
        return(buf[0]);
   else return(-1);
}
//+------------------------------------------------------------------+

Для вызова WPR:

//+------------------------------------------------------------------+
double _iWPR(string symbol,
                int tf,
                int period,
                int shift)
  {
   ENUM_TIMEFRAMES timeframe=TFMigrate(tf);
   int handle=iWPR(symbol,timeframe,period);
   if(handle<0)
     {
      Print("Объект iWPR не создан: Ошибка ",GetLastError());
      return(-1);
     }
   else
      return(_CopyBuffer(handle,shift));
  }
//+------------------------------------------------------------------+
double _CopyBuffer(int handle,int shift)
  {
   double buf[];
if(CopyBuffer(handle,0,shift,1,buf)>0)
         return(buf[0]);

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

В тех случаях когда есть несколько линий, функцию _CopyBuffer можно записать в виде:

//+------------------------------------------------------------------+
double _CopyBuffer(int handle,int index,int shift)
  {
   double buf[];
   switch(index)
     {
      case 0: if(CopyBuffer(handle,0,shift,1,buf)>0)
         return(buf[0]); break;
      case 1: if(CopyBuffer(handle,1,shift,1,buf)>0)
         return(buf[0]); break;
      case 2: if(CopyBuffer(handle,2,shift,1,buf)>0)
         return(buf[0]); break;
      case 3: if(CopyBuffer(handle,3,shift,1,buf)>0)
         return(buf[0]); break;
      case 4: if(CopyBuffer(handle,4,shift,1,buf)>0)
         return(buf[0]); break;
default: break;
     }
   return(EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

а в функция _iWPR изменит строку

return(_CopyBuffer(handle,shift)

на

return(_CopyBuffer(handle,0,shift)

Для обеих случаев функция TFMigrate() будет выглядеть как:

//+------------------------------------------------------------------+
ENUM_TIMEFRAMES TFMigrate(int tf)
  {
   switch(tf)
     {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 5: return(PERIOD_M5);
      case 15: return(PERIOD_M15);
      case 30: return(PERIOD_M30);
      case 60: return(PERIOD_H1);
      case 240: return(PERIOD_H4);
      case 1440: return(PERIOD_D1);
      case 10080: return(PERIOD_W1);
      case 43200: return(PERIOD_MN1);
      
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);      
      case 6: return(PERIOD_M6);
      case 10: return(PERIOD_M10);
      case 12: return(PERIOD_M12);
      case 20: return(PERIOD_M20);
      case 16385: return(PERIOD_H1);
      case 16386: return(PERIOD_H2);
      case 16387: return(PERIOD_H3);
      case 16388: return(PERIOD_H4);
      case 16390: return(PERIOD_H6);
      case 16392: return(PERIOD_H8);
      case 16396: return(PERIOD_H12);
      case 16408: return(PERIOD_D1);
      case 32769: return(PERIOD_W1);
      case 49153: return(PERIOD_MN1);      
      default: return(PERIOD_CURRENT);
     }
  }
//+------------------------------------------------------------------+

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

Написание информационных индикаторов (рис 1, рис 2, рис 3) ни чем не отличается от написания классических, поэтому мы сразу перейдем к рассмотрению более интересных, с моей точки зрения, — к классу графических индикаторов. Если информационным нужна только текущая информация о состояние рынка и нашего набора инструментов, то графические еще и предъявляют требования к построению. Мы все знаем, что для формирования периода М5 необходимо 5 баров периода М1, для М15 три бара М5 и так далее. То есть во время формирования линии на М5 линия с М15 рисуется в течении 3-х баров. Положение линии не фиксируется и изменяется пока свеча М15 не закроется. По этой причине возникает необходимость привязки по времени к открытию свечи. Рассмотрим вариант, как это сделать, на примере все той же МА.

//---- indicator settings
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  Blue
#property indicator_width1  1
//---- input parameters
input ENUM_TIMEFRAMES    tf              = 5;              // Time Frame 
input int                maPeriod        = 13;             // MA period
input int                Shift           = 0;              // Shift
input ENUM_MA_METHOD     InpMAMethod     = MODE_SMA;       // Moving average method
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE;    // Applied price
input  int               Bars_Calculated = 500;
//---- indicator buffers
double ExtMA[];
//---- handles for moving averages
int    MA_Handle;
//--- bars minimum for calculation
int    ExtBarsMinimum;
ENUM_TIMEFRAMES _tf;
int pf;
//--- будем хранить количество значений в индикаторе Moving Average 
int    bars_calculated=0; 
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   _tf=tf;
   ENUM_TIMEFRAMES timeframe;
   int draw_shift=Shift;// начальное значение PLOT_SHIFT
   int draw_begin=maPeriod;// начальное значение PLOT_DRAW_BEGIN
//---
   timeframe=_Period;
   if(_tf<=timeframe)_tf=timeframe;// еcли TF меньше или равен текущему, ставим его PERIOD_CURRENT
   pf=(int)MathFloor(_tf/timeframe);// расcчитаем коэффициент для PLOT_DRAW_BEGIN, PLOT_SHIFT и кол-ва расчетных баров.
   draw_begin=maPeriod*pf;// рассчитаем PLOT_DRAW_BEGIN
   draw_shift=Shift*pf;// рассчитаем PLOT_SHIFT
//---- indicator buffers mapping
   SetIndexBuffer(0,ExtMA,INDICATOR_DATA);
//--- 
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,draw_begin-pf);                      //sets first bar from what index will be drawn
   PlotIndexSetInteger(0,PLOT_SHIFT,draw_shift);                              //line shifts when drawing
   PlotIndexSetString(0,PLOT_LABEL,"MA("+string(tf)+" "+string(maPeriod)+")");//name for DataWindow
//---
   MA_Handle=iMA(NULL,_tf,maPeriod,0,InpMAMethod,InpAppliedPrice);            //get MA's handles
   if(MA_Handle==INVALID_HANDLE)
     {
      Print("getting MA Handle is failed! Error",GetLastError());
      return(INIT_FAILED);
     }
//--- set accuracy
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//--- bars minimum for calculation
   ExtBarsMinimum=draw_begin+draw_shift;// рассчитаем минимальное необходимое кол-во баров для расчета
//--- initialization done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- check for rates total
   if(rates_total<ExtBarsMinimum)
      return(0); // not enough bars for calculation
   int limit;
//--- индексация элементов в массивах как в таймсериях  
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(ExtMA,true);
//--- detect start position
//--- расчеты необходимого количества копируемых данных
//--- и стартового номера limit для цикла пересчета баров
   if(prev_calculated>rates_total || prev_calculated<=0|| calculated!=bars_calculated)// проверка на первый старт расчета индикатора
     {
      limit=rates_total-ExtBarsMinimum-1; // стартовый номер для расчета всех баров
     }
   else
     {
      limit=(rates_total-prev_calculated)+pf+1; // стартовый номер для расчета новых баров
     }
   if(Bars_Calculated!=0)   limit=MathMin(Bars_Calculated,limit);

Мы не будем выполнять поиск бара по номеру (iBarShift()), а сразу будем копировать значения по времени.

//--- main cycle
   for(int i=limit;i>=0 && !IsStopped();i--)
     {
      ExtMA[i]=_CopyBuffer(MA_Handle,time[i]);
     }
//---
   bars_calculated=calculated;
Для этого будем использовать выше упомянутую функцию.
//+--------- CopyBuffer MA Handle ----------------------------------+
double _CopyBuffer(int handle,datetime start_time)
  {
   double buf[];
   if(CopyBuffer(handle,0,start_time,1,buf)>0)
      return(buf[0]);

   return(EMPTY_VALUE);
  }
//+-------------------- END -----------------------------------------+

Наш результат будет выглядеть так:

Рис. 10. Линия MTF-индикатора

Данный метод с успехом можно использовать для всех типов линейных индикаторов. Основной недостаток хорошо заметен из рисунка — это пресловутые ступеньки. Если для МА это даже в некотором роде достоинство, более четко определены уровни поддержки-сопротивления, то для осцилляторов, в работе с которыми мы используем паттерны, это сильно затруднит нам задачу их выявления и построения. А для таких как WPR, CCI данное решение вообще неприемлемо т.к. вид линии изменится до неузнаваемости.

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

input bool               Interpolate     = true;
//--- main cycle
   for(int i=limit;i>=0 && !IsStopped();i--)
     {
      int n;
      datetime t=time[i];
      ExtMA[i]=_CopyBuffer(MA_Handle,t);
      if(!Interpolate) continue;
      //---
      datetime times= _iTime(t);
      for(n = 1; i+n<rates_total && time[i+n]>= times; n++) continue;
      double factor=1.0/n;
      for(int k=1; k<n; k++)
      ExtMA[i+k]=k*factor*ExtMA[i+n]+(1.0-k*factor)*ExtMA[i];
     }
//---

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

//+------------------------------------------------------------------+
datetime _iTime(datetime start_time)
{
   if(start_time < 0) return(-1);
   datetime Arr[];
   if(CopyTime(NULL,_tf, start_time, 1, Arr)>0)
        return(Arr[0]);
   else return(-1);
}
//+------------------------------------------------------------------+

Теперь наша линия приобрела более привычный для нас вид.

Рис. 11. Линия MTF-индикатора с _iTime

При кажущейся нецелесообразности написания таких сложных и энергоёмких систем, они имеют преимущества, а иногда и незаменимы. В случаях где используется классическое усреднение (МА, Alligator и т.д.), при увеличении периода расчета наблюдается некоторое запаздывание по сравнению с MTF версией. Особенно это заметно при малых периодах предполагаемого значения.

Рис. 12. Запаздывание в MTF-индикаторе MA

Рис. 13. Запаздывание в MTF-индикаторе Stochastic

Если для простых индикаторов, таких как МА и Alligator, это может быть и не столь существенно, то для тех, которые представляют сложную систему из двух и более МА, таких как MACD, AO и т.д., это может иметь существенное значение. Тем более что выше упомянутый АО или АС и им подобные вообще не имеют возможности изменять период усреднения. И для индикаторов, линия которых не сглаживается (WPR, CCI и т.д.), банальным увеличением периода расчета добиться сколь либо достойного результата довольно сложно, они сильно зашумлены.


Рис. 14 MTF-индикатор WRP


Рис. 15 MTF-индикатор CCI

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

Данный тип, помимо своей непосредственной функции, может выполнять и сугубо практичную — они способны компенсировать недостатки тестера стратегий MetaTrader 5 в режиме визуализации. При создании MTF советников для торговли или анализа эффективности подобного типа стратегий мы сталкиваемся с тем что не можем одновременно наблюдать положение индикаторов с различных ТФ на экране, а по итогам тестирования получаем набор закладок в зависимости от количества используемых периодов. Воспользуемся для примера советником по стратегии «Три экрана Элдера» из материала «РЕЦЕПТЫ MQL5 - РАЗРАБОТКА СХЕМЫ ДЛЯ ТОРГОВОЙ СИСТЕМЫ ТИПА "ТРИ ЭКРАНА ЭЛДЕРА"»  автора Anatoli Kazharski. Напомним, как звучит эта стратегия в классическом варианте: первый таймфрейм – самый крупный, например, недельный, дневной или 4-часовой. С помощью него определяют основной тренд. Второй таймфрейм отличается от первого на 1 или 2 порядка. С его помощью определяем окончание коррекции. Третий таймфрейм отличается еще на один порядок. По нему выявляем выгодную точку входа.

В первом окне, обычно это М30-W1, размещаем MACD (12,26,1) и EMA с периодом 13. Второй экран, М5-D1 соответственно, у нас расположился Стохастик (Stochastic Oscillator) (5,3,3). Третий экран может быть от M1 до H4, используем его для выставления Stop-ордеров в направлении основного тренда.

Рис. 16. Три экрана Элдера

Автор несколько отошел от данного варианта, но концепция «Три экрана» сохранена. Во время и по окончанию тестирования мы наблюдаем подобную картину:


Рис. 17. Тестирование стратегии "Три экрана Элдера"

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

#define EXPERT_MAGIC 1234502
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\OrderInfo.mqh>
//+------------------------------------------------------------------+
enum compTF
  {
   A, //m1-m5-m15
   B, //m5-m15-h1
   C, //m15-h1-h4
   E  //h1-h4-d1
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input string s0="//--- input parameters Lots+Trailing ---//";
input double InpLots          =0.1; // Lots
input int    InpTakeProfit    =150; // Take Profit (in pips)
input int    InpStopLoss      =60;  // StopLoss (in pips)
input int    InpLevel_S       =20;  // Level Signal
input compTF InpTFreg         =2;
input string s1="//--- input parameters MA ---//";
input int                Signal_MA_PeriodMA      =21;                             // Period of averaging
input string s2="//--- input parameters MACD ---//";
input int                Signal_MACD_PeriodFast  =12;                             // Period of fast EMA
input int                Signal_MACD_PeriodSlow  =26;                             // Period of slow EMA
input string s3="//--- input parameters Stochastic ---//";
input int                Signal_Stoch_PeriodK    =5;                              //  K-period
input int                Signal_Stoch_PeriodD    =3;                              //  D-period
input string s4="//--- input parameters Trailing ---//";
//--- inputs for trailing
input int    InpTrailingStop  =25;  // Trailing Stop Level (in pips)
input int    InpOffset        =5;   // Distance from the price (in pips)
//---
int ExtTimeOut=10; // время в секундах между торговыми операциями
int barsCalculated=3;
datetime t=0;
datetime time[];
//+------------------------------------------------------------------+
//| AC Sample expert class                                           |
//+------------------------------------------------------------------+
class CSampleExpert
  {
protected:
   double            m_adjusted_point;             // значение на 3 или 5 знаков
   CTrade            m_trade;                      // торговый объект
   CSymbolInfo       m_symbol;                     // информация о символе
   CPositionInfo     m_position;                   // торговая позиция
   CAccountInfo      m_account;                    // информация об аккаунте
   COrderInfo        m_order;                      // информация об ордере 
   //--- indicators
   ENUM_TIMEFRAMES   mas_tf[3];                   // массив набора timeframes
   int               handle_MACD;                 // MACD indicator handle
   int               handle_MA;                   // MA indicator handle
   int               handle_Stochastic;           // Stochastic indicator handle
   //--- indicator buffers
   double            macd_buff[];           // MACD indicator main buffer
   double            ma_buff[];             // MA indicator main buffer
   double            stoch_buff[];          // Stochastic indicator main buffer
   //---
   double            close[];
   double            open[];
   double            low[];
   double            high[];
   //--- данные индикатора для обработки
   double            macd_ind_0;
   double            macd_ind_1;
   double            ma_ind;
   double            stoch_ind_0;
   double            stoch_ind_1;
   int               level_stoch;
   //--- данные trailing stop для обработки
   double            m_traling_stop;
   double            m_take_profit;
   double            m_stop_losse;
public:
                     CSampleExpert(void);
                    ~CSampleExpert(void);
   bool              Init(void);
   void              Deinit(void);
   bool              Processing(void);

protected:
   bool              InitCheckParameters(const int digits_adjust);
   bool              Copy(void);              // 
   bool              InitIndicators(void);
   bool              LongModified(void);
   bool              ShortModified(void);
   bool              LongOpened(void);          // проверяем условия Long
   bool              ShortOpened(void);         // проверяем условия Short
   bool              OpenSellStop(void);        // ставим ордер SELLSTOP
   bool              OpenBuyStop(void);         // ставим ордер BUYSTOP
   bool              OrderModifySellStop(void); // изменяем ордер SELLSTOP
   bool              OrderModifyBuyStop(void);  // изменяем ордер BUYSTOP
   bool              DellSellStop(void);        // удаляем ордер SELLSTOP
   bool              DellBuyStop(void);         // удаляем ордер BUYSTOP
  };
//--- global expert
CSampleExpert ExtExpert;
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSampleExpert::CSampleExpert(void) : m_adjusted_point(0),
                                     handle_MACD(INVALID_HANDLE),
                                     handle_Stochastic(INVALID_HANDLE),
                                     handle_MA(INVALID_HANDLE),
                                     macd_ind_0(0),
                                     macd_ind_1(0),
                                     stoch_ind_0(0),
                                     stoch_ind_1(0),
                                     ma_ind(0),
                                     m_traling_stop(0),
                                     m_take_profit(0)
  {
   ArraySetAsSeries(macd_buff,true);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSampleExpert::~CSampleExpert(void)
  {
  }
//+------------------------------------------------------------------+
//| Инициализация и проверка входных параметров                      |
//+------------------------------------------------------------------+
bool CSampleExpert::Init(void)
  {
//--- инициализировать общую информацию
   m_symbol.Name(Symbol());                  // symbol
   m_trade.SetExpertMagicNumber(EXPERT_MAGIC); // magic
   m_trade.SetMarginMode();
   m_trade.SetTypeFillingBySymbol(Symbol());
//--- tuning for 3 or 5 digits
   int digits_adjust=1;
   if(m_symbol.Digits()==3 || m_symbol.Digits()==5)
      digits_adjust=10;
   m_adjusted_point=m_symbol.Point()*digits_adjust;
//--- установить отклонение по умолчанию для торговли 
   m_traling_stop    =InpTrailingStop*m_adjusted_point;
   m_take_profit     =InpTakeProfit*m_adjusted_point;
   m_stop_losse      =InpStopLoss*m_adjusted_point;
//--- set default deviation for trading in adjusted points
   m_trade.SetDeviationInPoints(3*digits_adjust);
//---
   int x=InpTFreg;
   switch(x)
     {
      case 0: {mas_tf[0]=PERIOD_M1;mas_tf[1]=PERIOD_M5;mas_tf[2]=PERIOD_M15;}
      break;
      case 1: {mas_tf[0]=PERIOD_M5;mas_tf[1]=PERIOD_M15;mas_tf[2]=PERIOD_H1;}
      break;
      case 2: {mas_tf[0]=PERIOD_M15;mas_tf[1]=PERIOD_H1;mas_tf[2]=PERIOD_H4;}
      break;
      case 3: {mas_tf[0]=PERIOD_H1;mas_tf[1]=PERIOD_H4;mas_tf[2]=PERIOD_D1;}
      break;
     }
//---
   if(!InitCheckParameters(digits_adjust))
      return(false);
   if(!InitIndicators())
      return(false);
//--- результат
   return(true);
  }
//+------------------------------------------------------------------+
//| Проверка входных параметров                                      |
//+------------------------------------------------------------------+
bool CSampleExpert::InitCheckParameters(const int digits_adjust)
  {
//--- проверка исходных данных
   if(InpTakeProfit*digits_adjust<m_symbol.StopsLevel())
     {
      printf("Take Profit должен быть больше, чем %d",m_symbol.StopsLevel());
      return(false);
     }
   if(InpTrailingStop*digits_adjust<m_symbol.StopsLevel())
     {
      printf("Trailing Stop должен быть больше, чем %d",m_symbol.StopsLevel());
      return(false);
     }
//--- проверить правильную сумму лота
   if(InpLots<m_symbol.LotsMin() || InpLots>m_symbol.LotsMax())
     {
      printf("Lots должен быть в диапазоне от %f to %f",m_symbol.LotsMin(),m_symbol.LotsMax());
      return(false);
     }
   if(MathAbs(InpLots/m_symbol.LotsStep()-MathRound(InpLots/m_symbol.LotsStep()))>1.0E-10)
     {
      printf("Сумма не соответствует шагу лота %f",m_symbol.LotsStep());
      return(false);
     }
//--- warning
   if(InpTakeProfit<=InpTrailingStop)
      printf("Warning: Trailing Stop должен быть меньше, чем Take Profit");
//--- результат
   return(true);
  }
//+------------------------------------------------------------------+
//| Инициализация индикаторов                                        |
//+------------------------------------------------------------------+
bool CSampleExpert::InitIndicators(void)
  {
//--- создать индикатор MACD
   if(handle_MACD==INVALID_HANDLE)
     {
      //---
      handle_MACD=iCustom(NULL,0,"MTF\\Oscillators\\MTF_MACD",mas_tf[2],Signal_MACD_PeriodFast,Signal_MACD_PeriodSlow);
      //---
      if(handle_MACD==INVALID_HANDLE)
        {
         printf("Ошибка при создании MACD индикатора");
         return(false);
        }
     }
//--- создать индикатор MA
   if(handle_MA==INVALID_HANDLE)
     {
      //---
      handle_MA=iCustom(NULL,0,"MTF\\Trend\\MA_MultiTF",mas_tf[2],Signal_MA_PeriodMA,0,MODE_EMA,PRICE_CLOSE);
      //---
      if(handle_MA==INVALID_HANDLE)
        {
         printf("Ошибка при создании MA индикатора");
         return(false);
        }
     }
//--- создать индикатор Stochastic
   if(handle_Stochastic==INVALID_HANDLE)
     {
      //---
      handle_Stochastic=iCustom(NULL,0,"MTF\\Oscillators\\MTF_Stochastic",mas_tf[1],Signal_Stoch_PeriodK,Signal_Stoch_PeriodD);
      //---
      if(handle_Stochastic==INVALID_HANDLE)
        {
         printf("Ошибка при создании Stochastic индикатора");
         return(false);
        }
     }
//--- результат
   return(true);
  }
//+------------------------------------------------------------------+
//|          Измененяем длинную позицию                              |
//+------------------------------------------------------------------+
bool CSampleExpert::LongModified(void)
  {
   bool res=false;
//--- проверить трейлинг-стоп
   if(InpTrailingStop>0)
     {
      if(m_symbol.Bid()-m_position.PriceOpen()>m_adjusted_point*InpTrailingStop)
        {
         double sl=NormalizeDouble(m_symbol.Bid()-m_traling_stop,m_symbol.Digits());
         double tp=m_position.TakeProfit();
         if(m_position.StopLoss()<sl || m_position.StopLoss()==0.0)
           {
            //--- изменить позицию
            if(m_trade.PositionModify(Symbol(),sl,tp))
               printf("Long position by %s to be modified",Symbol());
            else
              {
               printf("Error modifying position by %s : '%s'",Symbol(),m_trade.ResultComment());
               printf("Modify parameters : SL=%f,TP=%f",sl,tp);
              }
            //--- изменен и должен выйти из эксперта
            res=true;
           }
        }
     }
//--- результат
   return(res);
  }
//+------------------------------------------------------------------+
//|                    Изменяем короткую позицию                     |
//+------------------------------------------------------------------+
bool CSampleExpert::ShortModified(void)
  {
   bool   res=false;
//--- проверить трейлинг-стоп
   if(InpTrailingStop>0)
     {
      if((m_position.PriceOpen()-m_symbol.Ask())>(m_adjusted_point*InpTrailingStop))
        {
         double sl=NormalizeDouble(m_symbol.Ask()+m_traling_stop,m_symbol.Digits());
         double tp=m_position.TakeProfit();
         if(m_position.StopLoss()>sl || m_position.StopLoss()==0.0)
           {
            //--- изменить позицию
            if(m_trade.PositionModify(Symbol(),sl,tp))
               printf("Short position by %s to be modified",Symbol());
            else
              {
               printf("Error modifying position by %s : '%s'",Symbol(),m_trade.ResultComment());
               printf("Modify parameters : SL=%f,TP=%f",sl,tp);
              }
            //--- изменен и должен выйти из эксперта
            res=true;
           }
        }
     }
//--- результат
   return(res);
  }
//+------------------------------------------------------------------+
//| Проверить условия открытия длинной позиции                       |
//+------------------------------------------------------------------+
bool CSampleExpert::LongOpened(void)
  {
   bool res=false;
   level_stoch=InpLevel_S;
//--- проверяем возможность длинной позиции (BUY)
   if(stoch_ind_1<level_stoch && stoch_ind_0>level_stoch && macd_ind_1>macd_ind_0 && ma_ind<close[1] && ma_ind<open[1])//&& ma_ind<close[1] && ma_ind<open[1]
     {
      //--- выйти из эксперта
      res=true;
     }
//--- результат
   return(res);
  }
//+------------------------------------------------------------------+
//|         открыть Buy Stop позицию                                 |
//+------------------------------------------------------------------+
bool CSampleExpert::OpenBuyStop(void)
  {
   bool res=false;
   double tp=0,sl=0;
//---
   if(LongOpened())
     {
      res=true;
      //-- объявление и инициализация запроса и результата
      MqlTradeRequest request={0};
      MqlTradeResult  result={0};
      //--- параметры для установки отложенного ордера
      request.action   =TRADE_ACTION_PENDING;                             // тип торговой операции
      request.symbol   =Symbol();                                         // символ
      request.deviation=5;                                                // допустимое отклонение от цены
      request.volume   =InpLots;                                          // объем в лот
      request.magic    =EXPERT_MAGIC;                                     // MagicNumber ордера
      double offset=InpOffset;                                            // отступ от текущей цены для установки ордера, в пунктах
      double price;                                                       // цена срабатывания ордера
      double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);                // размер пункта
      int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // кол-во знаков после запятой (точность)
      //--- тип операции
      request.type=ORDER_TYPE_BUY_STOP;                                   // тип ордера
      price=high[1]+offset*m_adjusted_point;                              // цена для открытия 
      request.price=NormalizeDouble(price,digits);                        // нормализованная цена открытия 
      tp=price+m_take_profit;
      sl=price-m_stop_losse;
      request.sl       =NormalizeDouble(sl,_Digits);                       // заносим новое значение Buy Loss в структуру
      request.tp       =NormalizeDouble(tp,_Digits);                       // заносим новое значение Take Profit в структуру
      //--- отправка запроса
      if(!OrderSend(request,result))
        {res=false;printf("OrderSend error %d",GetLastError());}           // если отправить запрос не удалось, вывести код ошибки
      //--- информация об операции
      printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
      //--- выйти из эксперта
     }
//--- результат
   return(res);
  }
//+------------------------------------------------------------------+
//| Проверить условия открытия короткой позиции                      |
//+------------------------------------------------------------------+
bool CSampleExpert::ShortOpened(void)
  {
   bool res=false;
   level_stoch=100-InpLevel_S;
//--- проверить возможность короткой позиции  (SELL) 
   if(stoch_ind_1>level_stoch && stoch_ind_0<level_stoch && macd_ind_1<macd_ind_0 && ma_ind>close[1] && ma_ind>open[1])
     {
      //--- выйти из эксперта
      res=true;
     }
//--- результат
   return(res);
  }
//+------------------------------------------------------------------+
//|         открыть Sell Stop позицию                                 |
//+------------------------------------------------------------------+
bool CSampleExpert::OpenSellStop(void)
  {
   bool res=false;
   double tp=0,sl=0;
//---
   if(ShortOpened())
     {
      res=true;
      //-- объявление и инициализация запроса и результата
      MqlTradeRequest request={0};
      MqlTradeResult  result={0};
      //--- параметры для установки отложенного ордера
      request.action   =TRADE_ACTION_PENDING;                             // тип торговой операции
      request.symbol   =Symbol();                                         // символ
      request.deviation=5;                                                // допустимое отклонение от цены
      request.volume=InpLots;                                             // объем в лот
      request.magic=EXPERT_MAGIC;                                         // MagicNumber ордера
      int offset=InpOffset;                                                // отступ от текущей цены для установки ордера, в пунктах
      double price;                                                       // цена срабатывания ордера
      int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // кол-во знаков после запятой (точность)
      request.type=ORDER_TYPE_SELL_STOP;                                  // тип ордера
      price=low[1]-offset*m_adjusted_point;                                          // цена для открытия 
      request.price=NormalizeDouble(price,digits);                        // нормализованная цена открытия 
      tp=price-m_take_profit;
      sl=price+m_stop_losse;
      request.sl       =NormalizeDouble(sl,_Digits);                       // заносим новое значение StopLoss в структуру
      request.tp       =NormalizeDouble(tp,_Digits);                       // заносим новое значение TakeProfit в структуру
      //--- отправка запроса
      if(!OrderSend(request,result))
        {res=false;printf("OrderSend error %d",GetLastError());}     // если отправить запрос не удалось, вывести код ошибки
      //--- информация об операции
      printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
      //--- выйти из эксперта
     }
//--- результат
   return(res);
  }
//+------------------------------------------------------------------+
//| основная функция возвращает true, если какая-либо позиция        |
//| обрабатывается                                                   |
//+------------------------------------------------------------------+
bool CSampleExpert::Processing(void)
  {
//   MqlDateTime dt;
//--- частота обновления
   if(!m_symbol.RefreshRates())
      return(false);
//--- индикаторы обновления
   if(BarsCalculated(handle_Stochastic)<barsCalculated)
      return(false);
   if(CopyBuffer(handle_Stochastic,0,0,barsCalculated,stoch_buff)!=barsCalculated)
      return(false);
//---
   if(BarsCalculated(handle_MACD)<barsCalculated)
      return(false);
   if(CopyBuffer(handle_MACD,0,0,barsCalculated,macd_buff)!=barsCalculated)
      return(false);
//---
   if(BarsCalculated(handle_MA)<barsCalculated)
      return(false);
   if(CopyBuffer(handle_MA,0,0,barsCalculated,ma_buff)!=barsCalculated)
      return(false);
//---
   if(!Copy())return(false);
//--- для упрощения кодирования и ускорения доступа
//--- данные помещаются во внутренние переменные
   macd_ind_0   = macd_buff[1];
   macd_ind_1   = macd_buff[0];
   ma_ind       = ma_buff[1];
   stoch_ind_0  = stoch_buff[1];
   stoch_ind_1  = stoch_buff[0];

//--- важно выйти из него правильно ...   
//--- сначала проверьте, существует ли позиция - попробуйте выбрать ее
   bool bord=false,sord=false;
   ulong ticket;
//+--------- есть открытые позиции и traling stop включен -----------+
   if(m_position.Select(Symbol()) && PositionsTotal()>0 && m_traling_stop!=0)
     {
      if(m_position.PositionType()==POSITION_TYPE_BUY)
        {
         bord=true;
         //--- изменить длинную позицию
         if(LongModified())
            return(true);
        }
      else
        {
         sord=true;
         //---изменить короткую позицию
         if(ShortModified())
            return(true);
        }
     }
//+----- торговля  STOP ордерами ------------------------------------+
// в этом цикле поочередно перебираем все установленные отложенные ордера
   for(int i=0;i<OrdersTotal();i++)
     {
      // выбираем каждый из ордеров, получаем его тикет
      ticket=OrderGetTicket(i);
      // обслуживаем ордера Buy Stop
      if(m_order.OrderType()==ORDER_TYPE_BUY_STOP)
        {
         // устанавливаем флаг, индицирующий то, что присутствует ордер Buy Stop
         bord=true;
         //--- важно правильно войти на рынок,передвигаем ордер если надо 
         if(bord)OrderModifyBuyStop(); // изменяем ордер BUYSTOP
        }
      // обслуживаем ордера Sell Stop
      if(m_order.OrderType()==ORDER_TYPE_SELL_STOP)
        {
         // устанавливаем флаг, индицирующий то, что присутствует ордер Sell Stop
         sord=true;
         //--- важно правильно войти на рынок,передвигаем ордер если надо 
         if(sord)OrderModifySellStop(); // изменяем ордер SELLSTOP
        }
     }
//--- если нет ордеров выставим ------------------------------------+
   if(!sord)if(OpenSellStop()){sord=true;return(true);}
   if(!bord)if(OpenBuyStop()){bord=true;return(true);}
//--- выход без обработки позиции
   return(false);
  }
//+------------------------------------------------------------------+
//|Изменим параметры ранее установленного торгового ордера Sell Stop |
//+------------------------------------------------------------------+
bool CSampleExpert::OrderModifySellStop(void)
  {
   bool res=true;
   ulong ticket;
   double tp=0,sl=0;
   double offset=InpOffset;                                            // отступ от текущей цены для установки ордера, в пунктах
   double price;                                                       // цена срабатывания ордера
   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // кол-во знаков после запятой (точность)
                                                                       // объявление и инициализация запроса и результата
   MqlTradeRequest request={0};
   MqlTradeResult  result={0};
   int total=OrdersTotal(); // количество установленных отложенных ордеров
//--- перебор всех установленных отложенных ордеров
   for(int i=total-1; i>=0; i--)
     {
      // выбираем каждый из ордеров, получаем его тикет
      ticket=OrderGetTicket(i);
      // обслуживаем ордера Sell Stop
      if(m_order.OrderType()==ORDER_TYPE_SELL_STOP)
        {
         ulong  magic=OrderGetInteger(ORDER_MAGIC);               // MagicNumber ордера
         //--- если MagicNumber совпадает
         if(magic==EXPERT_MAGIC)
           {
               price=low[1]-offset*m_adjusted_point;                         // цена для открытия 
            if(price>m_order.PriceOpen()) // проверим условия
            if(low[1]>low[2]) // проверим условия
              {
               request.action=TRADE_ACTION_MODIFY;                           // тип торговой операции
               request.order = OrderGetTicket(i);                            // тикет ордера
               request.symbol   =Symbol();                                   // символ
               request.deviation=InpOffset;                                  // допустимое отклонение от цены
               price=low[1]-offset*m_adjusted_point;                         // цена для открытия 
               request.price=NormalizeDouble(price,digits);                  // нормализованная цена открытия 
               tp=price-m_take_profit;
               sl=price+m_stop_losse;
               request.sl       =NormalizeDouble(sl,_Digits);                // заносим новое значение StopLoss в структуру
               request.tp       =NormalizeDouble(tp,_Digits);                // заносим новое значение TakeProfit в структуру
               //--- отправка запроса
               if(!OrderSend(request,result))
                 {
                  // устанавливаем флаг, индицирующий то, что ордер Sell Stop не удален
                  res=true; printf("OrderSend error %d",GetLastError());
                 }  // если отправить запрос не удалось, вывести код ошибки
               //--- информация об операции   
               printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
              }
           }
        }
     }
   return(res);
  }
//+------------------------------------------------------------------+
//|Изменим параметры ранее установленного торгового ордера Buy Stop  |
//+------------------------------------------------------------------+
bool CSampleExpert::OrderModifyBuyStop(void)
  {
   bool res=true;
   ulong ticket;
   double tp=0,sl=0;
   double offset=InpOffset;                                            // отступ от текущей цены для установки ордера, в пунктах
   double price;                                                       // цена срабатывания ордера
   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // кол-во знаков после запятой (точность)
                                                                       //-- объявление и инициализация запроса и результата
   MqlTradeRequest request={0};
   MqlTradeResult  result={0};
   int total=OrdersTotal(); // количество установленных отложенных ордеров
//--- перебор всех установленных отложенных ордеров
   for(int i=total-1; i>=0; i--)
     {
      // выбираем каждый из ордеров, получаем его тикет
      ticket=OrderGetTicket(i);
      // обслуживаем ордера Buy Stop
      if(m_order.OrderType()==ORDER_TYPE_BUY_STOP)
        {
         ulong  magic=OrderGetInteger(ORDER_MAGIC);               // MagicNumber ордера
         //--- если MagicNumber совпадает
         if(magic==EXPERT_MAGIC)
           {
               price=high[1]+offset*m_adjusted_point;                        // цена для открытия 
            if(price<m_order.PriceOpen()) // проверим условия
              {
               request.action=TRADE_ACTION_MODIFY;                           // тип торговой операции
               request.symbol   =Symbol();                                   // символ
               request.action=TRADE_ACTION_MODIFY;                           // тип торговой операции
               request.order = OrderGetTicket(i);                            // тикет ордера
               request.symbol   =Symbol();                                   // символ
               request.deviation=InpOffset;                                  // допустимое отклонение от цены
               //--- установка уровня цены, тейк-профит и стоп-лосс ордера 

               request.price=NormalizeDouble(price,digits);                  // нормализованная цена открытия
               tp=price+m_take_profit;
               sl=price-m_stop_losse;
               request.sl       =NormalizeDouble(sl,_Digits);                       // заносим новое значение Stop Loss в структуру
               request.tp       =NormalizeDouble(tp,_Digits);                       // заносим новое значение Take Profit в структуру
               //--- отправка запроса
               if(!OrderSend(request,result))
                 {
                  // устанавливаем флаг, индицирующий то, что ордер Buy Stop не удален
                  res=true;
                  printf("OrderSend error %d",GetLastError());
                 }  // если отправить запрос не удалось, вывести код ошибки
               //--- информация об операции   
               printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
              }
           }
        }
     }
   return(res);
  }
//+------------------------------------------------------------------+
//| Функция инициализации эксперта                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- создать все необходимые объекты
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| функция обработки тиков                                          |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   static datetime limit_time=0; // последнее время обработки торговли + таймаут
//--- не обрабатывать, если тайм-аут
   if(TimeCurrent()>=limit_time)
     {
      //--- проверить данные
      if(Bars(Symbol(),Period())>barsCalculated)
        {
         //--- изменить предельное время на тайм-аут в секундах, если обработано
         if(ExtExpert.Processing())
            limit_time=TimeCurrent()+ExtTimeOut;
        }
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSampleExpert::Copy(void)
  {
//--- сколько копируем 
   int copied=3;
//---
   ArrayResize(high,copied);
   ArrayResize(low,copied);
   ArrayResize(close,copied);
   ArrayResize(open,copied);
//+------ Задаем направление индексации массива ---------------------+
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(open,true);
//--- копируем цены баров 
   if(CopyHigh(NULL,mas_tf[0],0,copied,high)<0)
     {printf("No copied High",GetLastError());return(false);}
//---
   if(CopyLow(NULL,mas_tf[0],0,copied,low)<0)
     {printf("No copied Low",GetLastError());return(false);}
//---
   if(CopyClose(NULL,mas_tf[2],0,copied,close)<0)
     {printf("No copied Close",GetLastError());return(false);}
//---
   if(CopyOpen(NULL,mas_tf[2],0,copied,open)<0)
     {printf("No copied Open",GetLastError());return(false);}
//---
   return(true);
  }
//+------------------------------------------------------------------+


Применение подобных инструментов дает другую картину:



Рис. 18. Тестирование советника с нашими MTF-инструментами

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


Заключение

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

Прикрепленные файлы.

Имя Тип Путь
MA_MultiPeriod Trend MQL5\Indicators\MA_MultiPeriod.mq5
MA_MultiTF Trend MQL5\Indicators\MTF\Trend\MA_MultiTF.mq5
MTF_BB Trend MQL5\Indicators\MTF\Trend\MTF_BB.mq5
MTF_Envelopes Trend MQL5\Indicators\MTF\Trend\MTF_Envelopes.mq5
MTF_ParabolicSAR Trend MQL5\Indicators\MTF\Trend\MTF_ParabolicSAR.mq5
MTF_StdDev Trend MQL5\Indicators\MTF\Trend\MTF_StdDev.mq5
MTF_CCI Oscillators MQL5\Indicators\MTF\Oscillators\MTF_CCI.mq5
MTF_Force_Index Oscillators MQL5\Indicators\MTF\Oscillators\MTF_Force_Index.mq5
MTF_MACD Oscillators MQL5\Indicators\MTF\Oscillators\MTF_MACD.mq5
MTF_Momentum Oscillators MQL5\Indicators\MTF\Oscillators\MTF_Momentum.mq5
MTF_RSI Oscillators MQL5\Indicators\MTF\Oscillators\MTF_RSI.mq5
MTF_RVI Oscillators MQL5\Indicators\MTF\Oscillators\MTF_RVI.mq5
MTF_Stochastic Oscillators MQL5\Indicators\MTF\Oscillators\MTF_Stochastic.mq5
MTF_WPR  Oscillators  MQL5\Indicators\MTF\Oscillators\MTF_WPR.mq5
Triple Screen Trading System 1.0  Expert