English Español Deutsch 日本語
preview
Создание самооптимизирующихся советников на MQL5

Создание самооптимизирующихся советников на MQL5

MetaTrader 5Примеры | 16 августа 2024, 14:02
214 16
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Введение

Самооптимизирующиеся автоматизированные системы имеют решающее значение на современных динамичных финансовых рынках. В цифровую эпоху рынки стали заметно более волатильными из-за широкого распространения алгоритмической торговли, особенно среди высокочастотных трейдеров. Согласно документу Комиссии по ценным бумагам и биржам (SEC) SEC High-Frequency Trading Paper, посвященной высокочастотной торговле, на долю высокочастотных трейдеров приходится почти половина всех сделок в Европе и США.

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

Вопреки некоторым представлениям, MQL5 идеально подходит для этой задачи. Его API предоставляет обширные матричные и векторные функции, позволяющие создавать компактные модели машинного обучения. В этом введении особое внимание уделяется использованию MQL5 для создания самооптимизирующихся ботов. Объектно-ориентированное программирование сокращает повторы кода и повышает адаптивность к различным временным рамкам и рыночным условиям.

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


Синопсис: Разработка самооптимизирующихся советников.

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

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

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

Прочитав эту статью, читатель освоит:

  1. Подборку полезных матричных и векторных функций
  2. Основополагающие концепции объектно-ориентированного программирования в MQL5
  3. Фреймворк для создания динамических и самоадаптирующихся советников на MQL5

Самооптимизация с градиентным спуском

Наша цель — разработать советника, способного последовательно адаптироваться к текущим рыночным условиям. Для этого мы реализуем алгоритм градиентного спуска на MQL5. Для читателей, незнакомых с алгоритмом градиентного спуска, может быть полезно сравнить его с процессом, с помощью которого диджей настраивает свое звуковое оборудование. Представьте, что вы диджей, готовящийся к выступлению. Вы включаете оборудование и замечаете, что громкость слишком велика. Что бы вы сделали дальше? Скорее всего, вы бы уменьшили громкость. Однако после этого громкость стала слишком слабой, и поэтому вы ее увеличиваете. Это увеличение и уменьшение происходит до тех пор, пока вы не найдете баланс.

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

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

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


Торговая стратегия

Наша торговая стратегия будет представлять собой гибридный подход с использованием технического анализа и машинного обучения. Мы будем использовать скользящую среднюю, чтобы определить тренд. Если цена выше скользящей средней, мы предполагаем, что тренд бычий, в противном случае, если цена ниже скользящей средней, мы предполагаем, что тренд медвежий. После того, как мы определили текущий тренд, мы будем искать подтверждение от двух подтверждающих индикаторов - индекса относительной силы (RSI) и процентного диапазона Уильямса (WPR). 

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

Индикатор WPR выдает показания от 0 до -100. Как и RSI, индикатор WPR определяет зоны перекупленности и перепроданности. Однако валюты не могут быть перекуплены или перепроданы, предложение валюты неограниченно, поэтому в нашей стратегии мы будем интерпретировать WPR несколько иначе. В нашей стратегии, когда индикатор WPR выше -20, мы рассматриваем это как сигнал на покупку, а если индикатор WPR ниже -80 - как сигнал на продажу.

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

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


Реализация на MQL5

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

Начнем с создания нового класса в редакторе MetaEditor.

Создание нового класса

Рис. 1. Создание нового класса в MQL5


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


Сохранение нового класса

Рис. 2. Построение нашего класса линейной регрессии


Если вы выполнили описанные выше шаги, Мастер MQL5 поможет вам создать код, подобный этому.

class LinearRegegression
  {
private:

public:
                     LinearRegegression();
                    ~LinearRegegression();
  };
LinearRegegression::LinearRegegression()
  {
  }
LinearRegegression::~LinearRegegression()
  {
  }

Если вы впервые имеете дело с ООП в MQL5, то давайте вместе разберем приведенный выше код. На самом верху у нас находится определение класса. Ключевое слово class определяет весь этот код как класс, после ключевого слова class следует имя класса. Отсюда мы переходим к основной части класса. Ключевое слово "private" определяет переменные и функции, к которым нельзя получить доступ за пределами класса, тогда как ключевое слово "public" определяет переменные и функции, к которым можно получить доступ за пределами класса. Обратите внимание, что в определениях нашего класса уже есть две функции.

Первая функция LinearRegression() называется конструктором. Это первая функция, вызываемая всякий раз, когда мы запускаем новый экземпляр нашего класса, а последняя функция ~LinearRegression() называется деструктором. Деструктор — это последняя функция, вызываемая всякий раз, когда мы удаляем класс нашего графика.

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

  • max_learning_rate_power определяет, насколько глубоко мы будем искать хорошую скорость обучения.
  • fetch - это количество свечей, которые мы хотим проанализировать на рынке.
  • start и predict определяют, когда мы начнем получать данные и точку, с которой мы будем делать наш прогноз.
  • look_ahead определяет, на сколько шагов в будущее мы хотим сделать прогноз.
  • mae_array - это массив, в котором будет храниться наша метрика ошибок.
  • trained - это флаг, который сообщает нам, обучена ли наша модель и готова ли она к использованию.
  • epochs_power - количество эпох, которые мы будем использовать для обучения нашей модели.
  • У нас есть 2 вектора mae_train и mae_validation, в которых хранятся наши метрики ошибок, полученные в ходе обучения и проверки.
  • У нас есть 4 вектора x и y_validation, а также x и y_train. Эти векторы содержат наши данные для обучения и проверки.
  • Векторы m и b содержат оценки соответствующих коэффициентов m и b для нашей модели.
  • double forecast - это предсказание нашей модели.
  • learning_rate_power - это степень, в которую мы возводим 0,1, чтобы определить нашу скорость обучения.
  • epochs - количество раз, которое мы будем обучать модель.
  • n — количество строк в наших данных, всегда равно fetch.
  • "output_end,output_start,input_end,input_start" определяют наше разделение на обучающий и тестовый наборы.
