English 中文 Deutsch 日本語
preview
Создание самооптимизирующихся советников на MQL5 (Часть 6): Самоадаптирующиеся торговые правила (II)

Создание самооптимизирующихся советников на MQL5 (Часть 6): Самоадаптирующиеся торговые правила (II)

MetaTrader 5Примеры |
60 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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

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

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

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

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

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



Визуализация проблемы

На случай если к нам присоединились новые читатели, на рисунке 1 ниже приведен скриншот дневного графика EURUSD с RSI за 2 периода. 

Рис. 1

Рис. 1: Визуализация качества сигналов, генерируемых RSI за короткий период времени

Под рисунком 1 мы применили 70-периодный RSI к тому же участку графика, что и на рисунке 2. Мы можем наблюдать, как сигнал RSI постепенно превращается в прямую линию с центром на уровне 50 RSI. Какие комментарии можно сделать при сравнении этих двух рисунков? Стоит отметить, что за период, охватываемый обоими рисунками, EURUSD упал с уровня 1,121 18 сентября 2024 года до минимума в 1,051 ко 2 декабря 2024 года. Однако двухпериодный RSI слишком часто менял уровни за тот же период времени, а RSI с периодом 70 вообще не менял уровни.

Означает ли это, что трейдеры должны навсегда ограничиться использованием лишь узкого диапазона периодов при применении RSI? Что потребуется для разработки алгоритмов, которые будут автоматически выбирать оптимальный период RSI без вмешательства человека? Кроме того, как можно написать алгоритмы, которые помогут нам находить выгодные торговые уровни независимо от начального периода?

Рис. 2

Рис. 2. Визуализация работы индикатора RSI в течение длительного периода


Начало работы с MQL5

Существует множество способов решения этой проблемы. Мы могли бы использовать библиотеки в Python для генерации показаний RSI с различными периодами и таким образом оптимизировать период и уровни RSI. Однако это может повлечь за собой определенные недостатки. Наибольшее ограничение может быть связано с незначительными различиями в методах вычисления значений этих технических индикаторов. 

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

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

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

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

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- The RSI class will manage our indicator settings and provide useful transformations we need
class RSI
  {
   //--- Private members
private:
   //--- Have the indicator values been copied to the buffer?
   bool              indicator_values_initialized;
   bool              indicator_differenced_values_initialized;

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

//--- Give the user feedback
string            user_feedback(int flag)
  {
   string message;

   //--- Check if the RSI indicator loaded correctly
   if(flag == 0)
     {
      //--- Check the indicator was loaded correctly
      if(IsValid())
         message = "RSI Indicator Class Loaded Correcrtly \n";
      return(message);
      //--- Something went wrong
      message = "Error loading RSI Indicator: [ERROR] " + (string) GetLastError();
      return(message);
     }

   //--- User tried getting indicator values before setting them
   if(flag == 1)
     {
      message = "Please set the indicator values before trying to fetch them from memory";
      return(message);
     }

   //--- We sueccessfully set our differenced indicator values
   if(flag == 2)
     {
      message = "Succesfully set differenced indicator values.";
      return(message);
     }

   //--- Failed  to set our differenced indicator values
   if(flag == 3)
     {
      message = "Failed to set our differenced indicator values: [ERROR] " + (string) GetLastError();
      return(message);
     }

   //--- The user is trying to retrieve an index beyond the buffer size and must update the buffer size first
   if(flag == 4)
     {
      message = "The user is attempting to use call an index beyond the buffer size, update the buffer size first";
      return(message);
     }

   //--- No feedback
   else
      return("");
  }

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

   //--- Protected members
protected:
   //--- The handler for our RSI
   int               rsi_handler;
   //--- The Symbol our RSI should be applied on
   string            rsi_symbol;
   //--- Our RSI period
   int               rsi_period;
   //--- How far into the future we wish to forecast
   int               forecast_horizon;
   //--- The buffer for our RSI indicator
   double            rsi_reading[];
   vector            rsi_differenced_values;
   //--- The current size of the buffer the user last requested
   int               rsi_buffer_size;
   int               rsi_differenced_buffer_size;
   //--- The time frame our RSI should be applied on
   ENUM_TIMEFRAMES   rsi_time_frame;
   //--- The price should the RSI be applied on
   ENUM_APPLIED_PRICE rsi_price;

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

//--- Now, we can define public members:
public:

   //--- Check if our indicator handler is valid
   bool              IsValid(void)
     {
      return((this.rsi_handler != INVALID_HANDLE));
     }

Наш конструктор по умолчанию создаст объект iRSI, устанавливающий значение EURUSD на дневном графике на период в 5 дней. Чтобы убедиться, что это именно выбор пользователя, наш класс выводит название рынка и период, с которыми он работает. Кроме того, конструктор по умолчанию специально выводит пользователю информацию о том, что текущий экземпляр объекта RSI был создан именно этим конструктором.

//--- Our default constructor
void              RSI(void):
                  indicator_values_initialized(false),
                  rsi_symbol("EURUSD"),
                  rsi_time_frame(PERIOD_D1),
                  rsi_period(5),
                  rsi_price(PRICE_CLOSE),
                  rsi_handler(iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price))
  {
   //--- Give the user feedback on initilization
   Print(user_feedback(0));
   //--- Remind the user they called the default constructor
   Print("Default Constructor Called: ",__FUNCSIG__," ",&this);
  }

В противном случае мы ожидаем, что пользователь вызовет параметрический конструктор объекта RSI и укажет все необходимые параметры.

//--- Parametric constructor
   void              RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price)
     {
      indicator_values_initialized = false;
      rsi_symbol                   = user_symbol;
      rsi_time_frame               = user_time_frame;
      rsi_period                   = user_period;
      rsi_price                    = user_price;
      rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
      //--- Give the user feedback on initilization
      Print(user_feedback(0));
     }

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

//--- Destructor
void             ~RSI(void)
  {
   //--- Free up resources we don't need and reset our flags
   if(IndicatorRelease(rsi_handler))
     {
      indicator_differenced_values_initialized = false;
      indicator_values_initialized = false;
      Print("RSI System logging off");
     }
  }

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

