Перенос кода индикатора в код эксперта. Общие схемы строения эксперта и индикаторных функций

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

Введение

В предыдущей статье (Перенос кода индикатора в код эксперта. Строение индикатора) мы достаточно подробно познакомили читателя с общим строением индикатора, код которого предназначен для переноса в код эксперта и изложили суть предварительного преобразования кода индикатора. Теперь мы займёмся преобразованием полученного кода в пользовательскую функцию, потому как это, пожалуй, самый удобный способ представления индикаторного кода в эксперте. Пользовательская функция может быть завёрнута в mqh-файл и её объявление в эксперте с помощью директивы #include займёт совсем немного места, а обращение к такой пользовательской функции выглядит немногим сложнее, чем обращение к пользовательскому индикатору. И самое главное, подобные пользовательские функции можно делать достаточно универсальными для их применения в дальнейшем в любых экспертах.

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


Строение эксперта с обращением к пользовательскому индикатору

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

//+------------------------------------------------------------------+
//|                                               ExpertIndPlan0.mqh |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
//---- Входные параметры эксперта
extern int period10 = 15;
extern int period11 = 15;
extern int period12 = 15;
//---- Входные параметры эксперта
extern int period20 = 15;
extern int period21 = 15;
extern int period22 = 15;
//---- Объявление буферов для индикаторных значений
double Ind_Buffer1[6];
double Ind_Buffer2[6];
//+------------------------------------------------------------------+
//| Custom Expert initialization function                            |
//+------------------------------------------------------------------+
int init()
  {
// Здесь помещён код инициализации эксперта
//---- завершение инициализации
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom Expert iteration function                                 |
//+------------------------------------------------------------------+
int start()
  {
//---- проверка количества баров на достаточность для дальнейшего расчёта
   if(iBars(Symbol(), 0) < period10 + period11 + period12 + 10)
       return(0);
   if(iBars(Symbol(), 0) < period20 + period21 + period22 + 10)
       return(0);
//---- получение индикаторных значений для дальнейшего расчёта
   for(int bar = 5; bar >= 1; bar--)
     {
       Ind_Buffer1[bar] = iCustom("IndicatorPlan", Symbol(), 0, period10, 
                                  period11, period12, 0, bar);
       Ind_Buffer2[bar] = iCustom("IndicatorPlan", Symbol(), 0, period20, 
                                  period21, period22, 0, bar);
     }
// Здесь помещён код эксперта, формирующий сигналы для сделок 
// на основе значений ячеек индикаторных буферов 
//----
// Здесь помещён код исполнительной части эксперта, 
// отдающей заявки на выставление ордеров
//----
   return(0);
  }
//+------------------------------------------------------------------+

В данной схеме мы на каждом тике достаем из нулевого буфера пользовательского индикатора IndicatorPlan.mq4 за два обращения посчитанные значения и расталкиваем их в обычные массивы Ind_Buffer1[] и Ind_Buffer2[]. Схема обращений к индикатору сделана исходя из того, что нам для дальнейших расчётов достаточно всего последних пяти значений индикатора за исключением нулевого.


Строение эксперта с обращением к пользовательской функции

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

Можно сделать проще. Эта функция должна в качестве входных параметров получать параметры пользовательского индикатора и буфер, а возвращать тот же буфер с эмуляцией индикаторного режима, в котором ячейки заполнены посчитанными значениями индикатора. Это легко осуществить, если в разрабатываемой нами функции обьявить внешнюю переменную функции, соответствующую индикаторному буферу, связанной по ссылке. На языке MQL4 это будет выглядеть следующим образом: double& InputBuffer , а саму индикаторную функцию обьявить как логическую, возвращающую true, если расчёт произошел удачно, или false, если расчет произошел неудачно по причине отсутствия надлежащего количества баров на графике. После подобных разъяснений будем считать, что индикаторная функция, построенная от индикаторной схемы, которую мы изучили в предыдущей статье будет выглядеть следующим образом:

bool Get_IndSeries(int Number,string symbol, int timeframe, 
                   bool NullBarRecount, int period0, int period1, 
                   int period2, double& InputBuffer0[],
                   double& InputBuffer1[], double& InputBuffer2[])

У индикаторной функции добавляется ещё одна внешняя переменная Number, которая принимает значение номера обращения к этой индикаторной функции.

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

Теперь можно представить схему строения эксперта, которую мы сделали для обращения к индикаторам, в виде, где присутствуют обращения к функциям:

//|                                               ExpertIndPlan1.mqh |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
//---- Входные параметры эксперта
extern int period10 = 15;
extern int period11 = 15;
extern int period12 = 15;
//---- Входные параметры эксперта
extern int period20 = 15;
extern int period21 = 15;
extern int period22 = 15;
//---- Объявление индикаторных буферов
double Ind_Buffer10[], Ind_Buffer11[], Ind_Buffer12[];
double Ind_Buffer20[], Ind_Buffer21[], Ind_Buffer22[];
//+------------------------------------------------------------------+
//| Get_IndSeries() function                                         |
//+------------------------------------------------------------------+
//---- Объявление функции Get_IndSeries()
bool Get_IndSeries(int Number,string symbol, int timeframe, 
                   bool NullBarRecount, int period0, int period1, 
                   int period2, double& InputBuffer0[], 
                   double& InputBuffer1[], double& InputBuffer2[]) 
  {
    //---- 
    // Здесь помещён код функции GetIdicator()
    //----
    return(true);
  }
//+------------------------------------------------------------------+
//| Custom Expert initialization function                            |
//+------------------------------------------------------------------+
int init()
  {
//----
// Здесь помещён код инициализации эксперта
//---- завершение инициализации
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom Expert iteration function                                 |
//+------------------------------------------------------------------+
int start()
  {
//---- проверка количества баров на достаточность для дальнейшего расчёта
   if(iBars(Symbol(), 0) < period10 + period11 + period12 + 10)
      return(0);
   if(iBars(Symbol(), 0) < period20 + period21 + period22 + 10)
      return(0);
//---- получение индикаторных значений для дальнейшего расчёта
   if(!Get_IndSeries(0,Symbol(), 0, false, period10, period11, period12,
      Ind_Buffer10, Ind_Buffer11, Ind_Buffer12))
       return(0);
   if(!Get_IndSeries(1, Symbol(), 0, false, period20, period21, period22, 
      Ind_Buffer20, Ind_Buffer21,Ind_Buffer22))
       return(0);  
//----
// Здесь помещён код эксперта, формирующий сигналы для сделок 
// на основе значений ячеек индикаторных буферов 
//----
// Здесь помещён код исполнительной части эксперта, 
// отдающей заявки на выставление ордеров
//----
 
   return(0);
  }
//+------------------------------------------------------------------+


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

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

1. Оставляем от всего кода только содержимое функции int start();

2. Добавляем сверху объявление функции Get_IndSeries():

bool Get_IndSeries(string symbol, int timeframe, bool NullBarRecount,
                   int period0, int period1, int period2, 
                   double& InputBuffer0, double& InputBuffer1, 
                   double& InputBuffer2)

3. Заменяем названия индикаторных буферов внутри кода (Ind_Buffer) соответственно на буферные названия (InputBuffer) внешних переменных функции Get_IndSeries();

4. Добавляем объявление переменной LastCountBar;

5. Делаем проверку на истинность переменной NullBarRecount:

if(!NullBarRecount)
    LastCountBar = 1;

6. Заменяем во всех циклах расчёта индикатора ноль на LastCountBar;

7. Делаем замену в самом начале кода, в проверке числа баров на достаточность: return(0) на return(false);

8. Делаем замену в самом конце кода return(0) на return(true);

Индикаторная функция готова:

//+------------------------------------------------------------------+
//|                                                Get_IndSeries.mqh |
//|                    
//|                                        https://www.metaquotes.net/ |
//+------------------------------------------------------------------+ 
bool Get_IndSeries(int Number, string symbol,int timeframe, 
                   bool NullBarRecount, int period0, int period1, 
                   int period2, double& InputBuffer0[], 
                   double& InputBuffer1[], double& InputBuffer2[])  
  {
//---- получение количества всех баров графика
   int IBARS = iBars(symbol, timeframe);
//---- Проверка количества баров на достаточность для дальнейшего расчёта
   if(IBARS < period0 + period1 + period2)
      return(false);
//---- ЭМУЛЯЦИЯ ИНДИКАТОРНЫХ БУФЕРОВ
   if(ArraySize(InputBuffer0) < IBARS)
     {
       ArraySetAsSeries(InputBuffer0, false);
       ArraySetAsSeries(InputBuffer1, false);
       ArraySetAsSeries(InputBuffer2, false);
       //----  
       ArrayResize(InputBuffer0, IBARS); 
       ArrayResize(InputBuffer1, IBARS); 
       ArrayResize(InputBuffer2, IBARS); 
       //----
       ArraySetAsSeries(InputBuffer0, true);
       ArraySetAsSeries(InputBuffer1, true);
       ArraySetAsSeries(InputBuffer2, true); 
     } 
//----+  введение статических переменных памяти
   static int IndCounted[];
//----+ изменение размеров статических переменных
   if(ArraySize(IndCounted) < Number + 1)
       ArrayResize(IndCounted, Number + 1); 
 //----+ Введение целой переменной
   int LastCountBar;
//----+ Проверка разрешения пересчёта нулевого бара
   if(!NullBarRecount)
       LastCountBar = 1;
   else 
       LastCountBar = 0;
//----+ Введение переменных с плавающей точкой
   double Resalt0, Resalt1, Resalt2;
//----+ Введение целых переменных и  получение уже подсчитанных баров
   int limit, MaxBar, bar, counted_bars = IndCounted[Number];
//----+ Запоминание количества всех баров графика( нулевой бар не считаем!)
   IndCounted[Number] = 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--)
         {
           InputBuffer0[bar] = 0.0;
           InputBuffer1[bar] = 0.0;
           InputBuffer2[bar] = 0.0;
         }
     }
//----+ ПЕРВЫЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= LastCountBar; bar--)
     {
       // Здесь код вычисления переменной Resalt1 на основе внешней 
       // переменной period1
       InputBuffer1[bar] = Resalt1;
     }
//----+ ВТОРОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= LastCountBar; bar--)
     {
       // Здесь код вычисления переменной Resalt2 
       // на основе значений буфера InputBuffer1[] и внешней 
       // переменной period2
       InputBuffer2[bar] = Resalt2;
     }
//----+ ОСНОВНОЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= LastCountBar; bar--)
     {
       // Здесь код вычисления переменной Resalt0 
       // на основе значений буфера InputBuffer2[] и внешней 
       // переменной period0
       InputBuffer0[bar] = Resalt0;
     }
   return(true);
  }
