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

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

MetaTrader 5Трейдинг | 17 июля 2017, 12:25
3 547 1
Andrei Novichkov
Andrei Novichkov

Введение

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

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

Для работы будем использовать индикатор "объединенный WPR", который мы разработали ранее. В предыдущей серии статей он использовался не раз, и именно на нем мы обнаружили большинство паттернов.

Чтобы немного сгладить график индикатора, увеличим период WPR с четырнадцати (14) до двадцати (20). Это позволит "выпрямить" график без потери качества отображения.

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

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

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

Начнем исследования с паттерна №3, описанного здесь. Паттерн не слишком сложный, его аналог для отдельной валютной пары известен уже давно. Для торговли корзинами валютных пар он применяется так:

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

Сразу же возникает вопрос: где находятся эти самые уровни перекупленности и перепроданности?  Для стандартного индикатора WPR на отдельной валютной паре ответ на этот вопрос известен:

  • Уровень перекупленности: - 20%
  • Уровень перепроданности: - 80%

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

Чтобы график индикатора пробил один из уровней, перед пробоем он должен быть либо выше уровня перекупленности, либо ниже уровня перепроданности. Посмотрим на истории, какое количество потенциальных входов нам предоставлял рынок. На этом этапе не станем использовать советники, а воспользуемся ранее разработанными индикаторами testIndexZig-Zag1.mq5 и testWPReur.mq5. В testWPReur.mq5 мы просто будем подставлять данные по составу корзин. Ну а исходный код индикатора  testIndexZig-Zag1.mq5 мы слегка упростим, ведь мы уже знаем максимальное и минимальное значения индикатора (от 100% до -100%):

#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;

string               name;

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);
   name = _Symbol + TimeFrameToShortString(Period()) + ".txt";
   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=100,mn=-100;
      double lg;
      lg=mx-mn;
      lg/=100;
      double levels[100];
      int    count[100];
      ArrayInitialize(count,0);
      for(int i=1; i<101; i++) levels[i-1]=NormalizeDouble(lg*i + mn,_Digits);
      for(int i=0;i<rates_total;i++)
        {
         if(ZigZagBuffer[i]==0 || ZigZagBuffer[i]==EMPTY_VALUE) continue;
         else 
           {
            for(int j=0; j<100; j++) 
              {
               if(ZigZagBuffer[i]<levels[j]) 
                 {
                  count[j]++;
                  break;
                 }
              }
           }
        }
      for(int i=0; i<100; i++)
        {
         FileWrite(h,i,levels[i],count[i]);
        }
      FileClose(h);
      Print("Work complete: ",name);
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+


string TimeFrameToShortString(ENUM_TIMEFRAMES period)
{
   switch (period )
   {
      case PERIOD_M1:  return ("M1");
      case PERIOD_M2:  return ("M2");
      case PERIOD_M3:  return ("M3");
      case PERIOD_M4:  return ("M4");
      case PERIOD_M5:  return ("M5");
      case PERIOD_M6:  return ("M6");
      case PERIOD_M10: return ("M10");
      case PERIOD_M12: return ("M12");
      case PERIOD_M15: return ("M15");
      case PERIOD_M20: return ("M20");
      case PERIOD_M30: return ("M30");
      case PERIOD_H1:  return ("H1");
      case PERIOD_H2:  return ("H2");
      case PERIOD_H3:  return ("H3");
      case PERIOD_H4:  return ("H4");
      case PERIOD_H6:  return ("H6");
      case PERIOD_H8:  return ("H8");
      case PERIOD_H12: return ("H12");
      case PERIOD_D1:  return ("D1");
      case PERIOD_W1:  return ("W1");
      case PERIOD_MN1: return ("MN1");
   }
   return ("");
} 

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

Возможное количество сделок

