English 中文 Español Deutsch 日本語 Português
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 1): Регрессионный анализ

Возможности Мастера MQL5, которые вам нужно знать (Часть 1): Регрессионный анализ

MetaTrader 5Тестер | 12 августа 2022, 15:02
1 738 0
Stephen Njuki
Stephen Njuki

1. Введение

Мастер MQL5 позволяет быстро создавать и запускать советников, так как большинство второстепенных аспектов торговли предварительно закодированы в библиотеке MQL5. Это позволяет трейдерам сосредоточиться на специальных аспектах своей торговли, таких как условия входа и выхода. В библиотеку включены некоторые классы сигналов входа и выхода, такие как сигналы индикаторов Accelerator Oscillator, Adaptive Moving Average и многих других. Однако они основаны на запаздывающих индикаторах. К тому же, их довольно сложно преобразовать в успешные стратегии. Поэтому особенно важна возможность создавать собственные сигналы. В этой статье мы рассмотрим, как это можно сделать с помощью регрессионного анализа.


2. Создание класса

2.1 Согласно Википедии, регрессионный анализ – это набор статистических методов исследования влияния одной или нескольких независимых переменных на зависимую переменную. Он может быть полезен для пользовательских советников, поскольку ценовые данные представляют собой временной ряд. Анализ позволяет проверить способность предыдущих цен и изменений цен влиять на будущие цены и изменения цен. Регрессионный анализ может быть представлен следующим уравнением  eqn_1

где y – зависимая и, следовательно, прогнозируемая переменная зависит от предшествующих значений x, каждый со своим коэффициентом β и ошибкой ε. Значения и можно рассматривать как предыдущий и прогнозируемый ценовой уровень соответственно. Помимо работы с уровнями цен, аналогичным образом можно исследовать и изменения цен. Неизвестное y зависит от xs, βs и ε. Из них, правда, известны только xs и β0 (y-пересечение). y-пересечение известно, потому что это цена непосредственно перед x i 1. Поэтому нам нужно найти соответствующие βs для каждого x, а затем для ε Так как каждый x i 1 был y I в предшествующий момент времени временного ряда, мы можем найти решение для значений β,  используя одновременные уравнения. Например, если следующее изменение цены зависит только от двух предыдущих изменений, наше текущее уравнение может быть таким:   eqn_2
А предыдущие уравнения будут такими:   eqn_3

Так как мы оцениваем ошибку ε отдельно, мы можем решить два одновременных уравнения для значений β. Нумерация значений x в формуле Википедии не соответствует формату "ряда" MQL5, то есть x с наибольшим номером является самым последним. Таким образом, я перенумеровал значения x в приведенных выше двух уравнениях, чтобы показать, как переделать их в одновременные. Мы снова начинаем с y-пересечений xi1 и xi0 для представления β0 в уравнении 1. Решение одновременных уравнений лучше обрабатывается с помощью матриц для большей эффективности. Необходимые инструменты есть в библиотеке MQL5.

2.2  Библиотека MQL5 имеет обширную коллекцию классов по статистике и общие алгоритмы, которые явно исключают необходимость их кодирования с нуля. Код библиотеки открыт, так что во всем можно убедиться самостоятельно. Для наших целей мы будем использовать функцию RMatrixSolve в классе CDenseSolver в файле solvers.mqh. В основе этой функции лежит использование разложения матрицы LU для быстрого и эффективного поиска решения для значений β. На эту тему в архиве MetaQuotes можно найти статьи. В Википедии также есть объяснение

