Перенос кода индикатора в код эксперта. Строение индикатора.

Nikolay Kositsin | 16 февраля, 2007

Введение

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

  1. MetaQuotes Software Corp. Особенности написания Пользовательских Индикаторов. https://www.mql5.com/ru/articles/1497
  2. Николай Косицин. Многократный пересчет нулевого бара в некоторых индикаторах. https://www.mql5.com/ru/articles/1411

Прежде, чем начать разговор на тему, которую я обозначил в названии статьи, следовало бы задать вполне резонный вопрос: "А зачем вообще нужно переносить код индикатора в код эксперта, если в большинстве случаев эксперт, работающий с пользовательскими индикаторами, выглядит несоизмеримо проще своего аналога, который содержит все необходимые для своей работы пользовательские индикаторы внутри своего кода? Особенно, если учесть тот факт, что при ГРАМОТНОМ написании кода результат в обоих случаях будет абсолютно одинаковым!"

На мой взгляд, это необходимо всего в двух случаях:

  1. Если в расчетах эксперта абсолютно не используются значения, рассчитанные на нулевом баре, то возникает вполне естественное желание избавиться в эксперте от ненужных пересчетов на нулевом баре, а так же и на первом барах. Это позволило бы в три раза сократить время оптимизации такого эксперта, что при очень сложном и ресурсоемком коде достаточно актуально!
  2. Коммерческое использование эксперта с максимальной защитой кода от его декомпилирования.


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


Примеры оптимизации индикаторов

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

int start()
  {
    int limit;
    int counted_bars = IndicatorCounted();
//---- последний посчитанный бар будет пересчитан
    if(counted_bars > 0) 
        counted_bars--;
    limit = Bars - counted_bars - 1;
//---- основной цикл
    for(int i = limit; i >= 0; i--)
      {
        //---- 
        ExtBlueBuffer[i] = iMA(NULL, 0, JawsPeriod, 0, MODE_SMMA, 
                               PRICE_MEDIAN, i);
        ExtRedBuffer[i] = iMA(NULL, 0, TeethPeriod, 0, MODE_SMMA, 
                              PRICE_MEDIAN, i);
        ExtLimeBuffer[i] = iMA(NULL, 0, LipsPeriod, 0, MODE_SMMA, 
                               PRICE_MEDIAN, i);
      }
//----
     return(0);
  }

В данном случае нас будет интересовать строка:

if(counted_bars > 0) 
    counted_bars--;

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

В пользовательских индикаторах переменные limit и counted_bars могут иметь другие названия, но сам программный код должен иметь эти проверки! Я так полагаю, что этого объяснения будет вполне достаточно, чтобы пролить свет истины на суть заявлений некоторых, не желающих вникать в тонкости программирования экспертописателей, что в Метатрейдере данные буфера индикатора и те же самые данные, полученные из пользовательского индикатора в эксперте не совпадают! Если код индикатора и код эксперта написаны правильно, то сколь сложным бы не был код индикатора, данные в эксперте оказываются все равно теми же самыми!

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


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

// вместо
for(int i = limit; i >= 0; i--)      
// включаем
for(int i = limit; i >= 1; i--)

В итоге исходный код будет выглядеть следующим образом:

int start()
  {
    int limit;
    int counted_bars = IndicatorCounted();
//---- последний посчитанный бар будет пересчитан
    if(counted_bars > 0) 
        counted_bars--;
    limit = Bars - counted_bars - 1;
      
  //---- основной цикл //теперь цикл заканчивается на 1
    for(int i = limit; i >= 1; i--)
      {
        //---- 
        ExtBlueBuffer[i] = iMA(NULL, 0, JawsPeriod, 0, MODE_SMMA, 
                               PRICE_MEDIAN, i);
        ExtRedBuffer[i] = iMA(NULL, 0, TeethPeriod, 0, MODE_SMMA, 
                              PRICE_MEDIAN, i);
        ExtLimeBuffer[i] = iMA(NULL, 0, LipsPeriod, 0, MODE_SMMA, 
                               PRICE_MEDIAN, i);
      }
  //----
    return(0);
  }
