English 中文 Español Deutsch 日本語 Português
Тестирование паттернов, возникающих при торговле корзинами валютных пар. Часть II

Тестирование паттернов, возникающих при торговле корзинами валютных пар. Часть II

MetaTrader 5Трейдинг | 13 октября 2017, 09:53
3 343 2
Andrei Novichkov
Andrei Novichkov

Введение

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

Напомню, что результатом проведенного тестирования стала оценка месторасположения уровней перекупленности и перепроданности. Для уровня перекупленности это значение располагается в интервале 60% - 70%, а уровень перепроданности находится между -60% и -70%. Тест работы самого паттерна показал его прибыльность на таймфрейме H4 по всем корзинам валютных пар. На интервале D1 было зафиксировано незначительное количество сделок. Интервал H1 продемонстрировал существенный убыток.

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

Инструменты для исследований

Основной инструмент для наших исследований — уже хорошо знакомый индикатор Объединенный WPR с периодом 20. На его график наложим МА с периодом 10. Такой короткий период выбран, поскольку у нас нет цели мониторить глобальные процессы: нас интересуют локальные тенденции. Поэтому длинные периоды не рассматриваются.

Как и в прошлой статье, изучим три таймфрейма — H1, H4 и D1. Мы помним, как на них показал себя первый паттерн, логично будет рассмотреть на этих таймфреймах и следующий. И, конечно же, не будем забывать, что накопленный опыт можно использовать для исследований и на любых других тайфреймах и корзинах валют.

Базовая терминология и основы методики изложены здесь.

Паттерн для исследований

Этот паттерн хорошо известен из классического технического анализа, мы уже описывали его, поэтому здесь просто напомним основные моменты и нюансы, характерные для торговли корзинами валютных пар:

  • Трейдер получает сигнал на вход в рынок, когда график объединенного индикатора пересекает график скользящей средней.

Сигналов всего два:

  • Сигнал на покупку корзины валютных пар — когда график МА пересекается графиком объединенного индикатора снизу вверх.
  • Сигнал на продажу корзины валютных пар — когда график МА пересекается графиком объединенного индикатора сверху вниз.
  • Трейдер входит в рынок покупкой или продажей корзины валют, для которой получен сигнал.
  • Трейдер выходит из рынка, получив сигнал, противоположный сигналу на вход.

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

Из опыта применения МА к графикам обычных валютных пар мы помним, что для быстрых скользящих средних всегда есть достаточно большое количество сигналов на вход. Мы вправе ожидать, что и в нашем случае таких сигналов будет не меньше. Для проверки этого утверждения написан индикатор testWPR&MA.mq5. Вы можете найти его в приложении к статье.

//+------------------------------------------------------------------+
//|                                                      testWPR.mq5 |
//|                                        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_minimum -100
#property indicator_maximum 100


#define LG 7

#property indicator_buffers 2
#property indicator_plots   2

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

string pair[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY"};
bool bDirect[]={false,false,false,false,true,true,true};

int h[LG];
double ind[],ma[];

int iUp,iDw;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   for(int i=0; i<LG; i++)
     {
      h[i]=iWPR(pair[i],0,WPR);
     }

   IndicatorSetString(INDICATOR_SHORTNAME,"testWPRusd");
   IndicatorSetInteger(INDICATOR_DIGITS,2);
   IndicatorSetInteger(INDICATOR_LEVELS,2);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE,0,STYLE_SOLID);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE,1,STYLE_SOLID);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR,0,clrRed);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR,1,clrRed);
   IndicatorSetInteger(INDICATOR_LEVELWIDTH,0,1);
   IndicatorSetInteger(INDICATOR_LEVELWIDTH,1,1);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-60);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,1,60);

   ArraySetAsSeries(ind,true);
   SetIndexBuffer(0,ind);
   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,"_tstWPRusd_");

   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,"Middle_Basket_line_MA");

   iUp=iDw=0;
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double GetValue(int shift)
  {
   double dBuf[1];
   double res=0.0;
   for(int i=0; i<LG; i++)
     {
      CopyBuffer(h[i],0,shift,1,dBuf);
      if(bDirect[i]==true)
         res+=dBuf[0];
      else
         res+=-(dBuf[0]+100);
     }//end for (int i = 0; i < iCount; i++)      
   res=res/LG;
   return (NormalizeDouble((res + 50) * 2, _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-WPR;
      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);
        }
      rt--;
      for(int i=1; i<rt; i++)
        {
         if(ind[i] > ma[i] && ind[i+1] < ma[i+1]) {iUp++; continue;}
         if(ind[i] < ma[i] && ind[i+1] > ma[i+1]) {iDw++; continue;}
        }
      PrintFormat("BUY count: %d SELL count: %d",iUp,iDw);
     }
   else
     {
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

void OnDeinit(const int reason)
  {
   for(int i=0; i<LG; i++)
     {
      if(h[i]!=INVALID_HANDLE) IndicatorRelease(h[i]);
     }
   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));
  }