Прежде чем мы займемся поиском решений для значений β, было бы полезно посмотреть, как устроен класс CExpertSignal, поскольку он является основой для нашего класса. Почти во всех классах сигналов советников, которые можно собрать в Мастере, имеются функции LongCondition и ShortCondition. Они возвращают значение, которое устанавливает, следует ли нам открывать длинную или короткую позицию соответственно. Это значение должно быть целым числом в диапазоне от 0 до 100, чтобы соответствовать входным параметрам Мастера Signal_ThresholdOpen и Signal_ThresholdClose. Как правило, при торговле нам необходимо, чтобы наши условия закрытия позиции были менее консервативными, чем условия открытия. Это означает, что порог открытия будет выше порога закрытия. Таким образом, при разработке сигнала у нас будут входные параметры для вычисления порога закрытия и отдельные, но аналогичные входные параметры для порога открытия. Выбор входных данных при вычислении условия будет определяться тем, есть ли у нас открытые позиции. Если у нас есть открытые позиции, мы будем использовать параметры закрытия. Если позиций нет, будем использовать параметры открытия. Ниже представлен листинг интерфейса сигнального класса нашего советника, в котором имеются эти два набора параметров.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSignalDUAL_RA : public CExpertSignal
  {
protected:
   CiMA              m_h_ma;             // highs MA handle
   CiMA              m_l_ma;             // lows MA handle
   CiATR             m_ATR;
   //--- adjusted parameters
   int               m_size;
   double            m_open_determination,m_close_determination;
   int               m_open_collinearity,m_open_data,m_open_error;
   int               m_close_collinearity,m_close_data,m_close_error;
public:
                     CSignalDUAL_RA();
                    ~CSignalDUAL_RA();
   //--- methods of setting adjustable parameters
   
   //--- PARAMETER FOR SETTING THE NUMBER OF INDEPENDENT VARIABLES
   void              Size(int value)                  { m_size=value;                  }
   
   //--- PARAMETERS FOR SETTING THE OPEN 'THRESHOLD' FOR THE EXPERTSIGNAL CLASS
   void              OpenCollinearity(int value)      { m_open_collinearity=value;     }
   void              OpenDetermination(double value)  { m_open_determination=value;    }
   void              OpenError(int value)             { m_open_error=value;            }
   void              OpenData(int value)              { m_open_data=value;             }
   
   //--- PARAMETERS FOR SETTING THE CLOSE 'THRESHOLD' FOR THE EXPERTSIGNAL CLASS
   void              CloseCollinearity(int value)     { m_close_collinearity=value;    }
   void              CloseDetermination(double value) { m_close_determination=value;   }
   void              CloseError(int value)            { m_close_error=value;           }
   void              CloseData(int value)             { m_close_data=value;            }
   
   //--- method of verification of settings
   virtual bool      ValidationSettings(void);
   //--- method of creating the indicator and timeseries
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- methods for detection of levels of entering the market
   virtual bool      OpenLongParams(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      OpenShortParams(double &price,double &sl,double &tp,datetime &expiration);
   //--- methods of checking if the market models are formed
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);
protected:
   //--- method of initialization of the oscillator
   bool              InitRA(CIndicators *indicators);
   //--- methods of getting data
   int               CheckDetermination(int ind,bool close);
   double            CheckCollinearity(int ind,bool close);
   //
   double            GetY(int ind,bool close);
   double            GetE(int ind,bool close);
   
   double            Data(int ind,bool close);
   //
  };

Ниже представлен листинг функций LongCondition и ShortCondition. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CSignalDUAL_RA::LongCondition(void)
   {
      int _check=CheckDetermination(0,PositionSelect(m_symbol.Name()));
      if(_check>0){ return(_check); }
      
      return(0);
   }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CSignalDUAL_RA::ShortCondition(void)
   {
      int _check=CheckDetermination(0,PositionSelect(m_symbol.Name()));
      if(_check<0){ return((int)fabs(_check)); }
      
      return(0);
   }

 Чтобы найти решение для значений β, используем функцию GetY. Листинг представлен ниже.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CSignalDUAL_RA::GetY(int ind,bool close)
  {
      double _y=0.0;
      
      CMatrixDouble _a;_a.Resize(m_size,m_size);
      double _b[];ArrayResize(_b,m_size);ArrayInitialize(_b,0.0);
      
      for(int r=0;r<m_size;r++)
      {
         _b[r]=Data(r,close);
         
         for(int c=0;c<m_size;c++)
         {
            _a[r].Set(c,Data(r+c+1, close));
         }
      }
      
      int _info=0;
      CDenseSolver _S;
      CDenseSolverReport _r;
      double _x[];ArrayResize(_x,m_size);ArrayInitialize(_x,0.0);
      
      _S.RMatrixSolve(_a,m_size,_b,_info,_r,_x);
      
      for(int r=0;r<m_size;r++)
      {
         _y+=(Data(r,close)*_x[r]);
      }
      //---
      return(_y);
  }

Функция Data будет переключаться между изменениями цены закрытия торгуемого символа или изменениями скользящей средней той же цены закрытия. Используемая опция будет определяться входным параметром m_open_data или m_close_data в зависимости от того, вычисляем ли мы порог открытия или порог закрытия. Листинг для выбора данных показан в перечислении ниже.

enum Edata
  {
      DATA_TREND=0,        // changes in moving average close
      DATA_RANGE=1         // changes in close
  };

