English 中文 Español Deutsch 日本語 Português
preview
Комбинаторно-симметричная перекрестная проверка в MQL5

Комбинаторно-симметричная перекрестная проверка в MQL5

MetaTrader 5Торговые системы | 21 марта 2024, 11:37
520 1
Francis Dube
Francis Dube

Введение

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

Хотя подгонки нельзя избежать, степень ее проявления может варьироваться от одной стратегии к другой. Поэтому было бы полезно иметь возможность определить ее степень. Комбинаторно-симметричная перекрестная проверка (Combinatorially Symmetrical Cross Validation, CSCV) — это метод, представленный в научной статье "The Probability of Backtest Overfitting" (вероятность подгонки при тестировании на истории) Дэвида Бэйли (David H. Bailey) и др. Проверку можно использовать для оценки степени подгонки при оптимизации параметров стратегии.

В этой статье мы продемонстрируем реализацию CSCV в MQL5 и на примере покажем, как ее можно применить к советнику.


Метод CSCV

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

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

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

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

  1. Первый шаг начинается со сбора данных во время оптимизации, когда проверяются различные варианты параметров. 
  2. После завершения оптимизации мы объединяем все данные, собранные в ходе тестовых прогонов, в матрицу. Каждая строка этой матрицы будет содержать все значения производительности по барам, которые будут использоваться для расчета некоторых показателей эффективности торговли для соответствующего тестового запуска.
  3. Матрица будет иметь столько строк, сколько опробованных комбинаций параметров, а количество столбцов будет равно числу столбцов, составляющих весь период тестирования. Эти столбцы затем делятся на произвольное четное количество наборов. Скажем, N наборов.
  4. Эти наборы представляют собой подматрицы, которые будут использоваться для формирования комбинаций групп размера N/2. Создаются в общей сложности N комбинаций, взятых N/2 за раз, то есть N C n/2 . Из каждой из этих комбинаций мы создаем набор в выборке (In-Sample-Set, ISS), объединяя подматрицы N/2, а также соответствующий набор вне выборки (Out-Of-Sample-Set, OOSS) из оставшихся подматриц, не включенных в ISS.
  5. Для каждой строки матриц ISS и OOSS мы рассчитываем соответствующую метрику производительности, а также обращаем внимание на строку в матрице ISS с лучшими показателями. Это и есть оптимальная конфигурация параметров. Соответствующая строка в матрице OOSS используется для вычисления относительного ранга путем подсчета количества испытаний параметров за пределами выборки с более низкой производительностью по сравнению с той, которая достигается с использованием оптимальной конфигурации параметров, и представления этого количества как доли от всех протестированных наборов параметров.
  6. Проходя все комбинации, мы накапливаем количество значений относительного ранга, меньшее или равное 0,5. Это количество конфигураций параметров, выходящих за пределы выборки, производительность которых ниже той, которая наблюдается при использовании оптимального набора параметров. После обработки всех комбинаций это число представляется как доля всех комбинаций + 1, представляя вероятность подгонки тестирования на истории (Probability of Backtest Overfitting, PBO).

 Ниже приведена визуализация только что описанных шагов при N = 4.

Визуализация матрицы данных

Подматрицы

Наборы внутри выборки и вне выборки

Комбинации

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


Реализация CSCV в MQL5

Класс Ccsvc, содержащийся в CSCV.mqh, инкапсулирует алгоритм CSCV. CSCV.mqh начинается с включения подфункций стандартной библиотеки MQL5 Mathematics.

//+------------------------------------------------------------------+
//|                                                         CSCV.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include <Math\Stat\Math.mqh>

Указатель функции Criterion определяет тип функции для расчета производительности с использованием массива как входного параметра.

#include <Math\Stat\Math.mqh>
typedef double (*Criterion)(const double &data[]); // function pointer for performance criterion

У Ccscv есть только один метод, с которым пользователям необходимо ознакомиться. Его можно вызвать после инициализации экземпляра класса. Этот метод CalculateProbabilty() возвращает значение PBO в случае успеха. Если обнаружена ошибка, метод возвращает -1. Ниже приводится описание его входных параметров:

//+------------------------------------------------------------------+
//| combinatorially symmetric cross validation class                 |
//+------------------------------------------------------------------+
class Cscv
  {
   ulong             m_perfmeasures;         //granular performance measures
   ulong             m_trials;               //number of parameter trials
   ulong             m_combinations;         //number of combinations

   ulong  m_indices[],           //array tracks combinations
          m_lengths[],           //points to number measures for each combination
          m_flags  [];           //tracks processing of combinations
   double m_data   [],           //intermediary holding performance measures for current trial
          is_perf  [],           //in sample performance data
          oos_perf [];           //out of sample performance data


public:
                     Cscv(void);                   //constructor
                    ~Cscv(void);                  //destructor

   double            CalculateProbability(const ulong blocks, const matrix &in_data,const Criterion criterion, const bool maximize_criterion);
  };
  • Первый входной параметр — blocks. Он соответствует количеству наборов (N наборов), на которые будут разбиты столбцы матрицы.
  • in_data — это матрица с количеством строк, равным общему числу вариантов параметров, опробованных в ходе оптимизации, и количеством столбцов, равным количеству столбцов, составляющих всю историю, выбранную для оптимизации.
  • criterion — это указатель на функцию, которая будет использоваться для расчета выбранного показателя производительности. Функция должна возвращать значение типа double и принимать в качестве входных данных массив типа double.
  •  maximize_criterion связан с criterion тем, что позволяет указать, определяется ли лучший из выбранных показателей производительности максимальным или минимальным значением. Например, если использовать просадку в качестве критерия производительности, лучше использовать наименьшее значение, поэтому maximize_criterion должно быть равен false.
double Cscv::CalculateProbability(const ulong blocks, const matrix &in_data,const Criterion criterion, const bool maximize_criterion)
  {
//---get characteristics of matrix
   m_perfmeasures = in_data.Cols();
   m_trials = in_data.Rows();
   m_combinations=blocks/2*2;
//---check inputs
   if(m_combinations<4)
      m_combinations = 4;
//---memory allocation
   if(ArrayResize(m_indices,int(m_combinations))< int(m_combinations)||
      ArrayResize(m_lengths,int(m_combinations))< int(m_combinations)||
      ArrayResize(m_flags,int(m_combinations))<int(m_combinations)   ||
      ArrayResize(m_data,int(m_perfmeasures))<int(m_perfmeasures)    ||
      ArrayResize(is_perf,int(m_trials))<int(m_trials)               ||
      ArrayResize(oos_perf,int(m_trials))<int(m_trials))
     {
      Print("Memory allocation error ", GetLastError());
      return -1.0;
     }
//---

В ComputeProbability мы начинаем с получения количества столбцов и строк матрицы in_data, а также проверки blocks, чтобы убедиться, что это четное число. Получение размеров входной матрицы необходимо для определения размера внутренних буферов экземпляра.

   int is_best_index ;               //row index of oos_best parameter combination
   double oos_best, rel_rank ;   //oos_best performance and relative rank values
//---
   ulong istart = 0 ;
   for(ulong i=0 ; i<m_combinations ; i++)
     {
      m_indices[i] = istart ;        // Block starts here
      m_lengths[i] = (m_perfmeasures - istart) / (m_combinations-i) ; // It contains this many cases
      istart += m_lengths[i] ;       // Next block
     }
//---
   ulong num_less =0;                    // Will count the number of time OOS of oos_best <= median OOS, for prob
   for(ulong i=0; i<m_combinations; i++)
     {
      if(i<m_combinations/2)        // Identify the IS set
         m_flags[i]=1;
      else
         m_flags[i]=0;               // corresponding OOS set
     }
//---

Как только память успешно выделена для внутренних буферов, мы начинаем готовиться к разбиению столбцов в соответствии с m_combinations. Массив m_indices заполняется индексами начальных столбцов для определенного раздела, а m_lengths будет содержать соответствующее количество столбцов, содержащихся в каждом из них. num_less поддерживает подсчет количества раз, когда производительность лучшего теста в выборке меньше, чем производительность остальных тестов за пределами выборки. m_flags — это целочисленный массив, значения которого могут содержать либо 1, либо 0. Это помогает идентифицировать подмножества, обозначенные как входящие и невходящие в состав выборки, при переборе всех возможных комбинаций.

ulong ncombo;
   for(ncombo=0; ; ncombo++)
     {
      //--- in sample performance calculated in this loop
      for(ulong isys=0; isys<m_trials; isys++)
        {
         int n=0;
         for(ulong ic=0; ic<m_combinations; ic++)
           {
            if(m_flags[ic])
              {
               for(ulong i=m_indices[ic]; i<m_indices[ic]+m_lengths[ic]; i++)
                  m_data[n++] = in_data.Flat(isys*m_perfmeasures+i);
              }
           }
         is_perf[isys]=criterion(m_data);
        }
      //--- out of sample performance calculated here
      for(ulong isys=0; isys<m_trials; isys++)
        {
         int n=0;
         for(ulong ic=0; ic<m_combinations; ic++)
           {
            if(!m_flags[ic])
              {
               for(ulong i=m_indices[ic]; i<m_indices[ic]+m_lengths[ic]; i++)
                  m_data[n++] = in_data.Flat(isys*m_perfmeasures+i);
              }
           }
         oos_perf[isys]=criterion(m_data);
        }

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

//--- get the oos_best performing in sample index
      is_best_index = maximize_criterion?ArrayMaximum(is_perf):ArrayMinimum(is_perf);
      //--- corresponding oos performance
      oos_best = oos_perf[is_best_index];

Индекс наилучшего значения производительности в массиве is_perf рассчитывается в соответствии с maximize_criterion. Соответствующее значение производительности за пределами выборки сохраняется в переменной oos_best.

//--- count oos results less than oos_best
      int count=0;
      for(ulong isys=0; isys<m_trials; isys++)
        {
         if(isys == ulong(is_best_index) || (maximize_criterion && oos_best>=oos_perf[isys]) || (!maximize_criterion && oos_best<=oos_perf[isys]))
            ++count;
        }

Пройдемся по массиву oos_perf и подсчитаем, сколько раз значение oos_best было равным или лучше.

//--- calculate the relative rank
      rel_rank = double (count)/double (m_trials+1);
      //--- cumulate num_less
      if(rel_rank<=0.5)
         ++num_less;

Подсчет используется для расчета относительного ранга. Наконец, num_less суммируется, если вычисленный относительный ранг меньше 0,5.

//---move calculation on to new combination updating flags array along the way
      int n=0;
      ulong iradix;
      for(iradix=0; iradix<m_combinations-1; iradix++)
        {
         if(m_flags[iradix]==1)
           {
            ++n;
            if(m_flags[iradix+1]==0)
              {
               m_flags[iradix]=0;
               m_flags[iradix+1]=0;
               for(ulong i=0; i<iradix; i++)
                 {
                  if(--n>0)
                     m_flags[i]=1;
                  else
                     m_flags[i]=0;
                 }
               break;
              }
           }
        }

Последний внутренний цикл используется для перехода к следующим наборам данных в выборке и вне выборки.

if(iradix == m_combinations-1)
        {
         ++ncombo;
         break;
        }
     }
//--- final result
   return double(num_less)/double(ncombo);
  }