//+------------------------------------------------------------------+

Индикатор подсчитывает число пересечений скользящей средней объединенным индикатором за всю имеющуюся историю. Разместим индикатор на графике EURUSD и получим данные по корзине USD для интересующих нас таймфреймов:

  Полученные сигналы
H1 H4 D1
Buy Sell Buy Sell Buy Sell
EURUSD 8992 8992 2448 2449 550 551
Глубина истории 2005.09.08 2004.10.11 2000.02.28

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

Проиллюстрируем работу индикатора скриншотом:

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

Для ближайших исследований обозначим следующую логику работы:

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

Очевидно, что при отсутствии какой-либо дополнительной фильтрации методика предполагает постоянное присутствие в рынке. Скорее всего, такая фильтрация нам понадобится, но этот вопрос мы рассмотрим позже. А пока в глаза бросается явно разное качество сигналов на скриншоте:

  • четкие, ясные сигналы в точках 7 и 8 (синий);
  • неплохой сигнал в точке 5 (красный);
  • скопление разнонаправленных сигналов в точках 2, 3, 4 (красный).

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

Искомый, "хороший" паттерн (сигнал)
Отсутствие паттерна, "плохой" сигнал





На этих изображениях зеленой линией изображен график объединенного WPR для USD, малиновой линией — скользящая средняя,  вертикальными синими линиями — границы паттерна.

Примем, что для паттернов на вход значения расстояний Delta1 и Delta2 не должны быть меньше 5%.

Но и "плохие" паттерны, как на изображении справа, мы тоже используем. Они не будут давать сигналов на вход, но будут предупреждать о возможном изменении или замедлении тренда. Таким образом, их можно использовать для выхода из рынка или для фильтрации, что мы и будем делать.

Начало тестирования

Для дальнейшей работы нам потребуется советник, код которого можно найти в прикрепленном файле testEAbasket.mq5:

//+------------------------------------------------------------------+
//|                                                 testEAbasket.mq5 |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade\\Trade.mqh>

#define LG 7

enum BSTATE 
  {
   BCLOSE = 0,
   BBUY   = 1,
   BSELL  = 2
  };
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
input int wpr = 20;
input int ma  = 10;
input double lt = 0.01; //lot

