Машинное обучение от Яндекс (CatBoost) без изучения Python и R

Aleksey Vyazmikin | 9 ноября, 2020

Предисловие

Уважаемый читатель, в настоящей статье я опишу процесс создания моделей, описывающих закономерность рынка при ограниченном наборе переменных и наличии гипотезы о закономерности его поведения, являющихся результатом работы алгоритма машинного обучения CatBoost от Яндекса. Для получения моделей не потребуется знание таких языков программирования, как Python или R, знание языка MQL5 будут востребованы неглубокие, впрочем, как и в наличии у автора этой статьи, поэтому смею надеяться, что данная статья послужит хорошим руководством для широкого круга заинтересованных лиц, желающих экспериментальным путем оценить возможности машинного обучения и внедрить их в свои разработки. В статье будет изложен минимум академических знаний, за ними я предлагаю обратиться к серии статей от Vladimir Perervenko.


О разнице классического подхода и машинном обучении в трейдинге

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

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

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

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

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

Итак, нам понадобятся предикторы и их целевая.


Предикторы

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

Я выделяю следующие типы предикторов, описывающих:

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


Целевая

В данной статье будем использовать бинарную целевую классификации, т. е. 0 и 1, это вызвано ограничением, о котором будет сказано позже. Итак, какой смысл можно заложить в ноль и единицу? На ум приходит два варианта:

Для генерации сигнала целевой можно использовать простые базовые стратегии, которые позволят получать достаточное число сигналов для машинного обучения:

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


Инструмент для машинного обучения

В качестве инструмента для машинного обучения предлагаю взять программный продукт CatBoost, последнюю версию которого можно скачать по этой ссылке. Так как в статье идет речь о самодостаточной версии, не требующей иных языков программирования, то достаточно загрузить надо файл последней версии с расширением "exe", например, файл "catboost-0.24.1.exe".

CatBoost — продукт, используемый для решения реальных задач в области машинного обучения известной фирмой Яндекс, что позволяет ожидать поддержку продукта, его улучшение и исправление ошибок, несмотря на то, что код продукта доступен для всех желающих.

Лучше меня расскажут о продукте его создатели на видеоролике ниже или в презентации тут.

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


Подготовка данных для машинного обучения

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

В качестве примера будем использовать следующие предикторы:

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

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

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

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

Индикаторы рассчитываются по всем тайм фреймам, доступным в MetaTrader 5, до дневного фрейма включительно.

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

Список исключенных индикаторов:

Для работы с таблицами CSV воспользуемся замечательной библиотекой "CSV fast.mqh" от Aliaksandr Hryshyn, данная библиотека позволяет:


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

Базовая стратегия:

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

  1. Цена пересекла индикатор скользящего среднего значения цены,
  2. Цена первый раз, после соблюдения пункта 1, не коснулась пересеченного скользящего среднего на прошлом баре.

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

Генератор сигнала представим в следующем виде:

//+-----------------------------------------------------------------+
//| Возвращает сигнал на покупку или продажу - базовая стратегия
//+-----------------------------------------------------------------+
bool Signal()
{
//Сбрасываем Флаг блокировки открытия позиций
   SellPrIMA=false;  //Открывать отложенный ордер на продажу
   BuyPrIMA=false;   //Открывать отложенный ордер на покупку
   SellNow=false;    //Открывать ордер с рынка на продажу
   BuyNow=false;     //Открывать ордер с рынка на покупку
   bool Signal=false;//результат работы функции
   int BarN=0;       //Число баров без касания МА
   if(iOpen(Symbol(),Signal_MA_TF,0)>MA_Signal(0) && iLow(Symbol(),Signal_MA_TF,1)>MA_Signal(1))
   {
      for(int i=2; i<100; i++)
      {
         if(iLow(Symbol(),Signal_MA_TF,i)>MA_Signal(i))break;//На этом цикле уже был отработан сигнал
         if(iClose(Symbol(),Signal_MA_TF,i+1)<MA_Signal(i+1) && iClose(Symbol(),Signal_MA_TF,i)>=MA_Signal(i))
         {
            for(int x=i+1; x<100; x++)
            {
               if(iLow(Symbol(),Signal_MA_TF,x)>MA_Signal(x))break;//На этом цикле уже был отработан сигнал
               if(iHigh(Symbol(),Signal_MA_TF,x)<MA_Signal(x))
               {
                  BarN=x;
                  BuyNow=true;
                  break;
               }
            }
         }
      }
   }
   if(iOpen(Symbol(),Signal_MA_TF,0)<MA_Signal(0) && iHigh(Symbol(),Signal_MA_TF,1)<MA_Signal(1))
   {
      for(int i=2; i<100; i++)
      {
         if(iHigh(Symbol(),Signal_MA_TF,i)<MA_Signal(i))break;//На этом цикле уже был отработан сигнал
         if(iClose(Symbol(),Signal_MA_TF,i+1)>MA_Signal(i+1) && iClose(Symbol(),Signal_MA_TF,i)<=MA_Signal(i))
         {
            for(int x=i+1; x<100; x++)
            {
               if(iHigh(Symbol(),Signal_MA_TF,x)<MA_Signal(x))break;//На этом цикле уже был отработан сигнал
               if(iLow(Symbol(),Signal_MA_TF,x)>MA_Signal(x))
               {
                  BarN=x;
                  SellNow=true;
                  break;
               }
            }
         }
      }
   }
   if(BuyNow==true || SellNow==true)Signal=true;
   return Signal;
}


Получение значений предикторов:

Предикторы будем получать с помощью функций — их код приложен к статье. Однако покажу, как просто это сделать для большого числа индикаторов. Отмечу, что будем брать показатели индикаторов в 3 точках — это 1 и 2 сформировавшийся бар, что теоретически позволит найти так называемой сигнальный уровень на индикаторе, и бар со смещением 15, что позволит примерно представлять динамику движения индикатора. Безусловно — это упрощенный вариант получения информации и он может быть значительно расширен.

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

#include "CSV fast.mqh";                 //Класс по работе с таблицами
CSV *csv_CB=new CSV();                   //Создаем экземпляр класса таблицы, где будем хранить текущие показатели предикторов

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   CB_Tabl();//Создание таблицы с предикторами
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//|Создание таблицы с предикторами                                   |
//+------------------------------------------------------------------+
void CB_Tabl()
{
//---Столбцы для осцилляторов
   Size_arr_Buf_OSC=ArraySize(arr_Buf_OSC);
   Size_arr_Name_OSC=ArraySize(arr_Name_OSC);
   Size_TF_OSC=ArraySize(arr_TF_OSC);
   for(int n=0; n<Size_arr_Buf_OSC; n++)SummBuf_OSC=SummBuf_OSC+arr_Buf_OSC[n];
   Size_OSC=3*Size_TF_OSC*SummBuf_OSC;
   for(int S=0; S<3; S++)//Цикл по числу сдвигов
   {
      string Shift="0";
      if(S==0)Shift="1";
      if(S==1)Shift="2";
      if(S==2)Shift="15";
      for(int T=0; T<Size_TF_OSC; T++)//Цикл по числу тайм фреймов
      {
         for(int o=0; o<Size_arr_Name_OSC; o++)//Цикл по числу индикаторов
         {
            for(int b=0; b<arr_Buf_OSC[o]; b++)//Цикл по числу буферов индикаторов
            {
               name_P=arr_Name_OSC[o]+"_B"+IntegerToString(b,0)+"_S"+Shift+"_"+arr_TF_OSC[T];
               csv_CB.Add_column(dt_double,name_P);//Добавляем новую колонку с названием для идентификации предиктора
            }
         }
      }
   }
}
//+------------------------------------------------------------------+
//---Вызов расчета предикторов
//+------------------------------------------------------------------+
void Pred_Calc()
{
//---Получение информации с индикаторов типа осциллятор
   double arr_OSC[];
   iOSC_Calc(arr_OSC);
   for(int p=0; p<Size_OSC; p++)
   {
      csv_CB.Set_value(0,s(),arr_OSC[p],false);
   }
}
//+------------------------------------------------------------------+
//| Получение значений индикаторов - осцилляторов                    |
//+------------------------------------------------------------------+
void iOSC_Calc(double &arr_OSC[])
{
   ArrayResize(arr_OSC,Size_OSC);
   int n=0;//Номер хэндла индикатора
   int x=0;//Общее число итерация
   for(int S=0; S<3; S++)//Цикл по числу сдвигов
   {
      n=0;
      int Shift=0;
      if(S==0)Shift=1;
      if(S==1)Shift=2;
      if(S==2)Shift=15;
      for(int T=0; T<Size_TF_OSC; T++)//Цикл по числу тайм фреймов
      {
         for(int o=0; o<Size_arr_Name_OSC; o++)//Цикл по числу индикаторов
         {
            for(int b=0; b<arr_Buf_OSC[o]; b++)//Цикл по числу буферов индикаторов
            {
               arr_OSC[x++]=iOSC(n, b,Shift);
            }
            n++;//Отметим переход к следующему хэндлу индикатора для расчета
         }
      }
   }
}
//+------------------------------------------------------------------+
//|Получение значения буфера индикатора                              |
//+------------------------------------------------------------------+
double iOSC(int OSC, int Bufer,int index)
{
   double MA[1]= {0.0};
   int handle_ind=arr_Handle[OSC];//Хэндл индикатора
   ResetLastError();
   if(CopyBuffer(handle_ind,0,index,1,MA)<0)
   {
      PrintFormat("Failed to copy data from the OSC indicator, error code %d",GetLastError());
      return(0.0);
   }
   return (MA[0]);
}


