English 中文 Español Deutsch 日本語 Português
preview
Переосмысливаем классические стратегии (Часть V): Анализ нескольких инструментов в валютной паре USDZAR

Переосмысливаем классические стратегии (Часть V): Анализ нескольких инструментов в валютной паре USDZAR

MetaTrader 5Примеры |
532 4
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Введение

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


Обзор торговой стратегии

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

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

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


Обзор методологии

Чтобы оценить взаимосвязь, мы экспортировали все наши рыночные данные из нашего терминала MetaTrader 5 с помощью скрипта, написанного на MQL5. Мы обучили различные модели, используя 2 группы возможных входных данных для моделей:

  1. Обычные котировки OHLC по курсу USDZAR.
  2. Комбинация цен на нефть и золото.

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

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

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

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

Наконец, мы экспортировали нашу настроенную модель в формат ONNX и интегрировали ее в наш советник на MQL5.


Извлечение данных

Я создал удобный скрипт, который поможет извлечь необходимые данные из вашего терминала MetaTrader 5. Просто перетащите скрипт на нужный вам инструмент, и он извлечет данные для вас и разместит их по адресу: “\MetaTrader 5\MQL5\Files\..”

//+------------------------------------------------------------------+
//|                                                      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

//---Amount of data requested
input int size = 100000; //How much data should we fetch?

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//---File name
   string file_name = "Market Data " + Symbol() + ".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");
        }

      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));
        }
     }

  }
//+------------------------------------------------------------------+



Исследовательский анализ данных в Python

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

#Libraries
import pandas as pd
import numpy as np
import seaborn as sns

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

#Dollar VS Rand
USDZAR = pd.read_csv("/home/volatily/market_data/Market Data USDZAR.csv")
#US Oil
USOIL = pd.read_csv("/home/volatily/market_data/Market Data US Oil.csv")
#SA Gold
SAGOLD = pd.read_csv("/home/volatily/market_data/Market Data XAUUSD.csv")

Проверка данных.

USOIL

USDZAR Oil

Рис 1: Наши данные перемещаются ретроспективно

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

#Format the data
USDZAR = USDZAR[::-1]
USOIL = USOIL[::-1]
SAGOLD = SAGOLD[::-1]

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

#Set the indexes
USOIL = USOIL.set_index("Time")
SAGOLD = SAGOLD.set_index("Time")
USDZAR = USDZAR.set_index("Time")

Слияние наборов данных.

#Merge the dataframes
merged_df = pd.merge(USOIL,SAGOLD,how="inner",left_index=True,right_index=True,suffixes=(" US OIL"," SA GOLD"))
merged_df = pd.merge(merged_df,USDZAR,how="inner",left_index=True,right_index=True)

Определение горизонта прогноза.

#Define the forecast horizon
look_ahead = 10

Целью будет будущая цена закрытия пары USDZAR, мы также включим бинарную цель для визуализации.

#Label the data
merged_df["Target"] = merged_df["Close"].shift(-look_ahead)
merged_df["Binary Target"] = 0
merged_df.loc[merged_df["Close"] < merged_df["Target"],"Binary Target"] = 1

Отбросим любые пустые строки.

#Drop empty rows
merged_df.dropna(inplace=True)

Наблюдение за уровнями корреляции.

#Let's observe the correlation levels
merged_df.corr()

Correlation levels in out dataset

Рис 2: Уровни корреляции в нашем наборе данных

Нефть, по-видимому, демонстрирует относительно более сильные уровни корреляции с парой USDZAR, приблизительно -0,4, в то время как золото имеет относительно более слабые уровни корреляции с валютной парой, приблизительно 0,1. Важно помнить, что корреляция не всегда подразумевает наличие взаимосвязи между переменными, иногда корреляция является результатом общей причины, влияющей на обе переменные.

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

Диаграммы рассеяния помогают нам визуализировать взаимосвязь между 2 переменными, поэтому мы создали диаграмму рассеяния цен на нефть по сравнению с ценами на золото и раскрасили точки в зависимости от того, повысился ли уровень цен USDZAR (красный) или понизился (зеленый). Как можно видеть, в данных нет четкого уровня разделения. На самом деле, ни одна из созданных нами диаграмм рассеяния не предполагает сильной взаимосвязи.

Scatter plot of gold prices against oil prices