Диапазон объединенного WPR колеблется от -100% до +100%, поэтому пока будем считать, что уровень перекупленности находится на уровне +60%, а перепроданности — на уровне -60%, что соответствует стандартному значению. Выясним, сколько раз график индикатора выходил за уровни перекупленности / перепроданности. Для этого воспользуемся описанным  здесь способом:

  • Накинем на график объединенного WPR  (testWPReur.mq5) индикатор testIndexZig-Zag1.mq5. Наша цель — определить количество экстремумов, которые превышают уровень в +70% и +80%, или -70% и -80%, как на рисунке ниже. Обратите внимание на проблемную зону, отмеченную синим прямоугольником. Пока такие экстремумы попадут в расчет, но в дальнейшем мы будем пытаться отфильтровывать подобные им:

  • Используемый индикатор testIndexZig-Zag1.mq5 разбивает диапазон индикатора testWPReur.mq5 на интервалы в 1% и считает, какое количество экстремумов попадает в каждый интервал. Результаты выводятся в файл. Расчет повторяем для всех выбранных таймфреймов. Затем меняем данные по составу корзины в testWPReur.mq5 и продолжаем работу уже со следующей корзиной валют.

Для удобства полученные данные сведены в таблицу по всем корзинам и по всем выбранным тайфреймам. Фрагмент этой таблицы, посвященный корзине по EUR, приведен ниже. Поясним смысл значений в строках и столбцах этой таблицы:

  • Num. — номер по порядку.
  • Indicator — значение индикатора в процентах. Например, строка со значением -96 означает интервал от -96% до -98% объединенного WPR.
  • EUR — три столбца с количеством экстремумов по каждому выбранному таймфрейму. Например, в уже упомянутой строке Num.1, со значением индикатора объединенного WPR от 96% до 98% указано, что на истории было найдено следующее количество экстремумов, попадающих в этот интервал: на D1 - ноль, на H4 и H1 по одному.
  • History Depth — доступная для расчетов глубина истории.
  • Trade count (80%) — суммарное количество возможных входов по каждому таймфрейму. Например, по корзине EUR на тайфрейме H4 было 83 возможных входа, т.е. индикатор объединенный WPR именно столько раз превышал значение (имел экстремумы) 80%, или был меньше -80%.
  • Trade count (70%) — то же, но только для значения объединенного WPR в 70%.
  • Trade total (80%) — общее количество потенциальных входов по всем корзинам и всем таймфреймам для значения объединенного WPR в 80%.
  • Trade total (70%) - то же самое для 70%.


EUR ----
Num. Indicator Timeframe ----
D1 H4 H1 ----
0 -98 2 3 4 ----
1 -96 0 1 1 ----
2 -94 0 0 1 ----
3 -92 0 3 3 ----
4 -90 1 4 5 ----
5 -88 3 4 10 ----
6 -86 1 2 7 ----
7 -84 2 8 7 ----
8 -82 1 8 21 ----
9 -80 4 6 22 ----
---- ---- ---- ---- ---- ----
95 92 0 2 6 ----
96 94 0 1 4 ----
97 96 0 0 3 ----
98 98 0 3 0 ----
99 100 0 0 0 ----
History Depth 2000.11.09 2005.04.12 2006.01.17 ----





----
Trade count (80%) 25 83 165 ----
Trade count (70%) 45 207 449 ----












Trade total (80%) 3793


Trade total (70%) 7885


Таблица приложена к статье и находится в архиве Pair.zip.

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

Форма паттерна

Определим форму паттерна, после идентификации которой трейдер может войти в рынок.

  • Трейдер продает корзину валютных пар, если объединенный индикатор WPR пересекает уровень перекупленности +60% сверху вниз. При этом на закрытии свечи значение индикатора было не меньше +50%.  Снижение линии индикатора должно происходить от значения не ниже +70%. Второй вариант для этой точки +80% и выше, а для уровня перекупленности — +70%.
  • Случай покупки корзины валютных пар симметричен описанному.

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

Значит, дальше нам потребуется советник.

Советник для тестирования паттерна

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

Покажем еще раз, какие паттерны будем искать:

Искомый паттерн
Отсутствие паттерна



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

Анализировать начнем с корзины валют по USD, разместив следующий советник на паре EURUSD на ранее выбранных таймфреймах:

//+------------------------------------------------------------------+
//|                                                   testBasket.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"
//--- input parameters

#include <Trade\\Trade.mqh>