Последний блок if определяет, когда следует выйти из основного внешнего цикла перед возвратом окончательного значения PBO, разделив num_less на ncombo.

Прежде чем мы рассмотрим пример применения класса Ccscv, нам нужно понять, что этот алгоритм говорит о конкретной стратегии.


Интерпретация результатов

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

Чем больше это значение, тем значительнее степень подгонки. Другими словами, существует большая вероятность того, что стратегия будет неэффективной, если ее применять вне выборки. Идеальный PBO должен быть ниже 0,1.

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


Пример

В этом разделе мы покажем применение класса Ccscv для советника. Изменим штатный советник Moving Average, включив в него расчет PBO. Для эффективной реализации метода CSCV мы будем использовать frames для сбора данных по барам. По завершении оптимизации данные каждого прохода будут собраны в матрицу. Это значит, что в код советника нужно добавить как минимум обработчики и OnTesterDeinit(). Наконец, выбранный советник должен быть подвергнут полной оптимизации с использованием опции медленного полного алгоритма в тестере стратегий.  

//+------------------------------------------------------------------+
//|                                    MovingAverage_CSCV_DemoEA.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Returns.mqh>
#include <CSCV.mqh>
#include <Trade\Trade.mqh>

Начнем с включения файлов CSCV.mqh и Returns.mqh, которые содержат определение класса CReturns. CReturns будет полезен для сбора доходности по барам, с помощью которой мы можем вычислить коэффициент Шарпа, среднюю доходность или общую доходность. Мы можем использовать любой из этих параметров в качестве критерия для определения оптимальной производительности. Как уже говорилось в начале статьи, выбранная метрика производительности не имеет значения, можно использовать любую.

