English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Усреднение ценовых рядов без дополнительных буферов для промежуточных расчетов

Усреднение ценовых рядов без дополнительных буферов для промежуточных расчетов

MetaTrader 5Индикаторы | 25 октября 2010, 16:29
19 367 25
Nikolay Kositsin
Nikolay Kositsin


Введение

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

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

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

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


1. Общая идея функций усреднения, работающих с одним баром

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

Процедура обработки этой выборки выглядит следующим образом:

SmoothVar(bar) = Function(Var(bar - (n-1)), Var(bar - (n-2)), ......... Var(bar)) 

где:

  • SmoothVar(bar) — усредненный параметр;
  • bar — номер бара, на котором делается расчет;
  • Var(bar - (n-1)) — усредняемый параметр со сдвигом на (n-1) баров;
  • n — число баров для усреднения.

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

SmoothVar(bar) = Function(Var(bar), bar)

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

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


2. Классическое усреднение как пример реализации функций, работающих с одним баром

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

Алгоритмы классического усреднения представлены классом CMoving_Average:

class CMoving_Average : public CMovSeriesTools
  {
public:  
   double    MASeries  (uint begin,               // Номер начала достоверного отсчета баров
                       uint prev_calculated,      // Количество истории в барах на предыдущем тике
                       uint rates_total,          // Количество истории в барах на текущем тике
                       int Length,               // Период усреднения
                       ENUM_MA_METHOD MA_Method, // Метод усреднения (MODE_SMA, MODE_EMA, MODE_SMMA, MODE_LWMA)
                       double series,              // Значение ценового ряда, рассчитанное для бара с номером bar
                       uint bar,                   // Номер бара
                       bool set                  // Направление индексирования массивов.
                      );

   double    SMASeries (uint begin,          // Номер начала достоверного отсчета баров
                       uint prev_calculated,// Количество истории в барах на предыдущем тике
                       uint rates_total,     // Количество истории в барах на текущем тике
                       int Length,            // Период усреднения
                       double series,         // Значение ценового ряда, рассчитанное для бара с номером bar
                       uint bar,              // Номер бара
                       bool set              // Направление индексирования массивов.
                      );
                        
   double    EMASeries (uint begin,            // Номер начала достоверного отсчета баров
                       uint prev_calculated, // Количество истории в барах на предыдущем тике
                       uint rates_total,     // Количество истории в барах на текущем тике
                       double Length,         // Период усреднения
                       double series,        // Значение ценового ряда, рассчитанное для бара с номером bar
                       uint bar,              // Номер бара
                       bool set              // Направление индексирования массивов.
                      );
                        
   double    SMMASeries(uint begin,              // номер начала достоверного отсчета баров
                         uint prev_calculated, // Количество истории в барах на предыдущем тике
                         uint rates_total,     // Количество истории в барах на текущем тике
                         int Length,            // Период усреднения
                         double series,         // Значение ценового ряда, рассчитанное для бара с номером bar
                         uint bar,              // Номер бара
                         bool set              // Направление индексирования массивов.
                      );

   double    LWMASeries(uint begin,               // номер начала достоверного отсчета баров
                         uint prev_calculated, // Количество истории в барах на предыдущем тике
                         uint rates_total,      // Количество истории в барах на текущем тике
                         int Length,             // Период усреднения
                         double series,          // Значение ценового ряда, рассчитанное для бара с номером bar
                         uint bar,               // Номер бара
                         bool set               // Направление индексирования массивов.
                        );

protected:
   double            m_SeriesArray[];
   int               m_Size_, m_count, m_weight;
   double            m_Moving, m_MOVING, m_Pr;
   double            m_sum, m_SUM, m_lsum, m_LSUM;
  };

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

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

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

Первая функция MASeries() представляет собой интегральный сборник остальных четырех функций для возможности выбора алгоритма усреднения через параметр MA_Method. Коды алгоритмов усреднения оптимизированы для максимального быстродействия, и именно по этой причине у функций помимо основных параметров (Length, series, bar) появились еще и дополнительные параметры begin, prev_calculated, rates_total и set, смысл которых абсолютно аналогичен одноименным индикаторным переменным.

Параметр set выставляет флаг индексации элементов ценового ряда series в функциях усреднения аналогично массивам переменных.

