Баг в расчете технического индикатора

 
При тестировании на ценах открытия код
#include <stdlib.mqh>
 
extern bool Kick;
 
int start()
{
    datetime date = D'26.10.2005 19:15';

    if (Kick)
        iCCI(NULL, 0, 20, PRICE_CLOSE, 1);
    
    if (Time[0] == date)
        Print(DoubleToStrMorePrecision(iCCI(NULL, 0, 20, PRICE_CLOSE, 1), 16));
    
    if (Time[0] == date + 300)
        Print(DoubleToStrMorePrecision(iCCI(NULL, 0, 20, PRICE_CLOSE, 2), 16));
    
    if (Time[0] == date + 600)
        Print(DoubleToStrMorePrecision(iCCI(NULL, 0, 20, PRICE_CLOSE, 3), 16));
    
    return(0);
}
выдает разные результаты в зависимости от того, вызывается индикатор в каждом тике или нет, то есть установлен или сброшен Kick:
2006.10.30 02:59:55 2005.10.26 19:25 test EURUSDm,M5: 0. 0000000000000000
2006.10.30 02:59:55 2005.10.26 19:20 test EURUSDm,M5: 0. 0000000000000000
2006.10.30 02:59:55 2005.10.26 19:15 test EURUSDm,M5: -0.0000000000510447
2006.10.30 02:59:55 test started for testing
2006.10.30 02:59:41 2005.10.26 19:25 test EURUSDm,M5: 0. 0000000000000000
2006.10.30 02:59:41 2005.10.26 19:20 test EURUSDm,M5: 0. 0000000000000000
2006.10.30 02:59:41 2005.10.26 19:15 test EURUSDm,M5: 0.0000000000510447
2006.10.30 02:59:41 test started for testing
При этом, как видно, значение индикатора еще и обнуляется в следующих тиках.
Из-за этого глючит пользовательский индикатор, который сравнивает CCI с нулем.

Data Window показывает CCI(20) -0.0000 на баре 19:15.

Сборка 4.198.19.10.06
Файлы:
eurusdm5_1.txt  14 kb
 
Вы подняли очень интересный вопрос.

Для начала скажу, что обнуления значений не происходит. Только перерасчёт. Почему же при перерасчёте получается другое значение?

Мы используем так называемый "экономный" метод расчёта скользящей средней. Приведу пример:
void sma()
  {
   double sum=0;
   int    i,pos=Bars-ExtCountedBars-1;
//---- initial accumulation
   if(pos<MA_Period) pos=MA_Period;
   for(i=1;i<MA_Period;i++,pos--)
      sum+=Close[pos];
//---- main calculation loop
   while(pos>=0)
     {
      sum+=Close[pos];
      ExtMapBuffer[pos]=sum/MA_Period;
       sum-=Close[pos+MA_Period-1];
        pos--;
     }
//---- zero initial bars
   if(ExtCountedBars<1)
      for(i=1;i<MA_Period;i++) ExtMapBuffer[Bars-i]=0;
  }
Сначала производится первоначальное накопление суммы, а потом только модификация - вычитание старого "левого" значения и прибавление очередного "правого" значения.

При экономном расчёте производится пересчёт только 2-х последних баров (высчитывается исходя из ExtCountedBars). При большом количестве итераций (я проводил тест на 15 тысячах барах) при первоначальном расчёте происходит накопление ошибки в 14-15 знаке после запятой. То есть, существует разница, которая зависит от того за сколько итераций мы подошли к нулевому бару, за 15 тысяч или за 2.

Спасибо, что подняли этот вопрос. Будем думать.
 
stringo:
Вы подняли очень интересный вопрос.

Для начала скажу, что обнуления значений не происходит. Только перерасчёт. Почему же при перерасчёте получается другое значение?
Зачем, интересно, пересчитывать значение? Цена следующего бара, ведь, не влияет на предыдущее значение индикатора? Я-то думал, эти значения кэшируются, по крайней мере, для нескольких последних баров. Опять же, в Data Window, т.е. на графике, значение, похоже, не пересчитывается.
Здесь, кстати, может быть зарыта и другая собака. Об этом в ветке 'Как замедлить работу советника в несколько раз?'.
_+_
/ @
 

Как зачем пересчитывать? Новое значение Close[0] меняет значение индикатора на текущем баре. Поэтому текущий бар надо пересчитывать. Вы спросите, а зачем пересчитывать 2 последних бара? Почитайте замечание к функции IndicatorCounted.

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

Какие 5 значащих цифр? Выведите значения скользящего среднего при помощи функции DoubleToStrMorePrecision и удивитесь результату. Индикаторные массивы - это массивы вещественных чисел. И мы их в процессе расчёта не нормализуем. Считаем как есть. Только вывод подсказок (тултипы, окно данных) производится с определённой точностью, как правило совпадающей с точностью инструмента, но бывают и исключения.

 
stringo:


Какие 5 значащих цифр? Выведите значения скользящего среднего при помощи функции DoubleToStrMorePrecision и удивитесь результату. Индикаторные массивы - это массивы вещественных чисел. И мы их в процессе расчёта не нормализуем. Считаем как есть. Только вывод подсказок (тултипы, окно данных) производится с определённой точностью, как правило совпадающей с точностью инструмента, но бывают и исключения.

Да, конечно, не удивлюсь. Так и есть.
Я снес свое ложное утверждение за несколько секунд до Вашего ответа - mid-air collision. Мои извинения.
 
stringo:

Как зачем пересчитывать? Новое значение Close[0] меняет значение индикатора на текущем баре. Поэтому текущий бар надо пересчитывать. Вы спросите, а зачем пересчитывать 2 последних бара? Почитайте замечание к функции IndicatorCounted.

В примере выше индикатор вообще не вызывается на текущем баре. Только на предыдущем. Или 2-м, или 3-м в зависимости от текущего времени. Лог показывает изменение значения при пересчете индикатора на 2-м баре. То есть, пересчитывается значение еще одного, заведомо завершенного бара, 2-го по индексу, 3-го по счету, помимо текущего и предыдущего. При этом индикатор уже был посчитан на 2-м баре, когда он еще был первым, т.е. уже был завершен, так как вызов осуществляется уже из следующего, 0-го в тот момент, бара.

Вообще говоря, рассуждения по поводу последнего и предпоследнего тика в документации к IndicatorCounted достаточно умозрительны. Почему только предпоследний? MetaQuotes в описании кодов ошибок торговых операций предлагает использовать немалые таймауты для обработки определенных ситуаций. На форумах также обсуждалась проблема с подвисанием торговых функций в течение таймаута сервера (3 минуты?). Так что, предпоследний тик ничего гарантировать здесь не может. Честнее было бы использовать в потоке эксперта внутренний флажок в разделяемой памяти, вернее, счетчик, показывающий, сколько новых баров (или тиков) набежало с момента последнего вызова start().

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

В любом случае, вся эта логика не имеет смысла в тестере, где новый тик никогда не появляется до завершения работы тик-хандлера. А значения индикатора на завершенных барах меняются.
 
Такой счётчик тиков есть. И он используется для определения необходимости пересчёта. Дело не в том, сколько тиков пришло "после последнего раза". Это может быть и 1 тик, и 10. Но если пришло более 1 тика, то существует ненулевая вероятность того, что какой-то из тиков был завершающим для одного бара, а последующие тики формировали уже другой бар. Для такой ситуации и надо пересчитывать 2 последних бара, а не 1.

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