Накопление и разметка выборки:

Для создания и последующего сохранения выборки будем накапливать показатели предикторов, копируя их из таблицы "csv_CB" в таблицу "csv_Arhiv".

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

//+-----------------------------------------------------------------+
//| Функция копирует предикторы в архив
//+-----------------------------------------------------------------+
void Copy_Arhiv()
{
   int Strok_Arhiv=csv_Arhiv.Get_lines_count();//Количество строк в таблице
   int Stroka_Load=0;//Начальная строка в таблице источника
   int Stolb_Load=1;//Начальный столбец в таблице источника
   int Stroka_Save=0;//Начальная строка записи в таблице
   int Stolb_Save=1;//Начальный столбец записи в таблице
   int TotalCopy_Strok=-1;//Сколько копируем строк из источника. -1 копируем до последней строки
   int TotalCopy_Stolb=-1;//Сколько копируем столбцов из источника -1 копируем до последнего столбца

   Stroka_Save=Strok_Arhiv;//копируем в последнюю строку
   csv_Arhiv.Copy_from(csv_CB,Stroka_Load,Stolb_Load,TotalCopy_Strok,TotalCopy_Stolb,Stroka_Save,Stolb_Save,false,false,false);//Функция копирования

//---Посчитаем финансовый результат и сделаем разметку целевой, если это не первый вход в рынок
   int Stolb_Time=csv_Arhiv.Get_column_position("Time",false);//Узнаем индекс столба "Time"
   int Vektor_P=0;//Укажем направление входа - "+1" - покупка и "-1" - продажа
   if(BuyNow==true)Vektor_P=1;//Вход на покупку
   else Vektor_P=-1;//Вход на продажу
   csv_Arhiv.Set_value(Strok_Arhiv,Stolb_Time+1,Vektor_P,false);
   if(Strok_Arhiv>0)
   {
      int Stolb_Target_P=csv_Arhiv.Get_column_position("Target_P",false);//Узнаем индекс столба "Time"
      int Load_Vektor_P=csv_Arhiv.Get_int(Strok_Arhiv-1,Stolb_Target_P,false);//Узнаем тип прошлой операции
      datetime Load_Data_Start=StringToTime(csv_Arhiv.Get_string(Strok_Arhiv-1,Stolb_Time,false));//Прочтем дату открытия позиции
      datetime Load_Data_Stop=StringToTime(csv_Arhiv.Get_string(Strok_Arhiv,Stolb_Time,false));//Прочтем дату закрытия позиции
      double F_Rez_Buy=0.0;//Финансовый результат в случае покупки
      double F_Rez_Sell=0.0;//Финансовый результат в случае продажи
      double P_Open=0.0;//Цена открытия позиции
      double P_Close=0.0;//Цена закрытия позиции
      int Metka=0;//Метка для целевой
      P_Open=iOpen(Symbol(),Signal_MA_TF,iBarShift(Symbol(),Signal_MA_TF,Load_Data_Start,false));
      P_Close=iOpen(Symbol(),Signal_MA_TF,iBarShift(Symbol(),Signal_MA_TF,Load_Data_Stop,false));
      F_Rez_Buy=P_Close-P_Open;//Прошлый вход был на покупку
      F_Rez_Sell=P_Open-P_Close;//Прошлый вход был на продажу
      if((F_Rez_Buy-comission*Point()>0 && Load_Vektor_P>0) || (F_Rez_Sell-comission*Point()>0 && Load_Vektor_P<0))Metka=1;
      else Metka=0;
      csv_Arhiv.Set_value(Strok_Arhiv-1,Stolb_Time+2,Metka,false);//Запишем метку в ячейку
      csv_Arhiv.Set_value(Strok_Arhiv-1,Stolb_Time+3,F_Rez_Buy,false);//Запишем финансовый результат от условной покупки в ячейку
      csv_Arhiv.Set_value(Strok_Arhiv-1,Stolb_Time+4,F_Rez_Sell,false);//Запишем финансовый результат от условной продажи в ячейку
      csv_Arhiv.Set_value(Strok_Arhiv,Stolb_Time+2,-1,false);//Сделаем отрицательную метку в ячейки для контроля разметки
   }
}