//+------------------------------------------------------------------+

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


Пример построения пользовательской индикаторной функции

Теперь приступим к построению индикаторной функции. Возьмем максимально простой индикатор:

//+------------------------------------------------------------------+
//|                                                         RAVI.mq4 |
//|                      Copyright © 2005, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2005, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
//---- отрисовка индикатора в отдельном окне
#property indicator_separate_window 
//---- количество индикаторных буфферов
#property indicator_buffers 1 
//---- цвет индикатора
#property indicator_color1 Red 
//---- ВХОДНЫЕ ПАРАМЕТРЫ ИНДИКАТОРА 
extern int Period1 = 7; 
extern int Period2 = 65; 
extern int MA_Metod = 0;
extern int PRICE = 0;
//---- индикаторные буфферы
double ExtBuffer[]; 
//+------------------------------------------------------------------+ 
//| RAVI initialization function                                     | 
//+------------------------------------------------------------------+ 
int init() 
  { 
//---- стиль отрисовки индикатора
   SetIndexStyle(0, DRAW_LINE); 
//---- индикаторные буферы 
   SetIndexBuffer(0,ExtBuffer); 
//---- название индикатора и лейбы для субокон 
   IndicatorShortName("RAVI (" + Period1+ ", " + Period2 + ")"); 
   SetIndexLabel(0, "RAVI"); 
//---- завершение инициализации
   return(0); 
  } 