#define LG 7

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;

input int WPR=20;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum BSTATE 
  {
   BCLOSE = 0,
   BBUY   = 1,
   BSELL  = 2
  };

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

double dWpr[3];
ulong  Ticket[LG];
double TradeResult[LG];
double TradeCurrency;
double Drw;
string sLog;

double TradeTotalResult[LG];
double TradeTotalCurrency;
int    iTradeCount;
double mDrw;

int h1[LG];
BSTATE bstate;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double GetValue1(int shift)
  {
   double dBuf[1];
   double res=0.0;
   for(int i=0; i<LG; i++)
     {
      CopyBuffer(h1[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) );
  }

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int lh;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   EventSetTimer(1);

   for(int i=0; i<LG; i++)
     {
      h1[i]=iWPR(pair[i],0,WPR);
     }
   bstate=BCLOSE;

   ArrayInitialize(TradeTotalResult,0);
   ArrayInitialize(dWpr,EMPTY_VALUE);
   TradeTotalCurrency=0;
   iTradeCount=0;
   mDrw=1000000;

   lh=INVALID_HANDLE;
   string lname = _Symbol + "_" + TimeFrameToShortString(Period() );
   string t1, t = lname;
   int i=0;
   for(;;) 
     {
      t+=".html";
      long lg=FileFindFirst(t,t1);
      if(lg==INVALID_HANDLE) 
        {
         lh= FileOpen(t,FILE_WRITE | FILE_TXT | FILE_ANSI);
         Print("CREATE ",t);
         break;
        }
      FileFindClose(lg);
      t=lname+"_"+IntegerToString(i++);
     }

   FileWriteString(lh,"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n");
   FileWriteString(lh,"<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n");
   FileWriteString(lh,"<head>\r\n");
   FileWriteString(lh,"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n");
   FileWriteString(lh,"<title>"+lname+"</title>\r\n");
   FileWriteString(lh,"</head>\r\n<body>\r\n");
   FileWriteString(lh,"<H2>"+_Symbol+" "+TimeFrameToShortString(Period())+"</H2>\r\n");
   FileWriteString(lh,"<H3>Pattern Params:</H3>\r\n");
   FileWriteString(lh,"<table width=\"100%\" cellspacing=\"0\" cellpadding=\"5\">\r\n");
   FileWriteString(lh,"<thead>\r\n<tr>\r\n<th>BUY</th>\r\n<th>SELL</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n");
   t=StringFormat("Point 1: %d Point 2 from: %d to: %d Close at: %d",BUY1LIMIT,BUY2FROM,BUY2TO,BUYPROFIT);
   FileWriteString(lh,"<td style=\"text-align:center;\">\r\n<ul>\r\n<li>"+t+"</li>\r\n</ul>\r\n</td>\r\n");
   t=StringFormat("Point 1: %d Point 2 from: %d to: %d Close at: %d",SELL1LIMIT,SELL2FROM,SELL2TO,SELLPROFIT);
   FileWriteString(lh,"<td style=\"text-align:center;\">\r\n<ul>\r\n<li>"+t+"</li>\r\n</ul>\r\n</td>\r\n");
   FileWriteString(lh,"</tr>\r\n</tbody>\r\n</table>\r\n");
   FileWriteString(lh,"<H2>"+"Tester Result"+"</H2>\r\n");
   FileWriteString(lh,"<table border=\"1\" width=\"100%\" cellspacing=\"0\" cellpadding=\"5\">\r\n");
   FileWriteString(lh,"<thead>\r\n<th>Num.</th>\r\n<th>Type</th>\r\n<th>WPR(P1/P2)</th>\r\n<th>Time(Begin/End/Length)</th>\r\n<th>Drawdown/<br/>Profit</th>\r\n<th>Pair Profit</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n");

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void PushWpr(double wpr) 
  {
   dWpr[2] = dWpr[1]; dWpr[1] = dWpr[0];
   dWpr[0] = wpr;
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void Stat() 
  {

   double d=0;
   for(int i=0; i<LG; i++) 
     {
      PositionSelectByTicket(Ticket[i]);
      d+=PositionGetDouble(POSITION_PROFIT);
     }
   if(d<Drw) Drw=d;
   if(Drw<mDrw) 
     {
      mDrw=Drw;
      TimeParam[2]=TimeCurrent();
     }
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(bstate!=BCLOSE) 
     {
      Stat();
     }
   if(IsNewCandle()) 
     {
      double res=GetValue1(0);
      PushWpr(res);
      if(dWpr[1]!=EMPTY_VALUE) 
        {
         if(bstate==BBUY && (dWpr[0]>=BUYPROFIT )) 
           {
            CloseAllPos();
            bstate=BCLOSE;
           }
         if(bstate==BSELL && (dWpr[0]<=SELLPROFIT )) 
           {
            CloseAllPos();
            bstate=BCLOSE;
           }
         if(bstate==BCLOSE && dWpr[0]<=SELL2FROM && dWpr[0]>=SELL2TO && dWpr[1]>=SELL1LIMIT) 
           {
            EnterSell(0.01);
            bstate=BSELL;
            TimeParam[0]=TimeCurrent();
            TradeCurrency=0;
            Drw=1000000;
            iTradeCount++;
            sLog=StringFormat("<tr>\r\n<td>%d</td>\r\n<td>SELL</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n<td>%s/<br/>",iTradeCount,dWpr[1],dWpr[0],TimeToString(TimeCurrent()));
            return;
           }
         if(bstate==BCLOSE && dWpr[0]>=BUY2FROM && dWpr[0]<=BUY2TO && dWpr[1]<=BUY1LIMIT) 
           {
            EnterBuy(0.01);
            bstate=BBUY;
            TimeParam[0]=TimeCurrent();
            TradeCurrency=0;
            Drw=1000000;
            iTradeCount++;
            sLog=StringFormat("<tr>\r\n<td>%d</td>\r\n<td>BUY</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n<td>%s/<br/>",iTradeCount,dWpr[1],dWpr[0],TimeToString(TimeCurrent()));
            return;
           }
        }//if (stc.Pick(1) != EMPTY_VALUE)
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseAllPos() 
  {

   CTrade Trade;
   Trade.LogLevel(LOG_LEVEL_NO);

   TimeParam[1]=TimeCurrent();
   string p="<td>";
   for(int i=0; i<LG; i++) 
     {
      TradeResult[i]=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP);
      p+=StringFormat("%s = %.2f<br/>",pair[i],TradeResult[i]);
      TradeCurrency       += TradeResult[i];
      TradeTotalResult[i] += TradeResult[i];
      Trade.PositionClose(Ticket[i]);
     }
   p+="</td>\r\n";
   TradeTotalCurrency+=TradeCurrency;
   sLog += StringFormat("%s/<br/>%s</td>\r\n<td>%.2f/<br/>%.2f</td>\r\n",TimeToString(TimeParam[1]), TimeIntervalToStr(TimeParam[0], TimeParam[1]), Drw, TradeCurrency );
   sLog += p;
   FileWriteString(lh,sLog);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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();
        }
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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();
        }
     }
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();

   FileWriteString(lh,"</tbody>\r\n</table>\r\n");
   FileWriteString(lh,"<H2>Total Result</H2>\r\n");
   FileWriteString(lh,"<table border=\"1\" width=\"100%\" cellspacing=\"0\" cellpadding=\"5\">\r\n");
   FileWriteString(lh,"<thead>\r\n<tr>\r\n<th>Deal's<br/>Count</th>\r\n<th>Profit</th>\r\n<th>Max.Drawdown</th>\r\n<th>Pair's Profit</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n");
   string p = StringFormat("<tr><td>%d</td>\r\n<td>%.2f</td>\r\n<td>%.2f at<br/>%s</td>\r\n<td>",iTradeCount,TradeTotalCurrency,mDrw,TimeToString(TimeParam[2]));
   for(int i=0; i<LG; i++)
     {
      if(h1[i]!=INVALID_HANDLE) IndicatorRelease(h1[i]);
      p+=StringFormat("%s = %.2f<br/>",pair[i],TradeTotalResult[i]);
     }
   p+="</td>\r\n</tr>\r\n";
   FileWriteString(lh,p);
   FileWriteString(lh,"</tbody>\r\n</table>\r\n");
   FileWrite(lh,"</body>\r\n</html>"); //End log
   FileClose(lh);
  }