Я еще раз отмечу тот факт, что все выше сказанное было сделано для экспертов, которые работают только на закрытых барах, то есть на всех барах, кроме нулевого!

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

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

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

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

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


Общая схема строения индикатора


Прежде чем приступить к основной теме статьи, мне следовало бы вкратце коснуться общего строения индикатора под углом зрения программиста, которого этот индикатор интересует, как будущая часть кода эксперта:
//+------------------------------------------------------------------+
//|                                                IndicatorPlan.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
//---- отрисовка индикатора в главном окне
#property indicator_chart_window 
//---- количество индикаторных буферов
#property indicator_buffers 1 
//---- цвет индикатора
#property indicator_color1 Gold
//---- ВХОДНЫЕ ПАРАМЕТРЫ ИНДИКАТОРА
extern int period0 = 15;
extern int period1 = 15;
extern int period2 = 15;
//---- индикаторные буферы
double Ind_Buffer0[];
double Ind_Buffer1[];
double Ind_Buffer2[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
//---- определение стиля исполнения графика
   SetIndexStyle(0, DRAW_LINE); 
//---- 3 индикаторных буфера использованы для счёта
   IndicatorBuffers(3);
   SetIndexBuffer(0, Ind_Buffer0); 
   SetIndexBuffer(1, Ind_Buffer1);
   SetIndexBuffer(2, Ind_Buffer2);
//---- завершение инициализации
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
//---- Проверка количества баров на достаточность для дальнейшего расчёта
   if(Bars < period0 + period1 + period2)
       return(0);
//----+ Введение переменных с плавающей точкой
   double Resalt0, Resalt1, Resalt2;
//----+ Введение целых переменных и получение уже подсчитанных баров
   int limit, MaxBar,bar, counted_bars = IndicatorCounted();
//---- проверка на возможные ошибки
   if(counted_bars < 0)
       return(-1);
//---- последний посчитанный бар должен быть пересчитан 
   if(counted_bars > 0) 
       counted_bars--;
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт новых баров
   limit = Bars - counted_bars - 1; 
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт всех баров
   MaxBar = Bars - 1 - (period0 + period1 + period2); 
//---- инициализация нуля 
   if(limit > MaxBar)
     {
       limit = MaxBar;
       for(bar = Bars - 1; bar >= MaxBar; bar--)
         {
           Ind_Buffer0[bar] = 0.0;
           Ind_Buffer1[bar] = 0.0;
           Ind_Buffer2[bar] = 0.0;
         }
     }
//----+ ПЕРВЫЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt1 на основе внешней 
       // переменной period1
       Ind_Buffer1[bar] = Resalt1;
     }
//----+ ВТОРОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt2 
       // на основе значений буфера Ind_Buffer1[] и 
       // внешней переменной period2
       Ind_Buffer2[bar] = Resalt2;
     }
//----+ ОСНОВНОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt0 
       // на основе значений буфера Ind_Buffer2[] и 
       // внешней переменной period0
       Ind_Buffer0[bar] = Resalt0;
     }
   return(0);
  }
//+------------------------------------------------------------------+
Вполне естественно, что у реального индикатора может быть другое количество отображаемых индикаторных значений, другое количество индикаторных буферов, участвующих в расчётах и другое количество циклов расчёта значений индикаторных буферов, но это никак не меняет сути понимания предложенной структурной схемы. В других случаях всё будет абсолютно аналогично.

Теперь удалим с этой схемы все лишние элементы, которые нас в данной ситуации абсолютно не интересуют и в эксперте абсолютно ни к чему:

//+------------------------------------------------------------------+
//|                                               IndicatorPlan1.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
//---- ВХОДНЫЕ ПАРАМЕТРЫ ИНДИКАТОРА
extern int period0 = 15;
extern int period1 = 15;
extern int period2 = 15;
//---- индикаторные буферы
double Ind_Buffer0[];
double Ind_Buffer1[];
double Ind_Buffer2[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
//---- завершение инициализации
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
//---- Проверка количества баров на достаточность для дальнейшего расчёта
   if(Bars < period0 + period1 + period2)
       return(0);
//----+ Введение переменных с плавающей точкой
   double Resalt0, Resalt1, Resalt2;
//----+ Введение целых переменных и получение уже посчитанных баров
   int limit, MaxBar, bar, counted_bars = IndicatorCounted();
//---- проверка на возможные ошибки
   if(counted_bars < 0)
       return(-1);
//---- последний посчитанный бар должен быть пересчитан 
   if(counted_bars > 0) 
       counted_bars--;
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт новых баров
   limit = Bars - counted_bars - 1; 
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт всех баров
   MaxBar = Bars - 1 - (period0 + period1 + period2); 
//---- инициализация нуля 
   if(limit > MaxBar)
     {
       limit = MaxBar;
       for(bar = Bars - 1; bar >= MaxBar; bar--)
         {
           Ind_Buffer0[bar] = 0.0;
           Ind_Buffer1[bar] = 0.0;
           Ind_Buffer2[bar] = 0.0;
         }
     }
//----+ ПЕРВЫЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt1 на 
       // основе внешней переменной period1
       Ind_Buffer1[bar]= Resalt1;
     }
//----+ ВТОРОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt2 
       // на основе значений буфера Ind_Buffer1[] и 
       // внешней переменной period2
       Ind_Buffer2[bar] = Resalt2;
     }
//----+ ОСНОВНОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt0 
       // на основе значений буфера Ind_Buffer2[] и 
       // внешней переменной period0
       Ind_Buffer0[bar] = Resalt0;
     }
   return(0);
  }
//+------------------------------------------------------------------+
По большому счёту, вполне можно было бы без особых проблем поместить данный код в код эксперта, если бы не пара маленьких досадных недоразумений!
  1. Прежде всего следует учесть тот факт, что функция IndicatorCounted() в экспертах не работает!
  2. Сделать пользовательские массивы индикаторными в блоке инициализации мы тоже не сможем!

Так что для полного сохранения кода индикатора прежде всего нам бы следовало сделать аналог функции IndicatorCounted() и каким-то образом эмулировать аналоги индикаторных буферов в эксперте. Эмуляцию индикаторных буферов в эксперте, к сожалению, с помощью стандартных функций напрямую сделать не получится. Аналогов для SetIndexBuffer() и IndicatorBuffers() для экспертов на данный момент нет! Так что эту проблему нам придётся обходить с другой стороны, благо возможностей в MQL4 для этого предостаточно.


Эмуляция индикаторных буферов в эксперте

Сперва рассмотрим сами процессы, происходящие в индикаторных буферах немного подробнее.

  1. Значения, которые присваиваются переменным индикаторного буфера, не теряются между циклами, пока индикатор присоединён к графику и работает терминал.
  2. Если происходит смена нулевого(самого нового) бара на графике, то все элементы индикаторного буфера сдвигаются.
  3. Если пришёл один новый бар, то значение переменной limit изменилось с единицы на двойку в индикаторе!, а все элементы буфера сдвинулись на единицу, то есть нулевой элемент стал первым, первый - вторым, второй - третьим и так далее.
  4. Вполне естественно, что размерность индикаторного буфера при этом меняется. Если на очередном тике нулевой бар остался тем же, то все элементы буфера остаются на своих местах.

Теперь займемся эмуляцией индикаторных буферов. Для этого мы воспользуемся следующими стандартными функциями языка MQL4: ArraySize(), ArrayResize() и ArraySetAsSeries(). Сам код эмуляции индикаторных буферов достаточно прост и суть его работы можно сформулировать в двух словах следующим образом: при смене нулевого бара восстанавливается прямой порядок определения элементов в буферах, делается с помощью функции ArrayResize() добавление новых ячеек в буферах со стороны новых баров, а после этого меняется на обратный порядок определения элементов в буферах, и пустые ячейки оказываются в эмулированном индикаторном буфере в числе самых первых.