//--- Copy readings for our RSI indicator
bool              SetIndicatorValues(int buffer_size,bool set_as_series)
  {
   rsi_buffer_size = buffer_size;
   CopyBuffer(this.rsi_handler,0,0,buffer_size,rsi_reading);
   if(set_as_series)
      ArraySetAsSeries(this.rsi_reading,true);
   indicator_values_initialized = true;

   //--- Did something go wrong?
   vector rsi_test;
   rsi_test.CopyIndicatorBuffer(rsi_handler,0,0,buffer_size);
   if(rsi_test.Sum() == 0)
      return(false);

   //--- Everything went fine.
   return(true);
  }

Простая функция для получения текущего значения RSI.

//--- Get the current RSI reading
double            GetCurrentReading(void)
  {
   double temp[];
   CopyBuffer(this.rsi_handler,0,0,1,temp);
   return(temp[0]);
  }

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

//--- Get a specific RSI reading
double            GetReadingAt(int index)
  {
   //--- Is the user trying to call indexes beyond the buffer?
   if(index > rsi_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

   //--- Get the reading at the specified index
   if((indicator_values_initialized) && (index < rsi_buffer_size))
      return(rsi_reading[index]);

   //--- User is trying to get values that were not set prior
   else
    {
      Print(user_feedback(1));
      return(-1e10);
     }
  }

Нас также интересует изменение значений RSI. Нам недостаточно иметь доступ к текущим показаниям RSI. Нам также необходим доступ к данным об изменении уровня RSI, происходящем в течение любого произвольного размера окна, которое мы укажем. Как и прежде, мы просто вызываем функции CopyBuffer, чтобы пользователь мог в фоновом режиме рассчитать рост уровня RSI, но класс также включает дополнительную проверку, чтобы убедиться, что результат вычисления не является вектором из 0, прежде чем вернуть пользователю найденный ответ.

//--- Let's set the conditions for our differenced data
 bool              SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series)
   {
    //--- Internal variables
    rsi_differenced_buffer_size = buffer_size;
    rsi_differenced_values = vector::Zeros(rsi_differenced_buffer_size);

    //--- Prepare to record the differences in our RSI readings
    double temp_buffer[];
    int fetch = (rsi_differenced_buffer_size + (2 * differencing_period));
    CopyBuffer(rsi_handler,0,0,fetch,temp_buffer);
    if(set_as_series)
       ArraySetAsSeries(temp_buffer,true);

    //--- Fill in our values iteratively
    for(int i = rsi_differenced_buffer_size;i > 1; i--)
      {
       rsi_differenced_values[i-1] = temp_buffer[i-1] - temp_buffer[i-1+differencing_period];
      }

    //--- If the norm of a vector is 0, the vector is empty!
    if(rsi_differenced_values.Norm(VECTOR_NORM_P) != 0)
      {
       Print(user_feedback(2));
       indicator_differenced_values_initialized = true;
       return(true);
      }

    indicator_differenced_values_initialized = false;
    Print(user_feedback(3));
    return(false);
   }

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

//--- Get a differenced value at a specific index
double            GetDifferencedReadingAt(int index)
  {
   //--- Make sure we're not trying to call values beyond our index
   if(index > rsi_differenced_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

   //--- Make sure our values have been set
   if(!indicator_differenced_values_initialized)
     {

      //--- The user is trying to use values before they were set in memory
      Print(user_feedback(1));
      return(-1e10);
     }

   //--- Return the differenced value of our indicator at a specific index
   if((indicator_differenced_values_initialized) && (index < rsi_differenced_buffer_size))
      return(rsi_differenced_values[index]);

   //--- Something went wrong.
   return(-1e10);
  }
};

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

//--- How far we want to forecast
#define HORIZON 10

//--- Our handlers for our indicators
int rsi_5_handle;

//--- Data structures to store the readings from our indicators
double rsi_5_reading[];

//--- File name
string file_name = Symbol() + " Testing RSI Class.csv";

//--- Amount of data requested
input int size = 3000;

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

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Testing the RSI Class
//--- Initialize the class
   RSI my_rsi(Symbol(),PERIOD_CURRENT,5,PRICE_CLOSE);
   my_rsi.SetIndicatorValues(size,true);
   my_rsi.SetDifferencedIndicatorValues(size,10,true);

//---Setup our technical indicators
   rsi_5_handle  = iRSI(Symbol(),PERIOD_CURRENT,5,PRICE_CLOSE);
   int fetch = size + (2 * HORIZON);

//---Set the values as series
   CopyBuffer(rsi_5_handle,0,0,fetch,rsi_5_reading);
   ArraySetAsSeries(rsi_5_reading,true);

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","RSI 5","RSI 5 Class","RSI 5 Difference","RSI 5 Class Difference");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   rsi_5_reading[i],
                   my_rsi.GetReadingAt(i),
                   rsi_5_reading[i]  - rsi_5_reading[i + HORIZON],
                   my_rsi.GetDifferencedReadingAt(i)
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+
#undef  HORIZON

Если наш класс реализован корректно, наш тестовый скрипт создаст файл EURUSD Testing RSI Class, который будет содержать дублированные показания RSI. Как видно из рисунка 3, наш класс RSI успешно прошел тест. Этот класс экономит время, поскольку нам не приходится многократно реализовывать одни и те же методы в разных проектах. Мы можем просто импортировать наш класс RSI и вызывать необходимые методы.

Рис. 3. Наш класс индикаторов RSI прошел проверку. Его использование идентично ручной работе с классом индикаторов RSI

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