private:
                     //This is the highest power that we will raise ten to, as we are searching for coefficients
                     ulong max_learning_rate_power; 
                     //This is how many bars we should fetch
                     int fetch;
                     //This is where we will start collecting data, it is the end of our validation data set. 
                     datetime start,predict;
                     //This is how many steps into the future we want to forecast
                     int look_ahead;
                     //This is the array that will contain our MAE readings from testing different learning rates on the validation data
                     double mae_array[30];
                     //Trained flag to inform us if the model has been fit and optimised succesfuly and is ready for use
                     bool trained;
                     //The number to raise the power of 10 buy when calculating the number of epochs
                     int epochs_power;
                     //Our error metrics
                     vector mae_train,mae_validation;
                     //This vector contains our inputs validation and training set
                     vector x_validation,x_train;
                     //This vector contains our outputs validation and training set
                     vector y_validation,y_train;
                     //This vector contains our predictions on the validation set
                     vector y_hat_validation,y_hat_train;
                     //This vector contains our gradient coefficient
                     vector m;
                     //This vector contains our model bias
                     vector b;
                     //This is our model's forecast
                     double forecast;
                     //This is our current learning rate power
                     ulong learning_rate_power;
                     //This is the learning rate power we are currently evaluating
                     int lr_error_index;
                     //This is our current learning rate
                     double learning_rate;
                     //This is the number of rounds we will allow whilst training our model
                     double epochs;
                     //This is used in calculations, it is the number of rows in our data, or the fetch size.
                     ulong n;
                     //These are the times for our input and output data
                     datetime output_end,output_start,input_end,input_start;
                     //These are the index times for our input and output data
                     int index_output_end,index_output_start,index_input_end,index_input_start;
                     //This is the value we will use to scale our data
                     double first_reading;
                     bool allowed_to_evaluate;
                     //Update the learning rate
                     bool UpdateLearningRate(void);
                     //Update the number of epochs
                     bool UpdateEpochs(void);
                     //Set the number of epochs
                     bool SetEpochs(int _epochs_power);
                     //Reset the number of epochs 
                     bool ResetEpochs(void);
                     //Reset the learning rate
                     bool ResetLearningRate(void);
                     //This function will fit the coeffeicients
                     bool Fit(void);
                     //This function evaluates the current settings
                     bool Evaluate(ulong _index,int _epochs_power);
                     //This function will scale the input data
                     bool ScaleInputs(void);
                     //This function sets the learning rate
                     bool SetLearningRate(ulong _learning_rate_power);

Теперь перейдем к публичным определениям в нашем классе.

public:
                     //Constructor 
                     LinearRegression();
                     //Fetch Current Validation Data
                     bool GetCurrentValidationData(void);
                     //Initialise the LinearRegressor Model
                     void Init(int _fetch,int _look_ahead);
                     //Function to determine if the model has been trained and is ready for use.
                     bool Trained(void);
                     //A function to train the model using the best learning rate and the most recent prices
                     bool Train(void);
                     //A function to predict future price using the current price.
                     double Predict(void);
                     //Destructor
                    ~LinearRegression();

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

Обратите внимание, что наш конструктор не принимает никаких входных данных, это называется конструктором по умолчанию или непараметрическим конструктором. Кроме того, обратите внимание, что конструктор не имеет возвращаемого типа, даже void.

LinearRegression::LinearRegression()
  {
      Print("Current Symbol: ",_Symbol);
  }

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

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

void LinearRegression::Init(int _fetch,int _look_ahead)
   {
      //Clear The Chart
      ObjectsDeleteAll(0);      
      //Allow evaluations
      allowed_to_evaluate = true;
      //Epochs power
      epochs_power =4;
      //Set the number of epochs
      epochs = 5 * MathPow(10,epochs_power);
      //Has the model been trained?
      trained = false;
      //Set the maximum learning rate power
      max_learning_rate_power = 30;
      //Set the end of our validation data
      start = iTime(_Symbol,PERIOD_CURRENT,1);
      //This is how much data we're going to fetch
      this.fetch = _fetch - 1;
      //This is how far into the future we want to forecast
      this.look_ahead = _look_ahead + 1;
      //Set the gradient coefficient to a random value
      m = vector::Zeros(1);
      //Set the bias to a random value
      b = vector::Zeros(1);
      //Set the forecast to 0
      forecast = 0;
      //Our model's learning rate will start at 0
      learning_rate_power = 0;
      //This is the learning rate we are evaluting
      lr_error_index = 0;
      mae_train = vector::Full(1,MathPow(10,100));
      mae_validation = vector::Full(30,MathPow(10,10000));
      //Set the initial learning rate
      learning_rate = MathPow(0.1,(learning_rate_power));
      //Set the number of rows
      n = fetch;
      if(GetCurrentValidationData())
         {
            //Scale the data
            ScaleInputs();
            //Fit the model
            Fit();   
         }      
   }

Функция predict предназначена для возврата типа данных double без каких-либо параметров, поэтому она имеет входные данные void. Примечательно, что для обозначения функции как члена класса мы добавляем к ней префикс в виде имени класса, за которым следуют два двоеточия и имя функции.