Ниже приведена функция Data, выбирающая данные.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CSignalDUAL_RA::Data(int ind,bool close)
   {
      if(!close)
      {
         if(Edata(m_open_data)==DATA_TREND)
         {
            m_h_ma.Refresh(-1);
            return((m_l_ma.Main(StartIndex()+ind)-m_l_ma.Main(StartIndex()+ind+1))-(m_h_ma.Main(StartIndex()+ind)-m_h_ma.Main(StartIndex()+ind+1)));
         }
         else if(Edata(m_open_data)==DATA_RANGE)
         {
            return((Low(StartIndex()+ind)-Low(StartIndex()+ind+1))-(High(StartIndex()+ind)-High(StartIndex()+ind+1)));
         }
      }
      else if(close)
      {
         if(Edata(m_close_data)==DATA_TREND)
         {
            m_h_ma.Refresh(-1);
            return((m_l_ma.Main(StartIndex()+ind)-m_l_ma.Main(StartIndex()+ind+1))-(m_h_ma.Main(StartIndex()+ind)-m_h_ma.Main(StartIndex()+ind+1)));
         }
         else if(Edata(m_close_data)==DATA_RANGE)
         {
            return((Low(StartIndex()+ind)-Low(StartIndex()+ind+1))-(High(StartIndex()+ind)-High(StartIndex()+ind+1)));
         }
      }
      
      return(0.0);
   }

Как только мы получим значения β, мы можем перейти к расчету ошибки.

2.3  Стандартная ошибка, согласно Википедиирассчитывается по формуле, приведенной ниже.

eqn_4  

где  – стандартное отклонение, а n – размер выборки. Ошибка служит напоминанием о том, что не все прогнозы, какими бы тщательными они ни были, всегда будут абсолютно точными. Мы всегда должны ожидать и учитывать ошибки с нашей стороны. Стандартное отклонение, показанное в формуле, будет измеряться между нашими прогнозируемыми и фактическими значениями. Для сравнения мы также можем посмотреть на последнюю разницу между нашим прогнозным и фактическим значениями. Эти два параметра можно выбрать из перечисления ниже. 

enum Eerror
  {
      ERROR_LAST=0,        // use the last error
      ERROR_STANDARD=1     // use standard error
  }

Затем функция GetE возвращает нашу оценку ошибки в зависимости от входного параметра m_open_error или m_close_error при использовании приведенной выше формулы. Листинг представлен ниже.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CSignalDUAL_RA::GetE(int ind,bool close)
  {
      if(!close)
      {
         if(Eerror(m_open_error)==ERROR_STANDARD)
         {
            double _se=0.0;
            for(int r=0;r<m_size;r++) { _se+=pow(Data(r,close)-GetY(r+1,close),2.0); }
            _se=sqrt(_se/(m_size-1)); _se=_se/sqrt(m_size); return(_se);
         }
         else if(Eerror(m_open_error)==ERROR_LAST){ return(Data(ind,close)-GetY(ind+1,close)); }
      }
      else if(close)
      {
         if(Eerror(m_close_error)==ERROR_STANDARD)
         {
            double _se=0.0;
            for(int r=0;r<m_size;r++){  _se+=pow(Data(r,close)-GetY(r+1,close),2.0); }
            _se=sqrt(_se/(m_size-1)); _se=_se/sqrt(m_size); return(_se);
         }
         else if(Eerror(m_close_error)==ERROR_LAST){ return(Data(ind,close)-GetY(ind+1,close)); }
      }
//---
      return(Data(ind,close)-GetY(ind+1,close));
  }


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

2.4  Коллинеарность (определение Википедии) можно охарактеризовать как наличие сильной взаимосвязи между двумя или более независимыми переменными в модели множественной регрессии (Investopedia). У коллинеарности нет формулы как таковой, и она обнаруживается по фактору инфляции дисперсии VIF (variance inflation factor). Этот фактор измеряется по всем независимым переменным (x), чтобы помочь понять, насколько каждая из этих переменных уникальна в прогнозировании y. Необходимая формула представлена ниже. В ней R - регрессия каждой независимой переменной по отношению к другим.

eqn_5

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

enum Echeck
  {
      CHECK_Y=0,           // check for y only
      CHECK_E=1,           // check for the error only
      CHECK_ALL=2,         // check for both the y and the error
      CHECK_NONE=-1        // do not use collinearity checks
  };

