![Создание самооптимизирующихся советников на MQL5](https://c.mql5.com/2/74/Build_Self_Optmising_Expert_Advisors_in_MQL5_600x314.jpg)
Создание самооптимизирующихся советников на MQL5
Введение
Самооптимизирующиеся автоматизированные системы имеют решающее значение на современных динамичных финансовых рынках. В цифровую эпоху рынки стали заметно более волатильными из-за широкого распространения алгоритмической торговли, особенно среди высокочастотных трейдеров. Согласно документу Комиссии по ценным бумагам и биржам (SEC) SEC High-Frequency Trading Paper, посвященной высокочастотной торговле, на долю высокочастотных трейдеров приходится почти половина всех сделок в Европе и США.
Вопреки некоторым представлениям, MQL5 идеально подходит для этой задачи. Его API предоставляет обширные матричные и векторные функции, позволяющие создавать компактные модели машинного обучения. В этом введении особое внимание уделяется использованию MQL5 для создания самооптимизирующихся ботов. Объектно-ориентированное программирование сокращает повторы кода и повышает адаптивность к различным временным рамкам и рыночным условиям.
Выбор матричных и векторных возможностей MQL5 имеет значительные преимущества по сравнению с такими альтернативами, как ONNX и Python. Использование модели ONNX требует отдельных экземпляров модели для каждого торгового символа и новых моделей для любых незначительных изменений параметров, таких как корректировки временных рамок. Напротив, MQL5 обеспечивает адаптивность без необходимости управления многочисленными моделями для различных условий.
Синопсис: Разработка самооптимизирующихся советников.
Нам нужна структура для оценки эффективности работы нашего советника. После определения окончательного показателя производительности мы можем соответствующим образом максимизировать или минимизировать выбранный нами показатель. При построении контролируемых моделей машинного обучения для прогнозирования цен наша цель — минимизировать погрешность между прогнозируемыми значениями и фактическими наблюдениями. С другой стороны, в задачах обучения с подкреплением цель состоит в максимизации общего дисконтированного ожидаемого вознаграждения.
В этой статье мы минимизируем разницу между ожидаемой будущей ценой, спрогнозированной нашим советником, и фактически наблюдаемой ценой в будущем. Этого можно достичь путем вычисления абсолютных разниц между этими ценами.
В статье рассматриваются основные аспекты построения самооптимизирующегося советника. В будущих статьях мы рассмотрим более продвинутые методики создания самооптимизирующихся советников с использованием расширенных функций API MQL5.
Прочитав эту статью, читатель освоит:
- Подборку полезных матричных и векторных функций
- Основополагающие концепции объектно-ориентированного программирования в MQL5
- Фреймворк для создания динамических и самоадаптирующихся советников на 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); } } } }
Наш советник теперь выглядит так:
Рис. 3. Самооптимизирующийся советник
Рис. 4. Входные данные для нашего самооптимизирующегося советника
Всякий раз, когда вы применяете советника к любому символу, вы можете видеть вычисления, которые он выполняет, на вкладке "Эксперты".
Рис. 5. Расчеты, выполненные нашим советником
Рис. 6. Тестирование нашего советника на истории
Рекомендации
Мы познакомились с простейшим возможным методом построения самооптимизирующихся советников. Это не самый лучший из возможных подходов, это ручной поиск оптимальных коэффициентов. Идеальным решением было бы использование более сложных матричных и векторных вычислений для автоматического поиска оптимальных коэффициентов. Фактически, используя матричные и векторные функции, мы можем построить нашу модель линейной регрессии, даже не используя ни одного цикла for. Наш код будет более компактным, а наши коэффициенты — более численно устойчивыми. Ручной поиск не всегда гарантирует решение.
Заключение
Создание самоадаптирующихся советников на MQL5 упростилось благодаря мощным матричным и векторным функциям API MQL5. По правде говоря, то, что мы можем создать на MQL5, ограничено только нашим пониманием API.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14630
![Нейросети в трейдинге: Комплексный метод прогнозирования траекторий (Traj-LLM)](https://c.mql5.com/2/89/logo-midjourney_image_15595_398_3845__1.png)
![Упрощаем торговлю на новостях (Часть 1): Создаем базу данных](https://c.mql5.com/2/74/News_Trading_Made_Easy_iPart_13_Creating_a_Database___LOGO__2.png)
![Алгоритм анархической социальной оптимизации — Anarchic Society Optimization (ASO)](https://c.mql5.com/2/89/logo-midjourney_image_15511_397_3830__1.png)
![Методы Уильяма Ганна (Часть I): Создаем индикатор углов Ганна](https://c.mql5.com/2/88/logo-midjourney_image_15556_393_3782.png)
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Мне нравится ваш проактивный подход. Вы правы, есть несколько исключений, которые могут возникнуть при попытке получить исторические данные. Например, если вы попытаетесь сменить таймфрейм в середине торговой сессии, проблема "-nan" может проявиться еще раз.
Приходилось искать компромисс между простотой изложения и исправлением всех замеченных мною ошибок. Если бы я пошел на последнее, то код мог бы быть обязательно более сложным и не таким простым для понимания, как сейчас. Поэтому я решил сохранить простоту изложения с расчетом на то, что вы сможете быстро расширить его.
Ваше решение звучит очень многообещающе, как оно продвигается?
Я попробовал запустить LinearRegressionEA и нахожу это захватывающей концепцией. Я торгую в основном GOLD CFD и установил симпатию к индикатору WPR в этом примере.
Иногда я получаю неправильные цены прогноза, которые выходят за пределы диапазона с фактором 100, но иногда правильные!
Если кто-то решит эту проблему, я был бы очень признателен. Отладка кода линейной регрессии - не моя компетенция.
Я не видел, писали ли вы что-нибудь о предпочтительном периоде для торговли, у меня он установлен на 30 минут.
В этом сценарии,
если еще нет торговли
и советник запускает свою функцию'analyse_indicators()',
это делается один раз на бар, то есть в моем случае один раз в 30 минут.
Это означает, что если индикаторы не выстроились в линию для принятия торгового решения, это не будет повторено до следующего бара, в моем случае через 30 минут.
IMO это слишком далеко для установления первоначальной сделки,
поэтому я добавил задачу Timer, которая запускает шаг анализа каждые 10 секунд, пока индикаторы не будут в пользу сделки;
я устанавливаю ордер на покупку или продажу, а затем возвращаюсь к обычной обработке функции manage_position() на каждый бар.
спасибо
приятного времяпрепровождения
Привет, Джулио.
Чтобы установить магическое число и пользовательский комментарий, вы просто расширите код, вызвав соответствующую функцию.
Я думаю, что PositionOpen может быть тем, что вы ищете, вы можете проверить документацию по этой ссылке.
Если это не удовлетворит ваши потребности, попробуйте этот учебник на YouTube по этой ссылке.
Если ни один из этих ресурсов вам не помог, у меня есть канал, на котором я публикую более полезные статьи, подобные этой. Вы можете найти его по этой ссылке.
Здравствуйте, мне снова пришлось создать свой аккаунт, чтобы войти в систему. В общем,
Я попробовал запустить 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)".
Кроме того, есть способы построить нашу модель так, чтобы она подстраивалась под имеющиеся у нас данные, вы можете найти эту информацию по этой ссылке.
Привет, это потрясающе! Спасибо!
Я получаю эти строки:
Как я могу это исправить?
Есть ли у кого-нибудь такая проблема?
Привет, это потрясающе! Спасибо!
Я получаю эти строки:
Как я могу это исправить?
Есть ли у кого-нибудь такая проблема?
Хавьер, не могли бы вы загрузить больше выходных данных из терминала?
Поскольку то, чем вы поделились, кажется нормальным, я бы ожидал таких результатов.
Однако проблема, которую я замечаю в ваших результатах, - это "0.0" в конце. Получение ошибки 0.0 подразумевает, что модель совершенна, а это нереально.