//+------------------------------------------------------------------+
//|                                                          RSI.mqh |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| This class will provide us with usefull functionality            |
//+------------------------------------------------------------------+
class RSI
  {
private:
   //--- Have the indicator values been copied to the buffer?
   bool              indicator_values_initialized;
   bool              indicator_differenced_values_initialized;

   //--- Give the user feedback
   string            user_feedback(int flag);

protected:
   //--- The handler for our RSI
   int               rsi_handler;
   //--- The Symbol our RSI should be applied on
   string            rsi_symbol;
   //--- Our RSI period
   int               rsi_period;
   //--- How far into the future we wish to forecast
   int               forecast_horizon;
   //--- The buffer for our RSI indicator
   double            rsi_reading[];
   vector            rsi_differenced_values;
   //--- The current size of the buffer the user last requested
   int               rsi_buffer_size;
   int               rsi_differenced_buffer_size;
   //--- The time frame our RSI should be applied on
   ENUM_TIMEFRAMES   rsi_time_frame;
   //--- The price should the RSI be applied on
   ENUM_APPLIED_PRICE rsi_price;

public:
                     RSI();
                     RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price);
                    ~RSI();
   bool              SetIndicatorValues(int buffer_size,bool set_as_series);
   bool              IsValid(void);
   double            GetCurrentReading(void);
   double            GetReadingAt(int index);
   bool              SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series);
   double            GetDifferencedReadingAt(int index);
  };
//+------------------------------------------------------------------+
//| Our default constructor for our RSI class                        |
//+------------------------------------------------------------------+
void RSI::RSI()
  {
   indicator_values_initialized = false;
   rsi_symbol                   = "EURUSD";
   rsi_time_frame               = PERIOD_D1;
   rsi_period                   = 5;
   rsi_price                    = PRICE_CLOSE;
   rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
//--- Give the user feedback on initilization
   Print(user_feedback(0));
//--- Remind the user they called the default constructor
   Print("Default Constructor Called: ",__FUNCSIG__," ",&this);
  }

//+------------------------------------------------------------------+
//| Our parametric constructor for our RSI class                     |
//+------------------------------------------------------------------+
void RSI::RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price)
  {
   indicator_values_initialized = false;
   rsi_symbol                   = user_symbol;
   rsi_time_frame               = user_time_frame;
   rsi_period                   = user_period;
   rsi_price                    = user_price;
   rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
//--- Give the user feedback on initilization
   Print(user_feedback(0));
  }

//+------------------------------------------------------------------+
//| Our destructor for our RSI class                                 |
//+------------------------------------------------------------------+
void RSI::~RSI()
  {
//--- Free up resources we don't need and reset our flags
   if(IndicatorRelease(rsi_handler))
     {
      indicator_differenced_values_initialized = false;
      indicator_values_initialized = false;
      Print(user_feedback(5));
     }
  }

//+------------------------------------------------------------------+
//| Get our current reading from the RSI indicator                   |
//+------------------------------------------------------------------+
double RSI::GetCurrentReading(void)
  {
   double temp[];
   CopyBuffer(this.rsi_handler,0,0,1,temp);
   return(temp[0]);
  }

//+------------------------------------------------------------------+
//| Set our indicator values and our buffer size                     |
//+------------------------------------------------------------------+
bool              RSI::SetIndicatorValues(int buffer_size,bool set_as_series)
  {
   rsi_buffer_size = buffer_size;
   CopyBuffer(this.rsi_handler,0,0,buffer_size,rsi_reading);
   if(set_as_series)
      ArraySetAsSeries(this.rsi_reading,true);
   indicator_values_initialized = true;

//--- Did something go wrong?
   vector rsi_test;
   rsi_test.CopyIndicatorBuffer(rsi_handler,0,0,buffer_size);
   if(rsi_test.Sum() == 0)
      return(false);

//--- Everything went fine.
   return(true);
  }
//+--------------------------------------------------------------+
//| Let's set the conditions for our differenced data            |
//+--------------------------------------------------------------+
bool              RSI::SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series)
  {
//--- Internal variables
   rsi_differenced_buffer_size = buffer_size;
   rsi_differenced_values = vector::Zeros(rsi_differenced_buffer_size);

//--- Prepare to record the differences in our RSI readings
   double temp_buffer[];
   int fetch = (rsi_differenced_buffer_size + (2 * differencing_period));
   CopyBuffer(rsi_handler,0,0,fetch,temp_buffer);
   if(set_as_series)
      ArraySetAsSeries(temp_buffer,true);

//--- Fill in our values iteratively
   for(int i = rsi_differenced_buffer_size;i > 1; i--)
     {
      rsi_differenced_values[i-1] = temp_buffer[i-1] - temp_buffer[i-1+differencing_period];
     }

//--- If the norm of a vector is 0, the vector is empty!
   if(rsi_differenced_values.Norm(VECTOR_NORM_P) != 0)
     {
      Print(user_feedback(2));
      indicator_differenced_values_initialized = true;
      return(true);
     }

   indicator_differenced_values_initialized = false;
   Print(user_feedback(3));
   return(false);
  }

 //--- Get a differenced value at a specific index
   double            RSI::GetDifferencedReadingAt(int index)
     {
      //--- Make sure we're not trying to call values beyond our index
      if(index > rsi_differenced_buffer_size)
        {
         Print(user_feedback(4));
         return(-1e10);
        }

      //--- Make sure our values have been set
      if(!indicator_differenced_values_initialized)
        {

         //--- The user is trying to use values before they were set in memory
         Print(user_feedback(1));
         return(-1e10);
        }

      //--- Return the differenced value of our indicator at a specific index
      if((indicator_differenced_values_initialized) && (index < rsi_differenced_buffer_size))
         return(rsi_differenced_values[index]);

      //--- Something went wrong.
      return(-1e10);
     }