Выбор применяемого веса также задается входным параметром m_open_collinearity или m_close_collinearity. Опять же, в зависимости от того, открыты ли позиции. Ниже приведен листинг CheckCollinearity.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double CSignalDUAL_RA::CheckCollinearity(int ind,bool close)
  {
      double _check=0.0;
      double _c=0.0,_array_1[],_array_2[],_r=0.0;
      ArrayResize(_array_1,m_size);ArrayResize(_array_2,m_size);
      ArrayInitialize(_array_1,0.0);ArrayInitialize(_array_2,0.0);
      for(int s=0; s<m_size; s++)
      {
         _array_1[s]=Data(ind+s,close);
         _array_2[s]=Data(m_size+ind+s,close);
      }
      _c=1.0/(2.0+fmin(-1.0,MathCorrelationSpearman(_array_1,_array_2,_r)));
      
      double   _i=Data(m_size+ind,close),    //y intercept
               _y=GetY(ind,close),           //product sum of x and its B coefficients
               _e=GetE(ind,close);           //error
      
      
      
      if(!close)
      {
         if(Echeck(m_open_collinearity)==CHECK_Y){ _check=_i+(_c*_y)+_e;          }
         else if(Echeck(m_open_collinearity)==CHECK_E){ _check=_i+_y+(_c*_e);     }
         else if(Echeck(m_open_collinearity)==CHECK_ALL){ _check=_i+(_c*(_y+_e)); }
         else if(Echeck(m_open_collinearity)==CHECK_NONE){ _check=_i+(_y+_e);     }
      }
      else if(close)
      {
         if(Echeck(m_close_collinearity)==CHECK_Y){ _check=_i+(_c*_y)+_e;          }
         else if(Echeck(m_close_collinearity)==CHECK_E){ _check=_i+_y+(_c*_e);     }
         else if(Echeck(m_close_collinearity)==CHECK_ALL){ _check=_i+(_c*(_y+_e)); }
         else if(Echeck(m_close_collinearity)==CHECK_NONE){ _check=_i+(_y+_e);     }
      }
      
//---
      return(_check);
  }

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

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

  eqn_6

Формула суммы квадратов (где y - фактическое значение, а f - прогнозируемое),  


eqn_7

Формула суммы конечных значений (где y - фактическое значение, а ÿ - скользящее среднее этих значений),  


eqn_8

Сам коэффициент представляет собой R в квадрате. 

Коэффициент измеряет степень, в которой xs влияет на y. Это важно, потому что, как уже упоминалось, бывают периоды, когда регрессия идет на убыль, а это означает, что безопаснее держаться подальше от рынков. Отслеживая это через фильтр, мы с большей вероятностью будем торговать, когда система надежна. Как правило, нам необходимо, чтобы коэффициент был выше 0, при этом 1 - идеальное значение. Входным параметром, используемым при определении нашего порога, будет m_open_determination или m_close_determination, опять же в зависимости от количества открытых позиций. Если коэффициент детерминации, вычисленный приведенной ниже функцией CheckDetermination, меньше этого параметра, то условия на покупку или продажу вернут ноль.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CSignalDUAL_RA::CheckDetermination(int ind,bool close)
  {
      int _check=0;
      m_h_ma.Refresh(-1);m_l_ma.Refresh(-1);
      double _det=0.0,_ss_res=0.0,_ss_tot=0.0;
      for(int r=0;r<m_size;r++)
      {
         _ss_res+=pow(Data(r,close)-GetY(r+1,close),2.0); 
         _ss_tot+=pow(Data(r,close)-((m_l_ma.Main(r)-m_l_ma.Main(r+1))-(m_h_ma.Main(r)-m_h_ma.Main(r+1))),2.0);
      }
      
      if(_ss_tot!=0.0)
      {
         _det=(1.0-(_ss_res/_ss_tot));
         if(_det>=m_open_determination)
         {
            double _threshold=0.0;
            for(int r=0; r<m_size; r++){ _threshold=fmax(_threshold,fabs(Data(r,close))); }
         
            double _y=CheckCollinearity(ind,close);
            
            _check=int(round(100.0*_y/fmax(fabs(_y),fabs(_threshold))));
         }
      }
//---
      return(_check);
  }

Как только мы сможем проверить коэффициент детерминации, у нас будет рабочий сигнал. Далее следует интегрировать этот сигнал в советник с помощью Мастера MQL5.


3. Сборка с помощью Мастера MQL5