//+------------------------------------------------------------------+

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);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string TimeIntervalToStr(datetime dt1,datetime dt2) 
  {
   string tm;
   if(dt1 >= dt2)   tm = TimeToString(dt1 - dt2);
   else tm = TimeToString(dt2 - dt1,TIME_DATE|TIME_MINUTES|TIME_SECONDS);
   string ta[],ta1[];
   StringSplit(tm,StringGetCharacter(" ",0),ta);
   StringSplit(ta[0],StringGetCharacter(".",0),ta1);
   ta1[0] = IntegerToString( StringToInteger(ta1[0]) - 1970);
   ta1[1] = IntegerToString( StringToInteger(ta1[1]) - 1);
   ta1[2] = IntegerToString( StringToInteger(ta1[2]) - 1);
   return (ta1[0] + "." + ta1[1] + "." + ta1[2] + " " + ta[1]);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string TimeFrameToShortString(ENUM_TIMEFRAMES period)
  {
   switch(period)
     {
      case PERIOD_H1:  return ("H1");
      case PERIOD_H4:  return ("H4");
      case PERIOD_D1:  return ("D1");
     }
   return ("");
  }
//+------------------------------------------------------------------+

Первую версию советника для тестирования можно найти и в прилагаемом файле testBasket.mq5. В самом алгоритме нет ничего необычного, зато много внимание уделено формам отчета. Проясним смысл входных параметров советника:

  • SELLPROFIT. Когда индикатор объединенного WPR достигает этого значения, закрываем все позиции, открытые для продажи корзины. По умолчанию это значение 0%.
  • SELL1LIMIT. Это минимальное значение объединенного WPR для точки №1 (см. рисунок выше) для начала идентификации паттерна продажи корзины. По умолчанию 70%.
  • SELL2FROM. Максимальное значение объединенного WPR для точки №2 для идентификации паттерна продажи корзины. По умолчанию 60%, это уровень перекупленности.
  • SELL2TO. Минимальное значение объединенного WPR для точки №2 для окончательной идентификации паттерна продажи корзины. По умолчанию 50%.
  • BUYPROFIT. Когда индикатор объединенного WPR достигает этого значения, закрываем все позиции, открытые для покупки корзины. По умолчанию 0%.
  • BUY1LIMIT. Это максимальное значение объединенного WPR для точки №1 для начала идентификации паттерна покупки корзины. По умолчанию -70%.
  • BUY2FROM. Минимальное значение объединенного WPR для точки №2 для идентификации паттерна покупки корзины. По умолчанию -60%, это уровень перепроданности.
  • BUY2TO. Максимальное значение объединенного WPR для точки №2 для окончательной идентификации паттерна покупки корзины. По умолчанию -50%.
  • WPR. Период стандартного технического индикатора WPR. По умолчанию 20.

Далее тестируем советник в Тестере, начиная с января 2016г. Выбранная дата тестирования зависит от наличия качественной истории. Анализировать будем две формы паттерна. Первая описана выше и задана по умолчанию. Вторая смещена относительно первой таким образом:

  • SELLPROFIT. Когда индикатор объединенного WPR достигает этого значения, закрываем все позиции, открытые для продажи корзины. Это значение 0%.
  • SELL1LIMIT. Это минимальное значение объединенного WPR для точки №1 (см. рисунок выше) для начала идентификации паттерна продажи корзины. Это значение 80%.
  • SELL2FROM. Максимальное значение объединенного WPR для точки №2 для идентификации паттерна продажи корзины. Это значение 70%, уровень перекупленности.
  • SELL2TO. Минимальное значение объединенного WPR для точки №2 для окончательной идентификации паттерна продажи корзины. Это значение 50%.
  • BUYPROFIT. Когда индикатор объединенного WPR достигает этого значения, закрываем все позиции, открытые для покупки корзины. Это значение 0%.
  • BUY1LIMIT. Это максимальное значение объединенного WPR для точки №1 для начала идентификации паттерна покупки корзины. Это значение -80%.
  • BUY2FROM. Минимальное значение объединенного WPR для точки №2 для идентификации паттерна покупки корзины. Это значение -70%, уровень перепроданности.
  • BUY2TO. Максимальное значение объединенного WPR для точки №2 для окончательной идентификации паттерна покупки корзины. Это значение -50%.

Итог — отчеты в формате html.

Форма отчетов советника.

Разобраться в отчетах советника совсем не сложно, рассмотрим структуру отчетов на примере отчета по корзине EUR.

Первая строка — заголовок с названиями чата и таймфрейма, на котором работал советник.

Затем идут параметры паттерна, с которыми работал советник раздельно для корзин на покупку и на продажу: Point 1 — это положение точки №1: SELL1LIMIT (BUY1LIMIT). Point 2 from: ... to: ... —  положение точки №2: SELL2FROM (BUY2FROM) и SELL2TO (BUY2TO). Close at — положение точки закрытия паттерна SELLPROFIT (BUYPROFIT):

EURUSD H4

Pattern Params:

BUY SELL
  • Point 1: -80 Point 2 from: -70 to: -50 Close at: 0
  • Point 1: 80 Point 2 from: 70 to: 50 Close at: 0

Следующей идет таблица "Tester Result" с информацией по каждой сделке за период тестирования в следующем порядке:

  • Num. — порядковый номер
  • Type — тип сделки: продажа или покупка корзины
  • WPR(P1/P2) — данные объединенного WPR в формате Точка №1 / Точка №2, на основании которых выполняется вход в рынок
  • Time(Begin/End/Length) — информация о времени сделки: Время входа в рынок / Время выхода / Длительность сделки
  • Drawdown/Profit — максимальная просадка за время сделки / профит в итоге. Это данные в валюте депозита.
  • Pair Profit — профит по отдельным парам, из которых состоит корзина валют. Это данные в валюте депозита.

Вот фрагмент этой таблицы. Можно видеть, что первая сделка длилась восемь часов и принесла убыток в 16.34 USD. В частности, ордер, открытый по EURUSD был закрыт с убытком 2.55 USD:

Num. Type WPR(P1/P2) Time(Begin/End/Length) Drawdown/
Profit
Pair Profit
1 SELL 86.26/
67.15
2016.03.23 20:00/
2016.03.24 04:00/
0.0.0 08:00:00
-21.70/
-16.34
EURUSD = -2.55
GBPUSD = -1.58
AUDUSD = -2.02
NZDUSD = -3.66
USDCAD = -2.15
USDCHF = -2.97
USDJPY = -1.41


Последней идет таблица "Total Result" с суммарными данными по периоду тестирования в следующем порядке:

  • Deal's Count — количество сделок за весь период тестирования.
  • Profit — полученный профит за весь период тестирования. Это данные в валюте депозита.
  • Max.Drawdown — максимальная просадка и в какой момент она зафиксирована. Это данные в валюте депозита и дата.
  • Pair's Profit — суммарный профит по каждой валютной паре, из которых состоит корзина валют. Это данные в валюте депозита.

Вот эта таблица непосредственно из отчета:

Deal's
Count
Profit Max.Drawdown Pair's Profit
22 189.06 -52.37 at
2016.05.02 19:53
EURUSD = 52.43
GBPUSD = 24.50
AUDUSD = 45.88
NZDUSD = 13.94
USDCAD = 15.73
USDCHF = 12.26
USDJPY = 24.32


Полученные отчеты находятся в прилагаемом архиве DocUSD.zip.

Обращает на себя внимание, что сделок на таймфрейме D1 неожиданно мало. Но есть и обнадеживающие сигналы:

  • На таймфреймах H4 и H1 советник показал положительный результат работы, причем для этого не потребовалось никаких усилий.

На этом материале, хоть он и весьма ограничен, уже можно сделать предварительные выводы, которые в дальнейшем могут быть уточнены.

  1. Паттерн крайне редко обнаруживается на дневном таймфрейме. Скорее всего, эта тенденция сохранится и на тайфреймах старше дневного.
  2. Уровень перекупленности / перепроданности находится в диапазоне 60% — 70%, если мы говорим о перекупленности, и -60% — -70%, если о перепроданности. Выше 70% и ниже -70% сделок явно мало. Чтобы паттерн мог быть идентифицирован в этом случае, точка №1 должна находиться выше 90%, либо ниже -90%, а это достаточно редкая ситуация. Ниже 60% и выше -60% точка №2 оказывается в районе 40% или -40% и приближается к зоне возможного начала бокового движения. Для этой зоны характерны еще более высокая волатильность в показаниях индикатора и, как следствие, множественные ложные сигналы на вход.

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

Добавим это значение в предпоследнюю колонку таблицы "Tester Result". Та колонка, кторая называлась "Drawdown/Profit" теперь будет называться Drawdown/+Drawdown/Profit и данные в каждой ячейке этой колонки будут идти в следующем порядке: Просада / Положительная просадка / Профит. Все данные будут в валюте депозита.

Кроме того, покажем максимальное значение положительной просадки в таблице "Total Result". Введем дополнительную предпоследнюю колонку "Max.+Drawdown" и будем показывать в ней максимальную положительную просадку за весь период тестирования и когда эта просадка была зафиксирована.

Исходный код этой следующей версии советника — в прилагаемом файле testBasket1.mq5.

Полученные отчеты по корзине NZD находятся в архиве DocNZD.zip. Выводы пока следующие:

  • Подтверждается ранее сделанное предположение о расположении уровней перекупленности и перепроданности. В архив добавлен отчет NZDUSD_H1_1.html с уровнями, приближенными к зоне возможного начала бокового движения с большим количеством ложных входов. Демонстрация последствий вполне наглядная.
  • Подтверждается то, что сделок по этому паттерну на таймфрейме D1 очень мало.
  • Получен разочаровывающий результат на таймфрейме H1. Можно предположить, что зашумленность таймфрейма также суммируется по зашумленности всех валютных пар корзины, в результате чего мы получаем ложные сигналы.

Завершим исследование, используя оставшиеся валюты корзин: AUD, EUR, GBP, CAD, JPY, CHF. Отчеты по этим валютам можно найти в прикрепленном архиве Doc.zip. А нам пора подводить итоги проделанной работы:

Итоги

  • Уровень перекупленности действительно находится в диапазоне 60% - 70%, а перепроданности — между -60% и -70%. Об этом говорят и полученные отчеты, и расположение соответствующего уровня на стандартном индикаторе WPR.
  • Проверка проводилась на трех таймфреймах и на всех восьми известных корзинах. Исследовались две формы паттерна пробития линий перекупленности / перепроданности:
    1. Точка №1 паттерна лежит выше 80%. Уровень перепроданности — на 70%. Точка №2 — между 70% и 50%. Корзина закрывается, когда индикатор ≤ 0%. Здесь приведена форма паттерна для входа в рынок на продажу корзины. Форма для входа на покупку симметрична со знаком минус.
    2. Точка №1 паттерна лежит выше 70%. Уровень перепроданности — на 60%. Точка №2 — между 60% и 50%. Корзина закрывается, когда индикатор ≤ 0%. Здесь приведена форма паттерна для входа в рынок на продажу корзины. Форма для входа в рынок на покупку корзины симметрична со знаком минус.
  • Еще раз отметим, что сделок на таймфрейме D1 очень мало и в этой статье обращаться к нему мы уже не будем. Возьмем данные из таблиц "Total Result" всех отчетов и на их основе сформируем итоговые таблицы по прочим таймфреймам. В таблицах приведены результаты торговли советника в валюте депозита для каждой корзины валют и для обоих форм паттерна, которые описаны в предыдущем пункте:

     

    Таймфрейм H1
      AUD EUR USD GBP NZD CAD CHF JPY
    Паттерн  №1 -590 90 -37 -991 -141 -80 -118 -514
    Паттерн  №2 -259 -67 328 -714 -352 -446 -118 -272

     

    Таймфрейм H4
      AUD EUR USD GBP NZD CAD CHF JPY
    Паттерн №1 286 -72 189 -400 104 60 -209 120
    Паттерн №2 -208 25 40 80 172 10 -69 -176

     

    Обратим внимание на печальный результат работы на таймфрейме H1. Вероятно, это следствие зашумленности валютных пар на данном таймфрейме.
  • А вот на таймфрейме H4 получен очень достойный результат. В дальнейшей работе уделим этому таймфрейму повышенное внимание.

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

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

  • Недостаточно только первоначальной идеи о закрытии корзины по показаниям объединенного WPR (когда его значение дойдет до нуля). Не решен и вопрос об ограничении убытков. Поскольку нам приходится иметь дело с корзиной ордеров, то логично было бы назначать стоп-лосс в валюте депозита и тралить профит так же в валюте депозита. Это позволило бы, имея прибыль, не уйти сразу же в минус и, с другой стороны, разумно этот "минус" ограничить. Такая методика могла бы стать слагаемым положительного результата на H1 и увеличением прибыльности на H4. Сейчас это не входит в наши планы, но в качестве технического решения пригодится в будущем.

Заключение

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

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

 # Имя
Тип
 Описание
1 Pair.zip Архив
Результаты подсчета количества возможных сделок по всем валютам корзины по трем выбранным таймфреймам.
2
testBasket.mq5 Советник
Советник для тестирования.
3
DocUSD.zip Архив Отчеты в формате html по работе советника testBasket.mq5 с корзиной по USD.
 4 DocNZD.zip Архив Отчеты в формате html по работе советника testBasket1.mq5 с корзиной по NZD.
5
testBasket1.mq5 Советник Советник для тестирования - следующая версия.
 6  Doc.zip Архив
Отчеты в формате html по работе советника testBasket1.mq5 по остальным корзинам.
7test.zip
 Архив Архив с индикаторами testIndexZig-Zag1.mq5 и testWPReur.mq5



Прикрепленные файлы |
testBasket.mq5 (26.57 KB)
DocUSD.zip (14.3 KB)
DocNZD.zip (27.8 KB)
testBasket1.mq5 (27.74 KB)
Doc.zip (99.72 KB)
test.zip (3.81 KB)
Pair.zip (7.92 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
RodgFX
RodgFX | 23 авг. 2017 в 18:23

Наконец что то практическое. Хочется надеяться, что будет продолжение.

Графические интерфейсы XI: Поля ввода и комбо-боксы в ячейках таблицы (build 15) Графические интерфейсы XI: Поля ввода и комбо-боксы в ячейках таблицы (build 15)
В этом обновлении библиотеки элемент "Таблица" (класс CTable) пополнится новыми опциями. Расширим линейку элементов в ячейках таблицы и на этот раз добавим в неё поля ввода и комбо-боксы. В качестве дополнения в это обновление была добавлена возможность управлять размерами окна пользователем MQL-приложения во время её выполнения.
Кроссплатформенный торговый советник: Мани-менеджмент Кроссплатформенный торговый советник: Мани-менеджмент
В этой статье обсуждается реализация мани-менеджмента в кроссплатформенном торговом советнике. Классы мани-менеджмента отвечают за расчет размера лота, которым советник войдет в следующую сделку.
Глубокие нейросети (Часть I). Подготовка данных Глубокие нейросети (Часть I). Подготовка данных
Эта серия статей продолжает и развивает тему глубоких нейросетей (DNN), которые в последнее время вошли во многие прикладные области, включая трейдинг. Рассматриваются новые направления темы, на практических экспериментах проверяются новые методы и идеи. Первая статья серии посвящена подготовке данных для DNN.
Графические интерфейсы XI: Нарисованные элементы управления (build 14.2) Графические интерфейсы XI: Нарисованные элементы управления (build 14.2)
В новой версии библиотеки все элементы библиотеки будут рисоваться на отдельных графических объектах типа OBJ_BITMAP_LABEL. Также продолжим описывать оптимизацию кода: рассмотрим изменения в классах, которые являются ядром библиотеки.