English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Реализация индикаторов в виде классов на примере Zigzag и ATR

Реализация индикаторов в виде классов на примере Zigzag и ATR

MetaTrader 5Индикаторы | 31 января 2011, 16:51
6 399 0
Aleksandr Chugunov
Aleksandr Chugunov


Для чего это может понадобиться?

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

Но работа одного алгоритма так и осталась неизменна. При разрывах связи с сервером или существенной синхронизации истории значение prev_calculated (или IndicatorCounted() для MetaTrader 4) обнуляется, что влечёт за собой полный пересчёт индикатора по всей истории (это сделано разработчиками терминала специально, что бы при любых изменениях истории гарантировать правильные значения индикаторов). Есть несколько моментов, которые могут отражаться на скорости вычислений индикаторов:

  • Большой период: rates_total;
  • Тяжёлые ресурсоёмкие вычисления;
  • Использование нескольких инструментов и периодов;
  • Слабый персональный компьютер;

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

Конечно же, вы можете через дополнительный input-параметр ограничить глубину расчета индикатора, но здесь есть один нюанс при использовании iCustom индикаторов. Максимальное количество баров, с которыми работает любой график или любой пользовательский индикатор, задаётся на глобальном уровне для всего терминала. Под пользовательский индикатор для каждого буфера выделяется оперативная память на всю глубину доступной истории, ограниченной только TERMINAL_MAXBARS.

Правда здесь есть серьёзное уточнение -  если в алгоритме расчёта индикатора принудительно ограничить максимальное количество рассчитываемых баров (например через input-параметр или жёстко в коде), то память будет при этом выделяться динамически на каждом новом баре (постепенно увеличиваться до отведённого лимита в TERMINAL_MAXBARS (или чуть больше - этот алгоритм уже полностью зависит от разработчиков, в следующих билдах программы они могут его изменить)).


Какими способами можно уйти от пересчета индикатора по всей истории

На сегодняшний момент я вижу следующие пути решения данной проблемы:
  1. Попросить MetaQuotes доработать этот момент на уровне платформы
  2. Создать отдельный класс для реализации аналога prev_calculated

Был ещё вариант-предположение, что можно непосредственно в индикатор встроить алгоритм вычисления prev_calculated, но как оказалось, MetaTrader 5, в отличие от MetaTrader 4, при обнулении prev_calculated "чистит" все индикаторные буферы (т.е. в принудительном порядке производит полное обнуление всех индикаторных массивов; управлять этим нельзя, так как данная реализация на уровне платформы).

Рассмотрим каждый вариант в отдельности.

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


Плюсы и минусы второго варианта решения проблемы

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


Создание класса CCustPrevCalculated для реализации аналога prev_calculated

В самой реализации класса нет особо интересных мест для описания. Алгоритм учитывает как расширение истории в обе стороны, так и её теоретически возможную «урезку» слева. Так же алгоритм умеет обрабатывать вставку истории внутрь просчитанных данных (для MetaTrader 4 это актуально, в МetaTrader 5 на сегодняшний момент я с этим ещё не сталкивался). Код класса находится в файле CustPrevCalculated.mqh

Расскажу о ключевых моментах.


Создание кольцевого доступа к элементам массива

При создании данного класса мы будем применять нестандартный приём – кольцевой доступ к элементам массива для однократного распределения памяти под массив и для исключения излишних процедур копирования массивов. Рассмотри этот момент подробнее на массиве из 5-ти элементов:



 
Изначально мы работаем с массивом, нумерация элементов которого начинается с 0. Но что делать, если требуется добавить следующее значение, сохранив при этом размер массива (добавление нового бара)? Есть два варианта:
  • скопировать ячейки памяти №№ 2-5 в ячейки памяти №№ 1-4 соответственно, при этом у нас освободится ячейка памяти № 5;
  • изменить индексацию массива без затрагивания хранящихся в нём данных (круговая адресация).

