Show Must Go On... или очередное возвращение к ZigZag'у

Rider | 30 мая, 2008


Введение

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

Давайте немного порассуждаем. Хотите верьте, хотите нет, но именно с этого все и начинается.

Лирика

Все мы, придя и ОСТАВШИСЬ на этом рынке, рано или поздно понимаем, что он не так прост, как нам казалось в начале. Осознав это, мы начинаем читать, кто умеет, конечно. Вот только воспринимаем прочитанное как-то уж очень своеобразно. Если коротко, то выбираем не то, что Правильно, а то, что Просто. Соответственно, и на вооружение берем только то, что лежит на поверхности, очевидно для понимания и легко (быстро) переводится на язык алгоритмов. Примеров множество. Вот только один из них, может не самый удачный, но какой уж есть.

Все знают и помнят базовый тезис ТА, никем еще не опровергнутый, как и строго не доказанный, впрочем:

Цена - это наше ВСЁ!

Понимая это прямо и в лоб, мы начинаем искать не опорные точки рынка, а непременно максимумы и минимумы – это же проще и очевидней. По линеечке чертить уровни сопротивления и поддержки, отсчитывать от них фибоуровни, считать циклы в барах и т.д. и т.п. Дальше больше – начинаем выдергивать из кем-то созданных торговых систем только то, что доступно нашему пониманию, зачастую не обращая внимания ни на предостережения, ни даже на то, для какого рынка и в какое время эта система создавалась ….. Что еще хуже, мы начинаем упрощать… упрощать Ганна(!!!), которому, как предполагаю, удалось найти ( или очень близко подойти) решение задачи Цены-Времени.... бред... как можно упростить то, что еще до конца никем не понято?

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

Нам, при всем нашем желании заработать денег, отчего-то никак не приходит в голову, что иногда нужно просто остановиться и немного подумать. Полезная штука. Начинаешь отчетливо осознавать всю правоту выражения, что "человек - единственное живое существо, которое раз за разом совершая одни и те же действия, надеется получить другой результат". Мы же не дрова колем, правда? :)

Что-то я увлекшись. Наверное потому, что и про себя пишу. Пора закругляться. Давайте просто поймем, что не все гуру - Гуру, а те кто по настоящему Гуру, никогда не скажут всего...еще и потому, что понимают, что пережеванное невкусно и не приносит никакой пользы.

К теме.

Мультифреймовый Фрактальный ZigZag


Мне потребовался индикатор, который отображает не просто минимумы и максимумы, а логически обоснованные движениями цены экстремальные точки рынка (ЛОЭТР - может приживется?), да еще и подтвержденные, по возможности. Задача сделать из него генератор торговых сигналов не ставилась изначально. Сначала попытался из стандартного (не того, что в МТ4, а в широком смысле "стандартного") ZigZag конструировать, но кое-что насторожило и заставило от этой идеи отказаться:

Последнее, лично для меня, самое неприемлемое. Не приглянулся он мне, в общем. Ничего личного.

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

Свое, оно может и не лучше, но, однозначно, роднее. Потому чужой код не искал, написал "свой" простенький индикатор типа фрактала, вернее его подобие – правила выбора точек немного от стандартных отличаются. Попробовал его с разных таймфреймов через iCustom повызывать, но понял, что рациональней на текущем (рабочем) таймфрейме (ТФ) все посчитать. Дальше логика программирования уже сама все подсказала и про модульную конструкцию, которая в дальнейшем напрашивается, и про нестандартные ТФ. Вот и получилось, то что получилось.

На картинках то, как он отображается с параметрами ТФ 1440, 360, 60. График H1 выбран для наглядности, чтобы можно было увидеть, что черная линия (60) не все фрактальные точки забирает, а кое-какие отбрасывает. Первая картинка - чтобы на кончик посмотреть, как это в динамике выглядит, а вторая, просто из середины графика.

