Паттерны, доступные при торговле корзинами валют. Часть II

Andrei Novichkov | 22 февраля, 2017

Введение

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

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

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


Особенности трендовых объединенных индикаторов

Трендовые объединенные индикаторы не могут быть построены на основе любого трендового родительского индикатора. На это есть ограничения.

Ограничение № 1. Объединенные индикаторы должны располагаться в отдельном окне. Действительно, в отображении такого индикатора в окне с графиком цены не будет смысла. Это доказывается принципом усреднения, по которому строятся объединенные индикаторы: совершенно непонятно, что они будут отображать в этом случае. Возникнет и проблема с единицами измерения: они будут отличаться от тех, которые имеются на графике. Из всего этого следует, что в качестве родительского индикатора не могут выступать ни скользящие средние, ни лента Боллинджера, ни прочие on chart-индикаторы.

Ограничение № 2.  Объединенный индикатор покажет состояние только одной валюты; следовательно, для отображения состояния валютной пары потребуются два объединенных индикатора. Поскольку каждый из них располагается в отдельном окне, то и дополнительных окон нам потребуется два. Причина такого разделения — разница в масштабе. Если объединенные индикаторы на основе осцилляторов всегда изменяются в заранее известных пределах, то с трендовыми это не так. Заранее неизвестны ни максимальное, ни минимальное значения. А значит, ранее описанный подход с применением скользящей средней к разнице между показаниями двух объединенных индикаторов теряет смысл. Подобные совместные вычисления не подходят в случае объединения трендовых индикаторов.

Ограничения на список родительских индикаторов сужают возможности использования трендовых объединенных индикаторов. Например, из всего списка трендовых индикаторов в меню MetaTrader 5 предварительно нам подойдут только ADX и StdDev.

Но это не повод отказываться от решения поставленной задачи. Будем работать с наличным инструментарием, и начнем с уже знакомого нам объединенного трендового индикатора — с индекса валюты корзины.


Индекс валюты корзины со скользящей средней

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

//+------------------------------------------------------------------+
//|                                                 testDistance.mq5 |
//|                                   2016 MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window

#property indicator_buffers 2
#property indicator_plots   2


input color   clr= clrGreen;
input color   clrMA = clrMagenta;
input int maperiod  = 10; //Period MA

double ind[],ma[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
//int h,h1;
int OnInit()
  {
//--- indicator buffers mapping
   ArraySetAsSeries(ind,true);
   SetIndexBuffer(0,ind);        
  
   IndicatorSetString(INDICATOR_SHORTNAME,"testdistance");
   IndicatorSetInteger(INDICATOR_DIGITS,2);
   PlotIndexSetInteger(0,PLOT_DRAW_TYPE,DRAW_LINE);
   PlotIndexSetInteger(0,PLOT_LINE_STYLE,STYLE_SOLID);
   PlotIndexSetInteger(0,PLOT_LINE_WIDTH,2);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,clr);
   PlotIndexSetString(0,PLOT_LABEL,"_tstdistance_");    

   ArraySetAsSeries(ma,true);  
   SetIndexBuffer(1,ma);
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE           );
   PlotIndexSetInteger(1, PLOT_LINE_STYLE, STYLE_SOLID            );
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 1            );
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrMA            );
   PlotIndexSetString (1, PLOT_LABEL, "_tstdistance_MA" );        
//---
   return(INIT_SUCCEEDED);
  }
  