string pair[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY"};
bool bDirect[]={false,false,false,false,true,true,true};

int h;
ulong  Ticket[LG];

double m[1],ml;
double w[1],wl;

BSTATE g_state;

double g_dMinSize = 5.0;

int OnInit()
  {
   h = iCustom(NULL,0,"testWPR&MA",wpr,ma);
   if (h == INVALID_HANDLE) {
      Print("Error while creating testWPReur");
      return (INIT_FAILED);
   }
   
   g_state = BCLOSE;
   
   EventSetTimer(1);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(h);
   EventKillTimer();
      
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(IsNewCandle()) 
     {
         wl = w[0];
         CopyBuffer(h,0,1,1,w);
         ml = m[0];
         CopyBuffer(h,1,1,1,m);
         if ( w[0] > m[0] && wl < ml) {
            if (g_state != BCLOSE) CloseAllPos();
            if ( w[0] - m[0] >= g_dMinSize && ml - wl >= g_dMinSize) {
               EnterBuy(lt);
               g_state = BBUY;
            }   
         }     
         if ( w[0] < m[0] && wl > ml) {
            if (g_state != BCLOSE) CloseAllPos();
            if ( m[0] - w[0] >= g_dMinSize && wl - ml >= g_dMinSize) {
               EnterSell(lt);
               g_state = BSELL;
            }   
         }            
     }      
  }
//+------------------------------------------------------------------+
  
void CloseAllPos() 
  {

   CTrade Trade;
   Trade.LogLevel(LOG_LEVEL_NO);

   for(int i=0; i<LG; i++) 
     {

      Trade.PositionClose(Ticket[i]);
     }

     g_state = BCLOSE;

  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void EnterBuy(double lot) 
  {

   CTrade Trade;
   Trade.LogLevel(LOG_LEVEL_NO);

   for(int i=0; i<LG; i++) 
     {
      if(bDirect[i]) 
        { //send buy
         Trade.Buy(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();
        }
      else 
        { //send sell
         Trade.Sell(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();
        }
     }
     g_state = BBUY;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void EnterSell(double lot) 
  {

   CTrade Trade;
   Trade.LogLevel(LOG_LEVEL_NO);

   for(int i=0; i<LG; i++) 
     {
      if(bDirect[i]) 
        { //send sell
         Trade.Sell(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();
        }
      else 
        { //send buy
         Trade.Buy(lot,pair[i]);
         Ticket[i]=Trade.ResultDeal();
        }
     }
     g_state = BSELL;
  }
  
bool IsNewCandle() 
  {

   static int candle=-1;

   int t1=0;
   switch(_Period)
     {
      case PERIOD_H1:  t1 = Hour();   break;
      case PERIOD_H4:  t1 = Hour4();  break;
      case PERIOD_D1:  t1 = Day();    break;
     }
   if(t1 != candle) {candle=t1; return(true);}
   return (false);
  }
int Hour4(){return((int)Hour()/4);}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int Day()
  {
   MqlDateTime tm;
   TimeCurrent(tm);
   return(tm.day);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int Hour()
  {
   MqlDateTime tm;
   TimeCurrent(tm);
   return(tm.hour);
  }  

Протестируем его в Тестере стратегий на данных за последний год, на таймфреймах H1, H4 и D1. Анализировать будем корзину валютных пар по USD.

Результаты анализа в форме стандартных отчетов находятся в прилагаемом архиве testEAbasket.zip. Сразу можно сказать, что результаты тестирования отрицательны. При существующих настройках советник убыточен на всех трех таймфреймах. Это негативный, но вполне ожидаемый результат. Трудно допустить, что при таком большом количестве сделок, которое было определено раньше, можно получить значимый профит. Повлияли на результат и следующие обстоятельства:

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

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

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

Корректировка формы паттерна

Первое, что приходит в голову, — это изменение формы паттерна, а именно —  значения параметров Delta1 и Delta2. Изначально мы выбрали 5%. Может быть, попытаемся его изменить? Если уменьшить его, то это увеличит количество входов в рынок. Если увеличить, то количество входов уменьшится, но одновременно возрастет и "сила" паттерна, скорость его движения, а следовательно — и "импульс к изменению тренда".

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

Однако имеет смысл сделать другой шаг в этом направлении. Величины Delta1 и Delta2 следует ограничить "сверху", задать их максимальное значение. Это будет логически оправдано тем, что слишком "большой" паттерн уже внутри себя может полностью исчерпать динамику движения, "выдохнуться". И тогда трейдера после входа в рынок будет ждать не продолжение движения, а боковик или изменение тренда. Мало того, "большой" паттерн может окончиться вблизи границы противоположной стороны диапазона объединенного WPR, что тоже очень плохо для входа в рынок на продолжение существующего тренда.

Поэтому примем, что максимальным значением для Delta1 и Delta2 будет 10%. Тогда "величина" самого паттерна будет Delta1 + Delta2 = 20%. Это не слишком много. Внесем изменения в советник:

1. Добавим глобальную переменную:

double g_dMaxSize = 10.0;

2. Изменим функцию обработки событий таймера:

void OnTimer()
  {
   if(IsNewCandle())
     {
      wl=w[0];
      CopyBuffer(h,0,1,1,w);
      ml=m[0];
      CopyBuffer(h,1,1,1,m);
      double d1 = MathAbs(w[0] - m[0]);
      double d2 = MathAbs(ml - wl);
      if(w[0]>m[0] && wl<ml) 
        {
         if(g_state!=BCLOSE) CloseAllPos();
         if(d1 >= g_dMinSize && d2 >= g_dMinSize &&
            d1 <= g_dMaxSize && d2 <= g_dMaxSize) 
           {
            EnterBuy(lt);
            g_state=BBUY;
           }
        }
      if(w[0]<m[0] && wl>ml) 
        {
         if(g_state!=BCLOSE) CloseAllPos();
         if(d1 >= g_dMinSize && d2 >= g_dMinSize &&
            d1 <= g_dMaxSize && d2 <= g_dMaxSize) 
           {
            EnterSell(lt);
            g_state=BSELL;
           }
        }
     }
  }

Продолжим тестирование с измененным советником на таймфрейме H1. Его результат можно найти в прилагаемом архиве testEAbasket1.zip. Иллюстрация результата:


Сразу видны положительные изменения. Результат все еще отрицателен, но в абсолютном выражении убыток гораздо меньше. Изменилась тенденция на потерю депозита, сменившись на колебательное, неустойчивое боковое движение. Есть глубокие просадки, но после них наблюдается и восстановление. Конечно, этого теста совершенно недостаточно для окончательных выводов, но можно признать применение фильтра по максимальным значениям Delta1 и Delta2 оправданным и положительным. Поэтому закрепляем внесенные изменения в коде советника.

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

Второй фильтр

Следующий шаг, который следует сделать, очевиден:

  • Следует с осторожностью входить в рынок по тренду, когда график объединенного WPR (да и любого другого осциллятора) приближается к своим границам.

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

Объединенный WPR колеблется в диапазоне от -100% до +100%. Ориентироваться непосредственно на эти цифры мы не можем, т.к. индикатор подходит к этим границам, но никогда их не достигает. Зато у нас есть уже известные границы перекупленности / перепроданности. В прошлой статье мы рассматривали пробой этих границ и располагаем данными об их местонахождении. Ими мы и воспользуемся.

Определим задачу более точно.

  • Трейдер НЕ будет покупать корзину валют, если график объединенного WPR коснулся или пробил границу перекупленности снизу вверх.
  • Трейдер НЕ будет продавать корзину валют, если график объединенного WPR коснулся или пробил границу перепроданности сверху вниз.

Местоположение границ перекупленности мы определили в районе 60% - 70%, а перепроданности — в районе -60% ... -70%. Для нового фильтра возьмем нижние значения границ: 60% и -60%. Примем, что для входа в рынок на покупку корзины кривая объединенного WPR должна находиться ниже границы фильтра, а на продажу корзины — выше нее. То есть мы не будем пытаться найти "полноценный" паттерн пересечения скользящей средней, а ограничимся фильтрацией, которая применяется в стандартном теханализе:

  • Будем учитывать только взаимное расположение скользящей средней и графиком индикатора по типу "выше — ниже".

Для реализации внесем минимальные изменения в блок кода, касающийся условий входа в рынок:

       if(w[0]>m[0] && wl<ml) 
        {
         if(g_state!=BCLOSE) CloseAllPos();
         if(d1 >= g_dMinSize && d2 >= g_dMinSize &&
            d1 <= g_dMaxSize && d2 <= g_dMaxSize && w[0] < 60) 
           {
            EnterBuy(lt);
            g_state=BBUY;
           }
        }
      if(w[0]<m[0] && wl>ml) 
        {
         if(g_state!=BCLOSE) CloseAllPos();
         if(d1 >= g_dMinSize && d2 >= g_dMinSize &&
            d1 <= g_dMaxSize && d2 <= g_dMaxSize && w[0] > -60) 
           {
            EnterSell(lt);
            g_state=BSELL;
           }
        }

Протестируем советник на ранее выбранном временном отрезке и таймфрейме H1. Вот результат теста:


Полный архив с результатами находится в прикрепленном файле testEAbasket2.zip. Мы видим, что результаты не слишком сильно отличаются от предыдущих, хотя и есть некоторая подвижка в сторону улучшения ситуации.

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

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

Завершение работы с паттерном

Мы выполнили намеченные планы по работе с вышеописанном паттерном на корзине с USD. Окончательную версию используемого советника можно найти в прикрепленном файле testEAbasketFinal.mq5. На основании проведенных тестов можно сделать главный вывод:

  • В качестве сигнала для входа в рынок этот паттерн применять не следует.

Возникает вопрос: Стоит ли проводить тесты на оставшихся корзинах? С очень высокой вероятностью результат предсказуем.

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

Использование данного паттерна в качестве фильтра

Сформулируем исходную задачу.

  • График объединенного WPR пробивает уровень перепроданности снизу вверх. Пробой фиксируется на закрытии свечи. Трейдер входит в рынок, покупая корзину только в том случае, если скользящая средняя находится "ниже" графика объединенного индикатора.
  • Для входа в рынок продажей корзины ситуация должна быть зеркальной описанной в предыдущем пункте.
  • Для выхода из рынка используется факт достижения графиком объединенного WPR зоны в районе 0%.

Легко заметить, что снова будет использоваться не "полноценный" паттерн, а его "облегченная" версия — фильтр "выше — ниже", примененный к взаимному расположению скользящей средней и графика объединенного WPR.

Для тестирования изменим советник testEAbasket.mq5. Добавим в него исходные данные для описания уровня перекупленности / перепроданности и условия применения паттерна в качестве фильтра:

input int SELLPROFIT =   0;
input int SELL1LIMIT =  70;
input int SELL2FROM  =  60;
input int SELL2TO    =  50;

input int BUYPROFIT  =   0;
input int BUY1LIMIT  = -70;
input int BUY2FROM   = -60;
input int BUY2TO     = -50;

//...................................................................

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(IsNewCandle())
     {
      wl=w[0];
      CopyBuffer(h,0,1,1,w);
      ml=m[0];
      CopyBuffer(h,1,1,1,m);

      if(g_state==BBUY && (w[0]>=BUYPROFIT))
        {
         CloseAllPos();
        }
      if(g_state==BSELL && (w[0]<=SELLPROFIT))
        {
         CloseAllPos();
        }
      if(g_state==BCLOSE && w[0]>=BUY2FROM && w[0]<=BUY2TO && wl<=BUY1LIMIT && w[0] > m[0])
        {
            EnterBuy(lt);
            return;        
        }
        
       
      if(g_state==BCLOSE && w[0]<=SELL2FROM && w[0]>=SELL2TO && wl>=SELL1LIMIT && w[0] < m[0])
        {
            EnterSell(lt);
            return;        
        }
    }
  }

Полный код советника можно найти в прилагаемом файле testEAbasket1.mq5.

Для тестирования используем таймфрейм H1. На этом таймфрейме было зафиксировано максимальное количество сделок, причем результат был резко отрицательным. Этот результат можно увидеть в прошлой статье, и здесь мы повторять тестирование не будем.

Проведем тестирование, используя паттерн из данной статьи в качестве фильтра на годовом интервале. Получаем следующий результат:

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

Полный отчет о тестировании находится в прикрепленном архиве Tester_EURUSD_Filter.zip.

Заключение

Мы закончили тестирование паттернов, возникающих при применении объединенных индикаторов на основе осцилляторов для отдельно взятой валюты корзины. За основу был взят объединенный WPR, но результаты можно распространить и на  RSI, и на Stoch.

Однако нужно признать, что до практического применения паттернов нам все-таки еще далеко. Накопленной статистики явно недостаточно. Это относится и к временным интервалам тестирования, и к параметрам самих паттернов. Крайне спорным остается и выбор периода МА из данной статьи.

Вместе с тем, мы получили и интересные результаты.

  1. Оценили месторасположение уровней перекупленности / перепроданности и проверили перспективность работы с ними.
  2. Оценили перспективность применения паттерна пересечения графиком объединенного индикатора скользящей средней.
  3. Оценили возможность совместного применения паттернов из двух предыдущих пунктов.

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

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

 # Имя
Тип
 Описание
1 testWPR&MA.mq5 Индикатор
Индикатор для проверки количества пересечений графиком объединенного индикатора скользящей средней.
2
testEAbasket.mq5 Советник
Советник для тестирования.
3
testEAbasket.zip Архив Стандартные отчеты в формате html по работе советника testEABasket.mq5 с корзиной по USD.
 4 testEAbasket1.zip Архив Стандартный отчет в формате html по работе советника testEABasket.mq5 с корзиной по USD и первым фильтром.
5
testEAbasket2.zip Советник Стандартный отчет в формате html по работе советника testEABasket.mq5 с корзиной по USD и двумя фильтрами.
 6  testEAbasketFinal.mq5 Советник
Окончательная версия советника testEAbasket.mq5 .
7 testEAbasket1.mq5
Советник Советник для тестирования.
 8  Tester_EURUSD_Filter.zip Архив Архив с отчетами тестирования советника testEAbasket1.mq5.


Прикрепленные файлы |
testWPRuMA.mq5 (6.02 KB)
Tester_EURUSD.zip (276.97 KB)
testEAbasket1.mq5 (5.14 KB)
testEAbasket.mq5 (5.29 KB)
testEAbasket1.zip (148.42 KB)
testEAbasket2.zip (145.62 KB)
testEAbasket.zip (581.26 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Stanislav Korotky
Stanislav Korotky | 13 окт. 2017 в 11:30

Два вопроса.

- Если торговля ведется по EURUSD, то почему анализируется только корзина USD и за скобками оставлена корзина EUR.

- Написано под графиком с сигналами: "Просто обозначим синим цветом сигналы на вход на покупку, а красным — на продажу." Тут либо что-то напутано, либо недосказано. Поскольку корзина USD, то покупка USD означает продажу EURUSD и наоборот. Либо цвет нужно поменять, либо добавить во фразу, что имеется в виду именно покупка и продажа USD, а не инструмента. На графике отчетливо видно, что индикатор в обратной зависимости к цене.

Andrei Novichkov
Andrei Novichkov | 13 окт. 2017 в 13:32

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

И торговля НЕ ведется по EURUSD. Торговля ведется по USD, в качестве примера.

Мини-эмулятор рынка, или Ручной тестер стратегий Мини-эмулятор рынка, или Ручной тестер стратегий
Мини-эмулятор рынка — индикатор, предназначенный для частичной эмуляции работы в терминале. Предположительно, его можно использовать для тестирования "ручных" стратегий анализа и торговли на рынке.
Кроссплатформенный торговый советник: Стоп-уровни Кроссплатформенный торговый советник: Стоп-уровни
В этой статье рассматривается реализация стоп-уровней в торговом советнике, совместимая с платформами MetaTrader 4 и MetaTrader 5.
Кроссплатформенный торговый советник: Пользовательские стопы, Безубыток и Трейлинг Кроссплатформенный торговый советник: Пользовательские стопы, Безубыток и Трейлинг
В статье обсуждается установка пользовательских стоп-уровней в кроссплатформенном советнике. Также описан тесно связанный с ними метод, который помогает задать изменение стоп-уровней с течением времени.
Использование фильтра Калмана в прогнозе направления цены Использование фильтра Калмана в прогнозе направления цены
Для успешного трейдинга почти всегда необходимы индикаторы, призванные отделить основное ценовое движение от шумовых колебаний. В этой статье рассматривается один из перспективнейших цифровых фильтров — фильтр Калмана. Описано его построение и использование на практике.