//---- ЭМУЛЯЦИЯ ИНДИКАТОРНЫХ БУФЕРОВ
  int NewSize = iBars(symbol, timeframe);
  //----  Проверка на смену нулевого бара
  if(ArraySize(Ind_Buffer0) < NewSize)
    {
      //---- Установить прямое направление индексирования в массиве 
      ArraySetAsSeries(Ind_Buffer0, false);
      ArraySetAsSeries(Ind_Buffer1, false);
      ArraySetAsSeries(Ind_Buffer2, false);
      //---- Изменить размер эмулируемых индикаторных буферов 
      ArrayResize(Ind_Buffer0, NewSize); 
      ArrayResize(Ind_Buffer1, NewSize); 
      ArrayResize(Ind_Buffer2, NewSize); 
      //---- Установить обратное направление индексирования в массиве 
      ArraySetAsSeries(Ind_Buffer0, true);
      ArraySetAsSeries(Ind_Buffer1, true);
      ArraySetAsSeries(Ind_Buffer2, true); 
    } 
//----

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



Замена функции IndicatorCounted().

Теперь следует разобраться с эмуляцией функции IndicatorCounted(). Эта функция возвращает количество баров текущего графика, не измененных после последнего вызова индикатора. Если немного перефразировать это определение, то можно сказать следующим образом. Эта функция возвращает количество баров текущего графика, которые имелись в наличии в клиентском терминале на предыдущем тике! Для полного совпадения значений от полученного числа баров следует отбросить единицу. Так что эту функцию мы можем запросто заменить статической целой переменной, которую после получения из неё значения инициализировать значением предопределенной переменной Bars-1. После этого индикаторная схема будет иметь следующий вид:

//+------------------------------------------------------------------+
//|                                            NewIndicatorPlan1.mqh |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
 
//---- ВХОДНЫЕ ПАРАМЕТРЫ ИНДИКАТОРА
extern int period0 = 15;
extern int period1 = 15;
extern int period2 = 15;
//---- индикаторные буферы
double Ind_Buffer0[];
double Ind_Buffer1[];
double Ind_Buffer2[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
//---- завершение инициализации
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
//---- Проверка количества баров на достаточность для дальнейшего расчёта
   if(Bars < period0 + period1 + period2)
       return(0);
//---- ЭМУЛЯЦИЯ ИНДИКАТОРНЫХ БУФЕРОВ
   if(ArraySize(Ind_Buffer0) < Bars)
     {
       ArraySetAsSeries(Ind_Buffer0, false);
       ArraySetAsSeries(Ind_Buffer1, false);
       ArraySetAsSeries(Ind_Buffer2, false);
       //----  
       ArrayResize(Ind_Buffer0, Bars); 
       ArrayResize(Ind_Buffer1, Bars); 
       ArrayResize(Ind_Buffer2, Bars); 
       //----
       ArraySetAsSeries(Ind_Buffer0, true);
       ArraySetAsSeries(Ind_Buffer1, true);
       ArraySetAsSeries(Ind_Buffer2, true); 
     } 
//----+ ВВЕДЕНИЕ СТАТИЧЕСКОЙ ЦЕЛОЙ ПЕРЕМЕННОЙ ПАМЯТИ
   static int IndCounted;
//----+ Введение переменных с плавающей точкой
   double Resalt0, Resalt1, Resalt2;
//----+ Введение целых переменных и получение уже посчитанных баров
   int limit, MaxBar, bar, counted_bars = IndCounted;
//---- проверка на возможные ошибки
   if(counted_bars < 0)
       return(-1);
//---- последний посчитанный бар должен быть пересчитан 
   if(counted_bars > 0) 
       counted_bars--;
//----+ ЗАПОМИНАНИЕ КОЛИЧЕСТВА ВСЕХ БАРОВ ГРАФИКА
   IndCounted = Bars - 1;
//---- определение номера самого старого бара, 
//     начиная с которого будет произедён пересчёт новых баров
   limit = Bars - counted_bars - 1; 
//---- определение номера самого старого бара, 
//     начиная с которого будет произедён пересчёт всех баров
   MaxBar = Bars - 1 - (period0 + period1 + period2); 
//---- инициализация нуля 
   if(limit > MaxBar)
     {
       limit = MaxBar;
       for(bar = Bars - 1; bar >= 0; bar--)
         {
           Ind_Buffer0[bar] = 0.0;
           Ind_Buffer1[bar] = 0.0;
           Ind_Buffer2[bar] = 0.0;
         }
     }
//----+ ПЕРВЫЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt1 на основе 
       // внешней переменной period1
       Ind_Buffer1[bar] = Resalt1;
     }