string pair[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY"};
bool bDirect[]={false,false,false,false,true,true,true};
int iCount=7;
  
double GetValue(int shift)
  {
   double res=1.0,t;
   double dBuf[1];
   for(int i=0; i<iCount; i++)
     {
      t=CopyClose(pair[i],PERIOD_CURRENT,shift,1,dBuf);
      if(!bDirect[i]) dBuf[0]=1/dBuf[0];
      res*=dBuf[0];
     }//end for (int i = 0; i < iCount; i++)
   return (NormalizeDouble(MathPow (res, 1/(double)iCount), _Digits) );  
  }  
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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(prev_calculated==0 || rates_total>prev_calculated+1)
     {
      int rt=rates_total;
      for(int i=1; i<rt; i++)
        {
         ind[i]= GetValue(i);
        }
         rt -= maperiod;  
         for (int i = 1; i< rt; i++)
           {
            ma[i] = GetMA(ind, i, maperiod, _Digits);
           }        
     }
   else
     {
         ind[0]= GetValue(0);
          ma[0] = GetMA(ind, 0, maperiod, _Digits);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
void OnDeinit(const int reason)
  {
   string text;
   switch(reason)
     {
      case REASON_PROGRAM:
         text="Indicator terminated its operation by calling the ExpertRemove() function";break;
      case REASON_INITFAILED:
         text="This value means that OnInit() handler "+__FILE__+" has returned a nonzero value";break;
      case REASON_CLOSE:
         text="Terminal has been closed"; break;
      case REASON_ACCOUNT:
         text="Account was changed";break;
      case REASON_CHARTCHANGE:
         text="Symbol or timeframe was changed";break;
      case REASON_CHARTCLOSE:
         text="Chart was closed";break;
      case REASON_PARAMETERS:
         text="Input-parameter was changed";break;
      case REASON_RECOMPILE:
         text="Program "+__FILE__+" was recompiled";break;
      case REASON_REMOVE:
         text="Program "+__FILE__+" was removed from chart";break;
      case REASON_TEMPLATE:
         text="New template was applied to chart";break;
      default:text="Another reason";
     }
   PrintFormat("%s",text);
  }
//+------------------------------------------------------------------+

double GetMA(const double& arr[], int index , int period, int digit) {
   double m = 0;
   for (int j = 0; j < period; j++)  m += arr[index + j];
   m /= period;
   return (NormalizeDouble(m,digit));
}
По этому набору исходных данных индикатор будет отрисовывать индекс доллара с быстрой скользящей средней. Изменим строки 49 и 50 на следующие:

string pair[]={"EURUSD", "EURJPY", "EURCHF", "EURGBP", "EURNZD", "EURCAD", "EURAUD"};
bool bDirect[]={true,true,true,true,true,true,true};
Повторим компиляцию с названием testIndexMA2.mq5. В результате мы получим такой же индикатор, но показывающий индекс евро. Разместим его на EURUSD H1:





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


Отметим пересечения МА и графика индекса доллара вертикальными линиями. Проанализируем получившуюся картину.

Из вышеизложенного можно сделать первый практический вывод: входить в рынок нужно с учетом индекса обеих валют. Лучше всего делать это на одновременном усилении одной валюты с ослаблением другой. Первый "маркер" возникновения такой ситуации — пересечение графика индекса одной из валют пары и МА. Но входить на этом в рынок нельзя: сперва надо дождаться хода второй валюты в противоположном направлении.

Остается открытым вопрос о запаздывании: на каком максимально возможном расстоянии могут отстоять друг от друга точки пересечения графика индекса и МА для двух валют пары? Очевидно, что минимальным (и идеальным) расстоянием будет ноль. По максимальному запаздыванию однозначный ответ дать сложно. Хотя очевидно, что такое расстояние должно присутствовать. Опасно входить в рынок, если ослабление одной валюты и усиление другой будут сильно разнесены во времени. В этом случае мы столкнемся с дивергенцией и, как следствие, с ослаблением тренда.

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


Экспресс-анализ с помощью индикатора ZigZag

Для дальнейшей работы воспользуемся одним из индикаторов на основе ZigZag из этой статьи за авторством уважаемого коллеги Dmitry Fedoseev. Разместим индикатор iUniZigZagPriceSW.mq5 непосредственно на графике индекса USD:




Здесь график ZigZag-a изображен толстой синей линией. Наша цель — проанализировать и систематизировать длину отрезков ZigZag-a. Не исключено, что таким образом мы получим "амплитуду свингов" индекса доллара.

Немного изменим код индикатора:

//+------------------------------------------------------------------+
//|                                                 iUniZigZagSW.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 6
#property indicator_plots   3
//--- plot High
#property indicator_label1  "High"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGreen
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot Low
#property indicator_label2  "Low"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGreen
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot ZigZag
#property indicator_label3  "ZigZag"
#property indicator_type3   DRAW_SECTION
#property indicator_color3  clrRed
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//--- plot Direction
#property indicator_label4  "Direction"
#property indicator_type4   DRAW_LINE
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- plot LastHighBar
#property indicator_label5  "LastHighBar"
#property indicator_type5   DRAW_LINE
#property indicator_style5  STYLE_SOLID
#property indicator_width5  1
//--- plot LastLowBar
#property indicator_label6  "LastLowBar"
#property indicator_type6   DRAW_LINE
#property indicator_style6  STYLE_SOLID
#property indicator_width6  1

#include <ZigZag\CSorceData.mqh>
#include <ZigZag\CZZDirection.mqh>
#include <ZigZag\CZZDraw.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum EDirection
  {
   Dir_NBars=0,
   Dir_CCI=1
  };
//--- input parameters
input EDirection  DirSelect=Dir_NBars;
input int                  CCIPeriod   =  14;
input ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL;
input int                  ZZPeriod=14;
input string               name="index-usd-zz.txt";

CZZDirection*dir;
CZZDraw*zz;

//--- indicator buffers
double         HighBuffer[];
double         LowBuffer[];
double         ZigZagBuffer[];
double         DirectionBuffer[];
double         LastHighBarBuffer[];
double         LastLowBarBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int h;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   switch(DirSelect)
     {
      case Dir_NBars:
         dir=new CNBars(ZZPeriod);
         break;
      case Dir_CCI:
         dir=new CCCIDir(CCIPeriod,CCIPrice);
         break;
     }
   if(!dir.CheckHandle())
     {
      Alert("Ошибка загрузки индикатора 2");
      return(INIT_FAILED);
     }
   zz=new CSimpleDraw();
//--- indicator buffers mapping
   SetIndexBuffer(0,HighBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,LowBuffer,INDICATOR_DATA);
   SetIndexBuffer(2,ZigZagBuffer,INDICATOR_DATA);
   SetIndexBuffer(3,DirectionBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(4,LastHighBarBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(5,LastLowBarBuffer,INDICATOR_CALCULATIONS);
   h=FileOpen(name,FILE_CSV|FILE_WRITE|FILE_ANSI,',');
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

   if(CheckPointer(dir)==POINTER_DYNAMIC)
     {
      delete(dir);
     }
   if(CheckPointer(zz)==POINTER_DYNAMIC)
     {
      delete(zz);
     }
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int ind=0;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[]
                )
  {
   int start;

   if(prev_calculated==0)
     {
      start=0;
     }
   else
     {
      start=prev_calculated-1;
     }

   for(int i=start;i<rates_total;i++)
     {
      HighBuffer[i]=price[i];
      LowBuffer[i]=price[i];
     }

   int rv;
   rv=dir.Calculate(rates_total,
                    prev_calculated,
                    HighBuffer,
                    LowBuffer,
                    DirectionBuffer);
   if(rv==0)return(0);
   zz.Calculate(rates_total,
                prev_calculated,
                HighBuffer,
                LowBuffer,
                DirectionBuffer,
                LastHighBarBuffer,
                LastLowBarBuffer,
                ZigZagBuffer);

   if(ind<= 10) ind++;
   if(ind == 10)
     {
      double mx=0,mn=1000000;
      double lg;
      for(int i=0;i<rates_total;i++)
        {
         if(ZigZagBuffer[i]==0 || ZigZagBuffer[i]==EMPTY_VALUE) continue;
         if(ZigZagBuffer[i] > mx) mx = ZigZagBuffer[i];
         if(ZigZagBuffer[i] < mn) mn = ZigZagBuffer[i];
        }
      lg=mx-mn;
      PrintFormat("Min index: %.05f Max index: %.05f Length: %.05f",mn,mx,lg);
      lg/=100;
      double levels[100];
      int    count[100];
      ArrayInitialize(count,0);
      for(int i=1; i<101; i++) levels[i-1]=NormalizeDouble(lg*i,_Digits);
      mn=0;
      for(int i=0;i<rates_total;i++)
        {
         if(ZigZagBuffer[i]==0 || ZigZagBuffer[i]==EMPTY_VALUE) continue;
         if(mn==0) mn=ZigZagBuffer[i];
         else
           {
            lg=MathAbs(mn-ZigZagBuffer[i]);
            for(int j=0; j<100; j++)
              {
               if(lg<levels[j])
                 {
                  count[j]++;
                  break;
                 }
              }
            mn=ZigZagBuffer[i];
           }
        }
      for(int i=0; i<100; i++)
        {
         PrintFormat("%d level: %.05f count: %d",i,levels[i],count[i]);
         FileWrite(h,i,levels[i],count[i]);
        }
      FileClose(h);
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+
Индикатор начинает работать и на десятом тике определяет максимально возможный размер отрезка ZigZag-a. Принимая этот размер за 100%, вычислим величину одного процента, затем упорядочим все прочие величины отрезков ZigZag-a. В результате имеем массив с количеством отрезков ZigZag-a, величиной от 1% до 100% от максимального. Результаты выведены в файл и диаграмму для Libre Office Calc (с ними вы можете познакомиться подробнее, загрузив их из архива ZZdata.zip). Воспроизведем здесь начало файла и соответствующий участок диаграммы:


Номер по порядку Величина длины отрезков
Количество отрезков
0
0.01193 2975
1
0.02387
850
2
0.0358
197
3
0.04773
54
4
0.05967
17
Другие участки диаграммы нам уже не так интересны, так как в основном заполнены нулями. Можно продолжить и уточнить это исследование, если уменьшить шаг, но мы остановимся на уже полученных результатах. Основной практический вывод мы уже можем сделать:

Разумеется, возникает вопрос о величине такого "критического" отрезка. Определить ее можно, применив к вышеприведенным данным статистические методы. При желании, трейдер может выполнить такой анализ самостоятельно, руководствуясь своими соображениями о допустимом риске. Для себя я принял, что величина "критического" отрезка не должна быть 0.03.

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

Анализ, подобный этому, можно выполнить для всех валют корзин и для многих таймфреймов. Было бы интересно пронаблюдать, как при этом будут изменяться диаграммы для разных наборов "валюта — таймфрейм". Разработчик сразу заметит, что при необходимости алгоритм легко можно воспроизвести в коде. Но при этом нельзя забывать прописную истину: не следует ориентироваться только на один сигнал, нужно искать подтверждения. Продемонстрированную методику можно применять и ко многим обычным трендовым индикаторам. А вот в случае осцилляторов она совершенно бессмысленна и результата не даст. 

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


Совместное использование различных объединенных индикаторов

Вспомним, что у нас уже есть сконструированный объединенный индикатор на основе WPR. В прошлой статье мы подробно изучили его, его код и доступные паттерны. На этот раз попытаемся применить его в паре с объединенным индикатором индекса. Получившаяся структура должна быть вполне работоспособна, т.к. многие торговые системы строятся именно таким образом: трендовый индикатор + осциллятор.

Итак, возьмем индикаторы testDistance.mq5, testWPR.mq5 и testWPRjpy.mq5 из прошлой статьи и разместим на графике вместе с объединенным индикатором индекса EUR testDistance.mq5. В прошлой статье мы изучали график EURJPY, поэтому индикатор testWPRjpy нужно переписать для работы на USD. Название индикатора сохраним, чтобы не переделывать индикатор testDistance.mq5. Все индикаторы из этого раздела можно найти в архиве wpr.zip.

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





На изображении отмечены возможные места входа в рынок (не все). Более надежными стоит считать те входы, где индикатор в верхнем (это объединенный WPR) и в нижнем окне (это объединенный индекс евро) демонстрируют нам однонаправленные паттерны. Здесь эти входы обозначены номерами  1 и 6. Это точки пересечения графиков индикатора со скользящей средней.

Интересен вход № 2, т.к. в этом месте объединенный WPR дарит нам просто академический паттерн с пересечением линии перепроданности. Прочие входы не демонстрируют нам достаточного уровня подтверждения. И хотя реальный вход в рынок в оставшихся местах и не привел бы к убыткам, но идти на такую степень риска при реальной торговле я бы не стал.

Насколько правильно использовать объединенный индикатор, отрисовывающий разницу между объединенными WPR, входящими в корзину валют? Не правильнее ли было бы использовать в паре с объединенным индексом евро объединенный WPR для евро? Попробуем это сделать, заменив testDistance.mq5 на testWPR.mq5:


Итак, на этом рисунке в нижнем окне мы видим индикатор объединенного WPR для евро. Оправдано ли его размещение? В данном случае очевидно, что оправдано. Этот индикатор подкорректировал наш вход в точках №№ 2 и 6 на одну свечу (направление указано стрелками). Не слишком уверенно подтвержден вход в точке № 1. Вход в точках № 5 и № 3 не рекомендован. Точку №4 индикатор скорректировал в новую точку № 7.

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


Вы можете возразить мне: "А что тут особенного? Совместное применение нескольких индикаторов не новость, зачем об этом специально рассказывать в статье? Да и паттерны здесь ни при чем."

Однако целью данной части статьи было ответить на три вопроса:

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


Заключение

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

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

Программы, используемые в статье:

 # Имя
Тип
 Описание
1 testIndexMA.mq5 Индикатор Тестовый объединенный индикатор доллара со скользящей средней.
2 testIndexMA2.mq5 Индикатор Тестовый объединенный индикатор евро со скользящей средней.
3 testIndexZig-Zag1.mq5 Индикатор Тестовый индикатор ZigZag с возможностью измерения  и протоколирования длины отдельных отрезков.
4 testWPR.mq5 Индикатор Тестовый объединенный индикатор WPR для евро
5 testWPRjpy.mq5 Индикатор Тестовый объединенный индикатор WPR для доллара
6 testDistance.mq5 Индикатор Тестовый объединенный индикатор, отрисовывающий разницу между двумя другими, в данном случае между testWPR.mq5 и testWPRjpy.mq5 (между евро и долларом)