Применение модели:

При помощи класса "Catboost.mqh" от Aliaksandr Hryshyn, который размещен тут, будем интерпретировать данные с помощью полученной модели CatBoost.

Для отладки я добавил таблицу "csv_Chek" — в неё при нашей необходимости будем сохранять показатель модели CatBoost.

//+-----------------------------------------------------------------+
//| Функция применяет предикторы на модели CatBoost
//+-----------------------------------------------------------------+
void Model_CB()
{
   CB_Siganl=1;
   csv_CB.Get_array_from_row(0,1,Solb_Copy_CB,features);
   double model_result=Catboost::ApplyCatboostModel(features,TreeDepth,TreeSplits,BorderCounts,Borders,LeafValues);
   double result=Logistic(model_result);
   if (result<Porog || result>Pridel)
   {
      BuyNow=false;
      SellNow=false;
      CB_Siganl=0;
   }
   if(Use_Save_Result==true)
   {
      int str=csv_Chek.Add_line();
      csv_Chek.Set_value(str,1,TimeToString(iTime(Symbol(),PERIOD_CURRENT,0),TIME_DATE|TIME_MINUTES));
      csv_Chek.Set_value(str,2,result);
   }
}


Сохранение выборки в файл:

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

//+------------------------------------------------------------------+
//--Функция записи предикторов в файл
//+------------------------------------------------------------------+
void Save_Pred_All()
{
//---Сохраним предикторы в файл
   if(Save_Pred==true)
   {
      int Stolb_Target=csv_Arhiv.Get_column_position("Target_100",false);//Узнаем индекс столба "Target_100"
      csv_Arhiv.Filter_rows_add(Stolb_Target,op_neq,-1,true);//Исключим строки с "-1" разметкой целевой
      csv_Arhiv.Filter_rows_apply(true);//Применим фильтр

      csv_Arhiv.decimal_separator=',';//Устанавливаем разделитель целого и десятичного числа
      string name=Symbol()+"CB_Save_Pred.csv";//Имя для файла
      csv_Arhiv.Write_to_file("Save_Pred\\"+name,true,true,true,true,false,5);//Сохраняем файл с точностью до 5 знака
   }
//---Сохраним показатели модели в файл для отладки - по требованию
   if(Use_Save_Result==true)
   {
      csv_Chek.decimal_separator=',';//Устанавливаем разделитель целого и десятичного числа
      string name=Symbol()+"Chek.csv";//Имя для файла
      csv_Chek.Write_to_file("Save_Pred\\"+name,true,true,true,true,false,5);//Сохраняем файл с точностью до 5 знака
   }
}