Следует учесть, что у всех усредняющих функций этого класса параметр Length является фиксированным, и его нельзя менять в процессе работы программного кода! Для функции EMASeries() класса CMoving_Average этот параметр имеет тип double!

Теперь после предварительного знакомства с классом CMoving_Average можно приступать к его практическому использованию в индикаторах. Для этого прежде всего следует директивой #include добавить содержимое файла MASeries_Cls.mqh на глобальном уровне в код проектируемого индикатора:

#include <MASeries_Cls.mqh> 

После этого определить необходимое количество усреднений в коде индикатора и в блоке OnCalculate() (до операторов циклов и фигурных скобок) сделать соответствующее количеству требуемых усреднений объявление статических переменных класса CMoving_Average. Для каждого усреднения должна быть своя переменная класса или своя ячейка в массиве класса.

//---- объявление переменных класса CMoving_Average из файла MASeries_Cls.mqh
static CMoving_Average MA1, MA2, MA3, MA4;

Переменные класса внутри функции OnCalculate() объявлены как статические по той причине, что их содержимое должно сохраняться между вызовами этой функции. Теперь можно приступать к самим усреднениям. Я в качестве примера приведу четыре последовательных усреднения ценового ряда SMA/EMA/SMMA/LWMA (индикатор MAx4.mq5):

//---- Основной цикл расчета индикатора 
for(bar = first; bar < rates_total; bar++) 
 {
  //----+ Четыре вызова функции MASeries.  
  ma1_ = MA1.MASeries(start1, prev_calculated, rates_total, Length1, MODE_SMA,  price[bar], bar, false);
  ma2_ = MA2.MASeries(start2, prev_calculated, rates_total, Length2, MODE_EMA,  ma1_,       bar, false);
  ma3_ = MA3.MASeries(start3, prev_calculated, rates_total, Length3, MODE_SMMA, ma2_,       bar, false);
  ma4_ = MA4.MASeries(start4, prev_calculated, rates_total, Length4, MODE_LWMA, ma3_,       bar, false);
  //----       
  MAx4[bar] = ma4_ + dPriceShift;
 }

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

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

//---- Инициализация переменных начала отсчета данных
start1 = 0 + begin;
start2 = Length1 + begin;
start3 = Length1 + begin; // предыдущее EMA усреднение не меняет начало отсчета данных
start4 = Length1 + Length3 + begin;

Следует отметить, что в данной ситуации алгоритм LWMA усреднения последний, и он ничего не определяет, но если бы он был не последним, то сдвиг начала отсчета данных был бы равен не Length4, а Length4+1!

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


3. Сравнение полученного индикатора с использованием классов с его аналогами с вызовами технических и пользовательских индикаторов в коде

Было бы крайне интересно провести сравнительные тесты скорости работы получившегося индикатора MAx4.mq5 с его идентичным аналогом (iMAx4.mq5), построенным с применением вызовов технического индикатора iMA().

Ну, и раз уж мы решили проводить тест, то вполне разумно было бы протестировать еще и такой же, как MAx4.mq5, индикатор (MAx3x1.mq5), но у которого первое усреднение сделано с использованием вызова технического индикатора iMA(), а остальные три усреднения с помощью класса CMoving_Average. А поскольку в состав стандартного набора индикаторов клиентского терминала входит еще и индикатор Custom Moving Average.mq5, то и на базе этого индикатора тоже был построен аналогичный индикатор для тестовых замеров (cMAx4.mq5).

Для предстоящего исследования были написаны четыре тестерных эксперта: MAx4_Test.mq5, iMAx4_Test.mq5, MAx3x1_Test.mq5 и cMAx4_Test.mq5 соответственно. Сами условия проведения таких тестов я достаточно подробно изложил в своей статье  "Принципы экономного пересчета индикаторов". В данной статье я не буду подробно останавливаться на всех деталях теста и ограничусь только итоговыми результатами прогона всех четырех экспертов в тестере за последние 12 месяцев на паре EURUSD Н4 с моделированием каждого тика и со значением входного параметра period всех экспертов равного 500.

Рис.1 Результаты тестирования индикаторов 

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

Настоящим лидером тестов оказался индикатор, полностью построенный с использованием классов!

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

Обзор реализованных классов усреднения

№  Алгоритм Название класса Название файла Сдвиг начала достоверного отсчета баров
 после алгоритма