Рис 3: Диаграмма рассеяния цен на золото по сравнению с ценами на нефть

Диаграмма рассеяния цен на нефть по сравнению с ценой закрытия USDZAR.

Рис 4: Диаграмма рассеяния цен на нефть по сравнению с ценой закрытия USDZAR

Диаграмма рассеяния цен на золото по сравнению с ценой закрытия USDZAR.

Рис 5: Диаграмма рассеяния цен на золото по сравнению с ценой закрытия USDZAR


Моделирование взаимосвязи

Выполним сброс индекса нашего набора данных, чтобы можно было выполнить кросс-валидацию.

#Reset the index
merged_df.reset_index(inplace=True)

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

#Import the libraries we need
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Lasso
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import AdaBoostRegressor
from sklearn.ensemble import BaggingRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.svm import LinearSVR
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import root_mean_squared_error
from sklearn.preprocessing import RobustScaler

Определение предикторов и цели.

#Define the predictors
normal_predictors = ["Open","High","Low","Close"]
oil_gold_predictors = ["Open US OIL","High US OIL","Low US OIL","Close US OIL","Open SA GOLD","High SA GOLD","Low SA GOLD","Close SA GOLD"]
target = "Target"

Масштабирование данных.

#Scale the data
all_predictors = normal_predictors + oil_gold_predictors
scaler = RobustScaler()
scaled_data = pd.DataFrame(scaler.fit_transform(merged_df.loc[:,all_predictors]),columns=all_predictors,index=np.arange(0,merged_df.shape[0]))

Инициализация моделей.

#Now prepare the models
models = [
        LinearRegression(),
        Lasso(),
        GradientBoostingRegressor(),
        RandomForestRegressor(),
        AdaBoostRegressor(),
        BaggingRegressor(),
        KNeighborsRegressor(),
        LinearSVR(),
        MLPRegressor(hidden_layer_sizes=(10,5),early_stopping=True),
        MLPRegressor(hidden_layer_sizes=(50,15),early_stopping=True)
]

columns = [
        "Linear Regression",
        "Lasso",
        "Gradient Boosting Regressor",
        "Random Forest Regressor",
        "AdaBoost Regressor",
        "Bagging Regressor",
        "KNeighbors Regressor",
        "Linear SVR",
        "Small Neural Network",
        "Large Neural Network"
]

Создание экземпляра объекта с временными рядами для кросс-валидации.

#Prepare the time-series split object
splits = 10
tscv = TimeSeriesSplit(n_splits=splits,gap=look_ahead)

Создание фрейма данных для хранения наших уровней ошибок.

#Prepare the dataframes to store the error levels
normal_error = pd.DataFrame(columns=columns,index=np.arange(0,splits))
new_error = pd.DataFrame(columns=columns,index=np.arange(0,splits))

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

#First we iterate over all the models we have available
for j in np.arange(0,len(models)):
        #Now we have to perform cross validation with each model
        for i,(train,test) in enumerate(tscv.split(scaled_data)):
        #Get the data
        X_train = scaled_data.loc[train[0]:train[-1],oil_gold_predictors]
        X_test = scaled_data.loc[test[0]:test[-1],oil_gold_predictors]
        y_train = merged_df.loc[train[0]:train[-1],target]
        y_test = merged_df.loc[test[0]:test[-1],target]
        #Fit the model
        models[j].fit(X_train,y_train)
        #Measure the error
        new_error.iloc[i,j] = root_mean_squared_error(y_test,models[j].predict(X_test))

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

normal_error

Наши уровни ошибок при прогнозировании с использованием предикторов OHLC.

Рис. 6: Наши уровни ошибок при прогнозировании с использованием предикторов OHLC

Наши уровни ошибок при прогнозировании с использованием предикторов OHLC II.

Рис. 7: Наши уровни ошибок при прогнозировании с использованием предикторов OHLC II

Теперь взгляните на наши уровни ошибок, используя только цены на нефть и золото.

new_error

Наши уровни точности при прогнозировании с использованием цен на нефть и золото.

Рис. 8: Наши уровни точности при прогнозировании с использованием цен на нефть и золото

Наши уровни точности при прогнозировании с использованием цен на нефть и золото II.

Рис. 9: Наши уровни точности при прогнозировании с использованием цен на нефть и золото II

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

#Let's see our average performance on the normal dataset
for i in (np.arange(0,normal_error.shape[0])):
        print(f"{models[i]} normal error {((normal_error.iloc[:,i].mean()))}")
