Пропуск отрисовки на начальных барах

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

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

Чтобы отказаться от визуализации индикатора на первых N‑1 барах истории, следует установить свойству PLOT_DRAW_BEGIN значение N для соответствующего графического построения index: PlotIndexSetInteger(index, PLOT_DRAW_BEGIN, N). По умолчанию данное свойство равно 0, то есть данные выводятся с самого начала.

В принципе, мы можем и сами подавить отображение линии на нужных барах, установив их в "пустое" значение (EMPTY_VALUE по умолчанию). Однако вызов функции PlotIndexSetInteger со свойством PLOT_DRAW_BEGIN делает и кое-что другое. Как разработчики своего индикатора, мы тем самым сообщаем внешним программам количество несущественных первых значений в нашем индикаторном буфере. В частности, другие индикаторы, которые потенциально могут быть построены на основе таймсерии нашего индикатора, получат значение свойства PLOT_DRAW_BEGIN в параметре begin своего обработчика OnCalculate. Таким образом, у них появится возможность пропустить "неполноценные" бары.

В примере индикатора IndColorWPR.mq5 добавим подобную настройку в функции OnInit.

input int WPRPeriod = 14// Period
   
void OnInit()
{
   ...
   PlotIndexSetInteger(0PLOT_DRAW_BEGINWPRPeriod - 1);
   ...
}

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

   if(prev_calculated == 0)
   {
      ArrayFill(WPRBuffer0WPRPeriod - 1EMPTY_VALUE);
   }

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

Чтобы продемонстрировать, как данное свойство может эксплуатироваться в другом индикаторе, рассчитываемом на базе нашего, подготовим еще один индикатор. Пусть это будет известный алгоритм тройного экспоненциального сглаживания (Triple Exponential Moving Average), "упакованный" в индикатор IndTripleEMA.mq5. Когда он будет готов, его будет легко применить как к ценовым таймсериям, так и к произвольным индикаторам, таким как предыдущий индикатор IndColorWPR.mq5.

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

Напомним формулу тройного EMA в виде нескольких вычислительных шагов.

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

K = 2.0 / (P + 1)
A[i] = T[i] * K + A[i - 1] * (1 - K)

где K — весовой коэффициент учета элементов исходного ряда, рассчитываемый через заданный период P; (1 - K) — коэффициент инерционности, применяемый к элементам сглаженного ряда A. Для получения i-го элемента ряда A суммируем K-ую часть i-го элемента исходного ряда T[i] и (1 - K)-ую часть предыдущего элемента A[i - 1].

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

EMA1 = E(AP), по всем i
EMA2 = E(EMA1P), по всем i
EMA3 = E(EMA2P), по всем i
TEMA = 3 * EMA1 - 3 * EMA2 + EMA3, по всем i

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

Сглаживание по EMA позволяет получить приблизительную оценку среднего, уже начиная со второго элемента ряда, причем для этого не требуется менять алгоритм. Это выгодно отличает EMA от других методов сглаживания, которые требуют наличия P предыдущих элементов или менять алгоритм для начальных отсчетов, когда доступно менее P элементов. Некоторые разработчики предпочитают объявлять первые P‑1 элементов сглаженного ряда недействительными даже при использовании EMA. Однако следует заметить, что влияние прошлых элементов ряда в формуле EMA не ограничено P элементами, и оно становится пренебрежимо малым лишь при стремлении количества элементов к бесконечности (в других известных алгоритмах MA влияние оказывают точно P предыдущих элементов).
 
В рамках данной книги, для исследования влияния пропуска начальных данных, мы не станем подавлять вывод начальных значений EMA.

Для расчета трех уровней EMA нам потребуются вспомогательные буферы и еще один для финального ряда: он отобразится линейной диаграммой.

#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   1
   
#property indicator_type1   DRAW_LINE
#property indicator_color1  Orange
#property indicator_width1  1
#property indicator_label1  "EMA³"
   
double TemaBuffer[];
double Ema[];
double EmaOfEma[];
double EmaOfEmaOfEma[];
   
void OnInit()
{
   ...
   SetIndexBuffer(0TemaBufferINDICATOR_DATA);
   SetIndexBuffer(1EmaINDICATOR_CALCULATIONS);
   SetIndexBuffer(2EmaOfEmaINDICATOR_CALCULATIONS);
   SetIndexBuffer(3EmaOfEmaOfEmaINDICATOR_CALCULATIONS);
   ...
}

Входная переменная InpPeriodEMA позволяет задать период сглаживания. Вторая переменная InpHandleBegin представляет собой переключатель режимов, с помощью которого мы сможем исследовать, как индикатор реагирует на учет или игнорирование параметра begin в обработчике OnCalculate. Доступные режимы сведены в перечисление BEGIN_POLICY и означают, по порядку:

  • строгое выполнение отступа согласно begin;
  • собственный анализ исходных данных на корректность, без учета begin;
  • полное отсутствие какой-либо обработки, то есть игнорирование begin и прямолинейный расчет по всем данным.

enum BEGIN_POLICY
{
   STRICT// strict
   CUSTOM// custom
   NONE,   // no
};
   
input int InpPeriodEMA = 14;                 // EMA period:
input BEGIN_POLICY InpHandleBegin = STRICT;  // Handle 'begin' parameter:

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

Исходя из InpPeriodEMA, подготавливается константа K для расчета EMA.

const double K = 2.0 / (InpPeriodEMA + 1);