3.1 При сборке советника список пользовательских вспомогательных кодов можно использовать вместе с кодом Мастера MQL5. Это совершенно необязательно и зависит от стиля работы трейдера. Для целей этой статьи мы рассмотрим открытие пользовательского отложенного ордера на основе преобладающего ATR символа, а также трейлинг открытых позиций, основанный на том же индикаторе. Уровни тейк-профита использовать не будем.

3.1.1  Отложенные ордера на основе ATR можно установить, перегрузив функции OpenLongParams и OpenShortParams и настроив их в нашем классе сигналов, как показано ниже.

//+------------------------------------------------------------------+
//| Detecting the levels for buying                                  |
//+------------------------------------------------------------------+
bool CSignalDUAL_RA::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration)
  {
   CExpertSignal *general=(m_general!=-1) ? m_filters.At(m_general) : NULL;
//---
   if(general==NULL)
     {
      m_ATR.Refresh(-1);
      //--- if a base price is not specified explicitly, take the current market price
      double base_price=(m_base_price==0.0) ? m_symbol.Ask() : m_base_price;
      
      //--- price overload that sets entry price to be based on ATR
      price      =m_symbol.NormalizePrice(base_price-(m_price_level*(m_ATR.Main(0)/m_symbol.Point()))*PriceLevelUnit());
      
      sl         =0.0;
      tp         =0.0;
      expiration+=m_expiration*PeriodSeconds(m_period);
      return(true);
     }
//---
   return(general.OpenLongParams(price,sl,tp,expiration));
  }
//+------------------------------------------------------------------+
//| Detecting the levels for selling                                 |
//+------------------------------------------------------------------+
bool CSignalDUAL_RA::OpenShortParams(double &price,double &sl,double &tp,datetime &expiration)
  {
   CExpertSignal *general=(m_general!=-1) ? m_filters.At(m_general) : NULL;
//---
   if(general==NULL)
     {
      m_ATR.Refresh(-1);
      //--- if a base price is not specified explicitly, take the current market price
      double base_price=(m_base_price==0.0) ? m_symbol.Bid() : m_base_price;
      
      //--- price overload that sets entry price to be based on ATR
      price      =m_symbol.NormalizePrice(base_price+(m_price_level*(m_ATR.Main(0)/m_symbol.Point()))*PriceLevelUnit());
      
      sl         =0.0;
      tp         =0.0;
      expiration+=m_expiration*PeriodSeconds(m_period);
      return(true);
     }
//---
   return(general.OpenShortParams(price,sl,tp,expiration));
  }

Советник, сгенерированный Мастером MQL5, имеет входной параметр Signal_PriceLevel. По умолчанию он равен нулю, но если ему присвоено значение, он регулирует расстояние в пунктах цены торгуемого символа от текущей цены, по которой будет размещен рыночный ордер. Когда этот параметр отрицательный, размещаются стоп-ордера. При размещении положительных лимитных ордеров параметр имеет тип данных double. Для наших целей этот параметр будет дробным или кратным текущим ценовым пунктам в ATR.

3.1.2 Класс трейлинга ATR также является настраиваемым классом CExpertTrailing, который использует ATR для установки и перемещения стоп-лосса. Реализация его ключевых функций представлена в листинге ниже.