//+------------------------------------------------------------------+
//| Get a reading at a specific index from our RSI buffer            |
//+------------------------------------------------------------------+
double            RSI::GetReadingAt(int index)
  {
//--- Is the user trying to call indexes beyond the buffer?
   if(index > rsi_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

//--- Get the reading at the specified index
   if((indicator_values_initialized) && (index < rsi_buffer_size))
      return(rsi_reading[index]);

//--- User is trying to get values that were not set prior
   else
     {
      Print(user_feedback(1));
      return(-1e10);
     }
  }

//+------------------------------------------------------------------+
//| Check if our indicator handler is valid                          |
//+------------------------------------------------------------------+
bool RSI::IsValid(void)
  {
   return((this.rsi_handler != INVALID_HANDLE));
  }

//+------------------------------------------------------------------+
//| Give the user feedback on the actions he is performing           |
//+------------------------------------------------------------------+
string RSI::user_feedback(int flag)
  {
   string message;

//--- Check if the RSI indicator loaded correctly
   if(flag == 0)
     {
      //--- Check the indicator was loaded correctly
      if(IsValid())
         message = "RSI Indicator Class Loaded Correcrtly \nSymbol: " + (string) rsi_symbol + "\nPeriod: " + (string) rsi_period;
      return(message);
      //--- Something went wrong
      message = "Error loading RSI Indicator: [ERROR] " + (string) GetLastError();
      return(message);
     }

//--- User tried getting indicator values before setting them
   if(flag == 1)
     {
      message = "Please set the indicator values before trying to fetch them from memory, call SetIndicatorValues()";
      return(message);
     }

//--- We sueccessfully set our differenced indicator values
   if(flag == 2)
     {
      message = "Succesfully set differenced indicator values.";
      return(message);
     }

//--- Failed  to set our differenced indicator values
   if(flag == 3)
     {
      message = "Failed to set our differenced indicator values: [ERROR] " + (string) GetLastError();
      return(message);
     }

//--- The user is trying to retrieve an index beyond the buffer size and must update the buffer size first
   if(flag == 4)
     {
      message = "The user is attempting to use call an index beyond the buffer size, update the buffer size first";
      return(message);
     }

//--- The class has been deactivated by the user
   if(flag == 5)
     {
      message = "Goodbye.";
      return(message);
     }

//--- No feedback
   else
      return("");
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define HORIZON 10

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
RSI *my_rsi_array[14];
string file_name = Symbol() + " RSI Algorithmic Input Selection.csv";

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- How much data should we store in our indicator buffer?
   int fetch = size + (2 * HORIZON);

//--- Store pointers to our RSI objects
   for(int i = 0; i <= 13; i++)
     {
      //--- Create an RSI object
      my_rsi_array[i] = new RSI(Symbol(),PERIOD_CURRENT,((i+1) * 5),PRICE_CLOSE);
      //--- Set the RSI buffers
      my_rsi_array[i].SetIndicatorValues(fetch,true);
      my_rsi_array[i].SetDifferencedIndicatorValues(fetch,HORIZON,true);
     }

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","True Close","Open","High","Low","Close","RSI 5","RSI 10","RSI 15","RSI 20","RSI 25","RSI 30","RSI 35","RSI 40","RSI 45","RSI 50","RSI 55","RSI 60","RSI 65","RSI 70","Diff RSI 5","Diff RSI 10","Diff RSI 15","Diff RSI 20","Diff RSI 25","Diff RSI 30","Diff RSI 35","Diff RSI 40","Diff RSI 45","Diff RSI 50","Diff RSI 55","Diff RSI 60","Diff RSI 65","Diff RSI 70");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iLow(_Symbol,PERIOD_CURRENT,i) - iLow(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iClose(_Symbol,PERIOD_CURRENT,i) - iClose(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   my_rsi_array[0].GetReadingAt(i),
                   my_rsi_array[1].GetReadingAt(i),
                   my_rsi_array[2].GetReadingAt(i),
                   my_rsi_array[3].GetReadingAt(i),
                   my_rsi_array[4].GetReadingAt(i),
                   my_rsi_array[5].GetReadingAt(i),
                   my_rsi_array[6].GetReadingAt(i),
                   my_rsi_array[7].GetReadingAt(i),
                   my_rsi_array[8].GetReadingAt(i),
                   my_rsi_array[9].GetReadingAt(i),
                   my_rsi_array[10].GetReadingAt(i),
                   my_rsi_array[11].GetReadingAt(i),
                   my_rsi_array[12].GetReadingAt(i),
                   my_rsi_array[13].GetReadingAt(i),
                   my_rsi_array[0].GetDifferencedReadingAt(i),
                   my_rsi_array[1].GetDifferencedReadingAt(i),
                   my_rsi_array[2].GetDifferencedReadingAt(i),
                   my_rsi_array[3].GetDifferencedReadingAt(i),
                   my_rsi_array[4].GetDifferencedReadingAt(i),
                   my_rsi_array[5].GetDifferencedReadingAt(i),
                   my_rsi_array[6].GetDifferencedReadingAt(i),
                   my_rsi_array[7].GetDifferencedReadingAt(i),
                   my_rsi_array[8].GetDifferencedReadingAt(i),
                   my_rsi_array[9].GetDifferencedReadingAt(i),
                   my_rsi_array[10].GetDifferencedReadingAt(i),
                   my_rsi_array[11].GetDifferencedReadingAt(i),
                   my_rsi_array[12].GetDifferencedReadingAt(i),
                   my_rsi_array[13].GetDifferencedReadingAt(i)
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);

//--- Delete our RSI object pointers
   for(int i = 0; i <= 13; i++)
     {
      delete my_rsi_array[i];
     }
  }
//+------------------------------------------------------------------+
#undef HORIZON


Анализ данных в Python

Теперь мы можем приступить к анализу данных, собранных с помощью терминала MetaTrader 5. Первым делом мы загрузим необходимые нам стандартные библиотеки.

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import plotly

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

#Let's read in our market data
data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
data['Bull'] = np.NaN

data.loc[data['True Close'] > data['True Close'].shift(10),'Bull'] = 1
data.loc[data['True Close'] < data['True Close'].shift(10),'Bull'] = 0

data.dropna(inplace=True)
data.reset_index(inplace=True,drop=True)

Нам также необходимо рассчитать фактическую рыночную доходность.

#Estimate the market returns
#Define our forecast horizon
HORIZON = 10
data['Target'] = 0

data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.loc[data['Return'] > 0,'Target'] = 1

#Drop missing values
data.dropna(inplace=True)

Most importantly, we need to delete all the data that overlaps with our intended back test period.

#No cheating boys.
_ = data.iloc[((-365 * 3) + 95):,:]
data = data.iloc[:((-365 * 3) + 95),:]
data

Рис. 4. Наш набор данных больше не содержит ни одной даты, совпадающей с периодом нашего тестирования на истории

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

plt.title('Distribution of EURUSD 10 Day Returns')
plt.grid()
sns.histplot(data['Return'],color='black')

Рис. 5. Визуализация распределения доходности EURUSD за 10 дней