Возможность динамического
 изменения параметра Length
   1  Классическое усреднение  CMoving_Average  MASeries_Cls.mqh Length/0/Length/Length + 1 (SMA/EMA/SMMA/LWMA) нет
   2  Стандартное отклонение   CStdDeviation  StdDevSeries_Cls.mqh Length нет
   3  JMA сглаживание  CJJMA  JJMASeries_Cls.mqh 30 есть
   4  T3 сглаживание  CT3  T3Series_Cls.mqh 0 есть
   5  Ультралинейное усреднение  CJurX  JurXSeries_Cls.mqh 0 есть
   6  Усреднение Тушара Чанда   CCMO  CMOSeries_Cls.mqh Length + 1 нет
   7  Усреднение Кауфмана  CAMA  AMASeries_Cls.mqh Length + 3 нет
   8  Параболическое усреднение  CParMA  ParMASeries_Cls.mqh Length нет
   9  Скорость изменения  CMomentum  MomSeries_Cls.mqh                                             Length + 1                               нет
  10  Нормированная скорость изменения  CnMomentum  nMomSeries_Cls.mqh                                             Length + 1                               нет
  11  Темп изменения  CROC  ROCSeries_Cls.mqh                                             Length + 1                               нет

Только что рассмотренный класс CMoving_Average содержит в своем составе пять алгоритмов усреднения.

В классе CCMO присутствуют алгоритмы усреднения и осциллятора.

В остальных классах имеется всего по одному алгоритму усреднения. Идеология использования любого из предложенных классов абсолютно аналогична вышеизложенной процедуре использования класса CMoving_Average. Код всех алгоритмов усреднения (за исключением параболического) оптимизирован на максимальную скорость выполнения. Код алгоритма параболического усреднения не оптимизировался по причине достаточной трудоемкости этого процесса. Последние три алгоритма усреднениями не являются. Я эти алгоритмы добавил по причине их очень высокой популярности и совместимости с трудами известных, технических аналитиков.

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

Для использования в индикаторах все предложенные классы упакованы в один файл SmoothAlgorithms.mqh. Помимо этого содержание этого файла было дополнено функциями файла iPriceSeries.mqh. В примерах этой статьи используется только функция PriceSeries():

double PriceSeries
 (
  uint applied_price,  // Ценовая константа
  uint   bar,          // Индекс сдвига относительно текущего бара
                          // на указанное количество периодов назад или вперед.
  const double& Open [],
  const double& Low  [],
  const double& High [],
  const double& Close[]
 )

Эта функция предназначена для работы в индикаторах, построенных с использованием второй формы вызова функции OnCalculate().

Основная идея создания такой функции в том, чтобы расширить ряд ценовых таймсерий перечисления  ENUM_APPLIED_PRICE своими собственными вариантами. Функция возвращает значение ценовой таймсерии по ее номеру, который может изменяться от 1 и до 11.


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

Достаточно привести еще один пример с использованием других классов, чтобы убедиться в том, что все делается абсолютно аналогично первому примеру с четырехкратным усреднением. Я представлю вариант реализации функции OnCalculate() аналога индикатора CCI с использованием классов CJJMA и CJurX (JCCX.mq5)

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[]
             )
  {
//----+   
   //---- проверка количества баров на достаточность для расчета
   if (rates_total < 0) return(0);
    
   //---- Объявление переменных с плавающей точкой  
   double price_, jma, up_cci, dn_cci, up_jccx, dn_jccx, jccx;
   //----+ Объявление целых переменных
   int first, bar;
   
   //---- расчет стартового номера first для цикла пересчета баров
   if (prev_calculated == 0) // проверка на первый старт расчета индикатора
        first = 0;           // стартовый номер для расчета всех баров
   else first = prev_calculated - 1; // стартовый номер для расчета новых баров
   
   //---- объявление переменных класса CJurX из файла SmoothAlgorithms.mqh
   static CJurX Jur1, Jur2;
   //---- объявление переменной класса CJMA из файла SmoothAlgorithms.mqh
   static CJJMA JMA;
   
   //---- Основной цикл расчета индикатора
   for(bar = first; bar < rates_total; bar++)
    {
     //----+ Вызов функции PriceSeries для получения входной цены price_
     price_ = PriceSeries(IPC, bar, open, low, high, close); 

     //----+ Один вызов функции JJMASeries для получения мувинга JMA
     jma = JMA.JJMASeries(0, prev_calculated, rates_total, 0, JMAPhase, JMALength, price_, bar, false); 

     //----+ Определяем отклонение цены от значения мувинга
     up_cci = price_ - jma;         
     dn_cci = MathAbs(up_cci);

     //----+ Два вызова функции JurXSeries.  
     up_jccx = Jur1.JurXSeries(30, prev_calculated, rates_total, 0, JurXLength, up_cci, bar, false);
     dn_jccx = Jur2.JurXSeries(30, prev_calculated, rates_total, 0, JurXLength, dn_cci, bar, false); 

     //---- Предотвращение деления на ноль на пустых значениях
     if (dn_jccx == 0) jccx = EMPTY_VALUE;
     else
      {
       jccx = up_jccx / dn_jccx;
       
       //---- Ограничение индикатора сверху и снизу 
       if (jccx > +1)jccx = +1;
       if (jccx < -1)jccx = -1;
      }

     //---- Загрузка полученного значения в индикаторный буфер
     JCCX[bar] = jccx;
    }
//----+     
   return(rates_total);
  }

