
Reimaginando Estratégias Clássicas (Parte V): Análise de Múltiplos Símbolos no USDZAR
Introdução
Existem inúmeras maneiras de integrar IA em nossas estratégias de negociação, mas, infelizmente, não podemos avaliar todas antes de decidir em qual confiar nosso capital. Hoje, revisitamos uma estratégia popular de análise de múltiplos símbolos para determinar se podemos aprimorá-la com o uso de IA. Forneceremos as informações necessárias para que você tome uma decisão informada sobre a adequação dessa estratégia ao seu perfil de investidor.
Visão Geral da Estratégia de Negociação
Estratégias de negociação que empregam a análise de múltiplos símbolos baseiam-se principalmente na correlação observada entre a cesta de ativos. A correlação é uma medida de dependência linear entre duas variáveis. No entanto, a correlação é frequentemente confundida com uma indicação de relação entre duas variáveis, o que nem sempre é o caso.
Traders ao redor do mundo aproveitam seu conhecimento fundamental sobre ativos correlacionados para orientar suas decisões de investimento, medir níveis de risco e até mesmo como um sinal de saída. Por exemplo, consideremos o par de moedas USDZAR. O governo americano é um dos principais exportadores de petróleo do mundo, enquanto, por outro lado, o governo sul-africano é o maior exportador de ouro do mundo.
Como essas commodities contribuem significativamente para o Produto Interno Bruto (PIB) desses dois países, poderíamos naturalmente esperar que os níveis de preço dessas commodities expliquem parte da variação no par de moedas USDZAR. Assim, se o petróleo estiver se saindo melhor do que o ouro no mercado à vista, podemos esperar que o Dólar esteja mais forte do que o Rand e vice-versa.
Visão Geral da Metodologia
Para avaliar essa relação, exportamos todos os nossos dados de mercado do terminal MetaTrader 5 utilizando um script escrito em MQL5. Treinamos vários modelos utilizando dois grupos de possíveis entradas para os modelos:
- Cotações OHLC padrão do USDZAR.
- Uma combinação dos preços do petróleo e do ouro.
A partir dos dados coletados, parece que o petróleo tem níveis de correlação mais fortes com o par de moedas USDZAR do que o ouro.
Como nossos dados estavam em diferentes escalas, padronizamos e normalizamos os dados antes do treinamento. Realizamos uma validação cruzada de 10 vezes sem embaralhamento aleatório para comparar nossa precisão entre os diferentes conjuntos de entradas.
Nossas descobertas sugerem que o primeiro grupo pode resultar no menor erro. O modelo de melhor desempenho foi a regressão linear utilizando os dados OHLC padrão. No entanto, no segundo grupo, o modelo de melhor desempenho foi o algoritmo KNeighborsRegressor.
Realizamos com sucesso o ajuste de hiperparâmetros usando 500 iterações de uma busca aleatória sobre cinco parâmetros do modelo. Testamos o sobreajuste comparando os níveis de erro do nosso modelo personalizado com um modelo padrão em um conjunto de validação que foi separado durante a otimização. Após treinar ambos os modelos em conjuntos de treinamento equivalentes, nosso modelo superou o modelo padrão no conjunto de validação.
Por fim, exportamos nosso modelo personalizado para o formato ONNX e o integramos ao nosso Expert Advisor em MQL5.
Extração de Dados
Criei um script prático para ajudar a extrair os dados necessários do seu terminal MetaTrader 5. Basta arrastar e soltar o script no símbolo desejado, e ele extrairá os dados para você e os salvará no caminho: “\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)); } } } //+------------------------------------------------------------------+
Análise Exploratória de Dados em Python
Começamos importando as bibliotecas padrão.
#Libraries import pandas as pd import numpy as np import seaborn as sns
Agora, vamos ler os dados que extraímos anteriormente.
#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")
Inspeção dos dados.
USOIL
Fig 1: Nossos dados estão organizados de trás para frente no tempo.
Observe que nossos timestamps estão organizados do presente para o passado, o que não é desejável para tarefas de aprendizado de máquina. Vamos inverter a ordem dos dados para que possamos prever o futuro, e não o passado.
#Format the data USDZAR = USDZAR[::-1] USOIL = USOIL[::-1] SAGOLD = SAGOLD[::-1]
Antes de mesclar nossos conjuntos de dados, primeiro precisamos garantir que todos utilizem a coluna de data como índice. Dessa forma, podemos garantir que selecionamos apenas os dias compartilhados por todos os conjuntos de dados, na ordem cronológica correta.
#Set the indexes USOIL = USOIL.set_index("Time") SAGOLD = SAGOLD.set_index("Time") USDZAR = USDZAR.set_index("Time")
Mesclagem dos conjuntos de dados.
#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)
Definição do horizonte de previsão.
#Define the forecast horizon look_ahead = 10
O alvo será o preço de fechamento futuro do par USDZAR, também incluiremos um alvo binário para fins de visualização.
#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
Vamos remover quaisquer linhas vazias.
#Drop empty rows
merged_df.dropna(inplace=True)
Observe os níveis de correlação.
#Let's observe the correlation levels merged_df.corr()
Fig 2: Níveis de correlação em nosso conjunto de dados
O petróleo parece demonstrar níveis de correlação relativamente mais fortes com o par USDZAR, aproximadamente -0,4, enquanto o ouro apresenta níveis de correlação relativamente mais fracos com o par de moedas, aproximadamente 0,1. É importante lembrar que correlação nem sempre implica que há uma relação entre as variáveis; às vezes, a correlação resulta de uma causa comum que está afetando ambas as variáveis.
Por exemplo, historicamente, a relação entre o ouro e o dólar era inversa. Sempre que o dólar se desvalorizava, os traders retiravam seu dinheiro do dólar e o investiam em ouro. Isso, historicamente, fazia com que os preços do ouro subissem sempre que o dólar apresentava um desempenho ruim. Assim, a causa comum, neste exemplo simples, seriam os traders que participavam de ambos os mercados.
Gráficos de dispersão nos ajudam a visualizar a relação entre duas variáveis, por isso criamos um gráfico de dispersão dos preços do petróleo em relação aos preços do ouro, colorindo os pontos dependendo de os preços do USDZAR terem se valorizado (vermelho) ou desvalorizado (verde). Como se pode ver, não há um nível claro de separação nos dados. De fato, nenhum dos gráficos de dispersão que criamos sugere uma relação forte.
Fig 3: Um gráfico de dispersão dos preços do ouro em relação aos preços do petróleo
Fig 4: Gráfico de dispersão dos preços do petróleo em relação ao preço de fechamento do USDZAR
Fig 5: Um gráfico de dispersão dos preços do ouro em relação ao fechamento do USDZAR
Modelando a Relação
Vamos redefinir o índice do nosso conjunto de dados para que possamos realizar a validação cruzada.
#Reset the index
merged_df.reset_index(inplace=True)
Agora vamos importar as bibliotecas necessárias para modelar a relação nos dados.
#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
Definindo os preditores e o alvo.
#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"
Normalizando os dados.
#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]))
Inicializando os modelos.
#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" ]
Instanciando o objeto de validação cruzada para séries temporais.
#Prepare the time-series split object splits = 10 tscv = TimeSeriesSplit(n_splits=splits,gap=look_ahead)
Criando um data frame para armazenar nossos níveis de erro.
#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))
Agora vamos realizar a validação cruzada usando um loop aninhado. O primeiro loop percorre nossa lista de modelos, enquanto o segundo valida cruzadamente cada modelo e armazena os níveis de erro.
#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))
Nossos níveis de erro usando os insumos de modelo ordinários.
normal_error
Fig 6: Nossos níveis de erro ao prever utilizando os preditores OHLC
Fig 7: Nossos níveis de erro ao prever utilizando os preditores OHLC II
Agora veja nossos níveis de erro usando apenas os preços do petróleo e do ouro.
new_error
Fig 8: Nossos níveis de acurácia ao prever utilizando os preços do petróleo e do ouro
Nossos níveis de acurácia ao prever utilizando os preços do petróleo e do ouro II.
Vamos ver o desempenho médio de cada modelo usando os preditores ordinários.
#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()))}")
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
Agora vamos avaliar nosso desempenho médio usando os novos preditores.
#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()))}")
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
Vamos observar as mudanças na acurácia.
#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()}%")
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%
Seleção de Atributos
Nosso modelo com melhor desempenho usando os preditores de petróleo e ouro é o regressor KNeighbors. Vamos ver quais atributos são mais importantes para ele.
#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
Criar uma nova instância do modelo.
#Let us select our best model
model = KNeighborsRegressor()
Usaremos a seleção progressiva (forward selection) para identificar os atributos mais importantes para o nosso modelo. Agora daremos ao nosso modelo acesso a todos os preditores de uma só vez.
#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 )
Ajustando o seletor de atributos sequencial.
#Fit the sequential selector sfs1 = sfs1.fit(scaled_data.loc[:,all_predictors],merged_df.loc[:,"Target"])
Observar os melhores atributos selecionados pelo algoritmo pode nos levar à conclusão de que nem os preços do petróleo nem do ouro são muito úteis na previsão do USDZAR, pois o algoritmo selecionou apenas 3 atributos, que foram as cotações de abertura, mínima e fechamento do USDZAR.
#Now let us see which predictors were selected
sfs1.k_feature_names_
Ajuste de Hiperparâmetros
Vamos tentar realizar o ajuste de hiperparâmetros utilizando o módulo RandomizedSearchCV do scikit-learn. O algoritmo nos ajuda a amostrar uma superfície de resposta que pode ser grande demais para ser totalmente explorada. Quando usamos modelos com inúmeros parâmetros, o total de combinações de entradas cresce em uma taxa significativamente rápida. Portanto, preferimos o algoritmo de busca aleatória quando lidamos com muitos parâmetros que possuem vários valores possíveis.
O algoritmo oferece um equilíbrio entre a precisão dos resultados e o tempo de computação. Esse equilíbrio é controlado ajustando o número de iterações que permitimos. Observe que, devido à natureza aleatória do algoritmo, pode ser difícil reproduzir exatamente os resultados demonstrados neste artigo.
Importar o módulo do scikit-learn.
#Now we will load the libraries we need
from sklearn.model_selection import RandomizedSearchCV
Preparar conjuntos de treino e teste dedicados.
#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"]
Para realizar o ajuste de parâmetros, precisamos passar um estimador que implemente a interface do scikit-learn, seguido de um dicionário que contenha chaves correspondentes aos parâmetros do estimador e valores que definem o intervalo de entradas permitidas para cada parâmetro. A partir daí, especificamos que queremos realizar uma validação cruzada com 5 divisões, e também precisamos especificar a métrica de avaliação como erro quadrático médio negativo.
#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")
Realizando o ajuste de parâmetros no conjunto de treinamento..
#Let's perform the hyperparameter tuning rs.fit(train_X,train_y)
Analisando os resultados obtidos, do melhor para o pior.
#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)
Fig 10: Os resultados do ajuste do nosso melhor modelo
Estes são os melhores parâmetros que encontramos.
#The best parameters we came across
rs.best_params_
'p': 1,
'n_neighbors': 4,
'leaf_size': 15,
'algorithm': 'ball_tree'}
Verificando o Overfitting
Vamos nos preparar para comparar nossos modelos personalizados e padrão. Ambos os modelos serão treinados com conjuntos de dados idênticos. Se o modelo padrão apresentar melhor desempenho que o nosso modelo personalizado no conjunto de validação, isso pode ser um sinal de que houve overfitting nos dados de treinamento. No entanto, se o nosso modelo personalizado tiver um desempenho melhor, isso pode sugerir que conseguimos ajustar os parâmetros do modelo sem overfitting.
#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"])
Vamos medir a acurácia do modelo padrão.
#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))
Agora, a acurácia do modelo personalizado.
#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))
Parece que conseguimos ajustar bem o modelo sem overfitting! Agora, vamos nos preparar para exportar o nosso modelo personalizado para o formato ONNX.
Exportando para o Formato ONNX
Open Neural Network Exchange (ONNX) é uma estrutura interoperável para construir e implantar modelos de aprendizado de máquina de maneira independente de linguagem. Ao usar o ONNX, nossos modelos de aprendizado de máquina podem ser facilmente utilizados em qualquer linguagem de programação, desde que essa linguagem suporte a API do ONNX. No momento da escrita, a API do ONNX está sendo desenvolvida e mantida por um consórcio das maiores empresas do mundo.
Import the libraries we need #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
Precisamos garantir que nossos dados sejam escalonados e normalizados de maneira que possamos reproduzir isso no terminal MetaTrader 5. Portanto, realizaremos uma transformação padrão que sempre poderemos fazer no nosso terminal mais tarde. Subtrairemos o valor médio de cada coluna, o que centralizará os dados. Em seguida, dividiremos cada valor pelo desvio padrão de sua respectiva coluna, isso ajudará nosso modelo a compreender melhor as mudanças entre variáveis com escalas diferentes.
#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
Fig 12: Alguns dos valores que utilizaremos para escalar e padronizar nossos dados, nem todas as colunas estão sendo exibidas
Agora vamos realizar a normalização e padronização.
for i in all_predictors:
merged_df.loc[:,i] = (merged_df.loc[:,i] - merged_df.loc[:,i].mean()) / merged_df.loc[:,i].std()
Vamos olhar nossos dados agora.
merged_df
Fig 11: Como nossos dados ficam após o escalonamento, nem todas as colunas estão sendo exibidas
Inicializar nosso modelo personalizado.
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"])
Definir a forma de entrada do nosso modelo.
#Define the input shape and type initial_type = [("float_tensor_type",FloatTensorType([1,train_X.shape[1]]))]
Criar a representação ONNX.
#Create an ONNX representation
onnx_model = convert_sklearn(customized_model,initial_types=initial_type)
Salvar o modelo ONNX.
#Store the ONNX model onnx_model_name = "USDZAR_FLOAT_M1.onnx" onnx.save_model(onnx_model,onnx_model_name)
Visualizando o modelo ONNX
Netron é um visualizador de código aberto para modelos de aprendizado de máquina. O Netron oferece suporte a muitos frameworks diferentes além do ONNX, como o Keras. Usaremos o Netron para garantir que nosso modelo ONNX tenha a forma de entrada e saída que esperávamos.
Importar o módulo do Netron.
#Let's visualize the model in netron import netron
Agora podemos visualizar o modelo usando o Netron.
#Run netron
netron.start(onnx_model_name)
Fig 12: As especificações do nosso modelo ONNX
Fig 13: A estrutura do nosso modelo ONNX
Nosso modelo ONNX está atendendo às nossas expectativas, a forma de entrada e saída estão precisamente onde esperamos que estejam. Agora podemos prosseguir para construir um Expert Advisor com base no nosso modelo ONNX.
Implementação no MQL5
Agora podemos começar a construir nosso Expert Advisor, vamos começar primeiro integrando nosso modelo ONNX em nossa aplicação. Ao especificar o arquivo ONNX como um recurso, o arquivo ONNX será incluído no programa compilado com a extensão .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[];
Agora vamos importar a biblioteca de negociação.
//+-----------------------------------------------------------------+ //| Libraries we need | //+-----------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade;
Vamos definir as entradas que o usuário final pode controlar.
//+------------------------------------------------------------------+ //| 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?
Agora precisamos de algumas variáveis globais.
//+------------------------------------------------------------------+ //| 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.
Agora vamos definir funções auxiliares para tarefas que precisaremos executar repetidamente. Primeiro, vamos controlar nossos níveis de risco. Se o lucro/perda total exceder os níveis de risco definidos, fecharemos automaticamente a posição.
//+------------------------------------------------------------------+ //| 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"); } }
Como temos um sistema de IA integrado, vamos usá-lo para detectar reversões. Se nosso sistema prever que o preço se moverá contra nós, fecharemos a posição e alertaremos o usuário final de que uma reversão potencial foi detectada.
//+------------------------------------------------------------------+ //| 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"); } }
Agora precisamos de uma função para encontrar oportunidades de entrada. Consideraremos uma entrada válida apenas se a previsão do nosso modelo estiver alinhada com as mudanças nos níveis de preço em períodos de tempo mais altos.
//+------------------------------------------------------------------+ //| 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; } } }
Agora precisamos de uma função para obter uma previsão do nosso modelo. Para fazer isso, precisamos primeiro buscar os preços atuais de mercado e então transformá-los, subtraindo a média e dividindo pelo desvio padrão.
//+------------------------------------------------------------------+ //| 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); }
Como estamos analisando múltiplos símbolos, precisamos adicioná-los ao monitor de mercado.
//+------------------------------------------------------------------+ //| 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); }
Precisamos de uma função responsável por carregar nossos fatores de escalonamento, a média e o desvio padrão de cada coluna.
//+------------------------------------------------------------------+ //| 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; }
Finalmente, precisamos de uma função responsável por carregar nosso arquivo 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); } //+------------------------------------------------------------------+
Agora que definimos essas funções auxiliares, podemos começar a usá-las em nosso Expert Advisor. Primeiro, vamos definir o comportamento de nossa aplicação sempre que ela for carregada pela primeira vez. Vamos começar carregando nosso modelo ONNX, preparando os valores de escalonamento e, em seguida, buscaremos os dados de mercado.
//+------------------------------------------------------------------+ //| 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); }
Sempre que nosso programa for desativado, precisamos liberar os recursos que não estamos mais usando.
//+------------------------------------------------------------------+ //| 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(); }
Finalmente, sempre que o preço mudar, precisamos obter uma nova previsão do nosso modelo, obter os preços de mercado atualizados e, então, ou abrir uma nova posição ou gerenciar as posições que temos abertas no momento.
//+------------------------------------------------------------------+ //| 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(); } }
Juntando tudo isso, agora podemos observar nosso programa em ação.
Fig 17: Nosso Expert Advisor
Fig 14: As entradas para o nosso expert advisor
Fig 15: Nosso programa em ação
Conclusão
Neste artigo, demonstramos como você pode construir um Expert Advisor de múltiplos símbolos alimentado por IA. Embora tenhamos obtido níveis de erro mais baixos usando o OHLC comum, isso não significa necessariamente que o mesmo será verdade para todos os símbolos que você tem no seu terminal MetaTrader 5. Pode existir um conjunto de símbolos diferentes que produza um erro menor do que as cotações OHLC do USDZAR.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15570
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Incrível, mais um excelente passo a passo. Obrigado pela pasta de trabalho, agora temos um modelo para testar nossas próprias correlações. Muito agradecido