Сама функция EMA достаточно проста (здесь опущен защитный фрагмент для варианта CUSTOM с проверками на EMPTY_VALUE).

void EMA(const double &source[], double &result[], const int posconst int begin = 0)
{
   ...
   if(pos <= begin)
   {
      result[pos] = source[pos];
   }   
   else
   {
      result[pos] = source[pos] * K + result[pos - 1] * (1 - K);
   }
}

А вот и полный расчет тройного сглаживания в OnCalculate.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
   const int _begin = InpHandleBegin == STRICT ? begin : 0;
   // свежий старт или обновление истории
   if(prev_calculated == 0)
   {
      Print("begin="begin" "EnumToString(InpHandleBegin));
      
      // мы можем менять настройки диаграмм динамически
      PlotIndexSetInteger(0PLOT_DRAW_BEGIN_begin);
      
      // подготовка массивов
      ArrayInitialize(EmaEMPTY_VALUE);
      ArrayInitialize(EmaOfEmaEMPTY_VALUE);
      ArrayInitialize(EmaOfEmaOfEmaEMPTY_VALUE);
      ArrayInitialize(TemaBufferEMPTY_VALUE);
      Ema[_begin] = EmaOfEma[_begin] = EmaOfEmaOfEma[_begin] = price[_begin];
   }
   
   // главный цикл с учетом старта от _begin   
   for(int i = fmax(prev_calculated - 1_begin);
      i < rates_total && !IsStopped(); i++)
   {
      EMA(priceEmai_begin);
      EMA(EmaEmaOfEmai_begin);
      EMA(EmaOfEmaEmaOfEmaOfEmai_begin);
      
      if(InpHandleBegin == CUSTOM// защита от пустых элементов в начале
      {
         if(Ema[i] == EMPTY_VALUE
         || EmaOfEma[i] == EMPTY_VALUE
         || EmaOfEmaOfEma[i] == EMPTY_VALUE)
            continue;
      }
      
      TemaBuffer[i] = 3 * Ema[i] - 3 * EmaOfEma[i] + EmaOfEmaOfEma[i];
   }
   return rates_total;
}

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

После успешной компиляции всё готово для экспериментов.

Первым делом набросим на график индикатор IndColorWPR (по умолчанию его период равен 14, что означает, согласно исходным кодам, установку свойства PLOT_DRAW_BEGIN в значение на 1 меньше, т.к. индексация начинается с 0, и 13-й бар будет первым, для которого появится значение). Затем перетащим индикатор IndTripleEMA в подокно, в котором выводится WPR. В открывшемся диалоге настройки свойств следует на закладке Параметры выбрать в выпадающем списке Применить к вариант Данные предыдущего индикатора. На закладке Входные параметры оставим значения по умолчанию.

На следующем изображении показано начало графика. В логе появится запись: begin=13 STRICT.

Индикатор тройного EMA, примененный к WPR с учетом начала данных

Индикатор тройного EMA, примененный к WPR с учетом начала данных

Обратите внимание, что усредненная линия начинается с отступом от начала, синхронно с WPR.

Внимание! Количество доступных баров для расчета индикаторов rates_total (оно же iBars(_Symbol, _Period)) может превышать максимально разрешенное количество баров на графике из настроек терминала, если имеется более длинная локальная история котировок. В этом случае пустые элементы в начале линии WPR (или любого другого индикатора с пропуском первых элементов, такого как MA) станут невидны — будут скрыты за левой границей графика. Для воспроизведения ситуации с отсутствием линий на начальных барах потребуется либо увеличить количество баров на графике, либо закрыть терминал и удалить локальную историю для конкретного символа.

Теперь переключим в настройках индикатора IndTripleEMA режим на CUSTOM (результат в журнале — begin=0 CUSTOM). Ощутимых изменений в показаниях индикатора быть не должно.

Наконец активируем режим NONE. В журнал будет выведено: begin=0 NONE.

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

Индикатор тройного EMA, примененный к WPR без учета начала данных

Индикатор тройного EMA, примененный к WPR без учета начала данных

Дело в том, что значения EMPTY_VALUE равны максимальному вещественному числу DBL_MAX. Поэтому без учета параметра begin расчеты с такими значениями генерируют тоже очень большие числа. В зависимости от специфики расчета из-за переполнения могут получаться специальные "не числа" (Not A Number, см. раздел Проверка вещественных чисел на нормальность): одно из них -nan(ind) подсвечено в изображении (Окно данных уже умеет выводить некоторые виды "не чисел", например, "inf" и "-inf", однако это пока не касается "-nan(ind)"). Как мы знаем, такие NaN-значения опасны, поскольку вычисления с их участием будут далее также давать NaN. В случае, если обойдется без NaN, по мере продвижения по барам вправо "переходный процесс" в расчете больших чисел затихает (из-за понижающего коэффициента (1 - K) в формуле EMA), и результат стабилизируется, становится адекватным. Если прокрутить график к настоящему времени, мы увидим нормальную тройную EMA.

Учет параметра begin — полезная практика, но она не дает гарантии, что поставщик данных (если это сторонний индикатор) корректно заполнил это свойство. Поэтому желательно предусмотреть в своем коде некоторую защиту. В данной реализации IndTripleEMA она реализована на начальном уровне.

Между прочим, если накладывать индикатор IndTripleEMA на график котировок, всегда будем получать begin = 0, потому что ценовые таймсерии с самого начала заполнены реальными данными, даже на самых старых барах.