Пользовательский показатель качества настроек стратегии:

Дальше нам понадобиться искать подходящие настройки индикатора, используемого базовой моделью, поэтому будем считать свой показатель для тестера стратегий, который определяет минимум трейдов и возвращает процент прибыльных сделок. Чем больше объектов для обучения — трейдов — и чем лучше сбалансирована выборка (то есть процент прибыльных сделок ближе к 50%), тем лучше будет проходить обучение. Расчет пользовательского показателя происходит в функции, представленной ниже.

//+------------------------------------------------------------------+
//| Функция расчета пользовательского показателя                     |
//+------------------------------------------------------------------+
double CustomPokazatelf(int VariantPokazatel)
{
   double custom_Pokazatel=0.0;
   if(VariantPokazatel==1)
   {
      double Total_Tr=(double)TesterStatistics(STAT_TRADES);
      double Pr_Tr=(double)TesterStatistics(STAT_PROFIT_TRADES);
      if(Total_Tr>0 && Total_Tr>15000)custom_Pokazatel=Pr_Tr/Total_Tr*100.0;
   }
   return(custom_Pokazatel);
}


Контроль частоты исполнения основной части кода:

Торговые решения нам нужно получать на открытии нового бара, поэтому будем проверять это обстоятельство с помощью функции

//+-----------------------------------------------------------------+
//| Возвращает TRUE, если появился новый бар на текущем ТФ
//+-----------------------------------------------------------------+
bool isNewBar()
{
   datetime tm[];
   static datetime prevBarTime=0;

   if(CopyTime(Symbol(),Signal_MA_TF,0,1,tm)<0)
   {
      Print("%s CopyTime error = %d",__FUNCTION__,GetLastError());
   }
   else
   {
      if(prevBarTime!=tm[0])
      {
         prevBarTime=tm[0];
         return true;
      }
      return false;
   }
   return true;
}

Торговые функции:

В советнике используется торговый класс "cPoza6", его идеи были разработаны мной, а основная реализация Vasiliy Pushkaryov. Класс был проверен в работе на Московской бирже, но его концепция до конца не реализована. Поэтому приглашаю всех, кому он кажется удобным, к его доработке, а именно, реализации функций по работе с историей. Для статьи была отключена проверка на тип счета, поэтому будьте внимательны, класс изначально делался для неттинг счетов, но для этого советника он будет работать в достаточной мере для изучения машинного обучения в рамках данной статьи.


Код советника для наглядности без описания функций:

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---Проверка на корректность значений интерпретации отклика модели
   if(Porog>=Pridel || Pridel<=Porog)return(INIT_PARAMETERS_INCORRECT);
   if(Use_Pred_Calc==true)
   {
      if(Init_Pred()==INIT_FAILED)return(INIT_FAILED);//Инициализируем хэндлы предикторов
      CB_Tabl();//Создание таблицы с предикторами
      Solb_Copy_CB=csv_CB.Get_columns_count()-3;//Количество столбцов в таблице с предикторами
   }
//---объявляем handle_MA_Slow
   handle_MA_Signal=iMA(Symbol(),Signal_MA_TF,Signal_MA_Period,1,Signal_MA_Metod,Signal_MA_Price);
   if(handle_MA_Signal==INVALID_HANDLE)
   {
      PrintFormat("Failed to create handle of the handle_MA_Signal indicator for the symbol %s/%s, error code %d",
                  Symbol(),EnumToString(Period()),GetLastError());
      return(INIT_FAILED);
   }