Рис.2 Индикатор JCCX 

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

#include <SmoothAlgorithms.mqh>

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

Например, было бы интересно построить канал Боллинджера от мувинга Vidya Тушара Чанда. В этом случае используются два класса - CCMO и CStdDeviation. С помощью первого класса получаем значение мувинга VIDYA, с помощью второго определяем значение стандартного отклонения ценового ряда от мувинга.

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

//+------------------------------------------------------------------+
// Описание классов CStdDeviation{}; и CCMO{};                       | 
//+------------------------------------------------------------------+ 
#include <SmoothAlgorithms.mqh> 
//+==================================================================+
//|  Алгоритм получения канала Боллинджера от мувинга VIDYA          |
//+==================================================================+
class CVidyaBands
{
public:
  double VidyaBandsSeries(uint begin,                // номер начала достоверного отсчета баров
                         uint prev_calculated,      // Количество истории в барах на предыдущем тике
                         uint rates_total,          // Количество истории в барах на текущем тике
                         int CMO_period,            // Период усреднения осциллятора CMO
                         double EMA_period,         // EMA период усреднения
                         ENUM_MA_METHOD MA_Method, // Метод усреднения
                         int BBLength,             // Период усреднения канала Боллинджера
                         double deviation,          // Девиация
                         double series,             // Значение ценового ряда, расчитанное для бара с номером bar
                         uint bar,                  // Номер бара
                         bool set,                  // Направление индексирования массивов
                         double& DnMovSeries,       // Значение нижней границы канала для текущего бара 
                         double& MovSeries,         // Значение средней линии канала для текущего бара 
                         double& UpMovSeries        // Значение верхней границы канала для текущего бара 
                        ) 
   {
//----+
    //----+ Вычисление средней линии    
    MovSeries = m_VIDYA.VIDYASeries(begin, prev_calculated, rates_total, 
                                    CMO_period, EMA_period, series, bar, set); 
    //----+ Вычисление канала Боллинджера
    double StdDev = m_STD.StdDevSeries(begin+CMO_period+1, prev_calculated, rates_total, 
                                      BBLength, deviation, series, MovSeries, bar, set);
    DnMovSeries = MovSeries - StdDev;
    UpMovSeries = MovSeries + StdDev;
//----+
    return(StdDev); 
   }

  protected:
    //---- объявление переменных классов CCMO и CStdDeviation
    CCMO           m_VIDYA;
    CStdDeviation  m_STD;
};

Получился совсем маленький и простой класс!

Три последних входных параметра функции VidyaBandsSeries() отдают по ссылке необходимые значения канала.

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

protected: 
  //---- объявление переменных классов CCMO и CStdDeviation 
  CCMO           m_VIDYA; 
  CStdDeviation  m_STD;

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

В данном классе я для обеспечения наибольшей степени свободы эти параметры сделал разными (EMA_period и BBLength). Сам индикатор (VidyaBBands.mq5), построенный на базе этого класса настолько прост в плане использования класса CVidyaBands, что анализ его кода в статье можно и не делать.

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

Рис.3 Индикатор VidyaBBands 