//+------------------------------------------------------------------+ 
//| RAVI iteration function                                          | 
//+------------------------------------------------------------------+ 
int start() 
  {
   int MinBars = MathMax(Period1, Period2); 
//---- проверка количества баров на достаточность для расчёта
   if(Bars < MinBars)
       return(0);
//----+ Введение переменных с плавающей точкой 
   double MA1, MA2, result; 
//----+ Введение целых переменных и получение уже посчитанных баров
   int MaxBar, bar, limit, counted_bars = IndicatorCounted();
//---- проверка на возможные ошибки
   if(counted_bars < 0)
       return(-1);
//---- последний подсчитанный бар должен быть пересчитан 
   if(counted_bars > 0)
       counted_bars--;
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт всех баров 
   MaxBar = Bars - 1 - MinBars;
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт новых баров 
   limit = Bars - counted_bars - 1; 
//---- инициализация нуля
   if(limit > MaxBar)
     {
       for(int ii = Bars - 1; ii >= MaxBar; ii--)
           ExtBuffer[ii] = 0.0;
       limit = MaxBar;
     }
//---- основной цикл 
   for(bar = 0; bar <= limit; bar++) 
     { 
       MA1 = iMA(NULL, 0, Period1, 0, MA_Metod, PRICE,bar); 
       MA2 = iMA(NULL, 0, Period2, 0, MA_Metod, PRICE,bar); 
       //---- 
       result = ((MA1 - MA2) / MA2)*100; 
       ExtBuffer[bar] = result; 
     }  
//---- 
   return(0); 
  } 