Это упражнение предоставляет нам уникальную возможность визуально увидеть разницу между распределением уровней RSI за короткий период и за длительный период. Пунктирные вертикальные красные линии обозначают стандартизированные уровни 30 и 70. Черные столбцы показывают распределение уровней RSI при периоде 5. Мы видим, что 5-периодный RSI будет генерировать множество сигналов, превышающих стандартизированные уровни. Белые столбцы представляют распределение уровней RSI при установке периода на 70. Визуально мы видим, что сигналы практически не будут генерироваться. Именно это изменение формы распределения создает сложности для алгоритмических трейдеров в соблюдении «лучших практик» использования индикатора.

plt.title('Comapring The Distribution of RSI Changes Across Different RSI Periods')
sns.histplot(data['RSI 5'],color='black')
sns.histplot(data['RSI 70'],color='white')
plt.xlabel('RSI Level')
plt.legend(['RSI 5','RSI 70'])
plt.axvline(30,color='red',linestyle='--')
plt.axvline(70,color='red',linestyle='--')
plt.grid()

Рис. 6. Сравнение распределения уровней RSI в разные периоды RSI

Построение диаграммы рассеяния с 10-периодным изменением 60-периодного RSI по осям x и y позволяет визуализировать наличие взаимосвязи между изменением индикатора и целевым значением. Похоже, что изменение уровня RSI на 10 уровней может быть разумным торговым сигналом для открытия коротких позиций, если значение RSI снизилось на 10 уровней. Or enter long positions if the RSI level increased by 10.

plt.title('Scatter Plot of 10 Day Change in 50 Period RSI & EURUSD 10 Day Return')
sns.scatterplot(data,y='Diff RSI 60',x='Diff RSI 60',hue='Target')
plt.xlabel('50 Period RSI')
plt.ylabel('50 Period RSI')
plt.grid()
plt.axvline(-10,color='red',linestyle='--')
plt.axvline(10,color='red',linestyle='--')

Рис. 7. Визуализация взаимосвязи между изменением 60-периодного RSI и целевым значением

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

plt.title('Scatter Plot of 10 Day Change in 5 Period RSI & EURUSD 10 Day Return')
sns.scatterplot(data,y='Diff RSI 60',x='Diff RSI 5',hue='Target')
plt.xlabel('5 Period RSI')
plt.ylabel('5 Period RSI')
plt.grid()


Рис. 8. По всей видимости, простое использование индикаторов RSI с разными периодами является плохим источником подтверждения

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

Обратите внимание, что у нас всегда есть выбор: использовать статистические модели для повышения точности прогнозирования или для интерпретации и получения ценных выводов. В этой статье мы займемся последним. Мы построим ридж-модель, используя разницу во всех 14 входных данных индикатора RSI. Модель Ridge имеет собственные параметры настройки. Поэтому мы выполним поиск по сетке в пространстве входных данных для статистической ридж-модели. В частности, мы будем корректировать параметры настройки:

  • Alpha - ридж-модель требует добавления штрафной функции для контроля коэффициентов модели. 
  • Tolerance (допуск) - определяет наименьшее изменение, необходимое для установки условий остановки/других условий для подпрограмм в зависимости от выбранного пользователем метода решения.

Для нашего обсуждения мы будем использовать ридж-модель с методом sparse_cg. Читатель также может по своему желанию доработать модель.

Ридж-модель особенно полезна для нас, поскольку она сводит свои коэффициенты к нулю, чтобы уменьшить потери модели. Поэтому мы проведем поиск в широком диапазоне начальных настроек для нашей модели, а затем сосредоточимся на тех начальных настройках, которые привели к наименьшей ошибке. Конфигурация весовых коэффициентов модели в режиме максимальной производительности позволяет быстро определить, от какого периода RSI наша модель зависела в наибольшей степени. В нашем сегодняшнем примере наибольшие коэффициенты в нашей наиболее эффективной модели были получены при изменении RSI на 10 периодов (55 периодов).

#Set the max levels we wish to check
ALPHA_LEVELS = 10
TOL_LEVELS   = 10

#DataFrame labels
r_c = 'TOL_LEVEL_'
r_r = 'ALHPA_LEVEL_'

results_columns = []
results_rows = []

for c in range(TOL_LEVELS):
    n_c = r_c + str(c)
    n_r = r_r + str(c)
    results_columns.append(n_c)
    results_rows.append(n_r)

#Create a DataFrame to store our results
results = pd.DataFrame(columns=results_columns,index=results_rows)

#Cross validate our model
for i in range(TOL_LEVELS):
    tol = 10 ** (-i)
    error = []
    for j in range(ALPHA_LEVELS):
        #Set alpha
        alpha = 10 ** (-j)
        
        #Its good practice to generally check the 0 case
        if(i == 0 & j == 0):
            model = Ridge(alpha=j,tol=i,solver='sparse_cg')

        #Otherwise use a float
        model = Ridge(alpha=alpha,tol=tol,solver='sparse_cg')

        #Store the error levels
        error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Diff RSI 5',
       'Diff RSI 10', 'Diff RSI 15', 'Diff RSI 20', 'Diff RSI 25',
       'Diff RSI 30', 'Diff RSI 35', 'Diff RSI 40', 'Diff RSI 45',
       'Diff RSI 50', 'Diff RSI 55', 'Diff RSI 60', 'Diff RSI 65',
       'Diff RSI 70',]],data['Return'],cv=tscv))))
    
    #Record the error levels
    results.iloc[:,i] = error
    
results
    

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

Параметр настройки  Ошибка модели
ALHPA_LEVEL_0     0.053509  
ALHPA_LEVEL_1     0.056245
ALHPA_LEVEL_2     0.060158
ALHPA_LEVEL_3     0.062230
ALHPA_LEVEL_4     0.061521
ALHPA_LEVEL_5     0.064312
ALHPA_LEVEL_6     0.073248
ALHPA_LEVEL_7     0.079310
ALHPA_LEVEL_8    0.081914
ALHPA_LEVEL_9     0.085171

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

import plotly.graph_objects as go

fig = go.Figure(data =
    go.Contour(
        z=results,
        colorscale='bluered'
    ))

fig.update_layout(
    width = 600,
    height = 400,
    title='Contour Plot Of Our Error Forecasting EURUSD Using Grid Search '
)