Цветовое решение не самое удачное, в аттаче картинка, как сам на него смотрю.


Код


Давайте посмотрим каким образом это реализовано, не "на пальцах" же нам объясняться ;)
Индикатор формирует на рабочем текущем (ТФ) последовательность узлов зигзага, вычисленных на сэмулированных трех старших. Работает на всех и со всеми, в т.ч. и нестандартными ТФ, со следующими ограничениями, реализованными в коде:
- старшие ТФ должны быть кратны рабочему, если это не так, то принудительно устаавливаются ближайшие правильные значения;
- рабочий ТФ не более наименьшего из старших;
- периоды в параметрах указываются в минутах и должны быть выставлены в порядке убывания;
- период самого большого ТФ не более 43200 (месяц) - это не предел, можно и больше;
Особенность в том, что для каждого ТФ используется только один буфер. Два не нужно, так как при разумной комбинации ТФ, вероятность появления двух разнонаправленных экстремумов на одном баре рабочего ТФ очень мала.

Вот этот фрагмент:

//-----------------------------------------------------------------------
// MF_Fractal_ZZ_3in1.mq4
//-----------------------------------------------------------------------
#property copyright "Copyright © 2008, BiViSi Corp."
#property link      "riderfin@bk.ru"
#property link      "ICQ 499949112"

#property indicator_chart_window    
#property indicator_buffers 3
//---- стиль  индикаторной линии
#property indicator_color1 Blue 
#property indicator_color2 Red
#property indicator_color3 Yellow        
#property indicator_style1 0
#property indicator_style2 0
#property indicator_style3 0
#property indicator_width1 5
#property indicator_width2 3
#property indicator_width3 1
//---- ВХОДНЫЕ ПАРАМЕТРЫ ИНДИКАТОРА 
extern int VolExt=50; // вычисление "VolExt+1" последних опорных точек
extern int TFLarge=1440;
extern int TFMidle=240;
extern int TFSmall=60;
//---- Variables 
double Large[],Midle[],Small[];  // опорные точки (буферы индикатора)
datetime PrevTimePer[4];         // времена последнего расчета по каждому ТФ
datetime PrevTimeCalc=0; 
double P60,CP60;
int CurPeriod, ErrorTF=0, NumberExt, Per,  largelast=0, midlelast=0, smalllast=0;
//-----------------------------------------------------------------------
int init() 
{
   // инициализация 
   IndicatorBuffers(3); // строка "на перспективу" :)
   SetIndexBuffer(0,Large); SetIndexStyle(0,DRAW_SECTION);
   SetIndexEmptyValue(0,0.0);
   SetIndexBuffer(1,Midle); SetIndexStyle(1,DRAW_SECTION);
   SetIndexEmptyValue(1,0.0); 
   SetIndexBuffer(2,Small); SetIndexStyle(2,DRAW_SECTION);
   SetIndexEmptyValue(2,0.0);
   ArrayInitialize(PrevTimePer,0);
   CurPeriod=Period(); CP60=CurPeriod*60;
   // Ограничения:
   // контроль ТФ и введенных параметров
   if (MathCeil(TFSmall/CurPeriod) != TFSmall/CurPeriod) 
      TFSmall=MathCeil(TFSmall/CurPeriod)*CurPeriod;
   if (MathCeil(TFMidle/CurPeriod) != TFMidle/CurPeriod)
      TFMidle=MathCeil(TFMidle/CurPeriod)*CurPeriod;
   if (MathCeil(TFLarge/CurPeriod) != TFLarge/CurPeriod)
       TFLarge=MathCeil(TFLarge/CurPeriod)*CurPeriod;
   if (CurPeriod > TFSmall) 
      {Alert ("Период графика должен быть меньше или равен ", TFSmall," мин.");
       ErrorTF=1;return;}
   if (TFSmall >= TFMidle || TFMidle >= TFLarge || TFLarge>43200)
      {Alert ("Некорректный выбор таймфреймов для расчета!!!"); ErrorTF=1;return;}
   return;              
}
//--------------------------------------------------------------------

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