//+------------------------------------------------------------------+


Алгоритм доработки

1. Избавляемся от всех лишних элементов в коде индикатора;

2. Пишем код эмуляции индикаторного буфера для единственного буфера ExtBuffer[];

3. Делаем замену функции IndicatorCounted() на переменную IndCounted;

4. Производим инициализацию переменной IndCounted количеством баров графика за вычетом единицы;

5. Меняем предопределённую переменную Bars на обращение к таймсерии iBars(symbol, timeframe);

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

7. Оставляем от всего кода только содержимое функции int start();

8. Добавляем сверху объявление функции Get_RAVISeries():

bool Get_RAVISeries(int Number, string symbol,int timeframe, 
                    bool NullBarRecount, int Period1, 
                    int Period2, int MA_Metod, int  PRICE, 
                    double& InputBuffer[])

9. Заменяем названия индикаторных буферов внутри кода (ExtBuffer) соответственно на буферные названия (InputBuffer) внешних переменных функции Get_RAVISeries();

10. Добавляем объявление переменной LastCountBar;

11. Превращаем статическую переменную IndCounted в массив IndCounted[Number] и добавляем код для изменения размера статических переменных в зависимости от количества обращений к функции Get_RAVISeries. mqh:

//----+ изменение размеров статических переменных
   if(ArraySize(IndCounted) < Number + 1)
     {
       ArrayResize(IndCounted, Number + 1); 
     }

12. Делаем проверку на истинность переменной NullBarRecount:

if(!NullBarRecount)
    LastCountBar = 1;

13. Заменяем во всех циклах расчёта индикатора ноль на LastCountBar:

for(bar = limit; bar >= LastCountBar; bar--)

14. Делаем замену в самом начале кода, в проверке числа баров на достаточность: return(0) на return(false);

15. Делаем замену в самом конце кода return(0) на return(true).

После всех преобразований кода получим индикаторную функцию Get_RAVISeries():

//+------------------------------------------------------------------+
//|                                               Get_RAVISeries.mqh |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
bool Get_RAVISeries(int Number, string symbol,int timeframe, 
                    bool NullBarRecount, int Period1, int Period2, 
                    int MA_Metod, int  PRICE, double& InputBuffer[])    
  {
//---- получение количества всех баров графика
   int IBARS = iBars(symbol, timeframe);  
//---- Проверка количества баров на достаточность для дальнейшего расчёта
   if(IBARS < MathMax(Period1, Period2))
       return(false);
//---- ЭМУЛЯЦИЯ ИНДИКАТОРНЫХ БУФЕРОВ
   if(ArraySize(InputBuffer) < IBARS)
     {
       ArraySetAsSeries(InputBuffer, false);
       //----  
       ArrayResize(InputBuffer, IBARS); 
       //----
       ArraySetAsSeries(InputBuffer, true);
     } 
//----+  введение статических переменных памяти
   static int IndCounted[]; 
//----+ изменение размеров статических переменных
   if(ArraySize(IndCounted) < Number + 1)
     {
       ArrayResize(IndCounted, Number + 1); 
     }
 //----+ Введение целой переменной
   int LastCountBar;
//----+ Проверка разрешения пересчёта нулевого бара
   if(!NullBarRecount)
       LastCountBar = 1;
//----+ Введение переменных с плавающей точкой 
   double MA1,MA2,result; 
//----+ Введение целых переменных и  получение уже посчитанных баров
   int MaxBar, bar, limit, counted_bars = IndCounted[Number];
//----+ Запоминание количества всех баров графика
   IndCounted[Number] = IBARS - 1;
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт новых баров
   limit = IBARS - counted_bars - 1; 
   // Print(IBARS - counted_bars); 
//---- определение номера самого старого бара, 
// начиная с которого будет произедён пересчёт всех баров
   MaxBar = IBARS - 1 - MathMax(Period1, Period2); 
//---- инициализация нуля 
   if(limit > MaxBar)
     {
       limit = MaxBar;
       for(bar = IBARS - 1; bar >= 0; bar--)
         {
           InputBuffer[bar] = 0.0;
         }
     } 
//----+ ПЕРВЫЙ ЦИКЛ ВЫЧИСЛЕНИЯ ИНДИКАТОРА 
   for(bar = limit; bar >= LastCountBar; bar--)
     { 
       MA1 = iMA(symbol, timeframe, Period1, 0, MA_Metod, PRICE, bar); 
       MA2 = iMA(symbol, timeframe, Period2, 0, MA_Metod, PRICE, bar); 
       //---- 
       result = ((MA1 - MA2) / MA2)*100; 
       InputBuffer[bar] = result; 
     } 
//----+  
   return(true);
  }