sinput uint  NumBlocks          = 4;


Добавлен новый неоптимизируемый параметр под названием NumBlocks, который определяет количество разделов, которые будут использоваться алгоритмом CSCV. Позже мы увидим, что изменение этого параметра повлияет на PBO.  

CReturns colrets;
ulong numrows,numcolumns;

Экземпляр CReturns объявляется глобально. Здесь также объявляются numrows и numcolumns, которые мы будем использовать для инициализации матрицы.

//+------------------------------------------------------------------+
//| TesterInit function                                              |
//+------------------------------------------------------------------+
void OnTesterInit()
  {
   numrows=1;
//---
   string name="MaximumRisk";
   bool enable;
   double par1,par1_start,par1_step,par1_stop;
   ParameterGetRange(name,enable,par1,par1_start,par1_step,par1_stop);
   if(enable)
      numrows*=ulong((par1_stop-par1_start)/par1_step)+1;

//---
   name="DecreaseFactor";
   double par2,par2_start,par2_step,par2_stop;
   ParameterGetRange(name,enable,par2,par2_start,par2_step,par2_stop);
   if(enable)
      numrows*=ulong((par2_stop-par2_start)/par2_step)+1;

//---
   name="MovingPeriod";
   long par3,par3_start,par3_step,par3_stop;
   ParameterGetRange(name,enable,par3,par3_start,par3_step,par3_stop);
   if(enable)
      numrows*=ulong((par3_stop-par3_start)/par3_step)+1;

//---
   name="MovingShift";
   long par4,par4_start,par4_step,par4_stop;
   ParameterGetRange(name,enable,par4,par4_start,par4_step,par4_stop);
   if(enable)
      numrows*=ulong((par4_stop-par4_start)/par4_step)+1;
  }

Добавим обработчик OnTesterInit(), в котором подсчитываем количество тестируемых наборов параметров.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   colrets.OnNewTick();
//---
   if(SelectPosition())
      CheckForClose();
   else
      CheckForOpen();
//---
  }

В обработчике событий OnTick() вызываем метод OnNewtick() из CReturns.

//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---
   double ret=0.0;
   double array[];
//---
   if(colrets.GetReturns(ENUM_RETURNS_ALL_BARS,array))
     {
      //---
      ret = MathSum(array);
      if(!FrameAdd(IntegerToString(MA_MAGIC),long(MA_MAGIC),double(array.Size()),array))
        {
         Print("Could not add frame ", GetLastError());
         return 0;
        }
      //---
     }
//---return
   return(ret);
  }

Внутри OnTester() собираем массив возвратов с помощью нашего глобально объявленного экземпляра CReturns. И, наконец, добавляем эти данные в кадр с помощью вызова FrameAdd().

//+------------------------------------------------------------------+
//| TesterDeinit function                                            |
//+------------------------------------------------------------------+
void OnTesterDeinit()
  {
//---prob value
   numcolumns = 0;
   double probability=-1;
   int count_frames=0;
   matrix data_matrix=matrix::Zeros(numrows,1);
   vector addvector=vector::Zeros(1);
   Cscv cscv;
//---calculate
   if(FrameFilter(IntegerToString(MA_MAGIC),long(MA_MAGIC)))
     {
      //---
      ulong pass;
      string frame_name;
      long frame_id;
      double passed_value;
      double passed_data[];
      //---
      while(FrameNext(pass,frame_name,frame_id,passed_value,passed_data))
        {
         //---
         if(!numcolumns)
           {
            numcolumns=ulong(passed_value);
            addvector.Resize(numcolumns);
            data_matrix.Resize(numrows,numcolumns);
           }
         //---
         if(addvector.Assign(passed_data))
           {
            data_matrix.Row(addvector,pass);
            count_frames++;
           }
         //---
        }
     }
   else
      Print("Error retrieving frames ", GetLastError());
//---results
   probability = cscv.CalculateProbability(NumBlocks,data_matrix,MathSum,true);
//---output results
   Print("cols ",data_matrix.Cols()," rows ",data_matrix.Rows());
   Print("Number of passes processed: ", count_frames, " Probability: ",probability);
//---
  }

