Тестер, iCustom, Print

 
Что-то я в тестере не понимаю. Советник использует iCustom. Советник простейший, специально для исследования проблемы.
int cnt;
double CurChlTop,CurChlBot;
double PreChlTop,PreChlBot;
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init() {
//---- 
  cnt = 0;
//----
  return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit() {
//---- 
   
//----
  return(0);
}

//+------------------------------------------------------------------+
//| expert start function                                            |
//+-----------------------------------------------------------------+
int start() {
  CurChlTop = iCustom(NULL,0,"TwoChannels",377,987,1,1);
  CurChlBot = iCustom(NULL,0,"TwoChannels",377,987,2,1);
  PreChlTop = iCustom(NULL,0,"TwoChannels",377,987,3,1);
  PreChlBot = iCustom(NULL,0,"TwoChannels",377,987,4,1);
  cnt++;
  if (cnt%600 == 0) {
    Print("Значения индикатора    Дно1: ",CurChlBot,"   Верх1: ",CurChlTop,"   Дно2: ",PreChlBot,"   Верх2: ",PreChlTop);
  }
  return(0);
}
//+------------------------------------------------------------------+

После тестера имею картинку

Из картинки следует, что в журнале должно печататься

2005.01.12 14:39  TestChannels EURUSD,M1: Значения индикатора    Дно1: 1.3082   Верх1: 1.3180   Дно2: 1.3157   Верх2: 1.3232

На самом деле печатается

2005.01.12 14:39  TestChannels EURUSD,M1: Значения индикатора    Дно1: 1.3082   Верх1: 1.3118   Дно2: 1.3224   Верх2: 1.3232

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

//+------------------------------------------------------------------+
//|                                                  TwoChannels.mq4 |
//|                                         Copyright © 2006, Candid |
//|                                                   likh@yandex.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2006, Candid"
#property link      "likh@yandex.ru"

#property indicator_chart_window
#property indicator_buffers 6
#property indicator_color1 DarkViolet
#property indicator_color2 Crimson
#property indicator_color3 Crimson
#property indicator_color4 Indigo
#property indicator_color5 Indigo
#property indicator_color6 White

extern int Period1 = 377;
extern int Period2 = 987;
//extern int TimeFrame = 0;
//extern int ArrowGap = 3;
//extern color ArrowColor = DarkOrchid;

int TimeFrame = 0;
int ArrowGap = 3;
color ArrowColor = DarkOrchid;

//---- buffers
double Ext[];
double CurChlTop[];
double CurChlBot[];
double PreChlTop[];
double PreChlBot[];
double ChlsParams[];

double Gap;
double Max, Min;
int MaxPos, MinPos;
int MaxBar, MinBar;
int preMaxPos, preMinPos;
int pre2MaxPos, pre2MinPos;
int preMaxBar, preMinBar;
int pre2MaxBar, pre2MinBar;
bool FindMax = TRUE;
bool FindMin = TRUE;

double CurA = 0;
double CurBt = 0;
double CurBb = 0;
double PreA = 0;
double PreBt = 0;
double PreBb = 0;
int CurBar = 0;
double CurAT = 0;
double PreAT = 0;


//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init() {
//---- indicators
   IndicatorDigits(Digits);

//   SetIndexStyle(0,DRAW_NONE);
   SetIndexStyle(0,DRAW_ARROW,EMPTY,EMPTY,ArrowColor);
   SetIndexArrow(0,93);
   SetIndexBuffer(0,Ext);

   SetIndexStyle(1,DRAW_LINE,STYLE_SOLID,2);
   SetIndexBuffer(1,CurChlTop);

   SetIndexStyle(2,DRAW_LINE,STYLE_SOLID,2);
   SetIndexBuffer(2,CurChlBot);

   SetIndexStyle(3,DRAW_LINE,STYLE_SOLID,2);
   SetIndexBuffer(3,PreChlTop);

   SetIndexStyle(4,DRAW_LINE,STYLE_SOLID,2);
   SetIndexBuffer(4,PreChlBot);

   SetIndexStyle(5,DRAW_NONE);
   SetIndexBuffer(5,ChlsParams);

   Gap = ArrowGap*Point;
   MaxBar = 0;
   MinBar = 0;
   preMaxBar = 0;
   preMinBar = 0;
   Max = 0;
   Min = 0;

//----
   return(0);
}
//+------------------------------------------------------------------+
//| Custor indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit() {
  return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start() { 
  double fEMA, sEMA;
  int i,j,limit;
  int counted_bars=IndicatorCounted();
//---- check for possible errors
   if (counted_bars<0) return(-1);
   limit = Bars-counted_bars-1;
//---- 
  for(i=limit; i>0; i--) {
    CurBar = Bars-i;
// Рассчитываем скользящие средние
    fEMA = iMA(NULL,0,377,0,MODE_EMA,PRICE_MEDIAN,i);
    sEMA = iMA(NULL,0,987,0,MODE_EMA,PRICE_MEDIAN,i);
//    fEMA = iMA(NULL,TimeFrame,Period1,0,MODE_EMA,PRICE_MEDIAN,i);
//    sEMA = iMA(NULL,TimeFrame,Period2,0,MODE_EMA,PRICE_MEDIAN,i);
// Если разность скользящих средних положительна, ищем максимум
    if (fEMA >= sEMA) {
      if (FindMin) {
// Пересечение средних. Фиксируем очередной минимум.
        FindMin = FALSE;
        FindMax = TRUE;
        pre2MinBar = preMinBar;
        preMinBar = MinBar;
        pre2MinPos = Bars-pre2MinBar;
        preMinPos = Bars-preMinBar;
        Ext[preMinPos] = Min - Gap;
// Стартовые значения для следующего максимума
        Max = High[preMinPos];
        MaxPos = preMinPos;
        for(j=preMinPos-1;j>i;j--) {
          if(High[j] >= Max) {
            Max = High[j];
            MaxPos = j;
          }
        }
        MaxBar = Bars-MaxPos;
// Текущий канал становится предыдущим
        PreA = CurA;       
        PreBt = CurBt;       
        PreBb = CurBb;       
// Рассчитываем новый канал по двум последним минимумам. y = Ax + B
//  A = (y2-y1)/(x2-x1)
//  B = (x2*y1-x1*y2)/(x2-x1)
        if ( Time[preMaxPos]-Time[pre2MaxPos] != 0) {
//  Рассчитываем А
          CurA = (Low[preMinPos]-Low[pre2MinPos])/(preMinBar-pre2MinBar);
// Рассчитываем смещение дна канала Bb
          CurBb = (preMinBar*Low[pre2MinPos]-pre2MinBar*Low[preMinPos])/(preMinBar-pre2MinBar);
// Рассчитываем смещение верха канала Bt = y3 - A*x3
          CurBt = High[preMaxPos] - CurA*preMaxBar;  
        }    
      }
      if(High[i] >= Max) {
        Max = High[i];
        MaxBar = CurBar;
      }
    }
// Если разность скользящих средних отрицательна, ищем минимум
    if (fEMA < sEMA) {
      if (FindMax) {
// Пересечение средних. Фиксируем очередной максимум.
        FindMax = FALSE;
        FindMin = TRUE;
        pre2MaxBar = preMaxBar;
        preMaxBar = MaxBar;
        pre2MaxPos = Bars-pre2MaxBar;
        preMaxPos = Bars-preMaxBar;
        Ext[preMaxPos] = Max + Gap;
// Стартовые значения для следующего минимума
        Min = Low[preMaxPos];
        MinPos = preMaxPos;
        for(j=preMaxPos-1;j>i;j--) {
          if(Low[j] <= Min) {
            Min = Low[j];
            MinPos = j;
          }
        }
        MinBar = Bars-MinPos;
// Текущий канал становится предыдущим
        PreA = CurA;       
        PreBt = CurBt;       
        PreBb = CurBb;       
// Рассчитываем новый канал по двум последним максимумам. y = Ax + B
//  A = (y2-y1)/(x2-x1)
//  B = (x2*y1-x1*y2)/(x2-x1)
        if ( preMaxBar-pre2MaxBar != 0) {
//  Рассчитываем А
          CurA = (High[preMaxPos]-High[pre2MaxPos])/(preMaxBar-pre2MaxBar);
// Рассчитываем смещение верха канала Bt
          CurBt = (preMaxBar*High[pre2MaxPos]-pre2MaxBar*High[preMaxPos])/(preMaxBar-pre2MaxBar);
// Рассчитываем смещение дна канала Bb = y3 - A*x3
          CurBb = Low[preMinPos] - CurA*preMinBar;  
        }    
      }
      if(Low[i] <= Min) {
        Min = Low[i];
        MinBar = CurBar;
      }
    }
    CurAT = CurA*CurBar;
    PreAT = PreA*CurBar;
    CurChlTop[i] = CurAT + CurBt;
    CurChlBot[i] = CurAT + CurBb;
    PreChlTop[i] = PreAT + PreBt;
    PreChlBot[i] = PreAT + PreBb;
    ChlsParams[1] = CurA; 
    ChlsParams[2] = CurBt; 
    ChlsParams[3] = CurBb; 
    ChlsParams[4] = PreA; 
    ChlsParams[5] = PreBt; 
    ChlsParams[6] = PreBb; 
  }
  return(0);
}
//+------------------------------------------------------------------+
 