5. Сравнение скорости работы индикатора с использованием классов с его аналогом, классов не использующим  

Хотелось бы сразу уточнить, а насколько замедляет работу индикатора само использование классов при написании его кода?

Для такой цели код индикатора JJMA.mq5 был написан без использования классов (JMA.mq5) и были проведены тесты с условиями, абсолютно аналогичными нашему предыдущему тестированию. Итоговые результаты тестов не очень сильно отличаются друг от друга:

Рис.4 Результаты тестов индикаторов JMA и JJMA 

Конечно, некоторые накладные расходы на содержание классов все-таки имеют место быть, но это сущая чепуха по сравнению с теми преимуществами, которые эти классы обеспечивают. 


6. Преимущества использования классов усреднения

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

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

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


7. Некоторые рекомендации по применению алгоритмов усреднения в конкретном, индикаторном коде

Достаточно одного, беглого просмотра любых индикаторов, реализованных с использованием различных алгоритмов, из числа приложенных в статье, чтобы понять, как сильно отличаются эти алгоритмы

 

 Рис. 5 Мувинги с использованием различных алгоритмов усреднения

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

Например, алгоритмы Тушара Чанда и Кауфмана предназначены для выявления трендовых ситуаций и абсолютно непригодны для дополнительного сглаживания с целью фильтрации шумов. Так что на вход индикаторов с использованием этих алгоритмов предпочтительнее подавать либо необработанные ценовые ряды, либо значения индикаторов, в которых изначально отсутствуют усреднения.  Вот результат обработки значений индикатора Momentum алгоритмом Кауфмана (индикатор 4c_Momentum_AMA.mq5)

Рис.6 Результат обработки значений индикатора Momentum алгоритмом Кауфмана 

Я полагаю, что алгоритмы классического усреднения в особых рекомендациях не нуждаются. Их область применения оказывается весьма широкой. Везде, где можно применять эти алгоритмы, с  большим успехом можно использовать оставшиеся четыре алгоритма (JMA, T3, ультралинейное и параболическое усреднения). Вот, к примеру, индикатор MACD с заменой EMA и SMA усреднений на JMA усреднения (индикатор JMACD.mq5):

 Рис.7 Диаграмма MACD с использованием JMA усреднений

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

Рис.8 Результат JMA сглаживания индикатора Momentum

Вполне естественно, что поведение финансовых рынков все время меняется, и поэтому было бы наивным считать, что можно найти такой всего один, идеально подходящий для какого-то сегмента финансовых рынков алгоритм, причем отныне и навсегда! Увы! Ничто не вечно под луной! И все-таки я так, например, на этом, бесконечно меняющемся рынке в своих, торговых системах часто использую такие индикаторы быстрого и среднесрочного трендов как JFATL.mq5 и J2JMA.mq5. Меня вполне устраивают прогнозы на их основе.

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


8. Общая идея построения кода алгоритмов усреднения

А теперь, в конце статьи я хотел бы вкратце остановиться на устройстве самих функций усреднения.

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

При появлении данных, значимых для расчета, необходимо один раз сделать распределение памяти под такой массив. Это делает функция SeriesArrayResize() класса CMovSeriesTools.

//----+ Изменение размеров массива переменных
if(bar==begin && !SeriesArrayResize(__FUNCTION__, Length, m_SeriesArray, m_Size_))
   return(EMPTY_VALUE);

После этого следует на каждом баре в этот массив на место самого старого значения дописывать текущее значение ценового ряда series и фиксировать номер этой позиции в переменной m_count. Это делается функцией Recount_ArrayZeroPos() класса CMovSeriesTools.

//----+ перестановка и инициализация ячеек массива m_SeriesArray 
Recount_ArrayZeroPos(m_count, Length_, prev_calculated, series, bar, m_SeriesArray);

А теперь, если нам необходимо найти в массиве элемент со сдвигом относительно текущего, то следует воспользоваться функцией Recount_ArrayNumber() класса CMovSeriesTools:

for(iii=1; iii<=Length; iii++, rrr--)
   {
    kkk = Recount_ArrayNumber(m_count, Length_, iii);
    m_sum += m_SeriesArray[kkk] * rrr;
    m_lsum += m_SeriesArray[kkk];
    m_weight += iii;
   }

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

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