Для реализации второго варианта нам понадобится переменная, назовём её DataStartInd, которая будет хранить позицию нулевого индекса массива. Для удобства дальнейших вычислений её нумерация будет соответствовать привычной индексации в массиве (т.е. начинаться с нуля). В переменной BarsLimit будет храниться количество элементов массива. Тогда реальный адрес элемента массива для виртуального индекса I будет вычисляться по простой формуле:

  • (DataStartInd+I) % BarsLimit – для привычной нумерации
  • (DataStartInd+DataBarsCount-1-I) % BarsLimit – для адресации как в тайм-сериях
В переменной DataBarsCount хранится реально используемое количество ячеек памяти (мы ведь можем использовать и 3 ячейки из 5).


Алгоритмы синхронизации истории

Для себя я выделил и реализовал три режима работы алгоритма синхронизации копии истории (локальная история) с историей в терминале:
  • CPCHSM_NotSynch – синхронизация локальной истории для уже сформировавшихся баров не выполняется (на ваш страх и риск и под вашу ответственность). На самом деле данный режим можно спокойно применять для индикаторов, где незначительные отклонения в значениях цен не могут глобально повлиять на точность расчетов (MA, ADX и т.д.). Данный режим может быть фатальным, скажем, для ZigZag, где важно превышение одной вершины над другой.
  • CPCHSM_Normal – локальная история синхронизируется на каждом новом баре по алгоритму, описанному ниже.
  • CPCHSM_Paranoid – локальная история синхронизируется при каждом вызове функции синхронизации данных по алгоритму, описанному ниже.

Сам механизм синхронизации строится на ещё одном, задаваемом программистом, параметре – HSMinute (хранится как HistorySynchSecond). Мы делаем предположение, что ДЦ можетподкорректировать историю только за последние HSMinute минут. Если при синхронизации локальной истории не найдены разночтения за этот временной период, то истории считаются идентичными и сравнение прерывается. Если же будет найдено хоть одно расхождение – проверится и исправится вся локальная история.

Кроме того, алгоритм позволяет проверять только заданные при инициализации цены/спреды/объёмы из структуры MqlRates. К примеру, для построения ZigZag необходимы только цены High и Low.


Использование класса CCustPrevCalculated на практике

Для инициализации класса CCustPrevCalculated необходимо однократно вызвать функцию InitData(), которая вернёт истину в случае успеха:
CCustPrevCalculated CustPrevCalculated;
CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
Для синхронизации истории необходимо по необходимости вызывать функцию PrepareData():
CPCPrepareDataResultCode resData;
resData = CustPrevCalculated.PrepareData();

Варианты возврата значений функцией PrepareData():

enum CPCPrepareDataResultCode
  {
   CPCPDRC_NoData,                     // Возвращается, когда нет данных для расчета (не подготовлены сервером)
   CPCPDRC_FullInitialization,         // Была произведена полная инициализация массива
   CPCPDRC_Synch,                      // Была произведена синхронизация с добавлением новых баров
   CPCPDRC_SynchOnlyLastBar,           // Была произведена синхронизация только последнего бара (возможно с урезкой истории)
   CPCPDRC_NoRecountNotRequired        // Перерасчета не было, так как данные не изменились
  };


Функции класса CCustPrevCalculated для доступа к данным

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

Наименование
Применение
 uint GetDataBarsCount()
 Возвращает количество доступных баров
 uint GetDataBarsCalculated()
 Возвращает количество не изменённых баров
 uint GetDataStartInd()
 Возвращает индекс для кольцевого доступа (для пользовательских индикаторов)
 bool GetDataBarsCuttingLeft()
 Возвращает результат урезки слева баров
 double GetDataOpen(int shift, bool AsSeries)
 Возвращает Open для shift-бара
 double GetDataHigh(int shift, bool AsSeries)
 Возвращает High для shift-бара
 double GetDataLow(int shift, bool AsSeries)
 Возвращает Low для shift-бара
 double GetDataClose(int shift, bool AsSeries)
 Возвращает Close для shift-бара
 datetime GetDataTime(int shift, bool AsSeries)
 Возвращает Time для shift-бара
 long GetDataTick_volume(int shift, bool AsSeries)
 Возвращает Tick_volume для shift-бара
 long GetDataReal_volume(int shift, bool AsSeries)
 Возвращает Real_volume для shift-бара
 int GetDataSpread(int shift, bool AsSeries)
 Возвращает Spread для shift-бара