Вы никогда не считаете текущий (нулевой) бар. Зато постоянно пересчитываете 2 предыдущих бара (1-й и 2-й). Только значение, взятое со смещения 3, остаётся неизменным.
 
Вы никогда не считаете текущий (нулевой) бар. Зато постоянно пересчитываете 2 предыдущих бара (1-й и 2-й). Только значение, взятое со смещения 3, остаётся неизменным.
Хм, я думал, что пересчитываю только один. Спасибо, всё проверять пока руки не доходят. Нулевой опускается сознательно. Это общие замечания, или Вы считаете, что проблема с тестером связана именно с этим?
 
Да, именно с этим проблема. Вы можете проверять, какое значение было в индикаторном буфере перед расчётом.
 
Общая закономерность - на графике и в принте всегда совпадают две пары значений, но иногда это дно, а иногда - верх.


Это логично, если индикатор пережат, поэтому, если хай или лоу был достигнут на времени открытия бара - будет совпадение, остальные варианты будут мимо кассы.
 
Slawa: Да, именно с этим проблема. Вы можете проверять, какое значение было в индикаторном буфере перед расчётом.
Для проверки добавил в индикатор буфер, изменённый фрагмент start
    CurAT = CurA*CurBar;
    PreAT = PreA*CurBar;

    CurChlTop2[i] = CurChlTop[i];

    CurChlTop[i] = CurAT + CurBt;
    CurChlBot[i] = CurAT + CurBb;
    PreChlTop[i] = PreAT + PreBt;
    PreChlBot[i] = PreAT + PreBb;

