
Инженерия признаков с Python и MQL5 (Часть I): AI-модели для долгосрочного прогнозирования по скользящим средним
Если говорить о применении искусственного интеллекта к любой задаче, пожалуй, первой важной ступенью является сбор как можно большего количества полезной информации о реальном мире, которую наша модель сможет использовать. Далее, чтобы описать модели различные признаки данных (в нашем случае это характеристики рынка), необходимо обработать и преобразовать входные данные — этот процесс называется инженерией признаков (feature engineering). В этой серии статей вы узнаете, как преобразовывать рыночные данные для снижения уровня ошибок в моделях. Сегодня мы поговорим о том, как использовать скользящие средние для увеличения горизонта прогнозирования AI-моделей. При этом попытаемся сохранить полный контроль и понимание общей эффективности стратегии.
Обзор стратегии
В прошлый раз, когда мы говорили о прогнозировании скользящих средних с помощью ИИ, я привел доказательства того, что для моделей ИИ проще предсказывать значения скользящих средних, чем будущие уровни цен (вот ссылка на эту статью). Чтобы убедиться в значимости наших выводов, я обучил две идентичные AI-модели на более чем 200 различных рыночных инструментах и сравнил точность прогнозирования цены с точностью прогнозирования скользящих средних. Результаты показали, что при прогнозировании цены точность в среднем снижается на 34% по сравнению с прогнозированием скользящих средних.
В среднем можно ожидать 70% точности при прогнозировании скользящих средних против 52% точности при прогнозировании цены. Всем известно, что в зависимости от выбранного периода индикатор скользящей средней не всегда следует за уровнем цен достаточно близко. Например, цена может снижаться в течение 20 свечей, а скользящие средние на этом интервале будут расти. Такое расхождение нежелательно, поскольку даже при правильном прогнозе направления скользящей средней цена может пойти в противоположную сторону. Примечательно, что уровень расхождения остается стабильным примерно на уровне 31% по всем рыночным инструментам, а точность прогнозирования самих расхождений составила в среднем 68%.
Кроме того, дисперсия точности прогнозирования расхождений и самих случаев расхождения составила 0.000041 и 0.000386 соответственно. Это демонстрирует, что модель обладает устойчивой способностью к самокоррекции. Если вы планируете применять AI в долгосрочных торговых стратегиях, может иметь смысл рассмотреть этот альтернативный подход на старших таймфреймах. Пока что мы ограничимся обсуждением таймфрейма M1, поскольку он обеспечивает достаточный объем данных по всем 297 рынкам для корректного сравнения.
Существует множество причин, почему скользящие средние проще прогнозировать, чем саму цену. Вероятно, это связано с тем, что прогнозирование скользящих средних больше соответствует принципам линейной регрессии, чем прогнозирование цены. Линейная регрессия предполагает, что данные являются линейной комбинацией (суммой) нескольких входных параметров: скользящие средние представляют собой сумму предыдущих значений цены, а значит — линейное допущение выполняется. Цена же сама по себе не является простой суммой реальных факторов, а представляет собой сложную зависимость множества переменных.
Начало работы
Для начала нужно импортировать стандартные библиотеки научных для вычислений в Python.
#Load the libraries we need import pandas as pd import numpy as np import MetaTrader5 as mt5 from sklearn.model_selection import TimeSeriesSplit,cross_val_score from sklearn.linear_model import LogisticRegression,LinearRegression import matplotlib.pyplot as plt
Инициализируем терминал MetaTrader 5.
#Initialize the terminal
mt5.initialize()
Сколько торговых символов у нас есть?
#The total number of symbols we have print(f"Total Symbols Available: ",mt5.symbols_total())
Получим названия всех символов.
#Get the names of all pairs symbols = mt5.symbols_get() idx = [s.name for s in symbols]
Создадим датафрейм для хранения уровней точности по всем инструментам.
global_params = pd.DataFrame(index=idx,columns=["OHLC Error","MAR Error","Noise Levels","Divergence Error"]) global_params
Рис. 1: Наш датафрейм, который будет хранить уровни точности по всем рынкам в терминале
Определим объект разбиения временного ряда.
#Define the time series split object tscv = TimeSeriesSplit(n_splits=5,gap=10)
Измерим точность по всем инструментам.
#Iterate over all symbols for i in np.arange(global_params.dropna().shape[0],len(idx)): #Fetch M1 Data data = pd.DataFrame(mt5.copy_rates_from_pos(cols[i],mt5.TIMEFRAME_M1,0,50000)) data.rename(columns={"open":"Open","high":"High","low":"Low","close":"Close"},inplace=True) #Define our period period = 10 #Add the classical target data.loc[data["Close"].shift(-period) > data["Close"],"OHLC Target"] = 1 #Calculate the returns data.loc[:,["Open","High","Low","Close"]] = data.loc[:,["Open","High","Low","Close"]].diff(period) data["RMA"] = data["Close"].rolling(period).mean() #Calculate our new target data.dropna(inplace=True) data.reset_index(inplace=True,drop=True) data.loc[data["RMA"].shift(-period) > data["RMA"],"New Target"] = 1 data = data.iloc[0:-period,:] #Calculate the divergence target data.loc[data["OHLC Target"] != data["New Target"],"Divergence Target"] = 1 #Noise ratio global_params.iloc[i,2] = data.loc[data["New Target"] != data["OHLC Target"]].shape[0] / data.shape[0] #Test our accuracy predicting the future close price score = cross_val_score(LogisticRegression(),data.loc[:,["Open","High","Low","Close"]],data["OHLC Target"],cv=tscv) global_params.iloc[i,0] = score.mean() #Test our accuracy predicting the moving average of future returns score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["New Target"],cv=tscv) global_params.iloc[i,1] = score.mean() #Test our accuracy predicting the future divergence between price and its moving average score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["Divergence Target"],cv=tscv) global_params.iloc[i,3] = score.mean() print(f"{((i/len(idx)) * 100)}% complete") #We are done print("Done")
Готово
Анализ результатов
Мы получили рыночные данные и оценили работу нашей модели по двум целевым показателям. Теперь можем подвести итоги наших тестов по всем рынкам. Начнем с оценки того, насколько точно удалось спрогнозировать изменения будущей цены закрытия. На Рис. 2 ниже представлены общие результаты прогнозирования изменения цены закрытия. Красная горизонтальная линия обозначает порог точности в 50%. Средняя точность методики показана синей горизонтальной линией. Как видите, средняя точность находится не сильно выше 50%-го порога, что, откровенно говоря, не самый выдающийся результат.
Тем не менее следует отметить, что на некоторых рынках точность значительно превышает среднее значение — где-то точность превысила 65%. Вот такие результаты впечатляют. Однако все равно требуется дополнительный анализ, чтобы определить, являются ли эти результаты статистически значимыми или это какие-то случайные выбросы.
global_params.iloc[:,0].plot() plt.title("OHLC Accuracy") plt.xlabel("Market") plt.ylabel("5-fold Accuracy %") plt.axhline(global_params.iloc[:,0].mean(),linestyle='--') plt.axhline(0.5,linestyle='--',color='red')
Рис. 2: Средняя точность прогнозирования цены
Теперь давайте посмотрим на точность при прогнозировании изменений скользящих средних. На рисунке 3 ниже показаны результаты. Здесь снова красной линией показан порог точности в 50%, золотая линия показывает среднюю точность прогнозирования изменений цены, а синяя — среднюю точность прогнозирования изменений скользящей средней. Сказать, что модель лучше предсказывает скользящие средние, — это ничего не сказать. Думаю, этот факт уже не подлежит обсуждению: наши модели однозначно лучше прогнозируют определенные индикаторы, чем сами ценовые уровни.
global_params.iloc[:,1].plot() plt.title("Moving Average Returns Accuracy") plt.xlabel("Market") plt.ylabel("5-fold Accuracy %") plt.axhline(global_params.iloc[:,1].mean(),linestyle='--') plt.axhline(global_params.iloc[:,0].mean(),linestyle='--',color='orange') plt.axhline(0.5,linestyle='--',color='red')
Рис. 3: Точность прогнозирования изменения скользящей средней
Давайте теперь посмотрим, с какой скоростью расходятся цена и скользящая средняя. Уровни расхождения в районе 50% являются нежелательными, так как в этом случае мы не можем с достаточной уверенностью определить, будут ли цена и скользящая средняя двигаться вместе или в противоположных направлениях. К счастью, в этом случае уровни шума оказались стабильными по всем анализируемым рынкам. Они колебались в диапазоне от 35% до 30%.
global_params.iloc[:,2].plot() plt.title("Noise Level") plt.xlabel("Market") plt.ylabel("Percentage of Divergence:Price And Moving Average") plt.axhline(global_params.iloc[:,2].mean(),linestyle='--')
Рис. 4: Визуализация уровней шума по всем рынкам.
Если между двумя переменными наблюдается почти постоянное соотношение, это может указывать на наличие связи, которую можно формализовать в модели. Давайте теперь проанализируем, насколько точно мы можем прогнозировать расхождение между ценой и скользящей средней. Логика здесь проста: если наша модель прогнозирует снижение скользящей средней, можем ли мы с приемлемой точностью определить, будет ли за цена тоже снижаться или, напротив, она начнет расходиться со скользящей средней? Оказалось, что модель способна спрогнозировать расхождение с достаточно высокой точностью — в среднем около 70%.
global_params.iloc[:,3].plot() plt.title("Divergence Accuracy") plt.xlabel("Market") plt.ylabel("5-fold Accuracy %") plt.axhline(global_params.iloc[:,3].mean(),linestyle='--')
Рис. 5: Точность прогнозирования расхождений между ценой и скользящей средней
Давайте соберем все результаты в табличную форму. Это позволит сравнить точность при предсказании между доходностями рынка и скользящей средней этих доходностей. Обратите внимание, что, несмотря на то что скользящая средняя может запаздывать по отношению к цене, точность прогнозирования разворотов с ее использованием все равно значительно превышает точность прогнозирования самой цены.
Метрика | Точность |
---|---|
Ошибка при прогнозировании доходностей | 0.525353 |
Ошибка при прогнозировании скользящей средней доходностей | 0.705468 |
Уровень шума | 0.317187 |
Ошибка при прогнозировании расхождений | 0.682069 |
Теперь давайте посмотрим, на каких рынках наша модель показала наилучшие результаты.
global_params.sort_values("MAR Error",ascending=False)
Рис 6: Рынки с лучшими результатами модели
Оптимизация для наиболее успешного рынка
Теперь адаптируем наш индикатор скользящей средней для одного из рынков, на котором наша модель показала наилучшие результаты. Мы также проведем визуальное сравнение новых признаков с классическими. Для начала укажем рынок.
symbol = "AUDJPY"
Инициализируем доступ в терминал.
#Reach the terminal
mt5.initialize()
Далее получим рыночные данные.
data = pd.DataFrame(mt5.copy_rates_from_pos(symbol,mt5.TIMEFRAME_D1,365*2,5000))
Импортируем необходимые библиотеки.
#Standard libraries import seaborn as sns from mpl_toolkits.mplot3d import Axes3D from sklearn.linear_model import LinearRegression from sklearn.neural_network import MLPRegressor from sklearn.metrics import mean_squared_error from sklearn.model_selection import cross_val_score,TimeSeriesSplit
Определим начальную и конечную точку для расчета и горизонт прогнозирования. Оба параметра должны иметь одинаковую размерность, иначе код сломается.
#Define the input range x_min , x_max = 2,100 #Look ahead y_min , y_max = 2,100 #Period
Дискретизируем пространство входных данных с шагом 5, чтобы расчеты были подробными и с минимальными вычислительными затратами.
#Sample input range uniformly x_axis = np.arange(x_min,x_max,2) #Look ahead y_axis = np.arange(y_min,y_max,2) #Period
Создадим сетку по осям x_axis и y_axis. Эта сетка состоит из двух двумерных массивов, которые содержат все возможные комбинации горизонтов прогноза и периодов, которые мы хотим оценить.
#Create a meshgrid
x , y = np.meshgrid(x_axis,y_axis)
Далее нам нужна функция, которая будет загружать рыночные данные из терминала и размечать их для оценки.
def clean_data(look_ahead,period): #Fetch the data from our terminal and clean it up data = pd.DataFrame(mt5.copy_rates_from_pos('AUDJPY',mt5.TIMEFRAME_D1,365*2,5000)) data['time'] = pd.to_datetime(data['time'],unit='s') data['MA'] = data['close'].rolling(period).mean() #Transform the data #Target data['Target'] = data['MA'].shift(-look_ahead) - data['MA'] #Change in price data['close'] = data['close'] - data['close'].shift(period) #Change in MA data['MA'] = data['MA'] - data['MA'].shift(period) data.dropna(inplace=True) data.reset_index(drop=True,inplace=True) return(data)
Следующая функция выполняет 5-кратную кросс-валидацию (5-fold cross validation) для оценки точности модели:
#Evaluate the objective function def evaluate(look_ahead,period): #Define the model model = LinearRegression() #Define our time series split tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead) temp = clean_data(look_ahead,period) score = np.mean(cross_val_score(model,temp.loc[:,["Open","High","Low","Close"]],temp["Target"],cv=tscv)) return(score)
Теперь мы добрались до нашей целевой функции. Наша функция цели — это просто ошибка на валидации при 5-кратной кросс-валидации модели в новых оцениваемых конфигурациях. Напомним, что мы пытаемся подобрать оптимальный горизонт прогнозирования, а также оптимальный период для расчета изменений цен.
#Define the objective def objective(x,y): #Define the output matrix results = np.zeros([x.shape[0],y.shape[0]]) #Fill in the output matrix for i in np.arange(0,x.shape[0]): #Select the rows look_ahead = x[i] period = y[i] for j in np.arange(0,y.shape[0]): results[i,j] = evaluate(look_ahead[j],period[j]) return(results)
Оценим, насколько хорошо наша модель понимает рыночные данные при попытке напрямую прогнозировать изменение ценовых уровней. Рис. 7 демонстрирует взаимосвязь между моделью и изменением цены, а Рис. 8 показывает взаимосвязь между моделью и изменением скользящей средней. Белая точка на каждом графике символизирует ту комбинацию параметров, при которой была достигнута наименьшая ошибка.
res = objective(x,y) res = np.abs(res)
Визуализируем наилучшие результаты нашей модели при прогнозировании дневной доходности по паре AUDJPY. Полученные данные показывают, что при попытке спрогнозировать будущие изменения цен наилучший результат достигается при прогнозе всего на один шаг вперед. Но для нас недостаточно иметь прогноз всего одной будущей свечи при открытии позиций. То есть при прямом прогнозировании доходностей мы сильно ограничены в возможностях: модели становятся зацикленными на ближайшем значении, не выходя за его пределы.
plt.contourf(x,y,res,100,cmap="jet") plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white') plt.ylabel("Differencing Period") plt.xlabel("Forecast Horizon") plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Return")
Рис. 7: Визуализация способности модели прогнозировать по уровню цены
Если переключиться с прогнозирования изменения цены на прогнозирование изменения скользящей средней, можно заметить, что оптимальный горизонт предсказания смещается вправо. На Рис. 8 видно, что с прогнозом скользящих средних мы можем вполне надежно предсказывать до 22 шагов вперед, в отличие от одного шага вперед при прогнозе цен.
plt.contourf(x,y,res,100,cmap="jet") plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white') plt.ylabel("Differencing Period") plt.xlabel("Forecast Horizon") plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Moving Average Return")
Рис. 7: Визуализация способности модели прогнозировать скользящую среднюю
Еще более поразительно, что в оптимальных точках уровни ошибки для обеих задач оказались идентичными. Другими словами, предсказание изменения скользящей средней на 40 шагов вперед по сложности аналогично предсказанию изменения цены на 1 шаг вперед. Таким образом, использование скользящей средней в качестве целевой переменной позволяет значительно расширить прогнозный горизонт без увеличения ошибки.
При визуализации результатов тестирования в 3D различие между двумя целевыми переменными становится особенно наглядным. Рис. 9 показывает взаимосвязь между изменениями цен и параметрами прогнозирования модели. Здесь видна явная тенденция: по мере увеличения горизонта прогноза точность модели резко падает. Это означает, что в таком виде модели обладают "близорукостью" и не способны разумно прогнозировать интервалы более 20 шагов.
Рис. 10 был построен на основе взаимосвязи между моделью и ее ошибкой при прогнозировании изменений скользящей средней. Поверхностная диаграмма демонстрирует желаемые свойства: видно, как при увеличении горизонта прогноза и периода дифференцирования ошибка модели плавно снижается до минимума, а затем снова начинает расти. Это наглядно демонстрирует, насколько проще для модели прогнозировать скользящую среднюю, чем предсказывать саму цену.
#Create a surface plot fig , ax = plt.subplots(subplot_kw={"projection":"3d"}) fig.set_size_inches(8,8) ax.plot_surface(x,y,optimal_nn_res,cmap="jet")
Рис. 9: Визуализация взаимосвязи между моделью и изменениями дневной цены AUDJPY.
Рис. 10: Взаимосвязь между моделью и изменениями значения скользящей средней по паре AUDJPY.
Нелинейные преобразования: вейвлет-фильтрация шума
До этого момента мы применяли к данным только линейные преобразования. Однако мы можем пойти дальше и использовать нелинейные преобразования входных данных модели. Инженерия признаков часто представляет собой процесс проб и ошибок. Сложно гарантировать, что в итоге мы получим лучшие результаты. Поэтому такие преобразования мы применяем для конкретного случая — у нас нет универсальной формулы, которая точно определяет, какое преобразование лучше всего использовать в конкретный момент времени.
Вейвлет-преобразование — это математический инструмент, позволяющий представить данные одновременно в частотной и временной областях. Оно широко применяется в задачах обработки сигналов и изображений для отделения шума от полезного сигнала. После применения преобразования наши данные переходят в частотную область. Основная идея заключается в том, что шум в данных обусловлен малыми частотными компонентами, которые определяет преобразование. Все значения ниже определенного порога сводятся к нулю одним из двух способов. В результате получается разреженное (sparse) представление исходных данных.
Вейвлет-фильтрация имеет несколько преимуществ по сравнению с другими популярными методами, такими как быстрое преобразование Фурье (FFT). Напомню, что преобразование Фурье представляет любой сигнал в виде суммы синусоид и косинусоид. К сожалению, оно отфильтровывает высокочастотные компоненты сигнала. Такой метод не подходит, если полезный сигнал как раз находится в высокочастотной области. Поскольку заранее неизвестно, относится ли наш сигнал к высокочастотной или низкочастотной области, нам требуется более гибкое преобразование, способное выполнять фильтрацию в режиме без подкрепления. Вейвлет-преобразование позволяет сохранить полезный сигнал в данных, максимально устраняя шум.
Чтобы продолжить далее, нужно иметь установленные пакеты scikit-learn-image и PyWavelets. Тем, кто планирует создавать полноценное торговое приложение в MQL5, реализация и отладка вейвлет-преобразования с нуля может оказаться довольно трудоёмкой. Поэтому проще продолжать работу без него. Но если вы планируете использовать интеграцию терминала с Python, данный инструмент стоит иметь в своем арсенале.
Мы можем сравнить изменения точности на валидационной выборке, чтобы определить, помогает ли преобразование нашей модели — и, как оказалось, помогает. Обратите внимание, что мы применяем преобразование только к входным данным модели, не изменяя целевую переменную. Она остается в исходном виде. При этом точность на валидационной выборке несколько снизилась. Мы используем вейвлет-преобразование с жестким порогом (hard threshold), при котором все коэффициенты шума обнуляются. В качестве альтернативы можно попробовать мягкий порог (soft threshold), при котором коэффициенты шума стремятся к нулю, но не обязательно становятся строго равными нулю.
#Benchmark Score np.mean(cross_val_score(LinearRegression(),data.loc[:,["MA"]],data["Target"]))
#Wavelet denoising data["Denoised"] = denoise_wavelet( data["MA"], method='BayesShrink', mode='hard', rescale_sigma=True, wavelet_levels = 3, wavelet='sym5' ) np.mean(cross_val_score(LinearRegression(),np.sqrt(np.log(data.loc[:,["Denoised"]])),data["Target"]))
Построение индивидуальных AI-моделей
Итак, мы определили оптимальные параметры горизонта прогнозирования и идеальный период скользящей средней. Теперь загрузим рыночные данные непосредственно из терминала MetaTrader 5. Это необходимо для того, чтобы наши AI-модели обучались на тех же значениях индикаторов, которые они будут использовать в процессе реальной торговли. Мы стремимся как можно точнее имитировать реальные условия торговли.
В скрипте период скользящей средней установим равным рассчитанному выше оптимальному значению. Кроме того, также загрузим значения индикатора RSI из терминала, чтобы улучшить стабильность поведения нашего советника с AI. Опираясь на прогнозы сразу от двух различных индикаторов, а не одного, наша модель сможет демонстрировать более стабильную работу на длительных интервалах.
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" #property script_show_inputs //+------------------------------------------------------------------+ //| Script Inputs | //+------------------------------------------------------------------+ input int size = 100000; //How much data should we fetch? //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_handler,rsi_handler; double ma_reading[],rsi_reading[]; //+------------------------------------------------------------------+ //| On start function | //+------------------------------------------------------------------+ void OnStart() { //--- Load indicator ma_handler = iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_CLOSE); rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE); //--- Load the indicator values CopyBuffer(ma_handler,0,0,size,ma_reading); CopyBuffer(rsi_handler,0,0,size,rsi_reading); ArraySetAsSeries(ma_reading,true); ArraySetAsSeries(rsi_reading,true); //--- File name string file_name = "Market Data " + Symbol() +" MA RSI " + " As Series.csv"; //--- Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(int i= size;i>=0;i--) { if(i == size) { FileWrite(file_handle,"Time","Open","High","Low","Close","MA","RSI"); } else { FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i), iOpen(Symbol(),PERIOD_CURRENT,i), iHigh(Symbol(),PERIOD_CURRENT,i), iLow(Symbol(),PERIOD_CURRENT,i), iClose(Symbol(),PERIOD_CURRENT,i), ma_reading[i], rsi_reading[i] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
Наш скрипт готов. Теперь можем накинуть его на нужный рынок и начать работу с рыночными данными. Чтобы результаты наших тестов были достоверными, мы исключили последние два года рыночных данных из созданного CSV-файла. Таким образом, когда мы будем тестировать нашу стратегию на данных за период с 2023 по 2024 годы, результаты будут отражать реальную работу модели на данных, которых она ранее не видела.
#Read in the data data = pd.read_csv("Market Data AUDJPY MA RSI As Series.csv") #Let's drop the last two years of data. We'll use that to validate our model in the back test data = data.iloc[365:-(365 * 2),:] data
Рис. 11: Обучение модели на выборке рыночных данных за 22 года, за исключением периода 2023-2024 гг.
Разметим данные для обучения модели. Мы хотим научить модель прогнозировать изменения цены на основе изменений технических индикаторов. Чтобы помочь модели изучить эту взаимосвязь, преобразуем входные данные таким образом, чтобы отражать текущее состояние индикаторов. Например, для индикатора RSI мы определим три возможных состояния:
- Выше 70.
- Ниже 30.
- Между 70 и 30.
#MA States data["MA 1"] = 0 data["MA 2"] = 0 data.loc[data["MA"] > data["MA"].shift(40),"MA 1"] = 1 data.loc[data["MA"] <= data["MA"].shift(40),"MA 2"] = 1 #RSI States data["RSI 1"] = 0 data["RSI 2"] = 0 data["RSI 3"] = 0 data.loc[data["RSI"] < 30,"RSI 1"] = 1 data.loc[data["RSI"] > 70,"RSI 2"] = 1 data.loc[(data["RSI"] >= 30) & (data["RSI"] <= 70),"RSI 3"] = 1 #Target data["Target"] = data["Close"].shift(-22) - data["Close"] data["MA Target"] = data["MA"].shift(-22) - data["MA"] #Clean up the data data = data.dropna() data = data.iloc[40:,:] data = data.reset_index(drop=True)
Перейдем к измерению точности.
from sklearn.linear_model import Ridge from sklearn.model_selection import TimeSeriesSplit,cross_val_score
С преобразованиями мы можем видеть среднее изменение цены при переходе индикатора RSI через три выделенные нами зоны. Коэффициенты нашей линейной модели можно интерпретировать как среднее изменение цены, соответствующее каждому из трех состояний RSI. Интересно, что полученные результаты иногда могут противоречить классическим методикам использования индикатора. Например, наша модель Ridge (гребневая регрессия) обучилась тому, что при значении RSI выше 70 уровни цен, как правило, начинают снижаться; напротив, когда RSI меньше 70 — цены, как правило, продолжают расти.
#Our model can suggest optimal ways of using the RSI indicator #Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises model = Ridge() model.fit(data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"]) model.coef_
Наша модель Ridge способна делать прогнозы будущих цен, опираясь исключительно на текущее состояние RSI.
#RSI state np.mean(cross_val_score(Ridge(),data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"],cv=tscv))
Аналогичным образом, модель обучилась собственным правилам для торговли на основе поведения скользящей средней. Первый коэффициент модели отрицателен — это означает, что если скользящая средняя за 40 свечей возрастает, то далее она, как правило, начинает снижаться. Второй коэффициент положителен. Это говорит о противоположной динамике: когда 40-дневная скользящая средняя AUDJPY снижается за последние 40 свечей — в дальнейшем она, как правило, растет. Таким образом, на основе исторических данных, загруженных из терминала, модель самостоятельно изучила стратегию возврата к среднему(mean reversion).
#Our model can suggest optimal ways of using the RSI indicator #Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises model = Ridge() model.fit(data.loc[:,["MA 1","MA 2"]] , data["Target"]) model.coef_
Модель работает еще лучше, если задать ей текущее состояние индикатора скользящей средней.
#MA state np.mean(cross_val_score(Ridge(),data.loc[:,["MA 1","MA 2"]] , data["Target"],cv=tscv))
Конвертация модели в формат ONNX
Мы определили оптимальные входные параметры для прогноза скользящей средней и можем подготовить модель к конвертации в формат ONNX. Open Neural Network Exchange (ONNX) позволяет разрабатывать модели машинного обучения в независимой от языка среде. ONNX — это протокол с открытым исходным кодом, который позволяет создавать и разворачивать модели на любом языке программирования, поддерживающем ONNX API.
Сначала загрузим необходимые данные в соответствии с оптимальными входными параметрами, которые мы ранее рассчитали.
#Fetch clean data new_data = clean_data(140,130)
Импортируем необходимые библиотеки.
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
Обучим модель RSI на всех имеющихся у нас данных.
#First we will export the RSI model rsi_model = Ridge() rsi_model.fit(data.loc[:,['RSI 1','RSI 2','RSI 3']],data.loc[:,'Target'])
Обучим модель скользящей средней на всех данных.
#Finally we will export the MA model ma_model = Ridge() ma_model.fit(data.loc[:,['MA 1','MA 2']],data.loc[:,'MA Target'])
Определим входную форму нашей модели и сохраним ее на диск.
initial_types = [('float_input', FloatTensorType([1, 3]))] onnx.save(convert_sklearn(rsi_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 RSI AI F22 P40.onnx") initial_types = [('float_input', FloatTensorType([1, 2]))] onnx.save(convert_sklearn(ma_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 MA AI F22 P40.onnx")
Реализация средствами MQL5
Теперь пора создать приложение на MQL5. Напишем торговую систему, которая будет открывать и закрывать позиции на основе выявленных закономерностей в поведении скользящих средних. Дополнительно мы будем использовать более медленные индикаторы, чтобы генерировать сигналы менее агрессивным образом. Так мы попробуем смоделировать поведение опытных трейдеров, у которых нет цели постоянно держать позицию в рынке.
Кроме того, мы реализуем систему трейдинг-стопов для управления рисками. Для определения уровней стоп-лоссов и тейк-профитов будем использовать индикатор Average True Range (ATR). Основой торговой стратегии будут каналы по скользящим средним.
Стратегия прогнозирует значения скользящей средней на 40 шагов вперед, чтобы получить подтверждение перед открытием позиций. Тестируем данную стратегию на исторических данных за один год, который не включен в обучающую выборку.
Начнем с импорта ранее созданного файла ONNX.
//+------------------------------------------------------------------+ //| GBPUSD AI.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Load our resources | //+------------------------------------------------------------------+ #resource "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[]; #resource "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[];
Затем импортируем нужные библиотеки.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; #include <Trade\OrderInfo.mqh> class COrderInfo;
Теперь определим нужные глобальные переменные.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ long onnx_model; int ma_handler,state; double bid,ask,vol; vectorf model_forecast = vectorf::Zeros(1); vectorf rsi_model_output = vectorf::Zeros(1); double min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity; double take_profit = 0; double close_price[3],atr_reading[],ma_buffer[]; long min_distance,login; int atr,close_average,ticket_1,ticket_2; bool authorized = false; double margin,lot_step; string currency,server; bool all_closed =true; int rsi_handler; long rsi_onnx_model; double indicator_reading[]; ENUM_ACCOUNT_TRADE_MODE account_type; const double stop_percent = 1;
Определим наши входные переменные.
//+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ input group "Money Management" input int lot_multiple = 10; // How big should the lot size be? input double profit_target = 0; // Profit Target input double loss_target = 0; // Max Loss Allowed input group "Money Management" const int atr_period = 200; //ATR Period input double atr_multiple =2.5; //ATR Multiple
Перейдем к инициализации приложения. Сначала проверим, дал ли пользователь приложению разрешение на торговлю. Если разрешено, загрузим технические индикаторы и модели ONNX.
int OnInit() { //Authorization if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program"); return(INIT_FAILED); } else if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) { Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading"); return(INIT_FAILED); } else { Comment("This License is Genuine"); setup(); } //Everything was okay return(INIT_SUCCEEDED); }
Если торговое приложение больше не используется, нужно освобождать неиспользуемые ресурсы — это хороша практика.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { OnnxRelease(onnx_model); OnnxRelease(rsi_onnx_model); IndicatorRelease(atr) }
При получении новых котировок сначала нужно сохранить обновленные рыночные цены в памяти. Если у нас уже есть открытые сделки, нужно обновить уровень трейлинг-стопа.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //Update technical data update(); if(PositionsTotal() == 0) { check_setup(); } if(PositionsTotal() > 0) { check_atr_stop(); } }
Чтобы получить прогноз с помощью нашей модели, необходимо определить текущие состояния RSI и индикатора скользящей средней.
//+------------------------------------------------------------------+ //| Get a prediction from our model | //+------------------------------------------------------------------+ int model_predict(void) { //MA Forecast vectorf model_inputs = vectorf::Zeros(2); vectorf rsi_model_inputs = vectorf::Zeros(3); CopyBuffer(ma_handler,0,0,40,ma_buffer); if(ma_buffer[0] > ma_buffer[39]) { model_inputs[0] = 1; model_inputs[1] = 0; } else if(ma_buffer[0] < ma_buffer[39]) { model_inputs[1] = 1; model_inputs[0] = 0; } //RSI Forecast CopyBuffer(rsi_handler,0,0,1,indicator_reading); if(indicator_reading[0] < 30) { rsi_model_inputs[0] = 1; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 0; } else if(indicator_reading[0] >70) { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 1; rsi_model_inputs[2] = 0; } else { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 1; } //Model predictions OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast); OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output); //Evaluate model output for buy setup if(((rsi_model_output[0] > 0) && (model_forecast[0] > 0))) { //AI Models forecast Comment("AI Forecast: UP"); return(1); } //Evaluate model output for a sell setup if((rsi_model_output[0] < 0) && (model_forecast[0] < 0)) { Comment("AI Forecast: DOWN"); return(-1); } //Otherwise no position was found return(0); }
Обновим глобальные переменные. Удобнее выполнять эти обновления за один вызов функции, а не выполнять весь код напрямую в обработчике OnTick().
//+------------------------------------------------------------------+ //| Update our market data | //+------------------------------------------------------------------+ void update(void) { ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); buy_stop_loss = 0; sell_stop_loss = 0; static datetime time_stamp; datetime time = iTime(_Symbol,PERIOD_CURRENT,0); check_price(3); CopyBuffer(atr,0,0,1,atr_reading); CopyBuffer(ma_handler,0,0,1,ma_buffer); ArraySetAsSeries(atr_reading,true); atr_stop = ((min_volume + atr_reading[0]) * atr_multiple); //On Every Candle if(time_stamp != time) { //Mark the candle time_stamp = time; OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin); } }
Загрузим необходимые ресурсы: технические индикаторы, информацию о счетах и рынках, и т.д.
//+------------------------------------------------------------------+ //| Load resources | //+------------------------------------------------------------------+ bool setup(void) { //Account Info currency = AccountInfoString(ACCOUNT_CURRENCY); server = AccountInfoString(ACCOUNT_SERVER); login = AccountInfoInteger(ACCOUNT_LOGIN); //Indicators atr = iATR(_Symbol,PERIOD_CURRENT,atr_period); //Setup technical indicators ma_handler =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW); vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple; rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE); //Market Information min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //Define our ONNX model ulong ma_input_shape [] = {1,2}; ulong rsi_input_shape [] = {1,3}; ulong output_shape [] = {1,1}; //Create the model onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT); rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT); if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE)) { Comment("[ERROR] Failed to load AI module correctly"); return(false); } //Validate I/O if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape))) { Comment("[ERROR] Failed to set input shape correctly: ",GetLastError()); return(false); } if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape))) { Comment("[ERROR] Failed to load AI module correctly: ",GetLastError()); return(false); } //Everything went fine return(true); }
Итак, получилось вот такое приложение.
//+------------------------------------------------------------------+ //| GBPUSD AI.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Load our resources | //+------------------------------------------------------------------+ #resource "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[]; #resource "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[]; //+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; #include <Trade\OrderInfo.mqh> class COrderInfo; //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ long onnx_model; int ma_handler,state; double bid,ask,vol; vectorf model_forecast = vectorf::Zeros(1); vectorf rsi_model_output = vectorf::Zeros(1); double min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity; double take_profit = 0; double close_price[3],atr_reading[],ma_buffer[]; long min_distance,login; int atr,close_average,ticket_1,ticket_2; bool authorized = false; double margin,lot_step; string currency,server; bool all_closed =true; int rsi_handler; long rsi_onnx_model; double indicator_reading[]; ENUM_ACCOUNT_TRADE_MODE account_type; const double stop_percent = 1; //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ input group "Money Management" input int lot_multiple = 10; // How big should the lot size be? input double profit_target = 0; // Profit Target input double loss_target = 0; // Max Loss Allowed input group "Money Management" input int bb_period = 36; //Bollinger band period input int ma_period = 4; //Moving average period const int atr_period = 200; //ATR Period input double atr_multiple =2.5; //ATR Multiple //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //Authorization if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program"); return(INIT_FAILED); } else if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) { Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading"); return(INIT_FAILED); } else { Comment("This License is Genuine"); setup(); } //--- Everything was okay return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- OnnxRelease(onnx_model); OnnxRelease(rsi_onnx_model); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update technical data update(); if(PositionsTotal() == 0) { check_setup(); } if(PositionsTotal() > 0) { check_atr_stop(); } } //+------------------------------------------------------------------+ //| Get a prediction from our model | //+------------------------------------------------------------------+ int model_predict(void) { //MA Forecast vectorf model_inputs = vectorf::Zeros(2); vectorf rsi_model_inputs = vectorf::Zeros(3); CopyBuffer(ma_handler,0,0,40,ma_buffer); if(ma_buffer[0] > ma_buffer[39]) { model_inputs[0] = 1; model_inputs[1] = 0; } else if(ma_buffer[0] < ma_buffer[39]) { model_inputs[1] = 1; model_inputs[0] = 0; } //RSI Forecast CopyBuffer(rsi_handler,0,0,1,indicator_reading); if(indicator_reading[0] < 30) { rsi_model_inputs[0] = 1; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 0; } else if(indicator_reading[0] >70) { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 1; rsi_model_inputs[2] = 0; } else { rsi_model_inputs[0] = 0; rsi_model_inputs[1] = 0; rsi_model_inputs[2] = 1; } //Model predictions OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast); OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output); //Evaluate model output for buy setup if(((rsi_model_output[0] > 0) && (model_forecast[0] > 0))) { //AI Models forecast Comment("AI Forecast: UP"); return(1); } //Evaluate model output for a sell setup if((rsi_model_output[0] < 0) && (model_forecast[0] < 0)) { Comment("AI Forecast: DOWN"); return(-1); } //Otherwise no position was found return(0); } //+------------------------------------------------------------------+ //| Check for valid trade setups | //+------------------------------------------------------------------+ void check_setup(void) { int res = model_predict(); if(res == -1) { Trade.Sell(vol,Symbol(),bid,0,0,"VD V75 AI"); state = -1; } else if(res == 1) { Trade.Buy(vol,Symbol(),ask,0,0,"VD V75 AI"); state = 1; } } //+------------------------------------------------------------------+ //| Update our market data | //+------------------------------------------------------------------+ void update(void) { ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); buy_stop_loss = 0; sell_stop_loss = 0; static datetime time_stamp; datetime time = iTime(_Symbol,PERIOD_CURRENT,0); check_price(3); CopyBuffer(atr,0,0,1,atr_reading); CopyBuffer(ma_handler,0,0,1,ma_buffer); ArraySetAsSeries(atr_reading,true); atr_stop = ((min_volume + atr_reading[0]) * atr_multiple); //On Every Candle if(time_stamp != time) { //Mark the candle time_stamp = time; OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin); } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Load resources | //+------------------------------------------------------------------+ bool setup(void) { //Account Info currency = AccountInfoString(ACCOUNT_CURRENCY); server = AccountInfoString(ACCOUNT_SERVER); login = AccountInfoInteger(ACCOUNT_LOGIN); //Indicators atr = iATR(_Symbol,PERIOD_CURRENT,atr_period); //Setup technical indicators ma_handler =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW); vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple; rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE); //Market Information min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //Define our ONNX model ulong ma_input_shape [] = {1,2}; ulong rsi_input_shape [] = {1,3}; ulong output_shape [] = {1,1}; //Create the model onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT); rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT); if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE)) { Comment("[ERROR] Failed to load AI module correctly"); return(false); } //--- Validate I/O if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape))) { Comment("[ERROR] Failed to set input shape correctly: ",GetLastError()); return(false); } if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape))) { Comment("[ERROR] Failed to load AI module correctly: ",GetLastError()); return(false); } //--- Everything went fine return(true); } //+------------------------------------------------------------------+ //| Close all our open positions | //+------------------------------------------------------------------+ void close_all() { if(PositionsTotal() > 0) { ulong ticket; for(int i =0;i < PositionsTotal();i++) { ticket = PositionGetTicket(i); Trade.PositionClose(ticket); } } } //+------------------------------------------------------------------+ //| Update our trailing ATR stop | //+------------------------------------------------------------------+ void check_atr_stop() { for(int i = PositionsTotal() -1; i >= 0; i--) { string symbol = PositionGetSymbol(i); if(_Symbol == symbol) { ulong ticket = PositionGetInteger(POSITION_TICKET); double position_price = PositionGetDouble(POSITION_PRICE_OPEN); double type = PositionGetInteger(POSITION_TYPE); double current_stop_loss = PositionGetDouble(POSITION_SL); if(type == POSITION_TYPE_BUY) { double atr_stop_loss = (ask - (atr_stop)); double atr_take_profit = (ask + (atr_stop)); if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } else if(type == POSITION_TYPE_SELL) { double atr_stop_loss = (bid + (atr_stop)); double atr_take_profit = (bid - (atr_stop)); if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } } } } //+------------------------------------------------------------------+ //| Close our open buy positions | //+------------------------------------------------------------------+ void close_buy() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal();i++) { if(PositionGetSymbol(i) == _Symbol) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { Trade.PositionClose(ticket); } } } } } //+------------------------------------------------------------------+ //| Close our open sell positions | //+------------------------------------------------------------------+ void close_sell() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal();i++) { if(PositionGetSymbol(i) == _Symbol) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_SELL) { Trade.PositionClose(ticket); } } } } } //+------------------------------------------------------------------+ //| Get the most recent price values | //+------------------------------------------------------------------+ void check_price(int candles) { for(int i = 0; i < candles;i++) { close_price[i] = iClose(_Symbol,PERIOD_CURRENT,i); } } //+------------------------------------------------------------------+
Теперь протестируем наш торговый алгоритм на истории, используя данные, которые не использовались при обучении модели. Для тестов возьмем период с начала января 2023 года по 28 июня 2024 года, дневные котировки пары AUDJPY. Параметр Forward установим в значение No, поскольку выбранные даты не входили в обучающую выборку модели.
Рис. 12: Символ и таймфрейм, которые мы будем использовать для тестирования торговой стратегии.
Дополнительно, чтобы сымитировать реальные торговые условия, установим параметр Delays в значение Random delay. Этот параметр управляет задержкой между размещением ордера и его исполнением. Задание случайной задержки приближает симуляцию к реальным рыночным условиям, поскольку задержки исполнения обычно непостоянны. Также выберем режим моделирования рынка по реальным тикам. Такой режим замедляет тестирование, поскольку терминалу потребуется загрузить подробные рыночные данные от брокера через интернет.
Оставшиеся параметры, такие как начальный депозит и кредитное плечо, нужно настроить в соответствии с вашей торговой стратегией. Если все необходимые данные успешно загружены, тестирование должно начаться.
Рис. 13: Параметры тестирования.
Рис. 14: Результаты тестирования стратегии на данных, которые ранее не использовались для обучения модели.
Рис. 15: Дополнительные детали тестирования на новых рыночных данных.
Заключение
На основе анализа такого обширного набора рыночных данных, мы можем сделать вывод, что если ваша задача — прогнозировать интервалы менее 40 шагов вперед, тогда, вероятно, лучше прогнозировать цену напрямую. Но если нужно предсказать более длинные горизонты — свыше 40 шагов, тогда эффективнее прогнозировать изменения скользящей средней, а не изменения самой цены. Существует множество потенциальных улучшений, которые еще предстоит исследовать и оценить их влияние на модель. Однако мы смогли убедиться, что любые усилия по преобразованию входных данных абсолютно оправданы, поскольку они позволяют более точно выявлять и раскрывать скрытые взаимосвязи для обучения моделей.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16230
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Результат выглядит многообещающе, будем пробовать.
Пожалуйста, побольше подобных материалов.
Спасибо.
Опубликована статья Инженерия признаков с Python и MQL5 (Часть I): AI-модели для долгосрочного прогнозирования по скользящим средним:
Автор: Gamuchirai Zororo Ndawana
Часть картинок не показывается...
Результаты выглядят многообещающе.
Побольше таких вещей, пожалуйста.
Больше подобных вещей, пожалуйста. Спасибо.
Не за что, Ту Че Нг.
Определенно, еще многое можно сказать, учитывая такое сильное начало.
Некоторые изображения не отображаются...
Мне очень жаль это слышать, но я уверен, что модераторы займутся исправлением этой проблемы, так как у них и так много работы.