//----+ Проверка начала достоверного отсчета баров 
if(BarCheck1(begin, bar, set)) return(EMPTY_VALUE);

момента, когда данных достаточно для стартовой инициализации переменных:

//----+ Инициализация нуля
if(BarCheck2(begin, bar, set, Length))

и ситуаций, когда бар последний закрытый:

//----+ Сохранение значений переменных 
if(BarCheck4(rates_total, bar, set))

или не закрытый:

//----+ Восстановление значений переменных
if(BarCheck5(rates_total, bar, set))

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

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

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


Заключение

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

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

Прикрепленные файлы |
Include.zip (19.62 KB)
Include_.zip (51 KB)
indicators.zip (109.46 KB)
experts.zip (51.56 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (25)
don36
don36 | 14 февр. 2020 в 16:44

Интересно, есть ли на сайте ветка, напрямую относящаяся к компилированию

IuriiPrugov
IuriiPrugov | 20 апр. 2020 в 11:50
Аналогичная проблема. МТ5 build 2361.
Aleksei Sergeev
Aleksei Sergeev | 19 мая 2020 в 21:51

Проблема решается, достаточно вынести перечисление...

enum Smooth_Method
     {
      MODE_SMA_,  //SMA
      MODE_EMA_,  //EMA
      MODE_SMMA_, //SMMA
      MODE_LWMA_, //LWMA
      MODE_JJMA,  //JJMA
      MODE_JurX,  //JurX
      MODE_ParMA, //ParMA
      MODE_T3,     //T3
      MODE_VIDYA,  //VIDYA
      MODE_AMA     //AMA
     }; 
     
     
//+X================================================================X+
//|  расчёт минимального количества необходимых баров алгоритма XMA  |
//+X================================================================X+     
int GetStartBars(Smooth_Method Method,int Length,int Phase)
don36
don36 | 25 мая 2020 в 19:52
LexTon:

Проблема решается, достаточно вынести перечисление...

Скажите, если не трудно, эти изменения нужно и можно вносить в любое место кода индикатора? Или всё же предпочтительнее данное перечисление поместить в конце кода? Спасибо!
don36
don36 | 28 июл. 2020 в 18:09
LexTon:

Проблема решается, достаточно вынести перечисление...

У меня получился такой результат. Вставку делал в строку 41. Получил три ошибки компиляции: '#property' - semicolon expected SmoothAlgorithms.mqh 7 1"; 'FastMethod' - cannot convert enum RAVI.mq5 197 64; 'SlowMethod' - cannot convert enum RAVI.mq5 198 64. Пока такой результат.



Взгляни на рынок через готовые классы Взгляни на рынок через готовые классы
Не секрет, что большую часть информации об окружающем мире человек получает при помощи зрения. Справедливо это и в такой области как трейдинг. Новая платформа MetaTrader 5 и язык MQL5 открывают новые возможности для представления визуальной информации трейдеру. В данной статье предлагается универсальная и расширяемая система классов, которая берет на себя всю черновую работу по организации вывода произвольной текстовой информации.
Строим анализатор спектра Строим анализатор спектра
Данная статья призвана познакомить читателя с возможным вариантом использования графических объектов языка MQL5. В статье рассматривается индикатор, в котором при помощи графических объектов создана панель управления простейшим анализатором спектра. Статья рассчитана на читателя, знакомого с основами языка MQL5.
Работа по Накоплению/Распределению и что из этого можно сделать Работа по Накоплению/Распределению и что из этого можно сделать
Индикатор Накопления/Распределения A/D имеет одно интересное свойство - пробитие трендовой линии, построенной на графике данного индикатора с определённой долей вероятности говорит нам о скором пробое линии тренда на графике цены. Данная статья будет полезна и интересна людям, только начинающим программировать на MQL4, поэтому я постарался изложить всё в наиболее доступной для понимания форме и использовать самые простые конструкции построения кода.
Интервью с Борисом Одинцовым (ATC 2010) Интервью с Борисом Одинцовым (ATC 2010)
Борис Одинцов - один из самых ярких участников Чемпионата, преодолевший на третьей торговой неделе планку в $100 000. Стремительный взлет своего эксперта Борис объясняет благоприятным стечением обстоятельств. В этом интервью он рассказывает, каким вещам стоит уделять внимание в трейдинге и о том, какой рынок неблагоприятен для его эксперта.