//+------------------------------------------------------------------+

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


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

Нам не мешало бы проверить эту функцию на совпадение результатов её вычислений с результатами расчёта пользовательского индикатора! Для осуществления этой процедуры лучше всего подходит советник, который вообще не занимается торговлей, а только получает значения из пользовательского индикатора RAVI.mq4 и пользовательской функции Get_RAVISeries(), находит их разность и после этого сбрасывает в логфайл индикаторное значение, значение пользовательской функции и их разность, так что нам останется проконтролировать содержание логфайла для окончательного заключения о соответствии полученного нами алгоритма пользовательской функции Get_RAVISeries() индикатору RAVI.mq4:

//+------------------------------------------------------------------+
//|                                           Get_RAVISeriesTest.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
//---- ВХОДНЫЕ ПАРАМЕТРЫ ЭКСПЕРТА
extern bool NullBarRecount = true;
//---- индикаторные буфферы
double RAVI_Buffer0[];
double RAVI_Buffer1[];
double RAVI_Buffer2[];
//+------------------------------------------------------------------+
//| Get_RAVISeries() function                                        |
//+------------------------------------------------------------------+
#include <Get_RAVISeries.mqh>
//+------------------------------------------------------------------+
//| Custom Expert initialization function                            |
//+------------------------------------------------------------------+
int init()
  {
//---- завершение инициализации
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom Expert iteration function                                 |
//+------------------------------------------------------------------+
int start()
  {
//---- 
   double Ind_Velue, Resalt;
//---- 
   if(!Get_RAVISeries(0, Symbol(), 0, NullBarRecount, 10, 20, 1, 0,
      RAVI_Buffer0))
       return(0);  
   if(!Get_RAVISeries(1, Symbol(), 240, NullBarRecount, 25, 66, 2, 1,
      RAVI_Buffer1))
       return(0);
   if(!Get_RAVISeries(2, Symbol(), 1440, NullBarRecount, 30, 70, 3, 3,
      RAVI_Buffer2))
       return(0);
//---- получение индикаторных значений для теста 0
   Ind_Velue = iCustom(NULL, 0, "RAVI", 10, 20, 1, 0, 0, 2); 
   Resalt = RAVI_Buffer0[2] - Ind_Velue; 
   Print("  " + Ind_Velue + "    " + RAVI_Buffer0[2] + "    " + Resalt+"");
//---- получение индикаторных значений для теста 1
   Ind_Velue = iCustom(NULL, 240, "RAVI", 25, 66, 2, 1, 0, 2);
   Resalt = RAVI_Buffer1[2] - Ind_Velue; 
   Print("  " + Ind_Velue + "    " + RAVI_Buffer1[2] + "    " + Resalt+"" );
//---- получение индикаторных значений для теста 2
   Ind_Velue = iCustom(NULL, 1440, "RAVI", 30, 70, 3, 3, 0, 2);
   Resalt = RAVI_Buffer2[2] - Ind_Velue; 
   Print("  " + Ind_Velue + "    " + RAVI_Buffer2[2] + "    " + Resalt + "");
//----
   return(0);
  }
//+------------------------------------------------------------------+

Запускаем в тестере стратегий советника Get_RAVISeriesTest. Вполне естественно, что откомпилированный файл RAVI.ex4 должен уже лежать в папке \expert\indicators, а файл Get_RAVISeries.mqh в папке\expert \include клиентского терминала MetaTrader. В журнале тестера стратегий и в лог-файле имеем два столбца со значениями индикатора и его аналога в виде функции и третий столбец разницы этих значений. Все значения последнего столбца на всех строках равны нулю. Следовательно мы имеем полное совпадение значений в обоих случаях, что позволяет сделать вывод о том, что с поставленной задачей построения индикаторной пользовательской функции мы справились успешно!


Заключение

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

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