fig.show()

Рис. 9: Мы нашли оптимальные входные параметры для нашей модели Ridge, прогнозирующей доходность EURUSD за 10 дней

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

#Let's visualize the importance of each column
model = Ridge(alpha=0,tol=0,solver='sparse_cg')

model.fit(data.loc[:,['Diff RSI 5',
       'Diff RSI 10', 'Diff RSI 15', 'Diff RSI 20', 'Diff RSI 25',
       'Diff RSI 30', 'Diff RSI 35', 'Diff RSI 40', 'Diff RSI 45',
       'Diff RSI 50', 'Diff RSI 55', 'Diff RSI 60', 'Diff RSI 65',
       'Diff RSI 70',]],data['Return'])

#Clearly our model relied on the 25 Period RSI the most, from all the data it had available at training
sns.barplot(np.abs(model.coef_),color='black')
plt.title('Rleative Feature Importance')
plt.ylabel('Coefficient Value')
plt.xlabel('Coefficient Index')
plt.grid()

Рис. 10. Коэффициенту, связанному с разностью RSI за 55 периодов, было присвоено наибольшее значение

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

def objective(x):
    data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
    data['0'] = 0
    data['1'] = 0
    data['2'] = 0
    HORIZON = 10
    data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
    data.dropna(subset=['Return'],inplace=True)
    data.iloc[data['Diff RSI 55'] > x[0],12] = 1
    data.iloc[data['Diff RSI 55'] < x[1],13] = 1
    data.iloc[(data['Diff RSI 55'] < x[0]) & (data['RSI 55'] > x[1]),14] = 1
    #Calculate or RMSE When using those levels
    model = Ridge(alpha=0,tol=0,solver='sparse_cg')
    error = np.mean(np.abs(cross_val_score(model,data.iloc[:,12:15],data['Return'],cv=tscv)))
    return(error)

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

#Bad rules for using the RSI
objective([0,0])

0.026897725573317266

Если мы подставим в нашу функцию значения 70 и 30, ошибка возрастет. Мы проведем поиск по сетке с шагом в 10, чтобы найти изменения уровней RSI, которые лучше подходят для 55-периодного RSI. Наши результаты показали, что оптимальный уровень изменения находится вблизи 10 уровней RSI.

#Bad rules for using the RSI
objective([70,30])

0.031258730612736006

LEVELS  = 10
results = []

for i in np.arange(0,(LEVELS)):
    results.append(objective([i * 10,-(i * 10)]))

plt.plot(results,color='black')
plt.ylabel('Error Rate')
plt.xlabel('Change in RSI as multiples of 10')
plt.grid()
plt.scatter(results.index(min(results)),min(results),color='red')
plt.title('Measuring The Strength of Changes In RSI Levels')

Рис. 11. Визуализация оптимального уровня изменения индикатора RSI

Теперь проведем еще один, более точный поиск, в интервале изменений RSI от 0 до 20. При более внимательном рассмотрении мы видим, что истинный оптимум, по-видимому, находится при значении 9. Однако мы не стремимся к идеальному соответствию историческим данным, это называется переобучением и является плохой практикой. Мы не занимаемся подгонкой кривых. Вместо того чтобы брать оптимальное значение точно там, где оно появляется в нашем анализе исторических данных, мы принимаем тот факт, что положение оптимума может меняться, и вместо этого стремимся находиться в пределах определенной доли стандартного отклонения от оптимального значения с обеих сторон, чтобы использовать эти значения в качестве доверительных интервалов.

LEVELS  = 20
coef = 0.5
results = []

for i in np.arange(0,(LEVELS),1):
    results.append(objective([i,-(i)]))

plt.plot(results)
plt.ylabel('Error Rate')
plt.xlabel('Change in RSI')
plt.grid()
plt.scatter(results.index(min(results)),min(results),color='red')
plt.title('Measuring The Strength of Changes In RSI Levels')


plt.axvline(results.index(min(results)),color='red')
plt.axvline(results.index(min(results)) - (coef * np.std(data['Diff RSI 55'])),linestyle='--',color='red')
plt.axvline(results.index(min(results)) + (coef * np.std(data['Diff RSI 55'])),linestyle='--',color='red')

Рис. 12. Визуализация частоты ошибок, связанных с установкой различных пороговых значений для изменений уровня RSI

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

sns.histplot(data['Diff RSI 55'],color='black')
coef = 0.5
plt.axvline((results.index(min(results))),linestyle='--',color='red')
plt.axvline(results.index(min(results)) - (coef * np.std(data['Diff RSI 55'])),color='red')
plt.axvline(results.index(min(results)) + (coef * np.std(data['Diff RSI 55'])),color='red')
plt.axvline(-(results.index(min(results))),linestyle='--',color='red')
plt.axvline(-(results.index(min(results)) - (coef * np.std(data['Diff RSI 55']))),color='red')
plt.axvline(-(results.index(min(results)) + (coef * np.std(data['Diff RSI 55']))),color='red')
plt.title("Visualizing our Optimal Point in The Distribution")

Рис. 13. Визуализация оптимальных регионов, которые мы выбрали для наших торговых сигналов RSI

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

results.index(min(results)) + ( coef * np.std(data['Diff RSI 55']))

10.822857254027287

И наша нижняя граница.

results.index(min(results)) - (coef * np.std(data['Diff RSI 55']))

7.177142745972713

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

def explanation(x):
    data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
    data['0'] = 0
    data['1'] = 0
    data['2'] = 0
    HORIZON = 10
    data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
    data.dropna(subset=['Return'],inplace=True)
    data.iloc[data['Diff RSI 55'] > x[0],12] = 1
    data.iloc[data['Diff RSI 55'] < x[1],13] = 1
    data.iloc[(data['Diff RSI 55'] < x[0]) & (data['RSI 55'] > x[1]),14] = 1
    #Calculate or RMSE When using those levels
    model = Ridge(alpha=0,tol=0,solver='sparse_cg')
    model.fit(data.iloc[:,12:15],data['Return'])
    return(model.coef_.copy())

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

opt = 9

print(explanation([opt,-opt]))

[ 1.97234840e-04 -1.64215118e-04 -7.55222156e-05]