LinearRegression() normal error 0.01136361865358375
Lasso() normal error 0.11138143304314707
GradientBoostingRegressor() normal error 0.03472997520534606
RandomForestRegressor() normal error 0.03616484012058101
AdaBoostRegressor() normal error 0.037484107657877755
BaggingRegressor() normal error 0.03670486223028821
KNeighborsRegressor() normal error 0.035113189373409175
LinearSVR() normal error 0.01085610361276552
MLPRegressor(early_stopping=True, hidden_layer_sizes=(10, 5)) normal error 2.558754334716706
MLPRegressor(early_stopping=True, hidden_layer_sizes=(50, 15)) normal error 1.0544369296125597

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

#Let's see our average performance on the new dataset
for i in (np.arange(0,normal_error.shape[0])):
        print(f"{models[i]} normal error {((new_error.iloc[:,i].mean()))}")

LinearRegression() normal error 0.13404065973045615
Lasso() normal error 0.11138143304314707
GradientBoostingRegressor() normal error 0.0893855335909897
RandomForestRegressor() normal error 0.08957454602573789
AdaBoostRegressor() normal error 0.08658796789785872
BaggingRegressor() normal error 0.08887059320664067
KNeighborsRegressor() normal error 0.07696901077705855
LinearSVR() normal error 0.15463529064256165
MLPRegressor(early_stopping=True, hidden_layer_sizes=(10, 5)) normal error 3.8970873719426784
MLPRegressor(early_stopping=True, hidden_layer_sizes=(50, 15)) normal error 0.6958177634524169

Проанализируем изменения точности.

#Let's see our average performance on the normal dataset
for i in (np.arange(0,normal_error.shape[0])):
    print(f"{models[i]} changed by {((normal_error.iloc[:,i].mean()-new_error.iloc[:,i].mean()))/normal_error.iloc[:,i].mean()}%")
LinearRegression() changed by -10.795596439535894%
Lasso() changed by 0.0%
GradientBoostingRegressor() changed by -1.573728690057642%
RandomForestRegressor() changed by -1.4768406476311784%
AdaBoostRegressor() changed by -1.3099914419240863%
BaggingRegressor() changed by -1.421221271695885%
KNeighborsRegressor() changed by -1.1920256220116057%
LinearSVR() changed by -13.244087580439862%
MLPRegressor(early_stopping=True, hidden_layer_sizes=(10, 5)) changed by -0.5230408480672479%
MLPRegressor(early_stopping=True, hidden_layer_sizes=(50, 15)) changed by 0.34010489967561475%



Выбор признаков

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

#Our best performing model was the KNeighbors Regressor
#Let us perform feature selection to test how stable the relationship is
from mlxtend.feature_selection import SequentialFeatureSelector as SFS

Создаем новый экземпляр модели.

#Let us select our best model
model = KNeighborsRegressor()

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

#Create the sequential selector object
sfs1 = SFS(
        model,
        k_features=(1,len(all_predictors)),
        forward=True,
        scoring="neg_mean_squared_error",
        cv=10,
        n_jobs=-1
)

Подбор последовательного селектора показателей.

#Fit the sequential selector
sfs1 = sfs1.fit(scaled_data.loc[:,all_predictors],merged_df.loc[:,"Target"])

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

#Now let us see which predictors were selected
sfs1.k_feature_names_
('Open', 'Low', 'Close')



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

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

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

Импорт модуля scikit-learn.
#Now we will load the libraries we need
from sklearn.model_selection import RandomizedSearchCV

Подготовим специальное обучение и тестовые наборы.