Соответственно, изменён эксперт

int start() {
  CurChlTop = iCustom(NULL,0,"TwoChannels1",377,987,1,1);
  CurChlBot = iCustom(NULL,0,"TwoChannels1",377,987,2,1);
  PreChlTop = iCustom(NULL,0,"TwoChannels1",377,987,3,1);
  PreChlBot = iCustom(NULL,0,"TwoChannels1",377,987,4,1);

  CurChlTop2 = iCustom(NULL,0,"TwoChannels1",377,987,6,1);

  cnt++;
//  if (cnt%600 == 0) {
    Print("Значения индикатора    Дно1: ",CurChlBot,"   Верх1: ",CurChlTop,"   Дно2: ",PreChlBot,"   Верх2: ",PreChlTop);
    Print("Буфер для Верх1 перед расчётом: ",CurChlTop2);
//  }
  return(0);
}

Вот фрагмент real time журнала

2006.02.06 23:22:33	TestChannels1 EURUSD,M1: Буфер для Верх1 перед расчётом: 2147483647
2006.02.06 23:22:33	TestChannels1 EURUSD,M1: Значения индикатора    Дно1: 1.1944   Верх1: 1.2049   Дно2: 1.2   Верх2: 1.2138
2006.02.06 23:22:32	TestChannels1 EURUSD,M1: Буфер для Верх1 перед расчётом: 2147483647
2006.02.06 23:22:32	TestChannels1 EURUSD,M1: Значения индикатора    Дно1: 1.1944   Верх1: 1.2049   Дно2: 1.2   Верх2: 1.2138
2006.02.06 23:21:43	TestChannels1 EURUSD,M1: Буфер для Верх1 перед расчётом: 2147483647
2006.02.06 23:21:43	TestChannels1 EURUSD,M1: Значения индикатора    Дно1: 1.1944   Верх1: 1.205   Дно2: 1.2   Верх2: 1.2138
2006.02.06 23:20:57	TestChannels1 EURUSD,M1: Буфер для Верх1 перед расчётом: 2147483647
2006.02.06 23:20:57	TestChannels1 EURUSD,M1: Значения индикатора    Дно1: 1.1944   Верх1: 1.205   Дно2: 1.2   Верх2: 1.2138

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

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

    CurAT = CurA*CurBar;
    PreAT = PreA*CurBar;
    CurChlTop[i] = CurAT + CurBt;
    CurChlBot[i] = CurAT + CurBb;
    PreChlTop[i] = PreAT + PreBt;
    PreChlBot[i] = PreAT + PreBb;