//---Создадим таблицу для записи показателей модели - для отладки
   if(Use_Save_Result==true)
   {
      csv_Chek.Add_column(dt_string,"Data");
      csv_Chek.Add_column(dt_double,"Rez");
   }
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(Save_Pred==true)Save_Pred_All();//Вызываем функцию для записи предикторов в файл
   delete csv_CB;//Удаляем экземпляр класса
   delete csv_Arhiv;//Удаляем экземпляр класса
   delete csv_Chek;//Удаляем экземпляр класса
}
//+------------------------------------------------------------------+
//| Обработчик события окончания тестирования                        |
//+------------------------------------------------------------------+
double OnTester()
{
   return(CustomPokazatelf(1));
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//---Операции выполняются только при появлении следующего бара
   if(!isNewBar()) return;
//---Получаем информацию по торговому окружению (сделки/ордера)
   OpenOrdersInfo();
//---Получаем сигнал от базовой стратегии
   if(Signal()==true)
   {
//---Рассчитываем предикторы
      if(Use_Pred_Calc==true)Pred_Calc();
//---Применяем модель CatBoost
      if(Use_Model_CB==true)Model_CB();
//---Если есть открытая позиция в момент сигнала, то закроем её
      if(PosType!="0")ClosePositions("Close Signal");
//---Откроем новую позицию
      if (BuyNow==true)OpenPositions(BUY,1,0,0,"Open_Buy");
      if (SellNow==true)OpenPositions(SELL,1,0,0,"Open_Sell");
//---Скопируем таблицу с текущими предикторами в архивную таблицу
      if(Save_Pred==true)Copy_Arhiv();
   }
}


Внешние настройки советника:

После того как мы ознакомились с кодом функций советника, стоит пояснить, какими настройками он обладает:

1. Настройка действий с предикторами:

2. Параметры индикатора MA для сигнала базовой стратегии:

3. Параметры применения модели CatBoost:


Поиск подходящих настроек базовой стратегии

Разобравшись с кодом и настройками советника, проведем оптимизацию индикатора базовой стратегии. Выберем пользовательский критерий оценки качества настроек стратегии. В рамках статьи тестирование проводилось на склейке фьючерсных контрактов Si на базовый актив USDRUB_TOM Брокера Открытие, инструмент называется "Si Splice", диапазон дат с 01.06.2014 по 31.10.2020, таймфрейм в настройках M1. Режим моделирования цен OHLC на M1.

Оптимизируемые параметры советника:


Результаты оптимизации

Рис. 1 "Результаты оптимизации"


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

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

Сделаем одиночный проход и посмотрим на график.

Рис. 2 Баланс перед обучением

Рис. 2 "Баланс перед обучением"

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

Изменим настройки советника, что бы посчитать и сохранить предикторы и сделаем одиночный проход:

Настройка действий с предикторами:

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

Найдем наш файл по адресу работы агента(я работаю в портативном режиме и у меня это адрес "F:\FX\Открытие Брокер_Demo\Tester\Agent-127.0.0.1-3002\MQL5\Files", " 3002" означает, что использовался 3 поток для работы агента) и проверим его содержимое. Если получилось открыть файл с таблицей — всё идет по плану.


Рис. 3  "Краткое содержание таблицы с предикторами"

Разбиение выборки

Для обучения нам понадобится разбить выборку на 3 части и сохранить их в файлы:

Воспользуемся для этих целей скриптом "CB_CSV_to3x.mq5".

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

Так же создается файл " Test_CB_Setup_0_000000000" в котором указаны номера столбцов с нуля, к которым можно применить условие — отключить лейбл "Auxiliary" и пометить столбец, который является целевой лейбл "Label". Для нашей выборки файл будет иметь следующее содержание:

2408    Auxiliary
2409    Auxiliary
2410    Label
2411    Auxiliary
2412    Auxiliary

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


Параметры CatBoost