//----+ ВТОРОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt2 
       // на основе значений буфера Ind_Buffer1[] и внешней переменной period2
       Ind_Buffer2[bar] = Resalt2;
     }
//----+ ОСНОВНОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt0 
       // на основе значений буфера Ind_Buffer2[] и внешней переменной period0
       Ind_Buffer0[bar] = Resalt0;
     }
   return(0);
  }
//+------------------------------------------------------------------+

Дальнейшие преобразования кода индикатора и итоговая схема его строения

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

С обработкой данных с других таймфреймов тоже все достаточно просто решается. Заменяем в индикаторе предопределенные переменные типа Bars на таймсерии типа
iBars(string symbol,  int timeframe);

NULL - на string symbol;, 0(в таймсериях) - на int timeframe;, Close[bar] - на

iClose(string symbol, int timeframe, bar);

и так далее.

Теперь следует разобраться со строками индикатора:

//---- проверка на возможные ошибки
if(counted_bars < 0)
    return(-1);
//---- последний подсчитанный бар должен быть пересчитан 
if(counted_bars > 0) 
    counted_bars--;

В плане предложенной замены функции IndicatorCounted() в нашем варианте построения индикатора переменная counted_bars никогда не будет меньше нуля, так что строки:

//---- проверка на возможные ошибки
if(counted_bars < 0)
    return(-1);

в коде эксперта можно удалить. Со следующими двумя строками:

//---- последний подсчитанный бар должен быть пересчитан 
if(counted_bars > 0) 
    counted_bars--;

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

//+------------------------------------------------------------------+
//|                                            NewIndicatorPlan2.mqh |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
 
//---- ВХОДНЫЕ ПАРАМЕТРЫ ИНДИКАТОРА
extern int period0 = 15;
extern int period1 = 15;
extern int period2 = 15;
//---- индикаторные буферы
double Ind_Buffer0[];
double Ind_Buffer1[];
double Ind_Buffer2[];
//---- ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ ДЛЯ ВЫБОРА ГРАФИКА
string symbol; int timeframe;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
//---- ВЫБОР ГРАФИКА ДЛЯ РАСЧЁТА ИНДИКАТОРА
   symbol = Symbol();//ИНИЦИАЛИЗАЦИЯ ПЕРЕМЕННОЙ symbol;
   timeframe =240;//ИНИЦИАЛИЗАЦИЯ ПЕРЕМЕННОЙ timeframe;
//---- завершение инициализации
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
// ПОЛУЧЕНИЕ КОЛИЧЕСТВА ВСЕХ БАРОВ ГРАФИКА
   int IBARS = iBars(symbol, timeframe);
//---- Проверка количества баров на достаточность для дальнейшего расчёта
   if(IBARS < period0 + period1 + period2)
       return(0);
// ЭМУЛЯЦИЯ ИНДИКАТОРНЫХ БУФЕРОВ
   if(ArraySize(Ind_Buffer0) < IBARS)
     {
       ArraySetAsSeries(Ind_Buffer0, false);
       ArraySetAsSeries(Ind_Buffer1, false);
       ArraySetAsSeries(Ind_Buffer2, false);
       //----  
       ArrayResize(Ind_Buffer0, IBARS); 
       ArrayResize(Ind_Buffer1, IBARS); 
       ArrayResize(Ind_Buffer2, IBARS); 
       //----
       ArraySetAsSeries(Ind_Buffer0, true);
       ArraySetAsSeries(Ind_Buffer1, true);
       ArraySetAsSeries(Ind_Buffer2, true); 
     } 