Именно в OnTesterDeinit() мы находим основную часть дополнений, внесенных в советник. Здесь мы объявляем экземпляр Ccscv вместе с переменными матричного и векторного типа. Мы перебираем все кадры и передаем их данные в матрицу. Вектор используется как посредник для добавления новой строки данных для каждого кадра.

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

Вот некоторые результаты запуска нашего модифицированного советника с различными настройками на разных таймфреймах. Результат PBO выводится на вкладку "Эксперты" терминала.

MovingAverage_CSCV_DemoEA (EURUSD,H1)   Number of passes processed: 23520 Probability: 0.3333333333333333
NumBlocks
Таймфрейм
Вероятность подгонки тестирования на истории (PBO)
4
W1
0,3333
4
D1
0,6666
4
H12
0,6666
8
W1
0,2
8
D1
0,8
8
H12
0,6
16
W1
0,4444
16
D1
0,8888
16
H12
0,6666

Лучший результат, который мы получили, — 0,2. Остальные были намного хуже. Это указывает на высокую вероятность того, что советник будет показывать плохую производительность при применении к любому набору данных, выходящему за пределы выборки. Мы также видим, что плохие показатели PBO сохраняются на разных таймфреймах. Изменение количества разделов, использованных в анализе, не улучшило изначально плохую оценку.

Настройка тестера торговых стратегий


Выбранные входные параметры


Заключение

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

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

Одним из заметных недостатков этого теста является то, что его нелегко применить к советникам, исходный код которых недоступен. Теоретически можно было бы провести отдельные бэктесты для каждой возможной конфигурации параметров, но это так же трудоемко, как и использование методов Монте-Карло.
 
Подробное описание CSCV и интерпретацию PBO можно найти в оригинальной статье. Ссылка приведена во втором абзаце этой статьи. Исходный код всех программ, упомянутых в статье, приложен ниже.

Название файла
Описание
Mql5\Include\Returns.mqh
Определяет класс CReturns для сбора данных о доходности или эквити в реальном времени.
Mql5\Include\CSCV.mqh
Содержит определение класса Ccscv, который реализует комбинаторно-симметричную перекрестную проверку.
Mql5\Experts\MovingAverage_CSCV_DemoEA.mq5
Модифицированный советник Moving Average, демонстрирующий применение класса Ccscv.


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

Прикрепленные файлы |
CSCV.mqh (6.9 KB)
Returns.mqh (9.58 KB)
Mql5.zip (7.37 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
RustyKanuck
RustyKanuck | 30 мая 2024 в 04:23

Просто интересно, повезло ли кому-нибудь с этим методом? Я попробовал реализовать его на бэктесте m5 за 10 лет с форвардом 1/2 и это безумно медленно, хотелось бы знать, нашел ли кто-нибудь способ закодировать его так, чтобы он был немного быстрее? Конечно, было бы интересно попробовать этот метод.

Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны
В этой статье мы продолжим изучать шаблоны проектирования, которые позволяют разработчикам создавать расширяемые и надежные приложений не только на MQL5, но и на других языках программирования. В этот раз мы поговорим о другом типе — о структурных шаблонах. Будем учиться проектировать системы, используя имеющиеся классы для формирования более крупных структур.
Популяционные алгоритмы оптимизации: Алгоритм оптимизации китов (Whale Optimization Algorithm, WOA) Популяционные алгоритмы оптимизации: Алгоритм оптимизации китов (Whale Optimization Algorithm, WOA)
Алгоритм оптимизации китов (WOA) - это метаэвристический алгоритм, вдохновленный поведением и охотничьими стратегиями горбатых китов. Основная идея WOA заключается в имитации так называемого "пузырькового сетевого" метода кормления, при котором киты создают пузыри вокруг добычи, чтобы затем нападать на нее в спиральном движении.
Разрабатываем мультивалютный советник (Часть 5): Переменный размер позиций Разрабатываем мультивалютный советник (Часть 5): Переменный размер позиций
В предыдущих частях разрабатываемый советник имел возможность использовать только фиксированный размер позиций для торговли. Это допустимо для тестирования, но нежелательно при торговле на реальном счёте. Давайте обеспечим возможность торговли с переменным размером позиций.
Опыт разработки торговой стратегии Опыт разработки торговой стратегии
В этой статье мы сделаем попытку разработать собственную торговую стратегию. Любая торговая стратегия должна быть построена на основе какого-то статистического преимущества. Причем это преимущество должно существовать в течение долгого времени.