Отказ от использования конструкции типа: int IC=IndicatorCounted(); и т.д., обусловлен самим алгоритмом расчета индикатора, который и без того обеспечивает достаточное быстродействие (см. Force #1-3 в коде), которое, кстати, можно еще и увеличить, чуть позже об этом.

//--------------------------------------------------------------------
int start()
{
   if ( ErrorTF==1 ) return; // некорректный таймфрейм   
   FractalCalc(); 
   return;
}
//======================================================================
// Поиск 5-ти барных фракталов и вычисление узлов зигзага 
// на эмуляции старших ТФ, удаление лишних и отображение на текущем ТФ
//======================================================================
void FractalCalc ()
{   
   // Force  №1 - выч только на полностью сформировавшемся баре рабочего ТФ
   if (PrevTimeCalc == Time[0]) return; else PrevTimeCalc=Time[0];
   int y, x, k, i, j, extr=0; 
   // время последнего бара тек ТФ, закрывающего бар №1-5 старшего ТФ
   int t1, t2, t3, t4, t5;                     
   // номер последнего бара тек ТФ, закрывающего бар №1-5 старшего ТФ
   int limit1, limit2, limit3, limit4, limit5; 
   // номера баров тек ТФ с пиками и донышками соотв барам 1-5 старшего ТФ
   int up1,up2,up3,up4,up5,dn1,dn2,dn3,dn4,dn5;
      
   for (y=1; y<=3; y++) // цикл по рассчитываемым ТФ
      {
      if (y==1) Per=TFLarge; if (y==2) Per=TFMidle; if (y==3) Per=TFSmall;
      P60=Per*60;
      // Force №2 - Вычисляем изломы только с формированием бара старшего ТФ
      if (PrevTimePer[y] !=0)
         { 
         if (Per<43200 && (Time[0] - PrevTimePer[y])<P60 )continue;
         if (Per==43200 && Month()==TimeMonth(PrevTimePer[y]))continue;
         }
      // Обработка пропуска баров
      // Если прямолинейно PrevTimePer[y]=Time[0], то в случае пропуска баров
      // на рабочем ТФ произойдет сдвиг всей цепочки расчета на величину пропуска
      PrevTimePer[y]=MathCeil(Time[0]/Per/60)*P60; 
      
      NumberExt=0;  extr=0;
      k=Per/CurPeriod;
      // ограничение цикла - в зависимости от того какой ТФ обсчитываем
      // и от последнего найденного фрактала
      i=MathCeil(Bars/k)-5;
      // Force #3 - обсчитываем, начиная с последнего излома
      if(y==1 && largelast !=0) i=largelast+k;
      if(y==2 && midlelast !=0) i=midlelast+k;
      if(y==3 && smalllast !=0) i=smalllast+k;
      for (x=1; x<=i; x++) 
         {
         // находим пики и донышки
         // время начала последнего бара тек ТФ, закрывающего бар №1 старшего ТФ
         if (PrevTimePer[y] !=0) t1=PrevTimePer[y]-x*P60+(k-1)*CP60;
         else t1=MathCeil(Time[0]/Per/60)*P60-x*P60+(k-1)*CP60;
         t2=t1-P60; t3=t2-P60; t4=t3-P60; t5=t4-P60;
         limit1=iBarShift(NULL,0,t1, false); limit2=iBarShift(NULL,0,t2, false);
         limit3=iBarShift(NULL,0,t3, false); limit4=iBarShift(NULL,0,t4, false);
         limit5=iBarShift(NULL,0,t5, false);         
         up1=iHighest(NULL,0,MODE_HIGH,k,limit1); up2=iHighest(NULL,0,MODE_HIGH,k,limit2);
         up3=iHighest(NULL,0,MODE_HIGH,k,limit3); up4=iHighest(NULL,0,MODE_HIGH,k,limit4);
         up5=iHighest(NULL,0,MODE_HIGH,k,limit5);
         dn1=iLowest(NULL,0,MODE_LOW,k,limit1); dn2=iLowest(NULL,0,MODE_LOW,k,limit2);
         dn3=iLowest(NULL,0,MODE_LOW,k,limit3); dn4=iLowest(NULL,0,MODE_LOW,k,limit4);
         dn5=iLowest(NULL,0,MODE_LOW,k,limit5);

         // ищем опорные точки
         if(High[up3]>High[up2] && High[up3]>High[up1] && High[up3]>=High[up4] && High[up3]>=High[up5])
            {
            if (y==1){Large[up3]=High[up3];largelast=up3;}
            if (y==2){Midle[up3]=High[up3];midlelast=up3;}
            if (y==3){Small[up3]=High[up3];smalllast=up3;}
            NumberExt++;  extr++;
            }
         if(Low[dn3]<Low[dn2] && Low[dn3]<Low[dn1] && Low[dn3]<=Low[dn4] && Low[dn3]<=Low[dn5])
            {
            if (y==1){Large[dn3]=Low[dn3];largelast=dn3;}
            if (y==2){Midle[dn3]=Low[dn3];midlelast=dn3;}
            if (y==3){Small[dn3]=Low[dn3];smalllast=dn3;}
            NumberExt++; extr++;
            }
         if (NumberExt>VolExt) break;   
         } 
      }

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

Следующий блок производит своего рода «прополку» - удаление лишних экстремумов (это когда между двумя пиками несколько донышек появляются и наоборот) для формирования правильного зигзага: из 2-х и более подряд идущих пиков\донышков выбираются максимальный\минимальный, а при равенстве - с наименьшим номером бара.... лишние обнуляются. С алгоритмом можно поспорить и варианты имеют место быть, но решил пока сделать так.
В коде есть закоментированные строки, которые позволяют избавиться от рассогласования в появлении пиков\донышков на различных ТФ, но их нужно дорабатывать - в том виде как сейчас, это работает некорректно. Временно отказался от этой идеи, кому интересно может попробовать сделать самостоятельно.
В этом же фрагменте скрыта возможность еще больше ускорить его работу, если использовать вместо конструкции if (NumberExt>VolExt) break; заранее рассчитанный сдвиг, но и от этого я тоже пока отказался.

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

   if (extr==0) return;
   for (y=1; y<=3; y++)
      {
      if (y==1) j=ArraySize(Large);if (y==2)j=ArraySize(Midle);
      if (y==3)j=ArraySize(Small);      
      int min=0, max=0, extmin=0, extmax=0;
      NumberExt=0;      
      for (x=1;x<=j;x++)
         {
         if (y==1)
            {
            if (Large[x] == 0.0 ) continue;
            if (Large[x] == High[x])
               {
               NumberExt++; extmax++; extmin=0;
               if (extmax==2)
                  {
                  if (Large[max]>=Large[x])Large[x]=0.0;
                  else {Large[max]=0.0;max=x;}
                  extmax--;  
                  }
               else max=x;
               }
            if (Large[x] == Low[x])
               {
               NumberExt++; extmax=0; extmin++;
               if (extmin==2)
                  {
                  if (Large[min]<=Large[x])Large[x]=0.0;
                  else {Large[min]=0.0;min=x;}
                  extmin--;  
                  }
               else min=x;
               }
            }         
         if (y==2)
            {
            if (Midle[x] == 0.0 ) continue;
            if (Midle[x] == High[x])
               {
               NumberExt++; extmax++; extmin=0;
               if (extmax==2)
                  {
                  if (Midle[max]>=Midle[x])Midle[x]=0.0;
                  else {Midle[max]=0.0;max=x;}
                  extmax--;  
                  // можно и попроще, как выше, но.... убираем расхождения
                  /*
                  if (Midle[max]>Midle[x])Midle[x]=0.0; 
                  if (Midle[max]==Midle[x])
                     {
                     if (Large[x] == High[x]) {Midle[max]=0.0;max=x;}
                     else Midle[x]=0.0; 
                     }
                  if (Midle[max]<Midle[x]){Midle[max]=0.0;max=x;}
                  */
                  }
               else max=x;
               }
            if (Midle[x] == Low[x])
               {
               NumberExt++; extmax=0; extmin++;
               if (extmin==2)
                  {
                  if (Midle[min]<=Midle[x])Midle[x]=0.0;
                  else {Midle[min]=0.0;min=x;}
                  extmin--;  
                  // можно и попроще, как выше, но.... убираем расхождения
                  /*
                  if (Midle[min]<Midle[x])Midle[x]=0.0; 
                  if (Midle[min]==Midle[x])
                     {
                     if (Large[x] == Low[x]) {Midle[min]=0.0;min=x;}
                     else Midle[x]=0.0; 
                     }
                  if (Midle[min]>Midle[x]){Midle[min]=0.0;min=x;}
                  */
                  }
               else min=x;
               }
            }         
         if (y==3)
            {
            if (Small[x] == 0.0 ) continue;
            if (Small[x] == High[x])
               {
               NumberExt++; extmax++; extmin=0;
               if (extmax==2)
                  {
                  if (Small[max]>=Small[x])Small[x]=0.0;
                  else {Small[max]=0.0;max=x;}
                  extmax--;  
                  // можно и попроще, как выше, но.... убираем расхождения
                  /*
                  if (Small[max]>Small[x])Small[x]=0.0; 
                  if (Small[max]==Small[x])
                     {
                     if (Midle[x] == High[x]) {Small[max]=0.0;max=x;}
                     else Small[x]=0.0; 
                     }
                  if (Small[max]<Small[x]){Small[max]=0.0;max=x;}
                  */
                  }
               else max=x;
               }
            if (Small[x] == Low[x])
               {
               NumberExt++; extmax=0; extmin++;
               if (extmin==2)
                  {
                  if (Small[min]<=Small[x])Small[x]=0.0;
                  else {Small[min]=0.0;min=x;}
                  extmin--;  
                  // можно и попроще, как выше, но.... убираем расхождения
                  /*
                  if (Small[min]<Small[x])Small[x]=0.0; 
                  if (Small[min]==Small[x])
                     {
                     if (Midle[x] == Low[x]) {Small[min]=0.0;min=x;}
                     else Small[x]=0.0; 
                     }
                  if (Small[min]>Small[x]){Small[min]=0.0;max=x;}
                  */
                  }
               else min=x;
               }
            }         
         if (NumberExt>VolExt) break;
         }
      }         

}


Заключение


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

Спросите, почему бы не сделать эту коррекцию сразу? Мне пока достаточно того что получилось.... ПОКА достаточно :)
А еще мне известно, что готовый работоспособный код можно вылизывать до бесконечности. Это как карандаши затачивать.... за сим увлекательным занятием можно забыть, что ими рисовать нужно. Я порисую, уже очень хочется :)

Что дальше и Что с этим делать? Готовых решений нет. Это, всего лишь, инструмент, а что с его помощью ваять, Буратино или табуретку, каждый Папа Карло решает сам.
Могу только сказать, что в сухом остатке 5 свободных индикаторных буферов, возможность легко добавлять новые модули и вполне адекватная реакция на вызов через iCustom().... фантазируйте.

Самое вкусное, БЛАГОДАРНОСТИ.

ANG3110 - за самое ценное - идеи, которые, собственно, и подтолкнули к написанию этого индикатора;
Korey
- за поддержку и помощь в диагностике ТimeCurrent и оптимизации вычислений;
Форумчанам
, которые вольно или невольно, осознано или неосознанно, но иногда высказывают мысли, которые хочется "думать".