//+------------------------------------------------------------------+
//| Checking trailing stop and/or profit for long position.          |
//+------------------------------------------------------------------+
bool CTrailingATR::CheckTrailingStopLong(CPositionInfo *position,double &sl,double &tp)
  {
//--- check
   if(position==NULL)
      return(false);
//---
   m_ATR.Refresh(-1);
   double level =NormalizeDouble(m_symbol.Bid()-m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits());
   
   //--- sl adjustment to be based on ATR
   double new_sl=NormalizeDouble(level-(m_atr_weight*(m_ATR.Main(0)/m_symbol.Point())),m_symbol.Digits());
   
   double pos_sl=position.StopLoss();
   double base  =(pos_sl==0.0) ? position.PriceOpen() : pos_sl;
//---
   sl=EMPTY_VALUE;
   tp=EMPTY_VALUE;
   if(new_sl>base && new_sl<level)
      sl=new_sl;
//---
   return(sl!=EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Checking trailing stop and/or profit for short position.         |
//+------------------------------------------------------------------+
bool CTrailingATR::CheckTrailingStopShort(CPositionInfo *position,double &sl,double &tp)
  {
//--- check
   if(position==NULL)
      return(false);
//---
   m_ATR.Refresh(-1);
   double level =NormalizeDouble(m_symbol.Ask()+m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits());
   
   //--- sl adjustment to be based on ATR
   double new_sl=NormalizeDouble(level+(m_atr_weight*(m_ATR.Main(0)/m_symbol.Point())),m_symbol.Digits());
   
   double pos_sl=position.StopLoss();
   double base  =(pos_sl==0.0) ? position.PriceOpen() : pos_sl;
//---
   sl=EMPTY_VALUE;
   tp=EMPTY_VALUE;
   if(new_sl<base && new_sl>level)
      sl=new_sl;
//---
   return(sl!=EMPTY_VALUE);
  }

m_atr_weight будет оптимизируемым параметром, как и m_price_level, устанавливая, насколько близко мы можем отслеживать открытые позиции.

  3.2 Дальнейшая сборка в Мастере достаточно проста. Единственный заметный этап – выбор нашего сигнала, как показано ниже.

wizard_1_crop


 

А также добавление нашего пользовательского метода трейлинга.

wizard_2_crop


 

4. Тестирование в тестере стратегий

4.1 Вслед за сборкой советника в Мастере MQL5 следует компиляция для создания файла советника. Также это позволит убедиться, что в нашем коде нет ошибок.

4.2 Входные параметры советника по умолчанию также необходимо установить на вкладке входных параметров тестера стратегий. Необходимо убедиться, что Signal_TakeLevel и Signal_StopLevel равны нулю. Это связано с тем, что для целей этой статьи выход определяется только трейлинг-стопом или входным параметром Signal_ThresholdClose.

4.3 В идеале оптимизация должна выполняться на реальных тиках брокера, с которым вы собираетесь торговать. В этой статье мы оптимизируем EURUSD на 4-часовом таймфрейме в V-образном периоде с 2018.01.01 по 2021.01.01. Для сравнения мы запустим две оптимизации: первая будет использовать только рыночные ордера, а во второй будут разрешены отложенные ордера. Я не зря написал "будут разрешены". Мы по-прежнему будем рассматривать только использование рыночных ордеров, так как Signal_PriceLevel может быть равен нулю, поскольку оптимизация идет от отрицательного значения к положительному. Оптимизацию можно настроить заранее на случай использования отложенных ордеров. Единственное различие от варианта, не использующего отложенные ордера, заключается в том, что в последнем случае входной параметр Signal_PriceLevel останется равным 0 и не будет являться частью оптимизированных входных данных. 


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



Часть отчета 1

И аналогичный отчет и кривая эквити при использовании отложенных ордеров.




Часть отчета 2

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

 

5. Заключение

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


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/11066

Прикрепленные файлы |
TrailingATR.mqh (6.24 KB)
SignalDUAL_RA.mqh (18.75 KB)
Машинное обучение и Data Science (Часть 05): Деревья решений на примере погодных условий для игры в теннис Машинное обучение и Data Science (Часть 05): Деревья решений на примере погодных условий для игры в теннис
Деревья решений классифицируют данные, имитируя то, каким образом размышляют люди. В этой статье посмотрим, как строить деревья и использовать их для классификации и прогнозирования данных. Основная цель алгоритма деревьев решений состоит в том, чтобы разделить выборку на данные с "примесями" и на "чистые" или близкие к узлам.
DoEasy. Элементы управления (Часть 15): WinForms-объект TabControl — несколько рядов заголовков вкладок, методы работы с вкладками DoEasy. Элементы управления (Часть 15): WinForms-объект TabControl — несколько рядов заголовков вкладок, методы работы с вкладками
В статье продолжим работу над WinForm-объектом TabControl — создадим класс объекта-поля вкладки, сделаем возможность расположения заголовков вкладок в несколько рядов и добавим методы для работы с вкладками объекта.
Разработка торговой системы на основе индикатора Ишимоку Разработка торговой системы на основе индикатора Ишимоку
Эта статья продолжает серию, в которой мы учимся строить торговые системы на основе самых популярных индикаторов. На этот раз мы поговорим об индикаторе Ишимоку и создадим торговую систему по его показателям.
Популяционные алгоритмы оптимизации Популяционные алгоритмы оптимизации
Вводная статья об алгоритмах оптимизации (АО). Классификация. В статье предпринята попытка создать тестовый стенд (набор функций), который послужит в дальнейшем для сравнения АО между собой, и, даже, возможно, выявления самого универсального алгоритма из всех широко известных.