Создание советника

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

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define RSI_PERIOD 55
#define RSI_TIME_FRAME PERIOD_D1
#define SYSTEM_TIME_FRAME PERIOD_D1
#define RSI_PRICE  PRICE_CLOSE
#define RSI_BUFFER_SIZE 20
#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)

Загрузим наши библиотеки.

//+------------------------------------------------------------------+
//| Load our RSI library                                             |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <Trade\Trade.mqh>

Нам понадобится несколько глобальных переменных.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
RSI    rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
double last_value;
int    count;
int    ma_o_handler,ma_c_handler;
double ma_o[],ma_c[];
double trade_sl;

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Update our system variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_c_handler,0,0,1,ma_c);
      CopyBuffer(ma_o_handler,0,0,1,ma_o);

      if((count == 0) && (PositionsTotal() == 0))
        {
         rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true);
         last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);
         count = 1;
        }

      if(PositionsTotal() == 0)
         check_signal();

      if(PositionsTotal() > 0)
         manage_setup();
     }
  }

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

//+------------------------------------------------------------------+
//| Manage our open trades                                           |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl);
      double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl);
      //--- Buy setup
      if((current_tp > current_sl) && (new_sl < current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);

      //--- Sell setup
      if((current_tp < current_sl) && (new_sl > current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);
     }
  }

Наша функция настройки отвечает за запуск системы с нуля. Это подготовит необходимые нам индикаторы и сбросит счетчики.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_OPEN);
   count        = 0;
   last_value   = 0;
   trade_sl     = 1.5e-2;
  }

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

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   double current_reading = rsi_55.GetCurrentReading();
   Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading));
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double cp_lb = 7.17;
   double cp_ub = 10.82;

   if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb))))
     {
      if(ma_o[0] > ma_c[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl));
            count = 0;
           }
        }
     }

   if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub))
     {
      if(ma_c[0] < ma_o[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
            count = 0;
           }
        }
     }
  }
//+------------------------------------------------------------------+

#undef RSI_BUFFER_SIZE
#undef RSI_PERIOD
#undef RSI_PRICE
#undef RSI_TIME_FRAME
#undef SYSTEM_TIME_FRAME
#undef TRADING_VOLUME
//+------------------------------------------------------------------+

Давайте теперь приступим к тестированию торговой системы на истории. Напомним, что на рис. 4 мы удалили все данные, которые пересекаются с результатами нашего тестирования на истории. Таким образом, это может служить нам приблизительным представлением того, как наша стратегия может работать в реальном времени. Мы проведем трехлетнее тестирование нашей стратегии на основе ежедневных данных, начиная с 1 января 2022 года и до марта 2025 года.

Наши даты

Рис. 14. Даты, которые мы будем использовать для тестирования торговой стратегии на истории

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

Рис. 15. Условия, в которых мы будем проводить наши тесты, имеют огромное значение и влияют на прибыльность нашей стратегии

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

Рис. 16. Кривая эквити, построенная на основе торговой стратегии EURUSD с 55-периодным RSI

При применении нашей стратегии в тестере были получены следующие статистические данные:

  • Коэффициент Шарпа: 0,92
  • Матожидание выигрыша: 2,49
  • Чистая прибыль: USD151,87
  • Прибыльные сделки: 57,38%

Однако обратите внимание на то, что из 61 совершенной сделки только 4 были на покупку. Почему наша стратегия в такой непропорциональной степени ориентирована на продажу? Как мы можем это исправить? Давайте еще раз взглянем на график EURUSD и попробуем разобраться вместе.

Тестирование на истории

Рис. 17. Подробная статистика исторической эффективности советника на EURUSD D1.


Улучшение советника

На рис. 18 я приложил скриншот месячного курса EURUSD. Две красные вертикальные линии обозначают начало 2009 года и конец 2021 года соответственно. Зеленая вертикальная линия обозначает начало обучающих данных, которые мы использовали для нашей статистической модели. Нетрудно понять, почему модель выработала склонность к коротким позициям, учитывая устойчивый медвежий тренд, начавшийся в 2008 году.

Рис. 18. Понимание того, почему наша модель имеет такой медвежий настрой

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

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


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

Для начала импортируем несколько библиотек.

from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestRegressor

Затем определим наши зависимые и независимые переменные.

#Independent variable
X = data[['Diff RSI 55']]
#Dependent variable
y = data['Target']

Подберем модель.

model = RandomForestRegressor()
model.fit(X,y)

Подготовимся к экспорту в ONNX.

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Определим формы входных данных.

inital_params = [("float_input",FloatTensorType([1,1]))]

Создадим прототип ONNX и сохраним его на диск.

onnx_proto = convert_sklearn(model=model,initial_types=inital_params,target_opset=12)
onnx.save(onnx_proto,"EURUSD Diff RSI 55 D1 1 1.onnx")

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

import netron
netron.start("EURUSD Diff RSI 55 D1 1 1.onnx")

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

Модель ONNX

Рис. 19. Визуализация нашей модели регрессора случайного леса ONNX

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

Данные о модели ONNX

Рис. 20. Характеристики нашей модели ONNX соответствуют ожидаемым параметрам, которые мы хотели проверить


Улучшение советника

Теперь, когда мы экспортировали вероятностную модель рынка EURUSD, мы можем импортировать нашу модель ONNX в наш советник.

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Diff RSI 55 D1 1 1.onnx" as uchar onnx_model_buffer[];

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

#define ONNX_INPUTS 1
#define ONNX_OUTPUTS 1

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

long   onnx_model;
vectorf onnx_output(1);
vectorf onnx_input(1);

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

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our technical indicators
  ...

//--- Create our ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      return(false);
     }

//--- Define the I/O signature
   ulong onnx_param[] =  {1,1};

   if(!OnnxSetInputShape(onnx_model,0,onnx_param))
      return(false);

   if(!OnnxSetOutputShape(onnx_model,0,onnx_param))
      return(false);

   return(true);
  }

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

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
   onnx_input[0] = (float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - 1);