Функция predict сначала проверит, прошла ли модель обучение, вызвав функцию Trained(). После подтверждения статуса обучения модели мы приступаем к сбору данных в реальном времени, в частности текущей цены и цены закрытия, а также данных временных меток для контекста прогнозирования. Мы вычисляем прогнозируемую цену, умножая текущую цену на m и добавляя b. Затем мы возвращаем прогноз или возвращаем 0, если модель не обучена.

double LinearRegression::Predict(void)
   {
      if(Trained())
         {
            double _current_reading = iClose(_Symbol,PERIOD_CURRENT,0);
            predict = iTime(_Symbol,PERIOD_CURRENT,0);
            
            double prediction = (m[0]*_current_reading)+b[0];
            if(prediction > _current_reading)
               {
                  Comment("Buy, forecast: ",prediction);
               }
            else if(prediction < _current_reading)
               {
                  Comment("Sell, forecast: ",prediction);
               }
            
            ObjectCreate(0,"prediction point",OBJ_VLINE,0,predict,0);
            ObjectCreate(0,"forecast",OBJ_HLINE,0,predict,prediction);
            return(prediction);
         }
         
      return(0);
   }

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

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

bool LinearRegression::GetCurrentValidationData(void)
   {
      //Indexes
      index_output_end = 1;
      index_output_start = index_output_end + fetch;
      index_input_end = index_output_end + look_ahead;
      index_input_start = index_output_start + look_ahead; 
      
      //Assigning time stamps
      output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
      output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
      input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
      input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
      
      //Get the output data
      if(!y_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Get the input data
      if(!x_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Print the vectors we have
      if(x_validation.Size() != y_validation.Size())
         {
            Print("Failed to get market data: Our vectors aren't the same length.");
            return(false);
         }
         
         //Print the vectors and plot the data points
         Print("X validation: ",x_validation);
         ObjectCreate(0,"X validation end",OBJ_VLINE,0,input_end,0);
         ObjectCreate(0,"X validation start",OBJ_VLINE,0,input_start,0);
         
         //Print the vectors and plot the data points
         Print("y validation: ",y_validation);
         ObjectCreate(0,"y validation end",OBJ_VLINE,0,output_end,0);
         ObjectCreate(0,"y validation start",OBJ_VLINE,0,output_start,0);
         
         //Set the training data
         index_output_end = index_input_start + (look_ahead * 2);
         index_output_start = index_output_end + fetch;
         index_input_end = index_output_end + look_ahead;
         index_input_start = index_output_start + look_ahead; 
         
         //Assigning time stamps
         output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
         output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
         input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
         input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
         
         //Copy the training data   
         if(!y_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
            
         //Copy the training data   
         if(!x_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
         
         //Check if the data matches
         if(x_train.Size() != y_train.Size())
            {
               Print("Error fetching training dataL: The x and y vectors are not the same size");
            }
           
           //Print the vectors and plot the data points 
            Print("X training: ",x_train);
            ObjectCreate(0,"X training end",OBJ_VLINE,0,input_end,0);
            ObjectCreate(0,"X training start",OBJ_VLINE,0,input_start,0);
            
            Print("y training: ",y_train);
            ObjectCreate(0,"y training end",OBJ_VLINE,0,output_end,0);
            ObjectCreate(0,"y training start",OBJ_VLINE,0,output_start,0);
            return(true);
   }

Теперь определим нашу функцию соответствия (Fit). Функция начинается с использования текущих значений m и b для генерации прогнозов на основе обучающих данных. Затем она оценивает ошибку в обучающих данных, вычисляя абсолютные разницы между фактическими наблюдениями Y и нашими прогнозируемыми наблюдениями Y.

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

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

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

bool LinearRegression::Fit()
   {
      Print("Fitting a linear regression on the training set with learning rate ",learning_rate_power);

      Print("Evalutaions: ",allowed_to_evaluate);
      for(int i =0; i < epochs;i++)
         {
            //Measure error
            y_hat_train = (m[0]*x_train) + b[0];
            vector y_minus_y_hat = (y_train - y_hat_train);
            vector y_minus_y_hat_sqaured = MathAbs((y_train - y_hat_train));
            mae_train.Set(0,( y_minus_y_hat_sqaured.Mean()));
            vector x_times_y_minus_y_hat = (x_train*(y_train -y_hat_train));
            
            //Aproximate the derivatives
            double derivative_m = (-2.0/n) * x_times_y_minus_y_hat.Sum();
            double derivative_b = (-2.0/n) * y_minus_y_hat.Sum();
            
            //Update the linear parameters
            m[0] = m[0] - (learning_rate * derivative_m);
            b[0] = b[0] - (learning_rate * derivative_b);
         }
         
         //Finished fitting the coefficients
         Print("Fit on training data complete.\nm: ",m[0]," b: ",b[0]," mae ",mae_train[0],"\nlearning rate: ",learning_rate);
         
         if(allowed_to_evaluate)
            {
               Evaluate(learning_rate_power,epochs_power);
            }
            
         //Return true
         return(true);
   }

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

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

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

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

//This function evaluates the current coefficient settings and learning rate
bool LinearRegression::Evaluate(ulong _index)
   {
      Print("Evaluating the coefficients m:",m[0]," b: ",b[0]," at learning rate: ",learning_rate);
      
      //First check if the coefficient and learning rate are valid
      if((m.HasNan() > 0 || b.HasNan() > 0 || m[0] == 0 || b[0] == 0 || _index > max_learning_rate_power) && (_index < max_learning_rate_power))
         {
            Print("Coefficients are invalid");
            m[0] = 0;
            b[0] = 0 ;
            mae_array[_index] = MathPow(10,100000);
            //Update the learning rate
            UpdateLearningRate();
            //Fit the model again
            Fit();   
         }
         
      else
         {
            //Validation predictions
            if(_index < max_learning_rate_power)
               {
                  Print("Coefficients are valid, solution at index ",_index);
                  y_hat_validation = (m[0] * x_validation) + b[0];   
                  vector y_minus_y_hat_squared = MathAbs(y_validation - y_hat_validation);
                  //If everything is fine, let's assess the validation mae
                  mae_array[_index] = (1.0/n) * y_minus_y_hat_squared.Sum();
                  //What was the validation error?
                  Print("Validation error: ",(1.0/n) * y_minus_y_hat_squared.Sum());
                  //Update the learning rate
                  UpdateLearningRate();
                  //Fit the model again
                  Fit();   
               }
         }  
         
      if(_index == max_learning_rate_power)
         {
            for(int i = 0; i < max_learning_rate_power;i++)
               {
                  mae_validation[i] = mae_array[i];   
               }
            allowed_to_evaluate = false;
            trained = true;
            Print("Validation mae: \n",mae_validation);
            Print("Lowest validation mae: ",mae_validation.Min());
            ulong chosen_learning_rate = mae_validation.ArgMin();
            Print("Chosen learning rate ",MathPow(0.1,(chosen_learning_rate)));
            SetLearningRate(chosen_learning_rate);
            Fit();
         }
         
         return(true);
   }

Определим также функцию для масштабирования наших входных данных. Функция проста для понимания: она делит все наши входные данные на первую запись в нашем обучающем векторе.

//This function will scale our inputs
bool LinearRegression::ScaleInputs(void)
   {
      //Set the first reading
      first_reading = x_train[0];
      x_train = x_train / first_reading;
      x_validation = x_validation / first_reading;
      return(true);
   }

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

LinearRegression::~LinearRegression()
  {
      ResetLearningRate();
      ResetLastError();
  }

Функции, вызываемые деструктором, определяются следующим образом:

bool LinearRegression::ResetLearningRate(void)
   {
         learning_rate_power = 0;
         learning_rate = MathPow(0.1,learning_rate_power);
         return(true);
   }

Когда мы собираем всё это вместе, то получаем наше определение класса:

#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com"
#property version   "1.00"
class LinearRegression
  {
private:
                     //This is the highest power that we will raise ten to, as we are searching for coefficients
                     ulong max_learning_rate_power; 
                     //This is how many bars we should fetch
                     int fetch;
                     //This is where we will start collecting data, it is the end of our validation data set. 
                     datetime start,predict;
                     //This is how many steps into the future we want to forecast
                     int look_ahead;
                     //This is the array that will contain our MAE readings from testing different learning rates on the validation data
                     double mae_array[30];
                     //Trained flag to inform us if the model has been fit and optimised succesfuly and is ready for use
                     bool trained;
                     //The number to raise the power of 10 buy when calculating the number of epochs
                     int epochs_power;
                     //Our error metrics
                     vector mae_train,mae_validation;
                     //This vector contains our inputs validation and training set
                     vector x_validation,x_train;
                     //This vector contains our outputs validation and training set
                     vector y_validation,y_train;
                     //This vector contains our predictions on the validation set
                     vector y_hat_validation,y_hat_train;
                     //This vector contains our gradient coefficient
                     vector m;
                     //This vector contains our model bias
                     vector b;
                     //This is our model's forecast
                     double forecast;
                     //This is our current learning rate power
                     ulong learning_rate_power;
                     //This is the learning rate power we are currently evaluating
                     int lr_error_index;
                     //This is our current learning rate
                     double learning_rate;
                     //This is the number of rounds we will allow whilst training our model
                     double epochs;
                     //This is used in calculations, it is the number of rows in our data, or the fetch size.
                     ulong n;
                     //These are the times for our input and output data
                     datetime output_end,output_start,input_end,input_start;
                     //These are the index times for our input and output data
                     int index_output_end,index_output_start,index_input_end,index_input_start;
                     //This is the value we will use to scale our data
                     double first_reading;
                     bool allowed_to_evaluate;
                     //Update the learning rate
                     bool UpdateLearningRate(void);
                     //Update the number of epochs
                     bool UpdateEpochs(void);
                     //Set the number of epochs
                     bool SetEpochs(int _epochs_power);
                     //Reset the number of epochs 
                     bool ResetEpochs(void);
                     //Reset the learning rate
                     bool ResetLearningRate(void);
                     //This function will fit the coeffeicients
                     bool Fit(void);
                     //This function evaluates the current settings
                     bool Evaluate(ulong _index);
                     //This function will scale the input data
                     bool ScaleInputs(void);
                     //This function sets the learning rate
                     bool SetLearningRate(ulong _learning_rate_power);
                     
public:
                     //Constructor 
                     LinearRegression();
                     //Fetch Current Validation Data
                     bool GetCurrentValidationData(void);
                     //Initialise the LinearRegressor Model
                     void Init(int _fetch,int _look_ahead);
                     //Function to determine if the model has been trained and is ready for use.
                     bool Trained(void);
                     //A function to train the model using the best learning rate and the most recent prices
                     bool Train(void);
                     //A function to predict future price using the current price.
                     double Predict(void);
                     //Destructor
                    ~LinearRegression();
  };

bool LinearRegression::UpdateEpochs(void)
   {
      epochs_power = epochs_power + 1;
      epochs = MathPow(10,epochs_power);
      return(true);
   }

bool LinearRegression::ResetEpochs(void)
   {
      epochs_power = 0 ;
      epochs = MathPow(10,epochs_power);
      return(true);
   }

bool LinearRegression::SetEpochs(int _epochs_power)
   {
      epochs_power = _epochs_power;
      epochs = MathPow(10,epochs_power);
      return(true);
   }

double LinearRegression::Predict(void)
   {
      if(Trained())
         {
            double _current_reading = iClose(_Symbol,PERIOD_CURRENT,0);
            predict = iTime(_Symbol,PERIOD_CURRENT,0);
            
            double prediction = (m[0]*_current_reading)+b[0];
            if(prediction > _current_reading)
               {
                  Comment("Buy, forecast: ",prediction);
               }
            else if(prediction < _current_reading)
               {
                  Comment("Sell, forecast: ",prediction);
               }
            
            ObjectCreate(0,"prediction point",OBJ_VLINE,0,predict,0);
            ObjectCreate(0,"forecast",OBJ_HLINE,0,predict,prediction);
            return(prediction);
         }
         
      return(0);
   }

bool LinearRegression::GetCurrentValidationData(void)
   {
      //Indexes
      index_output_end = 1;
      index_output_start = index_output_end + fetch;
      index_input_end = index_output_end + look_ahead;
      index_input_start = index_output_start + look_ahead; 
      
      //Assigning time stamps
      output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
      output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
      input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
      input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
      
      //Get the output data
      if(!y_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Get the input data
      if(!x_validation.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
         {
            Print("Failed to get market data: ",GetLastError());
            return(false);
         }
      //Print the vectors we have
      if(x_validation.Size() != y_validation.Size())
         {
            Print("Failed to get market data: Our vectors aren't the same length.");
            return(false);
         }
         
         //Print the vectors and plot the data points
         Print("X validation: ",x_validation);
         ObjectCreate(0,"X validation end",OBJ_VLINE,0,input_end,0);
         ObjectCreate(0,"X validation start",OBJ_VLINE,0,input_start,0);
         
         //Print the vectors and plot the data points
         Print("y validation: ",y_validation);
         ObjectCreate(0,"y validation end",OBJ_VLINE,0,output_end,0);
         ObjectCreate(0,"y validation start",OBJ_VLINE,0,output_start,0);
         
         //Set the training data
         index_output_end = index_input_start + (look_ahead * 2);
         index_output_start = index_output_end + fetch;
         index_input_end = index_output_end + look_ahead;
         index_input_start = index_output_start + look_ahead; 
         
         //Assigning time stamps
         output_end =  iTime(Symbol(),PERIOD_CURRENT,index_output_end);
         output_start = iTime(Symbol(),PERIOD_CURRENT,index_output_start);
         input_end = iTime(Symbol(),PERIOD_CURRENT,index_input_end);
         input_start = iTime(Symbol(),PERIOD_CURRENT,index_input_start);
         
         //Copy the training data   
         if(!y_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,output_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
            
         //Copy the training data   
         if(!x_train.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,input_end,fetch))
            {
               Print("Error fetching training data ",GetLastError());
            }
         
         //Check if the data matches
         if(x_train.Size() != y_train.Size())
            {
               Print("Error fetching training dataL: The x and y vectors are not the same size");
            }
           
           //Print the vectors and plot the data points 
            Print("X training: ",x_train);
            ObjectCreate(0,"X training end",OBJ_VLINE,0,input_end,0);
            ObjectCreate(0,"X training start",OBJ_VLINE,0,input_start,0);
            
            Print("y training: ",y_train);
            ObjectCreate(0,"y training end",OBJ_VLINE,0,output_end,0);
            ObjectCreate(0,"y training start",OBJ_VLINE,0,output_start,0);
            return(true);
   }

bool LinearRegression::Train(void)
   {
      m = vector::Zeros(1);
      //Set the bias to a random value
      b = vector::Zeros(1);
      forecast = 0;
      
      if(GetCurrentValidationData())
         {
            if(Fit())
               {
                  Print("Model last updated: ",iTime(_Symbol,PERIOD_CURRENT,0));
                  return(true);
               }
         }
      return(false);
   }

void LinearRegression::Init(int _fetch,int _look_ahead)
   {
      //Clear The Chart
      ObjectsDeleteAll(0);      
      //Allow evaluations
      allowed_to_evaluate = true;
      //Epochs power
      epochs_power =4;
      //Set the number of epochs
      epochs = 5 * MathPow(10,epochs_power);
      //Has the model been trained?
      trained = false;
      //Set the maximum learning rate power
      max_learning_rate_power = 30;
      //Set the end of our validation data
      start = iTime(_Symbol,PERIOD_CURRENT,1);
      //This is how much data we're going to fetch
      this.fetch = _fetch - 1;
      //This is how far into the future we want to forecast
      this.look_ahead = _look_ahead + 1;
      //Set the gradient coefficient to a random value
      m = vector::Zeros(1);
      //Set the bias to a random value
      b = vector::Zeros(1);
      //Set the forecast to 0
      forecast = 0;
      //Our model's learning rate will start at 0
      learning_rate_power = 0;
      //This is the learning rate we are evaluting
      lr_error_index = 0;
      mae_train = vector::Full(1,MathPow(10,100));
      mae_validation = vector::Full(30,MathPow(10,10000));
      //Set the initial learning rate
      learning_rate = MathPow(0.1,(learning_rate_power));
      //Set the number of rows
      n = fetch;
      if(GetCurrentValidationData())
         {
            //Scale the data
            ScaleInputs();
            //Fit the model
            Fit();   
         }      
   }

bool LinearRegression::Trained(void)
   {
      return(trained);
   }

bool LinearRegression::SetLearningRate(ulong _learning_rate_power)
   {
       learning_rate_power = _learning_rate_power;
       learning_rate = MathPow(0.1,(learning_rate_power));
       return(true);
   }

bool LinearRegression::UpdateLearningRate(void)
   {
         learning_rate_power = learning_rate_power + 1;
         learning_rate = MathPow(0.1,(learning_rate_power));
         Print("New learning rate: ",learning_rate," learning rate power: ",learning_rate_power);
         return(true);
   }

bool LinearRegression::ResetLearningRate(void)
   {
         learning_rate_power = 0;
         learning_rate = MathPow(0.1,learning_rate_power);
         return(true);
   }

LinearRegression::LinearRegression()
  {
            
      Print("Current Symbol: ",_Symbol);
  }

bool LinearRegression::Fit()
   {
      Print("Fitting a linear regression on the training set with learning rate ",learning_rate_power);

      Print("Evalutaions: ",allowed_to_evaluate);
      for(int i =0; i < epochs;i++)
         {
            //Measure error
            y_hat_train = (m[0]*x_train) + b[0];
            vector y_minus_y_hat = (y_train - y_hat_train);
            vector y_minus_y_hat_sqaured = MathAbs((y_train - y_hat_train));
            mae_train.Set(0,( y_minus_y_hat_sqaured.Mean()));
            vector x_times_y_minus_y_hat = (x_train*(y_train -y_hat_train));
            
            //Aproximate the derivatives
            double derivative_m = (-2.0/n) * x_times_y_minus_y_hat.Sum();
            double derivative_b = (-2.0/n) * y_minus_y_hat.Sum();
            
            //Update the linear parameters
            m[0] = m[0] - (learning_rate * derivative_m);
            b[0] = b[0] - (learning_rate * derivative_b);
         }
         
         //Finished fitting the coefficients
         Print("Fit on training data complete.\nm: ",m[0]," b: ",b[0]," mae ",mae_train[0],"\nlearning rate: ",learning_rate);
         
         if(allowed_to_evaluate)
            {
               Evaluate(learning_rate_power);
            }
            
         //Return true
         return(true);
   }

//This function evaluates the current coefficient settings and learning rate
bool LinearRegression::Evaluate(ulong _index)
   {
      Print("Evaluating the coefficients m:",m[0]," b: ",b[0]," at learning rate: ",learning_rate);
      
      //First check if the coefficient and learning rate are valid
      if((m.HasNan() > 0 || b.HasNan() > 0 || m[0] == 0 || b[0] == 0 || _index > max_learning_rate_power) && (_index < max_learning_rate_power))
         {
            Print("Coefficients are invalid");
            m[0] = 0;
            b[0] = 0 ;
            mae_array[_index] = MathPow(10,100000);
            //Update the learning rate
            UpdateLearningRate();
            //Fit the model again
            Fit();   
         }
         
      else
         {
            //Validation predictions
            if(_index < max_learning_rate_power)
               {
                  Print("Coefficients are valid, solution at index ",_index);
                  y_hat_validation = (m[0] * x_validation) + b[0];   
                  vector y_minus_y_hat_squared = MathAbs(y_validation - y_hat_validation);
                  //If everything is fine, let's assess the validation mae
                  mae_array[_index] = (1.0/n) * y_minus_y_hat_squared.Sum();
                  //What was the validation error?
                  Print("Validation error: ",(1.0/n) * y_minus_y_hat_squared.Sum());
                  //Update the learning rate
                  UpdateLearningRate();
                  //Fit the model again
                  Fit();   
               }
         }  
         
      if(_index == max_learning_rate_power)
         {
            for(int i = 0; i < max_learning_rate_power;i++)
               {
                  mae_validation[i] = mae_array[i];   
               }
            allowed_to_evaluate = false;
            trained = true;
            Print("Validation mae: \n",mae_validation);
            Print("Lowest validation mae: ",mae_validation.Min());
            ulong chosen_learning_rate = mae_validation.ArgMin();
            Print("Chosen learning rate ",MathPow(0.1,(chosen_learning_rate)));
            SetLearningRate(chosen_learning_rate);
            Fit();
         }
         
         return(true);
   }

//This function will scale our inputs
bool LinearRegression::ScaleInputs(void)
   {
      //Set the first reading
      first_reading = x_train[0];
      x_train = x_train / first_reading;
      x_validation = x_validation / first_reading;
      return(true);
   }

LinearRegression::~LinearRegression()
  {
      ResetLearningRate();
      ResetEpochs();
      ResetLastError();
  }

Теперь, когда мы определили наш класс LinearRegression, мы готовы использовать его в нашем советнике.

Начнем с создания нового советника и включения класса в наш советник.

#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com"
#property version   "1.00"

//Include our linear regression class
#include  <LinearRegression/LinearRegression.mqh>
LinearRegression ExtLinearRegression;

Приведенный выше код вызывает конструктор по умолчанию нашего класса LinearRegression.

Оттуда мы также включаем другие полезные классы.

//Include the trade class
#include  <Trade/Trade.mqh>
CTrade Trade;

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

//Inputs
int input look_ahead = 10; //How many steps into the future should we forecast?
int input fetch_data = 100; //How much data should we fetch?
int input ma_period = 10;  //Moving Average Period
int input rsi_period = 10; //RSI Period
int input wr_period = 10;  //Williams Percent R Period

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

//Technical Analysis
double min_volume =SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
//Indicator Handlers
int ma_handler,rsi_handler,wr_handler,total_time;
vector ma_vector,rsi_vector,wr_vector;
double _price;
ulong _ticket;

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

int OnInit()
  {
   //Setup our model
   ExtLinearRegression.Init(fetch_data,look_ahead);
   //Keep Track Of Time
   total_time = 0;
   //Set up our technical indicators
   ma_handler = iMA(_Symbol,PERIOD_CURRENT,ma_period,0,MODE_EMA,PRICE_CLOSE);
   rsi_handler = iRSI(_Symbol,PERIOD_CURRENT,rsi_period,PRICE_CLOSE);
   wr_handler = iWPR(_Symbol,PERIOD_CURRENT,wr_period);
   return(INIT_SUCCEEDED);
  }

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

void OnTick()
  {
//---
         
      static datetime time_stamp;
      datetime current_time = iTime(_Symbol,PERIOD_CURRENT,0);
      
      if(time_stamp != current_time)
         {
            //Update the values of the indicators
            update_vectors();
            total_time += 1;
            
            if(total_time > look_ahead)
               {
                  total_time = 0;
                  //Let the model adapt to the market dynamically
                  ExtLinearRegression.Train();
               }
            
            //If our model is ready then let's start trading
            if(ExtLinearRegression.Trained())
               {
                  if(PositionsTotal() == 0)
                     {
                        analyse_indicators();   
                     }
               }
               
            if(PositionsTotal() == 1)
               {
                  //Get position ticket
                  _ticket = PositionGetTicket(0);
                  //Manage the position
                  manage_position(_ticket);
               }
            time_stamp = current_time;
         }
  }

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

void update_vectors(void)
   {
            //Get the current reading of our indicators
            ma_vector.CopyIndicatorBuffer(ma_handler,0,1,1);
            rsi_vector.CopyIndicatorBuffer(rsi_handler,0,1,1);
            wr_vector.CopyIndicatorBuffer(wr_handler,0,1,1);
            _price = iClose(_Symbol,PERIOD_CURRENT,1);
   }
   

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

void analyse_indicators(void)
   {
         double forecast = ExtLinearRegression.Predict();
         Comment("Forecast: ",forecast," Price: ",_price);
         //If price is above the moving average, check if the other indicators also confirm the buy signal
         if(_price - ma_vector[0] > 0)
            {
               if(rsi_vector[0] > 50)
                  {
                     if(wr_vector[0] > -20)
                        {
                            if(forecast > _price)
                                {
                                  Trade.Buy(min_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0);
                                }
                        }
                  }
            }
            
         //If price is below the moving average, check if the other indicators also confirm the sell signal
         if(_price - ma_vector[0] < 0)
            {
               if(rsi_vector[0] < 50)
                  {
                     if(wr_vector[0] < -80)
                        {
                           if( forecast < _price)
                              {
                                 Trade.Sell(min_volume,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0);
                              }
                        }
                  }
            }

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

void manage_position(ulong m_ticket)
   {
      if(PositionSelectByTicket(m_ticket))
         {
            double volatility =  2 * MathAbs(ma_vector[0] - _price);
            double entry = PositionGetDouble(POSITION_PRICE_OPEN);
            double current_sl = PositionGetDouble(POSITION_SL);
            double current_tp = PositionGetDouble(POSITION_TP);
            
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
               {
                  double new_sl = _price - volatility;
                  double new_tp = _price + volatility;
               
                  if(current_sl == 0 || current_tp == 0)
                     {
                        Trade.PositionModify(m_ticket,new_sl,new_tp);
                     }
               }

            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
               {
                  double new_sl = _price + volatility;
                  double new_tp = _price - volatility;
               
                  if(current_sl == 0 || current_tp == 0)
                     {
                        Trade.PositionModify(m_ticket,new_sl,new_tp);
                     }
               }
         }
   }

Наш советник теперь выглядит так:

LinearRegression EA

Рис. 3. Самооптимизирующийся советник


Входные параметры самооптимизирующегося советника

Рис. 4. Входные данные для нашего самооптимизирующегося советника


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


Расчеты, выполненные нашим советником

Рис. 5. Расчеты, выполненные нашим советником


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

Рис. 6. Тестирование нашего советника на истории


Рекомендации

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


Заключение

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

 

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (16)
Gunnar Forsgren
Gunnar Forsgren | 25 апр. 2024 в 00:29
Gamuchirai Zororo Ndawana #:
Мне нравится ваш проактивный подход. Вы правы, есть несколько исключений, которые могут возникнуть при попытке получить исторические данные. Например, если вы попытаетесь сменить таймфрейм в середине торговой сессии, проблема "-nan" может проявиться еще раз.


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

Ваше решение звучит очень многообещающе, как оно продвигается?

Здравствуйте, мне снова пришлось создать свою учетную запись, чтобы войти. В любом случае,
Я попробовал запустить LinearRegressionEA и нахожу это захватывающей концепцией. Я торгую в основном GOLD CFD и установил симпатию к индикатору WPR в этом примере.
Иногда я получаю неправильные цены прогноза, которые выходят за пределы диапазона с фактором 100, но иногда правильные!
Если кто-то решит эту проблему, я был бы очень признателен. Отладка кода линейной регрессии - не моя компетенция.

Я не видел, писали ли вы что-нибудь о предпочтительном периоде для торговли, у меня он установлен на 30 минут.

В этом сценарии,
если еще нет торговли
и советник запускает свою функцию'analyse_indicators()',
это делается один раз на бар, то есть в моем случае один раз в 30 минут.
Это означает, что если индикаторы не выстроились в линию для принятия торгового решения, это не будет повторено до следующего бара, в моем случае через 30 минут.
IMO это слишком далеко для установления первоначальной сделки,
поэтому я добавил задачу Timer, которая запускает шаг анализа каждые 10 секунд, пока индикаторы не будут в пользу сделки;
я устанавливаю ордер на покупку или продажу, а затем возвращаюсь к обычной обработке функции manage_position() на каждый бар.
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 30 апр. 2024 в 20:01
Giulio Braga управление капиталом)?

спасибо

приятного времяпрепровождения

Привет, Джулио.

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

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

Если это не удовлетворит ваши потребности, попробуйте этот учебник на YouTube по этой ссылке.

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

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 30 апр. 2024 в 20:20
Gunnar Forsgren #:
Здравствуйте, мне снова пришлось создать свой аккаунт, чтобы войти в систему. В общем,
Я попробовал запустить LinearRegressionEA и нахожу это интересной концепцией. Я торгую в основном GOLD CFD и установил симпатию к индикатору WPR в этом примере.
Иногда я получаю неправильные прогнозные цены, которые выходят за пределы диапазона с коэффициентом 100, но иногда правильные!
Если кто-то решит эту проблему, я был бы очень признателен за это. Отладка кода линейной регрессии не является моей компетенцией.

Я не видел, писали ли вы что-нибудь о предпочтительном периоде для торговли, у меня он установлен на 30 минут.

В этом сценарии,
если еще нет торговли
и советник запускает свою функцию'analyse_indicators()',
это делается один раз на бар, так что в моем случае один раз в 30 минут.
Это означает, что если индикаторы не выстроились в линию для принятия торгового решения, то попытка повторить это не предпринимается до следующего бара, в моем случае через 30 минут.
IMO это слишком большой промежуток времени для установления первоначальной сделки,
поэтому я добавил задачу Timer, которая выполняет шаг анализа каждые 10 секунд, пока индикаторы не будут в пользу сделки;
я устанавливаю ордер на покупку или продажу, а затем возвращаюсь к обычной обработке функции manage_position() на каждый бар.
Рад слышать вас, Гуннар.

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

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

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

Мы используем простую реализацию алгоритма Gradient Descent для оптимизации коэффициентов нашей модели. К сожалению, Gradient Descent может быть чувствителен к начальным позициям наших коэффициентов. Чтобы исправить это, был разработан алгоритм стохастического градиентного спуска (SGD). SGD выполняет оптимизацию, каждый раз меняя начальные коэффициенты, чтобы максимизировать вероятность нахождения оптимальных коэффициентов. Для простоты мы оставили коэффициенты фиксированными, что может привести к застреванию модели в унылых состояниях. Это видео на youtube может быть полезным, воспользуйтесь этой ссылкой.

Да, вы правы, я намеренно призвал проводить расчеты на каждой свече. Это было сделано для ускорения бэктестов, чтобы отключить эту функцию, просто удалите проверку условия "if(timestamp != current_time)".

Кроме того, есть способы построить нашу модель так, чтобы она подстраивалась под имеющиеся у нас данные, вы можете найти эту информацию по этой ссылке.
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera | 8 июн. 2024 в 07:04

Привет, это потрясающе! Спасибо!

Я получаю эти строки:

2024.06.08 07:00:42.212 Core 01 2024.05.01 00:00:00   Validation mae: 
2024.06.08 07:00:42.212 Core 01 2024.05.01 00:00:00   [inf,0.006448762386408615,0.006380585312229991,0.006373808727022462,0.006373131478053022,0.006373063757231861,0.006373056987294832,0.006373056310301126,0.006373056241302974,0.00637305623338131,0.00637305623026851,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.006373056230211261,0.0

Как я могу это исправить?

Есть ли у кого-нибудь такая проблема?

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 8 июн. 2024 в 10:19
Javier Santiago Gaston De Iriarte Cabrera #:

Привет, это потрясающе! Спасибо!

Я получаю эти строки:

Как я могу это исправить?

Есть ли у кого-нибудь такая проблема?

Хавьер, не могли бы вы загрузить больше выходных данных из терминала?

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

Однако проблема, которую я замечаю в ваших результатах, - это "0.0" в конце. Получение ошибки 0.0 подразумевает, что модель совершенна, а это нереально.


Нейросети в трейдинге: Комплексный метод прогнозирования траекторий (Traj-LLM) Нейросети в трейдинге: Комплексный метод прогнозирования траекторий (Traj-LLM)
В данной статье я хочу познакомить вас с одним интересным методом прогнозирования траекторий, разработанным для решения задач в области автономного движения транспортных средств. Авторы метода объединили в нем лучшие элементы различных архитектурных решений.
Упрощаем торговлю на новостях (Часть 1): Создаем базу данных Упрощаем торговлю на новостях (Часть 1): Создаем базу данных
Торговля на новостях может быть сложной и утомительной. В этой статье мы рассмотрим шаги по получению новостных данных. Кроме того, мы узнаем об экономическом календаре MQL5 и о том, что он может предложить.
Алгоритм анархической социальной оптимизации — Anarchic Society Optimization (ASO) Алгоритм анархической социальной оптимизации — Anarchic Society Optimization (ASO)
В очередной статье мы познакомимся с алгоритмом Anarchic Society Optimization (ASO) и обсудим, как алгоритм, основанный на иррациональном и авантюрном поведении участников анархического общества - аномальной системы социального взаимодействия, свободной от централизованной власти и различного рода иерархий способен исследовать пространство решений и избегать ловушек локального оптимума. В статье будет представлена унифицированная структура ASO, применимая как к непрерывным, так и к дискретным задачам.
Методы Уильяма Ганна (Часть I): Создаем индикатор углов Ганна Методы Уильяма Ганна (Часть I): Создаем индикатор углов Ганна
В чем суть теории Ганна? Как строятся углы Ганна? Создаем индикатор углов Ганна для MetaTrader 5.