CatBoost имеет множество параметров и настроек, некоторые сильно влияют на результат обучение, а иные слабо, со всеми параметрами для обучения модели можно ознакомится по ссылке. Я отмечу основные параметры (и их ключи в скобках при наличии), оказывающие более выраженное влияние на результаты обучения модели и доступные для настроек в скрипте "CB_Bat":

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


Стратегия обучения

Стратегию обучения я разделяю на три этапа:

  1. Базовые настройки — параметры, отвечающие за глубину и количество деревьев в модели, а так же за темп обучения, вес классов в модели и иные настройки для начала процесса обучения. Эти параметры, как правило, не перебираются, в большинстве случаев настроек по умолчанию, формируемых скриптом, будет достаточно.
  2. Поиск оптимальной сетки разбиения параметров — CatBoost делает предобработку таблицы с предикторами для перебора диапазона значений по границам сетки, поэтому следует найти ту сетку, на которой происходит лучшее обучение. Имеет смысл перебрать все типы сеток с диапазоном 8-512 — я использую увеличения шага на каждом значении — 8, 16, 32 и так далее.
  3. Настраиваем снова скрипт, устанавливаем в настройках найденную сетку разбиения (квантования) предикторов и можем перебирать остальные доступные параметры - я обычно ограничиваюсь "Seed" от 1-1000. 

В рамках данной статьи, используем для первого этапа "стратегии обучения" настройки скрипта "CB_Bat" по умолчанию, метод разбиения установим "MinEntropy" и сделаем перебор сетки с 16 до 512 с шагом 16.

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

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


Обучение моделей

После того, как мы получили файлы, переименуем файл "_00_Dir_All.txt" на "_00_Dir_All.bat" и запустим его — он создаст необходимые директории для размещения моделей и изменит расширение оставшихся файлов на "bat".

Теперь у нас в директории проекта имеется директория "Setup" со следующим содержанием:

Запускаем файл "_01_Train_All.bat" и наблюдаем начавшийся процесс обучения.

Рис. 4 "Процесс обучения CatBoost"


Красным цветом на рисунке выше я пронумеровал столбцы для описания значений в них:

  1. Число построенных деревьев  — равно числу итераций;
  2. Результат вычисления выбранной функции потерь на выборке для обучения;
  3. Результат вычисления выбранной функции потерь на выборке для контроля;
  4. Лучший результат вычисления выбранной функции потерь на выборке для контроля, в скобках указан номер итерации;
  5. Фактическое время, прошедшее с момента начала обучения модели;
  6. Расчетное время, оставшееся до конца обучения, если будут обучены все деревья, установленные настройками.

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

FOR %%a IN (*.) DO (                                                                                                                                                                                                                                                                            
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_16\result_4_%%a      --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 16       --feature-border-type MinEntropy        --output-borders-file quant_4_00016.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_32\result_4_%%a      --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 32       --feature-border-type MinEntropy        --output-borders-file quant_4_00032.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_48\result_4_%%a      --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 48       --feature-border-type MinEntropy        --output-borders-file quant_4_00048.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_64\result_4_%%a      --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 64       --feature-border-type MinEntropy        --output-borders-file quant_4_00064.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_80\result_4_%%a      --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 80       --feature-border-type MinEntropy        --output-borders-file quant_4_00080.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_96\result_4_%%a      --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 96       --feature-border-type MinEntropy        --output-borders-file quant_4_00096.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_112\result_4_%%a     --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 112      --feature-border-type MinEntropy        --output-borders-file quant_4_00112.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_128\result_4_%%a     --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 128      --feature-border-type MinEntropy        --output-borders-file quant_4_00128.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_144\result_4_%%a     --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 144      --feature-border-type MinEntropy        --output-borders-file quant_4_00144.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
catboost-0.24.1.exe fit  --learn-set train.csv   --test-set test.csv     --column-description %%a        --has-header    --delimiter ;   --model-format CatboostBinary,CPP       --train-dir ..\Rezultat\RS_160\result_4_%%a     --depth 6       --iterations 1000       --nan-mode Forbidden    --learning-rate 0.03    --rsm 1         --fold-permutation-block 1      --boosting-type Plain   --l2-leaf-reg 6         --loss-function Logloss         --use-best-model        --eval-metric Logloss   --custom-metric Logloss         --od-type Iter  --od-wait 100   --random-seed 0         --random-strength 1     --auto-class-weights SqrtBalanced       --sampling-frequency PerTreeLevel       --border-count 160      --feature-border-type MinEntropy        --output-borders-file quant_4_00160.csv         --bootstrap-type Bayesian       --bagging-temperature 1         --leaf-estimation-method Newton         --leaf-estimation-iterations 10                
)       