#Let us see if we can tune the model
#First we will create train test splits
train_X = scaled_data.loc[:(scaled_data.shape[0]//2),:]
train_y = merged_df.loc[:(merged_df.shape[0]//2),"Target"]

test_X = scaled_data.loc[(scaled_data.shape[0]//2):,:]
test_y = merged_df.loc[(merged_df.shape[0]//2):,"Target"]

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

#Create the tuning object
rs = RandomizedSearchCV(KNeighborsRegressor(n_jobs=-1),{
        "n_neighbors": [1,2,3,4,5,8,10,16,20,30,60,100],
        "weights":["uniform","distance"],
        "leaf_size":[1,2,3,4,5,10,15,20,40,60,90],
        "algorithm":["ball_tree","kd_tree"],
        "p":[1,2,3,4,5,6,7,8]
},cv=5,n_iter=500,return_train_score=False,scoring="neg_mean_squared_error")

Выполнение настройки параметров на обучающем наборе.

#Let's perform the hyperparameter tuning
rs.fit(train_X,train_y)

Посмотрим на полученные нами результаты от наилучших к наихудшим.

#Let's store the results from our hyperparameter tuning
tuning_results = pd.DataFrame(rs.cv_results_)
tuning_results.loc[:,["param_n_neighbors","param_weights","param_leaf_size","param_algorithm","param_p","mean_test_score"]].sort_values(by="mean_test_score",ascending=False)

Результаты настройки нашей лучшей модели.

Рис. 10: Результаты настройки нашей лучшей модели

Приведены лучшие параметры, обнаруженные нами.

#The best parameters we came across
rs.best_params_

{'weights': 'distance',
 'p': 1,
 'n_neighbors': 4,
 'leaf_size': 15,
 'algorithm': 'ball_tree'}


Проверка на переобучение

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

#Create instances of the default model and the custmoized model
default_model = KNeighborsRegressor()
customized_model = KNeighborsRegressor(p=rs.best_params_["p"],weights=rs.best_params_["weights"],n_neighbors=rs.best_params_["n_neighbors"],leaf_size=rs.best_params_["leaf_size"],algorithm=rs.best_params_["algorithm"])

Измерим точность модели по умолчанию.

#Measure the accuracy of the default model
default_model.fit(train_X,train_y)
root_mean_squared_error(test_y,default_model.predict(test_X))
0.06633226373900612

Теперь - точность настроенной модели.

#Measure the accuracy of the customized model
customized_model.fit(train_X,train_y)
root_mean_squared_error(test_y,customized_model.predict(test_X))
0.04334616246844129

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


Экспортирование в формат ONNX

Open Neural Network Exchange (ONNX) — это совместимая платформа для создания и развертывания моделей машинного обучения независимо от языка программирования. Используя ONNX, наши модели машинного обучения можно легко использовать на любом языке программирования, если этот язык поддерживает ONNX API. На момент написания данной статьи ONNX API разрабатывается и поддерживается консорциумом крупнейших компаний в мире.

Импортируем необходимые нам библиотеки
#Let's prepare to export the customized model to ONNX format
import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

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

#Train the model on all the data we have
#But before doing that we need to first scale the data in a way we can repeat in MQL5
scale_factors = pd.DataFrame(columns=all_predictors,index=["mean","standard deviation"])
for i in np.arange(0,len(all_predictors)):
        scale_factors.iloc[0,i] = merged_df.loc[:,all_predictors[i]].mean()
        scale_factors.iloc[1,i] = merged_df.loc[:,all_predictors[i]].std()
scale_factors

Наши коэффициенты масштабирования.

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

Теперь проведем нормализацию и стандартизацию.

for i in all_predictors:
        merged_df.loc[:,i] = (merged_df.loc[:,i] - merged_df.loc[:,i].mean()) / merged_df.loc[:,i].std()

Посмотрим на наши данные теперь.

merged_df

Наши масштабированные данные

Рис. 11: Как выглядят наши данные после масштабирования, отображаются не все столбцы

Инициализируем нашу настроенную модель.

customized_model = KNeighborsRegressor(p=rs.best_params_["p"],weights=rs.best_params_["weights"],n_neighbors=rs.best_params_["n_neighbors"],leaf_size=rs.best_params_["leaf_size"],algorithm=rs.best_params_["algorithm"])
customized_model.fit(merged_df.loc[:,all_predictors],merged_df.loc[:,"Target"])

Определим входную форму нашей модели.

#Define the input shape and type
initial_type = [("float_tensor_type",FloatTensorType([1,train_X.shape[1]]))]

Создадим представление ONNX.

#Create an ONNX representation
onnx_model = convert_sklearn(customized_model,initial_types=initial_type)

Сохраним модель ONNX.

#Store the ONNX model
onnx_model_name = "USDZAR_FLOAT_M1.onnx"
onnx.save_model(onnx_model,onnx_model_name)


Визуализация модели ONNX

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

Импорт модуля netron.

#Let's visualize the model in netron
import netron

Теперь можем визуализировать модель с помощью netron.

#Run netron
netron.start(onnx_model_name)

Метаданные нашей модели ONNX.

Рис. 12: Характеристики нашей модели ONNX

Структура нашей модели ONNX.

Рис. 13: Структура нашей модели ONNX

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


Реализация средствами MQL5

Теперь можем приступить к созданию нашего советника. Начнем с интеграции нашей модели ONNX в наше приложение. При указании файла ONNX в качестве ресурса файл ONNX будет включен в скомпилированную программу с расширением .ex5.

//+------------------------------------------------------------------+
//|                                                       USDZAR.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"

//+-----------------------------------------------------------------+
//| Require the ONNX file                                           |
//+-----------------------------------------------------------------+
#resource "\\Files\\USDZAR_FLOAT_M1.onnx" as const uchar onnx_model_buffer[];

Теперь импортируем торговую библиотеку.

//+-----------------------------------------------------------------+
//| Libraries we need                                               |
//+-----------------------------------------------------------------+
#include <Trade/Trade.mqh>
CTrade Trade;

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

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
double input sl_width = 0.4;              //How tight should our stop loss be?
int input lot_multiple = 10;              //How many times bigger than minimum lot should we enter?
double input max_risk = 10;               //After how much profit/loss should we close?

Теперь нам требуется несколько глобальных переменных.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;                          //Our onnx model
double mean_values[12],std_values[12];    //The scaling factors we used for our data
vector model_inputs = vector::Zeros(12);  //Our model's inputs
vector model_forecast = vector::Zeros(1); //Our model's output
double bid,ask;                           //Market prices
double minimum_volume;                    //Smallest lot size
double state = 0;                         //0 means we have no positions, 1 means we have buy position, 2 means we have sell position.

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

//+------------------------------------------------------------------+
//| Check if we have reached our risk level                          |
//+------------------------------------------------------------------+
void check_risk_level(void)
  {
//--- Check if we have surpassed our maximum risk level
   if(MathAbs(PositionGetDouble(POSITION_PROFIT)) > max_risk)
     {
      //--- We should close our positions
      Trade.PositionClose("USDZAR");
     }
  }

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

//+------------------------------------------------------------------+
//| Check if there is a reversal may be coming                       |
//+------------------------------------------------------------------+
void check_reversal(void)
  {
   if(((state == 1) && (model_forecast[0] < iClose("USDZAR",PERIOD_M1,0))) ||((state == 2) && (model_forecast[0] > iClose("USDZAR",PERIOD_M1,0))))
     {
      //--- There may be a reversal coming
      Trade.PositionClose("USDZAR");
      //--- Give the user feedback
      Alert("Potential reversal detected");
     }
  }

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

//+------------------------------------------------------------------+
//| Find an entry opportunity                                        |
//+------------------------------------------------------------------+
void find_entry(void)
  {
//---Check for the change in price on higher timeframes
   if(iClose("USDZAR",PERIOD_D1,0) > iClose("USDZAR",PERIOD_D1,21))
     {
      //--- We're looking for buy oppurtunities
      if(model_forecast[0] > iClose("USDZAR",PERIOD_M1,0))
        {
         //--- Open the position
         Trade.Buy(minimum_volume,"USDZAR",ask,(ask - sl_width),(ask + sl_width),"USDZAR AI");
         //--- Update the system state
         state = 1;
        }
     }
//---Check for the change in price on higher timeframes
   else
      if(iClose("USDZAR",PERIOD_D1,0) < iClose("USDZAR",PERIOD_D1,21))
        {
         //--- We're looking for sell oppurtunities
         if(model_forecast[0] < iClose("USDZAR",PERIOD_M1,0))
           {
            //--- Open sell position
            Trade.Sell(minimum_volume,"USDZAR",bid,(bid + sl_width),(bid - sl_width),"USDZAR AI");
            //--- Update the system state
            state = 2;
           }
        }
  }

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

//+------------------------------------------------------------------+
//| Obtain a forecast from our model                                 |
//+------------------------------------------------------------------+
void model_predict(void)
  {
//Let's fetch our model's inputs
//--- USDZAR
   model_inputs[0] = ((iOpen("USDZAR",PERIOD_M1,0) - mean_values[0]) / std_values[0]);
   model_inputs[1] = ((iHigh("USDZAR",PERIOD_M1,0) - mean_values[1]) / std_values[1]);
   model_inputs[2] = ((iLow("USDZAR",PERIOD_M1,0) - mean_values[2]) / std_values[2]);
   model_inputs[3] = ((iClose("USDZAR",PERIOD_M1,0) - mean_values[3]) / std_values[3]);
//--- XTI OIL US
   model_inputs[4] = ((iOpen("XTIUSD",PERIOD_M1,0) - mean_values[4]) / std_values[4]);
   model_inputs[5] = ((iHigh("XTIUSD",PERIOD_M1,0) - mean_values[5]) / std_values[5]);
   model_inputs[6] = ((iLow("XTIUSD",PERIOD_M1,0) - mean_values[6]) / std_values[6]);
   model_inputs[7] = ((iClose("XTIUSD",PERIOD_M1,0) - mean_values[7]) / std_values[7]);
//--- GOLD SA
   model_inputs[8] = ((iOpen("XAUUSD",PERIOD_M1,0) - mean_values[8]) / std_values[8]);
   model_inputs[9] = ((iHigh("XAUUSD",PERIOD_M1,0) - mean_values[9]) / std_values[9]);
   model_inputs[10] = ((iLow("XAUUSD",PERIOD_M1,0) - mean_values[10]) / std_values[10]);
   model_inputs[11] = ((iClose("XAUUSD",PERIOD_M1,0) - mean_values[11]) / std_values[11]);
//--- Get a prediction
   OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast);
  }

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

//+------------------------------------------------------------------+
//| Load the symbols we need and add them to the market watch        |
//+------------------------------------------------------------------+
void load_symbols(void)
  {
   SymbolSelect("XAUUSD",true);
   SymbolSelect("XTIUSD",true);
   SymbolSelect("USDZAR",true);
  }

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

//+------------------------------------------------------------------+
//| Load the scale values                                            |
//+------------------------------------------------------------------+
void load_scale_values(void)
  {
//--- Mean
//--- USDZAR
   mean_values[0] = 18.14360511919699;
   mean_values[1] = 18.145737421580925;
   mean_values[2] = 18.141568574864074;
   mean_values[3] = 18.14362306984525;
//--- XTI US OIL
   mean_values[4] = 80.76956702216644;
   mean_values[5] = 80.7864452112087;
   mean_values[6] = 80.75236177331661;
   mean_values[7] = 80.76923546633206;
//--- GOLD SA
   mean_values[8] = 2430.5180384776245;
   mean_values[9] = 2430.878959640318;
   mean_values[10] = 2430.1509598494354;
   mean_values[11] = 2430.5204140526976;
//--- Standard Deviation
//--- USDZAR
   std_values[0] = 0.11301636249300206;
   std_values[1] = 0.11318116432297631;
   std_values[2] = 0.11288670156099372;
   std_values[3] = 0.11301994613848391;
//--- XTI US OIL
   std_values[4] = 0.9802409859148413;
   std_values[5] = 0.9807944310705999;
   std_values[6] = 0.9802449355481064;
   std_values[7] = 0.9805961626626833;
//--- GOLD SA
   std_values[8] = 26.397404261230328;
   std_values[9] = 26.414599597905003;
   std_values[10] = 26.377605644853944;
   std_values[11] = 26.395208330942864;
  }

Наконец, нам нужна функция, отвечающая за загрузку нашего файла ONNX.

//+------------------------------------------------------------------+
//| Load the onnx file from buffer                                   |
//+------------------------------------------------------------------+
bool load_onnx_file(void)
  {
//--- Create the model from the buffer
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DEFAULT);

//--- The input size for our onnx model
   ulong input_shape [] = {1,12};

//--- Check if we have the right input size
   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Incorrect input shape, the model has input shape ",OnnxGetInputCount(onnx_model));
      return(false);
     }

//--- The output size for our onnx model
   ulong output_shape [] = {1,1};

//--- Check if we have the right output size
   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Incorrect output shape, the model has output shape ",OnnxGetOutputCount(onnx_model));
      return(false);
     }

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

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Load the onnx file
   if(!load_onnx_file())
     {
      return(INIT_FAILED);
     }

//--- Load our scaling values
   load_scale_values();

//--- Add the symbols we need to the market watch
   load_symbols();

//--- The smallest lotsize we can use
   minimum_volume = SymbolInfoDouble("USDZAR",SYMBOL_VOLUME_MIN) * lot_multiple;

//--- Everything went fine
   return(INIT_SUCCEEDED);
  }

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

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release the resources we used for our onnx model
   OnnxRelease(onnx_model);

//--- Remove the expert advisor
   ExpertRemove();
  }

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- We always need a forecast from our model
   model_predict();
//--- Fetch market prices
   bid = SymbolInfoDouble("USDZAR",SYMBOL_BID);
   ask = SymbolInfoDouble("USDZAR",SYMBOL_ASK);

//--- If we have no open positions, find an entry
   if(PositionsTotal() == 0)
     {
      //--- Find an entry
      find_entry();
      //--- Reset the system state
      state = 0;
     }

//--- If we have open postitions, manage them
   else
     {
      //--- Check for a reveral warning from our AI
      check_reversal();
      //--- Check if we have not reached our max risk levels
      check_risk_level();
     }

  }

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

Наш советник

Рис. 17: Наш советник

Наша система в действии

Рис. 14: Входные данные для нашего советника

Наша система в действии

Рис. 15: Наша программа в действии



Заключение

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

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

Прикрепленные файлы |
USDZAR_FLOAT_M1.onnx (524.58 KB)
USDZAR.ipynb (694.01 KB)
FetchData.mq5 (2.05 KB)
USDZAR.mq5 (10.54 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
linfo2
linfo2 | 19 авг. 2024 в 07:12
Потрясающе, еще один отличный обзор. Спасибо за рабочую тетрадь, теперь у нас есть шаблон для проверки наших собственных корреляций. Очень признателен
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 21 авг. 2024 в 15:32
linfo2 #:
Потрясающе, еще один отличный обзор. Спасибо за рабочую тетрадь, теперь у нас есть шаблон для проверки наших собственных корреляций. Очень признателен
С удовольствием, Нил, я помню, вы как-то говорили мне, что у вас есть идея, которая включает в себя поиск корреляции между показателями, не стесняйтесь поделиться, как ваши выводы в том проекте могут помочь нам здесь, и, надеюсь, мы сможем готовить 💯🔥
Carl Schreiber
Carl Schreiber | 21 окт. 2024 в 13:48
В скрипте вы не закрываете файл, открытый для записи даты, как в статье, так и в fetchData.mq5
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 21 окт. 2024 в 18:14
Carl Schreiber файл, открытый для записи даты, как в статье, так и в fetchData.mq5.
Спасибо, что обратили внимание на это, я исправлюсь в будущем
Нейросети в трейдинге: Интеграция теории хаоса в прогнозирование временных рядов (Окончание) Нейросети в трейдинге: Интеграция теории хаоса в прогнозирование временных рядов (Окончание)
Продолжаем интеграцию методов, предложенных авторами фреймворка Attraos, в торговые модели. Напомню, что данный фреймворк использует концепции теории хаоса для решения задач прогнозирования временных рядов, интерпретируя их как проекции многомерных хаотических динамических систем.
Машинное обучение и Data Science (Часть 29): Как отбирать лучшие форекс-данные для обучения ИИ Машинное обучение и Data Science (Часть 29): Как отбирать лучшие форекс-данные для обучения ИИ
В этой статье мы подробно рассмотрим важные аспекты при выборе наиболее релевантных и качественных данных с рынка Forex для повышения производительности моделей искусственного интеллекта.
Бильярдный алгоритм оптимизации — Billiards Optimization Algorithm (BOA) Бильярдный алгоритм оптимизации — Billiards Optimization Algorithm (BOA)
Метод BOA, вдохновленный классической игрой в бильярд, моделирует процесс поиска оптимальных решений, как игру с шарами, стремящимися попасть в лузы, олицетворяющие наилучшие результаты. В данной статье мы рассмотрим основы работы BOA, его математическую модель и эффективность в решении различных оптимизационных задач.
Индикатор оценки силы и слабости валютных пар на чистом MQL5 Индикатор оценки силы и слабости валютных пар на чистом MQL5
Создаем профессиональный индикатор для анализа силы валют на MQL5. Пошаговое руководство научит вас разрабатывать мощный торговый инструмент с визуальной панелью для MetaTrader 5. Вы узнаете, как рассчитывать силу валютных пар по нескольким таймфреймам (H1, H4, D1), реализовывать динамическое обновление данных и создавать удобный пользовательский интерфейс.