// ВВЕДЕНИЕ СТАТИЧЕСКОЙ ЦЕЛОЙ ПЕРЕМЕННОЙ ПАМЯТИ
   static int IndCounted;
//----+ Введение переменных с плавающей точкой
   double Resalt0, Resalt1, Resalt2;
//----+ Введение целых переменных и ПОЛУЧЕНИЕ УЖЕ ПОСЧИТАННЫХ БАРОВ
   int limit, MaxBar, bar, counted_bars = IndCounted;
//----+ ЗАПОМИНАНИЕ КОЛИЧЕСТВА ВСЕХ БАРОВ ГРАФИКА
   IndCounted = IBARS - 1;
//---- определение номера самого старого бара,   
//     начиная с которого будет произедён пересчёт новых баров
   limit = IBARS - counted_bars - 1; 
//---- определение номера самого старого бара, 
//     начиная с которого будет произедён пересчёт всех баров
   MaxBar = IBARS - 1 - (period0 + period1 + period2); 
//---- инициализация нуля 
   if(limit > MaxBar)
     {
       limit = MaxBar;
       for(bar = IBARS - 1; bar >= 0; bar--)
         {
           Ind_Buffer0[bar] = 0.0;
           Ind_Buffer1[bar] = 0.0;
           Ind_Buffer2[bar] = 0.0;
         }
     }
//----+ ПЕРВЫЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt1 на основе внешней 
       // переменной period1
       Ind_Buffer1[bar] = Resalt1;
     }
//----+ ВТОРОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt2 
       // на основе значений буфера Ind_Buffer1[] и внешней переменной period2
       Ind_Buffer2[bar] = Resalt2;
     }
//----+ ОСНОВНОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= 0; bar--)
     {
       // Здесь код вычисления переменной Resalt0 
       // на основе значений буфера Ind_Buffer2[] и внешней переменной period0
       Ind_Buffer0[bar] = Resalt0;
     }
   return(0);
  }
//+------------------------------------------------------------------+
Но тут следует учесть один момент. Существуют индикаторы, которые при многократном пересчёте нулевого бара, на первом баре в начале циклов расчёта запоминают значения некоторых переменных для возврата кода в исходное состояние (Статья). В эксперте это запоминание после удаления последних двух строк должно происходить не на первом, а на нулевом баре. Обычно в таких индикаторах присутствуют в начале циклов расчёта индикатора вот такие фрагменты кода:
// Сохранение значений переменных
if(bar == 1)
    if(((limit == 1) && (time == Time[2])) || (limit > 1))
      {
        time = Time[2];
        // Эти переменные нас не интересуют
        PRICE = price;
        TREND = trend;
        RESALT = Resalt;
      }
//+------------------------------------------------------------------+
Этот фрагмент следует немного переписать для сохранения значений переменных не на первом, а на нулевом баре. Все единицы в коде следует поменять на нули, а двойки на единицы. Ну и если предполагается использование кода индикатора не на текущем графике, а на каком-нибудь другом, то следует заменить обращение к массиву таймсерии
time = Time[1];

на

time = iTime(symbol, timeframe, 1);

В результате имеем следующий переписанный фрагмент:

// Сохранение значений переменных
if(bar == 0)
    if(((limit == 0) && (time == iTime(symbol, timeframe, 1))) || (limit > 0))
      {
        time = iTime(symbol, timeframe, 1);
        PRICE = price;
        TREND = trend;
        RESALT = Resalt;
      }
//+------------------------------------------------------------------+

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



Заключение.

Бесспорно, что на данный момент предложенная схема переноса кода индикатора в код эксперта в таком виде достаточно громоздка и совсем неоптимальная, но в данной статье мы не ставили задачу подробного изложения всех нюансов этого процесса. Главное назначение этой статьи - это дать общее представление об индикаторе и рассмотреть общую идею переноса кода в максимально простом виде, не загромождая её сразу второстепенными деталями. Я считаю, что данную задачу мы выполнили! В следующей статье мы займёмся изучением общей структуры эксперта и структурной схемы индикаторных функций.