//--- Our Random forest model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output))
      Comment("Failed to obtain a forecast from our model!");

   else
     {
      if(onnx_output[0] > 0.5)
         if(ma_o[0] < ma_c[0])
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
      Print("Model Bullish Probabilty: ",onnx_output);
     }
}

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

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Diff RSI 55 D1 1 1.onnx" as uchar onnx_model_buffer[];

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define RSI_PERIOD 55
#define RSI_TIME_FRAME PERIOD_D1
#define SYSTEM_TIME_FRAME PERIOD_D1
#define RSI_PRICE  PRICE_CLOSE
#define RSI_BUFFER_SIZE 20
#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)
#define ONNX_INPUTS 1
#define ONNX_OUTPUTS 1
#define HORIZON 10

//+------------------------------------------------------------------+
//| Load our RSI library                                             |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
RSI    rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
double last_value;
int    count;
int    ma_o_handler,ma_c_handler;
double ma_o[],ma_c[];
double trade_sl;
long   onnx_model;
vectorf onnx_output(1);
vectorf onnx_input(1);

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Update our system variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_c_handler,0,0,1,ma_c);
      CopyBuffer(ma_o_handler,0,0,1,ma_o);

      if((count == 0) && (PositionsTotal() == 0))
        {
         rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true);
         rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
         last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);
         count = 1;
        }

      if(PositionsTotal() == 0)
         check_signal();

      if(PositionsTotal() > 0)
         manage_setup();
     }
  }

//+------------------------------------------------------------------+
//| Manage our open trades                                           |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl);
      double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl);
      //--- Buy setup
      if((current_tp > current_sl) && (new_sl < current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);

      //--- Sell setup
      if((current_tp < current_sl) && (new_sl > current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);
     }
  }

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our technical indicators
   ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_OPEN);
   count        = 0;
   last_value   = 0;
   trade_sl     = 1.5e-2;

//--- Create our ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      return(false);
     }

//--- Define the I/O signature
   ulong onnx_param[] =  {1,1};

   if(!OnnxSetInputShape(onnx_model,0,onnx_param))
      return(false);

   if(!OnnxSetOutputShape(onnx_model,0,onnx_param))
      return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
   last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);

   double current_reading = rsi_55.GetCurrentReading();
   Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading));
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double cp_lb = 7.17;
   double cp_ub = 10.82;
   onnx_input[0] = (float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - 1);

//--- Our Random forest model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output))
      Comment("Failed to obtain a forecast from our model!");

   else
     {
      if(onnx_output[0] > 0.5)
         if(ma_o[0] < ma_c[0])
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
      Print("Model Bullish Probabilty: ",onnx_output);
     }

//--- The trading rules we learned from our Ridge Regression Model
//--- Ridge Regression Sell
   if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb))))
     {
      if(ma_o[0] > ma_c[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl));
            count = 0;
           }
        }
     }
//--- Ridge Regression Buy
   else
      if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub))
        {
         if(ma_c[0] < ma_o[0])
           {
            if(PositionsTotal() == 0)
              {
               Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
               count = 0;
              }
           }
        }
  }
//+------------------------------------------------------------------+

#undef RSI_BUFFER_SIZE
#undef RSI_PERIOD
#undef RSI_PRICE
#undef RSI_TIME_FRAME
#undef SYSTEM_TIME_FRAME
#undef TRADING_VOLUME
#undef ONNX_INPUTS
#undef ONNX_OUTPUTS
//+------------------------------------------------------------------+

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

Рис. 21. Визуализация прибыльности второй торговой стратегии 

Различия между двумя стратегиями начинают проявляться только при рассмотрении подробной статистики. Коэффициент Шарпа и ожидаемая прибыль снизились. Наша новая стратегия позволила совершить 85 сделок, что на 39% больше, чем 61 сделка, совершенная по нашей первоначальной стратегии. Кроме того, общее количество наших длинных позиций увеличилось с 4 в первом тесте до 42 во втором. Это увеличение на 950%. Таким образом, с учетом значительного объема дополнительного риска, точность и прибыльность показателей снижаются лишь незначительно. Это хороший знак. В нашем предыдущем тесте прибыльными оказались 57,38% всех сделок, а теперь этот показатель составляет 56,47%, что означает снижение точности примерно на 1,59%.

Рис. 22: Подробная статистика эффективности пересмотренной версии нашей торговой стратегии



Заключение

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

Имя файла Описание
Algorithmic Inputs Selection.ipynb Для проведения численного анализа с использованием Python мы использовали Jupyter Notebook.
EURUSD Testing RSI Class.mql5 MQL5-скрипт для тестирования пользовательской реализации класса RSI.
EURUSD RSI Algorithmic Input Selection.mql5 Скрипт для получения исторических данных о рынке.
Algorithmic Input Selection.mql5 Первоначальная версия разработанной нами торговой стратегии.
Algorithmic Input Selection 2.mql5 Усовершенствованная версия торговой стратегии, в которой исправлены ошибки, выявленные в ходе разработки первоначальной торговой стратегии.

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

Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Знакомство с языком MQL5 (Часть 27): Освоение API и функции WebRequest в языке MQL5 Знакомство с языком MQL5 (Часть 27): Освоение API и функции WebRequest в языке MQL5
В этой статье рассматривается, как использовать функцию WebRequest() и API в языке MQL5 для взаимодействия с внешними платформами. Вы узнаете, как создать Telegram-бота, получать идентификаторы чатов и групп, а также отправлять, редактировать и удалять сообщения непосредственно из MetaTrader 5, и тем самым заложите прочный фундамент для интеграции API в ваши будущие проекты на языке MQL5.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
От новичка до эксперта: Алгоритмическая дисциплина трейдера — советник Risk Enforcer вместо эмоций От новичка до эксперта: Алгоритмическая дисциплина трейдера — советник Risk Enforcer вместо эмоций
Для многих трейдеров разрыв между знанием правил управления рисками и последовательным их соблюдением приводит к гибели счетов. Эмоциональное подавление, торговля с целью отыграться и простая оплошность могут разрушить даже самую лучшую стратегию. Сегодня мы превратим платформу MetaTrader 5 в надежного исполнителя ваших торговых правил, разработав советник по управлению рисками под названием Risk Enforcement Expert Advisor. Присоединяйтесь к этой дискуссии, чтобы узнать больше.