Примеры дальнейшей оптимизации класса CCustPrevCalculated

  • Отказ от MqlRates с переходом на несколько (определяется конкретной задачей) массивов (уменьшение требования к оперативной памяти, но увеличение нагрузки на количество вызовов копирования массивов).
  • Разделение каждой функций доступа к данным на две независимые для однозначного использования с определённым видом порядка индексации массива (уход от параметра «bool AsSeries»). Выигрыш только в логическом условии «if (AsSeries)».


Создание класса CCustZigZagPPC для расчета пользовательского индикатора ZigZag на основе данных класса CCustPrevCalculated

Данный алгоритм основывается на пользовательском индикаторе Профессиональный ZigZag. Код класса находится в файле ZigZags.mqh, дополнительно используется библиотека OutsideBar.mqh для работы с внешним баром.

Создадим отдельную структуру для описание одного бара нашего индикатора:

struct ZZBar
  {
   double UP, DN;                      // Буферы индикатора ЗигЗаг
   OrderFormationBarHighLow OB;       // Буфер для кэширования внешнего бара
  };

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

enum CPCZZResultCode
  {
   CPCZZRC_NotInitialized,             // Класс не инициализирован
   CPCZZRC_NoData,                     // Не смогли получить данные (в том числе и для внешнего бара)
   CPCZZRC_NotChanged,                 // Изменений лучей ZZ не было
   CPCZZRC_Changed                     // Были изменения ZZ
  };

Для инициализации класса CCustZigZagPPC необходимо однократно вызвать функцию Init(), которая вернёт истину в случае успеха:

CCustZigZagPPC ZZ1;
ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);

Для вычислений индикатора необходимо сначала вызывать обновление данных на основе ранее просчитанных данных класса CCustPrevCalculated:

CPCPrepareDataResultCode resZZ1;
resZZ1 = ZZ1.PrepareData(resData);

А затем процедуру Calculate():

if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
   ZZ1.Calculate();

Полный пример использования одного класса CCustPrevCalculated совместно с несколькими классами CCustZigZagPPC находится в файле ScriptSample_CustZigZagPPC.mq5


Функции класса CCustZigZagPPC для доступа к данным

Наименование
Применение
 uint GetBarsCount()
 Возвращает количество доступных баров
 uint GetBarsCalculated()  Возвращает количество рассчитанных баров
 double GetUP(uint shift, bool AsSeries)
 Возвращает значение вершины ZigZag для бара
 double GetDN(uint shift, bool AsSeries)
 Возвращает значение впадины ZigZag для бара
 OrderFormationBarHighLow GetOB(uint shift, bool AsSeries)  Возвращает значение Outside бара для бара


Визуальная и программная проверка

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

Красный - оригинальный, синий - наш, рассчитанный на 100 последних барах.

Так же мы можем сравнить их и в эксперте - будет ли разница? В тестовом эксперте Expert_CustZigZagPPC_test.mq5 на каждом тике используется сравнение расчетов, полученных с помощью iCustom("AlexSTAL_ZigZagProf") и класса CCustZigZagPPC. В лог выводится информация о расчетах (возможно отсутствие расчета на первых барах из-за недостаточности истории для алгоритма):

(EURUSD,M1)                1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // это нормально
(EURUSD,M1) Тик обработан: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 
(EURUSD,M1) Расхождение на баре: 7 

Рассмотрим данный эксперт чуть по-подробнее. Определим глобальные переменные для работы:

#include <ZigZags.mqh>

CCustPrevCalculated CustPrevCalculated;
CCustZigZagPPC ZZ1;
int HandleZZ;

Проинициализируем переменные:

int OnInit()
  {
   // Создание нового класса и его инициализация
   CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
   
   // Инициализация класса ZZ
   ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
   
   // Получение хэндла для пользовательского индикатора
   HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true);
   Print("ZZ_handle = ", HandleZZ, "  error = ", GetLastError());

   return(0);
  }
Обработка тиков в эксперте:
void OnTick()
  {
   // Расчет данных
   CPCPrepareDataResultCode resData, resZZ1;
   resData = CustPrevCalculated.PrepareData();
   
   // Для каждого индикатора вызов пересчета! PrepareData в обязательном порядке!
   resZZ1 = ZZ1.PrepareData(resData);
   
   // Расчет данных ZZ1
   if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) )
      return;

   // Получим результаты расчета
   ZZ1.Calculate();

Теперь у нас есть ZZ1.GetBarsCalculated() просчитанных баров классом CCustZigZagPPC. Добавим код сравнения данных индикатора iCustom("AlexSTAL_ZigZagProf") и класса CCustZigZagPPC:

   int tmpBars = (int)ZZ1.GetBarsCalculated();
   double zzUP[], zzDN[];
   CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP);
   CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN);
   
   // Произведём сравнение
   string tmpSt1 = "", tmpSt2 = "";
   for (int i = (tmpBars-1); i >= 0; i--)
     {
      double tmpUP = ZZ1.GetUP(i, false);
      double tmpDN = ZZ1.GetDN(i, false);
      if (tmpUP != zzUP[i])
         Print("Расхождение на баре: ", i);
      if (tmpDN != zzDN[i])
         Print("Расхождение на баре: ", i);
      if (tmpUP != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; ";
      if (tmpDN != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; ";

      if (zzUP[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; ";
      if (zzDN[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; ";
     }
  Print("Тик обработан: ", tmpSt1);
  Print("                              ", tmpSt2);
  }

Вот, собственно говоря, и простое практическое применение класса CCustZigZagPPC в эксперте или скрипте. Вместо CopyBuffer() функции прямого обращения GetUP(), GetDN(), GetOB().


Переносим свой индикатор в отдельный класс (на примере iATR)

На основе файла ZigZags.mqh я изготовил шаблон MyIndicator.mqh для самостоятельного быстрого построения пользовательских индикатор по описанному принципу.

Общий план действий:

1. Подготовительный этап.

  • Скопируем MyIndicator.mqh в файл с другим именем (в моём примере ATRsample.mqh) и откроем последний в редакторе MetaEditor 5.
  • Заменим текст "MyInd" на название своего индикатора (в моём примере "ATR").

2. Определим внешние переменные, которые будут переноситься из исходного (оригинального) индикатора в класс, объявим их, проинициализируем.

В моём примере в индикаторе ATR один внешний параметр:
input int InpAtrPeriod=14;  // ATR period
  • добавим этот параметр в наш класс и в функцию инициализации класса:
class CCustATR
  {
protected:
   ...
   uchar iAtrPeriod;
   ...
public:
   ...
   bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
  • изменим заголовок тела функции Init и проинициализируем переменную-параметр входным значением:
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod)
{
      ...
      BarsLimit = Limit;
      iAtrPeriod = AtrPeriod;
      ...

3. Определим необходимое количество буферов в исходном индикаторе, объявим их в своём классе. Так же объявим функции возврата буферов INDICATOR_DATA.

  • Изменим структуру
struct ATRBar
  {
   double Val;                          // Буферы индикатора
  };

на свою структуру:

struct ATRBar
  {
   double ATR;
   double TR;
  };
  • Определим нулевые значения:
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData)
{
   ...
   for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].ATR = EMPTY_VALUE;
      Buf[PInd(i, false)].TR = EMPTY_VALUE;
     }
   ...
  • Изменим и добавим функции возврата значений буферов INDICATOR_DATA:

изменяем (если буфер один, то можно и не изменять)

class CCustATR
  {
   ...
   double GetVal(uint shift, bool AsSeries);                      // Возвращает значение буфера Val для бара
   ...

на

class CCustATR
  {
   ...
   double GetATR(uint shift, bool AsSeries);                      // Возвращает значение буфера ATR для бара
   ...

и меняем код соответствующих функций:

double CCustATR::GetATR(uint shift, bool AsSeries)
{
   if ( shift > (DataBarsCount-1) )
      return(EMPTY_VALUE);
   return(Buf[PInd(shift, AsSeries)].ATR);
}
Примечание: можно вместо нескольких функций возврата значений буферов использовать одну, у которой будет дополнительный параметр - номер или наименование буфера.


4. Перенесём логику функции OnCalculate() исходного индикатора в соответствующую функцию класса

  • Первоначальные проверки
CPCATRResultCode CCustATR::Calculate()
{
   ...
   // Проверим - достаточно ли количество баров для расчета
   if (DataBarsCount <= iAtrPeriod)
      return(CPCATRRC_NoData);
   ...
  • Вычисления: на первом тике и количество баров для расчета при последующих тиках:
   if ( DataBarsCalculated != 0 )
      BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1;
   else
     {
      Buf[PInd(0, false)].TR = 0.0;
      Buf[PInd(0, false)].ATR = 0.0;
      //--- filling out the array of True Range values for each period
      for (uint i = 1; i < DataBarsCount; i++)
         Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                                  MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      //--- first AtrPeriod values of the indicator are not calculated
      double firstValue = 0.0;
      for (uint i = 1; i <= iAtrPeriod; i++)
        {
         Buf[PInd(i, false)].ATR = 0;
         firstValue += Buf[PInd(i, false)].TR;
        }
      //--- calculating the first value of the indicator
      firstValue /= iAtrPeriod;
      Buf[PInd(iAtrPeriod, false)].ATR = firstValue;
      
      BarsForRecalculation = DataBarsCount - iAtrPeriod - 2;
     }
  • Непосредственно вычисление на каждом тике:
   for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                               MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod;
      ...

Всё. Наш класс создан. Для визуальной проверки можно создать тестовый индикатор (в моём примере это Indicator_ATRsample.mq5):



Во время корректировки статьи мне пришла в голову мысль, что при использовании класса CCustPrevCalculated в связке только с одним пользовательским индикатором, можно создание, инициализацию и синхронизацию данного класса интегрировать в пользовательский класс индикатора (в моих примерах это CCustZigZagPPC и CCustATR). При вызове функции инициализации пользовательских индикаторов для этого необходимо использовать нулевой указатель на объект:

   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);

При этом общая конструкция

#include <CustPrevCalculated.mqh>
#include <ATRsample.mqh>
CCustPrevCalculated CustPrevCalculated;
CCustATR ATR;

int OnInit()
  {
   CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30);
   ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData();
   CPCPrepareDataResultCode resATR = ATR.PrepareData(resData);
   if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) )
      ATR.Calculate();
  }

упростится до:

#include <ATRsample.mqh>
CCustATR ATR;

int OnInit()
  {
   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   ATR.Calculate();
  }
Практический пример показан в файле Indicator_ATRsample2.mq5.

Влияние описанной технологии на производительность в тестере стратегий

Для проверки был создан простой советник (TestSpeed_IndPrevCalculated.mq5), который на каждом тике получает значение индикатора нулевого бара, по одному из трёх вариантов:

enum eTestVariant
  {
   BuiltIn,    // Встроенный индикатор iATR
   Custom,     // Пользовательский индикатор iCustom("ATR")
   IndClass    // Расчет в классе
  };

Данный советник был прогнан по 10 раз на 1 запущенном агенте со следующими параметрами оптимизации:

  • Инструмент EURUSD
  • Интервал: вся история [1993..2001]
  • Режим торговли: каждый тик
  • Внешний параметр FalseParameter [0..9]

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


    Код эксперта, на котором измерялось время оптимизации:

    //+------------------------------------------------------------------+
    //|                                  TestSpeed_IndPrevCalculated.mq5 |
    //|                                         Copyright 2011, AlexSTAL |
    //|                                           http://www.alexstal.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2011, AlexSTAL"
    #property link      "http://www.alexstal.ru"
    #property version   "1.00"
    //--- подключим включаемый файл с классом CustATR
    #include <ATRsample.mqh>
    //--- перечислением лучше задавать выбор параметра
    enum eTestVariant
      {
       BuiltIn,    // Встроенный индикатор iATR
       Custom,     // Пользовательский индикатор iCustom("ATR")
       IndClass    // Расчет в классе
      };
    //--- входные переменные
    input eTestVariant TestVariant;
    input int          FalseParameter = 0;
    //--- период индикатора ATR
    const uchar        InpAtrPeriod = 14;
    //--- хэндл встроенного или пользовательского индикатора
    int                Handle;
    //--- индикатор на базе класса 
    CCustATR           *ATR;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       //---
       switch(TestVariant)
         {
          case BuiltIn:
             Handle = iATR(_Symbol, _Period, InpAtrPeriod);
             break;
          case Custom:
             Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod);
             break;
          case IndClass:
             ATR = new CCustATR;
             ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod);
             break;
         };
       //---
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
       switch(TestVariant)
         {
          case IndClass:
             delete ATR;
             break;
         };
      }
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       double tmpValue[1];
       switch(TestVariant)
         {
          case BuiltIn:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case Custom:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case IndClass:
             ATR.Calculate();
             tmpValue[0] = ATR.GetATR(0, true);
             break;
         };
      }
    //+------------------------------------------------------------------+

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


    Замечания по практическому применению данной технологии

    • при тестировании советника в тестере стратегий не может произойти обнуление значения prev_calculated в пользовательском индикаторе, поэтому синхронизация истории в этом режиме принудительно отключена;
    • расчет индикатора производится только на последних n-барах, жёстко заданных при первичной инициализации классов;
    • расчет подразумевает жёсткую привязку к конкретному инструменту и периоду инициализированного класса. Для расчета на других инструментах или периодах необходимо создавать новые экземпляры классов.


    Заключение

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

    P.S. Кто не работает, тот не ошибается! Если Вы найдёте ошибку – сообщите, пожалуйста, мне об этом.

    Универсальная регрессионная модель для прогнозирования рыночной цены Универсальная регрессионная модель для прогнозирования рыночной цены
    Рыночная цена складывается в результате устойчивого равновесия между спросом и предложением, а те, в свою очередь, зависят от множества экономических, политических и психологических факторов. Непосредственный учет всех составляющих осложнен как различием природы, так и причиной воздействия этих факторов. На основании разработанной регрессионной модели в статье сделана попытка прогнозирования рыночной цены.
    Курс Монетки и основанный на нем Индикатор Трендовости Курс Монетки и основанный на нем Индикатор Трендовости
    Модели случайных блужданий даётся название "Курс Монетки". Приводятся свойства курса монетки с точки зрения трейдера. Предлагается создать симулятор курса на основе курса монетки с трендом. Для отличия реального курса от курса монетки создан индикатор трендовости. Рассматривается трендовость реального курса.
    Торговый эксперт по книге Б. Вильямса "Новые измерения в биржевой торговле" Торговый эксперт по книге Б. Вильямса "Новые измерения в биржевой торговле"
    В данной статье я расскажу о создании торгового эксперта по книге Б. Вильямса "Новые измерения в биржевой торговле" для платформы MetaTrader 5 на языке MQL5. Сама стратегия хорошо известна и до сих пор вызывает споры среди трейдеров о ее работоспособности. В статье рассматриваются торговые сигналы системы Б. Вильямса, особенности их реализации и результаты тестирования на исторических данных.
    Торговые события в MetaTrader 5 Торговые события в MetaTrader 5
    Мониторинг текущего состояния торгового счета подразумевает контроль над открытыми позициями и ордерами. Прежде чем торговый сигнал станет сделкой, он должен быть отправлен из клиентского терминала в виде запроса торговому серверу, где он будет помещен в очередь запросов и ждать своей обработки. Принятие запроса сервером, удаление его по времени истечения или проведение на его основе сделки - все это сопровождается торговыми событиями, о которых сервер сообщает терминалу.