После окончания обучения запустим сразу 6 оставшихся bat файлов для получения результатов обучения в виде меток и статистических показателей.


Экспресс оценка результатов обучения

Для получения метрических показателей моделей и их финансового результата воспользуемся скриптом "CB_Calc_Svod.mq5".

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

После запуска скрипта ждем окончания его работы, должна создастся директория "Analiz", в которой будет файл "CB_Svod.csv" и графики баланса по названию модели, если их построение было выбрано в настройках, а так же директория "Models_mqh", где находятся сами модели, конвертированные в формат mqh.

Открыв файл "CB_Svod.csv" мы увидим метрические показатели каждом модели по каждой выборке отдельно, включая финансовый результат.


Рис. 5 "Фрагмент таблицы с результатами построения моделей  - файл "CB_Svod.csv""


Выберем понравившуюся модель из поддиректории "Models_mqh" директории в которой проходило обучение наших моделей, и поместим в директорию с советником. Поставим знак комментария "//" перед строкой с пустыми буферами в самом начале кода советника, как в коде ниже, и теперь нам осталось только подключить файл с моделью в нашем советнике:

//Если есть модель CatBoost в файле mqh, то нужно комментировать эту строку ниже:
//uint TreeDepth[];uint TreeSplits[];uint BorderCounts[];float Borders[];double LeafValues[];double Scale[];double Bias[];
#include "model_RS_208_0.mqh";                 //Файл с моделью

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

1. Настройка действий с предикторами:

2. Параметры индикатора MA для сигнала базовой стратегии:

3. Параметры применения модели CatBoost:


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


Рис. 6 "Баланс после обучения за период с 01.06.2014 по 31.10.2020


Сравним два графика баланса на интервале (с 01.08.2019 по 31.10.2020) вне обучения, что соответствует выборке "exam.csv" до обучения и после.


Рис. 7 "Баланс до обучения за период с 01.08.2019 по 31.10.2020"



Рис. 8 "Баланс после обучения за период с 01.08.2019 по 31.10.2020"


Результаты не выдающиеся, но можно отметить, что главное правило в трейдинге "не терять деньги" соблюдается. Даже если бы выбор пал не на эту модель, а на другую из файла "CB_Svod.csv", то всё равно эффект был бы положительным, ведь финансовый результат самой неудачной модели, что мы получили, составляет -25 пунктов, а средний показатель финансового результата всех моделей составляет 3889,9 пунктов.


Рис. 9 "Финансовый результат обученных моделей за период с 01.08.2019 по 31.10.2020"


Анализ предикторов

В каждой директории модели (у автора MQL5\Files\CB_Stat_03p50Q\Rezultat\RS_208\result_4_Test_CB_Setup_0_000000000) имеется 3 файла:

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


Рис. 10 "Сводная таблица анализа значимости предикторов"

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

Выводы

  1. Методы машинного обучения, такие как градиентный бустинг, могут быть не менее эффективны, чем бесконечный перебор параметров и ручное создание дополнительных торговых условий для улучшения показателей стратегии.
  2. Индикаторы, идущие с поставкой MetaTrader 5, могут быть полезны для машинного обучения.
  3. CatBoost — качественная библиотека, имеющая свою обертку, позволяющая эффективно использовать градиентный бустинг без изучения Python и R.


Послесловие

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

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