Вот картинка индикатора в реальном времени (включение в 20:40 по времени графика)

А это тот же интервал из тестера

Значения индикатора совпадают (то есть он как будто одинаково работает на и на истории и в реальном времени и правильно обрабатывает момент включения). Тем не менее в 21:28 принт печатает

Дно1: 1.2003   Верх1: 1.2049   Дно2: 1.2   Верх2: 1.2015

, хотя картинка показывет

Дно1: 1.1944   Верх1: 1.2049   Дно2: 1.2   Верх2: 1.2138


Даже если бы значения индикатора пересчитывались, они не должны меняться, потому что CurBar = Bars-i, то есть отсчёт ведётся от начала графика и с появлением нового бара CurBar для конкретного бара не меняется.

Rosh: Это логично, если индикатор пережат, поэтому, если хай или лоу был достигнут на времени открытия бара - будет совпадение, остальные варианты будут мимо кассы.
Но ведь совпадения распределены по времени отнюдь не случайно.

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

 
Я модифицировал Вашего индикатора таким образом, чтобы он пересчитывался не на последнем баре (эффективный пересчёт), а на всей истории. Всего одна строчка
   counted_bars=0;


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

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

 
Я модифицировал Вашего индикатора таким образом, чтобы он пересчитывался не на последнем баре (эффективный пересчёт), а на всей истории. Всего одна строчка
   counted_bars=0;


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

Я это сделал, значения индикатора совпадают. То есть был запущен терминал, с прикреплённым к М1 индикатором, создан ещё один график М1 на том же символе(eur/usd) и через несколько минут на него посажен индикатор. То, что добавка counted_bars=0; убирает проблему, не доказывает, что тестер корректно имитирует приход котировок (в смысле их обработку). Конечно, дело может быть и в индикаторе, нужно ещё думать.
Теперь про экстремум там, где его не было.
Необходимо учитывать тот факт, что при открытии тестового графика на него подгружаются текущие исторические данные. Которые вполне могут не совпадать с данными, на которых Вы тестировались. Для исключения несоответствия нажимайте галку "Пересчёт".
Паразитная звёздочка была на real-time графике. Кстати, она опять появилась на графике, где индикатор сидел уже при запуске терминала. Но она не появилась на графике, к которому индикатор прикреплялся через несколько минут (то есть, когда индикатор стартовал при уже работающем терминале)
 
Только вот я пока не вижу, чтобы мой индикатор в реал-тайме некорректно работал. А можно Вас попросить погонять его у себя, это ведь не в коде детально разбираться, много времени не отнимет? Теоретически ведь может и мой конкретный комп глючить :). Насчёт прорисовки значений стрелками я думал, не хотелось вешать на терминал столько объектов. Но, пожалуй, надо сделать.
 
Я ведь рассказывал, как можно увидеть неправильность "эффективного пересчёта" Вашего индикатора.
Открыть график, кинуть на него индикатор и подождать несколько новых баров.
Потом открыть такой же график и опять кинуть на него индикатор.
Сравнить результаты на последних барах.

Кстати, при написании mql4-аналогов некоторых наших встроенных индикаторов (например, Parabolic SAR) у нас возникала проблема "эффективного пересчёта". И решить её не очень просто - надо очень хорошо представлять алгоритм расчёта.
Причина обращения: