English Русский 中文 Español Deutsch 日本語
preview
Reimaginando Estratégias Clássicas (Parte VIII): Mercados de Câmbio e Metais Preciosos no USDCAD

Reimaginando Estratégias Clássicas (Parte VIII): Mercados de Câmbio e Metais Preciosos no USDCAD

MetaTrader 5Exemplos |
126 3
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Ao longo desta série de artigos, buscamos explorar o vasto campo de possíveis aplicações da IA em estratégias de negociação. Nosso objetivo é fornecer as informações necessárias para que você tome uma decisão informada antes de investir seu capital em uma estratégia baseada em IA. Esperamos que você consiga identificar uma estratégia que seja adequada ao seu nível particular de tolerância ao risco.


Visão Geral da Estratégia de Negociação

Na discussão de hoje, procuraremos descobrir relações entre o mercado de moedas e o de metais preciosos. Os metais preciosos constituem uma parte integral de qualquer economia moderna. Isso se deve ao seu amplo uso industrial, variando da eletrônica à área da saúde. As flutuações no preço dos metais preciosos geram inflação para os produtores de bens acabados, o que pode reduzir os níveis de produção doméstica ou, por outro lado, levar a uma queda na demanda dos países que exportam esses metais.

O ouro é, simultaneamente, um dos principais produtos minerais de exportação do Canadá e dos Estados Unidos. A resistência natural do ouro à corrosão mantém sua alta demanda tanto por desenvolvedores de equipamentos eletrônicos sensíveis quanto por joalheiros. O paládio, por outro lado, é altamente demandado por diversas indústrias, especialmente a indústria automotiva. Existem mais de 20 componentes em um carro que requerem conversores catalíticos, sendo os mais conhecidos aqueles embutidos nos canos de escape. Cada um desses conversores contém quantidades significativas de paládio. Canadá e Estados Unidos são ambos líderes na exportação de veículos. Portanto, os preços desses metais preciosos têm impacto na produção doméstica desses dois países.

No passado, a correlação entre o preço do ouro e o dólar era inversa. Sempre que o dólar apresentava um desempenho ruim, os investidores tendiam a fechar quaisquer posições apostando no dólar e proteger seu dinheiro investindo em ouro. Contudo, nos tempos atuais de afrouxamento quantitativo, essas correlações já não são tão claras.


Visão Geral da Metodologia

Gostaríamos que nosso computador aprendesse uma estratégia própria a partir dos preços desses três mercados. Naturalmente, tendemos a acreditar mais em estratégias que nos pareçam intuitivas do que naquelas que não fazem sentido imediato. No entanto, ao aprender uma estratégia de forma algorítmica, nosso computador pode ser capaz de descobrir relações que levaríamos uma vida inteira para identificar. Alternativamente, também pode expor limitações na precisão de nossa estratégia.

Nosso objetivo foi avaliar o quão confiáveis são os mercados de metais preciosos para prever os mercados cambiais. Tentamos prever a taxa de câmbio USDCAD usando 3 grupos de preditores:
  1. Cotações do par USDCAD
  2. Cotações de XAUUSD e XPDUSD
  3. Um conjunto combinado dos acima.

Buscamos todos os dados diretamente do terminal MetaTrader 5 usando um script personalizado escrito em MQL5. Exportamos os dados para o formato CSV e os processamos em Python. Além disso, observamos níveis significativos de correlação negativa: -0,5 entre XAUUSD e USDCAD, e -0,66 entre XPDUSD e USDCAD. Entretanto, apenas níveis moderados de correlação (0,37) foram observados diretamente entre os dois metais.

Tentamos visualizar os dados. Contudo, não conseguimos identificar relações discerníveis nos dados. Tentamos representar os dados em dimensões superiores, utilizando gráficos de dispersão 3D, mas sem sucesso. Os dados se mostraram difíceis de separar.

A partir daí, treinamos vários modelos para prever a taxa de câmbio do USDCAD usando os três grupos de preditores mencionados anteriormente; o modelo de melhor desempenho foi o Modelo Linear utilizando o primeiro grupo de preditores. Contudo, como o modelo linear não possui parâmetros ajustáveis, selecionamos o segundo melhor modelo, o Regressor Vetorial de Suporte Linear (LSVR), como nosso modelo de melhor desempenho.

Conseguimos ajustar os hiperparâmetros do modelo LSVR sem sobreajustar o conjunto de treinamento, evidenciado pelo fato de termos superado o desempenho do modelo padrão de LSVR em dados não vistos. Infelizmente, não conseguimos superar o Modelo Linear nos mesmos dados de validação. Utilizamos a média do RMSE calculado por validação cruzada em séries temporais com 5 divisões (sem embaralhamento aleatório) para realizar a seleção dos modelos tanto no treinamento quanto na validação.

Posteriormente, exportamos nosso modelo LSVR personalizado para o formato ONNX e construímos nosso próprio Expert Advisor com IA integrada utilizando MQL5.


Coleta de Dados

Incluímos um script prático para você utilizar e buscar dados do seu terminal MetaTrader 5. Basta anexar o script a qualquer gráfico que você deseje analisar e começar.

O script buscará a quantidade de barras que você especificar nas entradas e gravará os dados em formato CSV. Registrar o tempo é vital porque utilizaremos essa informação posteriormente ao unir nossos arquivos CSV em um único DataFrame no Python. Uniremos nossos dados apenas nos dias em que todos compartilhem registros em comum.

Observe que incluímos a propriedade: “#property script_show_inputs” — se você não incluir essa propriedade em seus scripts, não poderá ajustar as entradas do script.

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

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

Agora que nossos dados estão prontos, podemos começar a limpar os dados em Python.


Limpeza de Dados

Primeiramente, vamos importar as bibliotecas padrão que precisaremos.

#Import the libraries we need
import pandas as pd
import numpy as np

Esta é a versão das bibliotecas que estamos utilizando nesta demonstração.

#Display library versions
print(f"Pandas version {pd.__version__}")
print(f"Numpy version {np.__version__}")
Versão do Pandas 2.2.1
Versão do Numpy 1.26.4

Agora, vamos ler os dados CSV que buscamos.

#Read in the data we need
usdcad = pd.read_csv("\\home\\volatily\\.wine\\drive_c\\Program Files\\MetaTrader 5\\MQL5\\Files\\Market Data USDCAD.csv")
usdcad = usdcad[::-1]
xauusd = pd.read_csv("\\home\\volatily\\.wine\\drive_c\\Program Files\\MetaTrader 5\\MQL5\\Files\\Market Data XAUUSD.csv")
xauusd = xauusd[::-1]
xpdusd = pd.read_csv("\\home\\volatily\\.wine\\drive_c\\Program Files\\MetaTrader 5\\MQL5\\Files\\Market Data XPDUSD.csv")
xpdusd = xpdusd[::-1]

Utilize a coluna de tempo como índice.

#Set the time column as the index
usdcad.set_index("Time",inplace=True)
xauusd.set_index("Time",inplace=True)
xpdusd.set_index("Time",inplace=True)

Faça a junção (merge) dos dados.

#Let's merge the data
merged_data = usdcad.merge(xauusd,suffixes=('',' XAU'),left_index=True,right_index=True)
merged_data = merged_data.merge(xpdusd,suffixes=('',' XPD'),left_index=True,right_index=True)

Este é o visual dos nossos dados.

merged_data

Figura 1: Nosso quadro de dados mesclado

Vamos definir quão longe no futuro desejamos fazer a previsão.

#Define the forecast horizon
look_ahead = 20

Definimos os 3 grupos de preditores que desejamos testar.

#Define the predictors and target
ohlc_predictors = ['Open','High','Low','Close']
new_predictors = ['Open XAU','High XAU','Low XAU','Close XAU','Open XPD','High XPD','Low XPD','Close XPD']
predictors = ohlc_predictors + new_predictors

Rotulando os dados.

#Let's add labels to the data
merged_data["Target"] = merged_data["Close"].shift(-look_ahead)

Também vamos criar rótulos que serão úteis quando estivermos visualizando os dados. Esses rótulos irão resumir as mudanças em cada mercado.

#Let's also add labels to help us visualize the relationships
merged_data["Binary Target"] = np.nan
merged_data["XAU Target"] = np.nan
merged_data["XPD Target"] = np.nan

#Define the target values
#Changes in the USDCAD Exchange rate
merged_data.loc[merged_data["Close"] > merged_data["Target"],"Binary Target"] = 0
merged_data.loc[merged_data["Close"] < merged_data["Target"],"Binary Target"] = 1
#Changes in the price of Gold
merged_data.loc[merged_data["Close XAU"] > merged_data["Close XAU"].shift(-look_ahead),"XAU Target"] = 0
merged_data.loc[merged_data["Close XAU"] < merged_data["Close XAU"].shift(-look_ahead),"XAU Target"] = 1
#Changes in the price of Palladium
merged_data.loc[merged_data["Close XPD"] > merged_data["Close XPD"].shift(-look_ahead),"XPD Target"] = 0
merged_data.loc[merged_data["Close XPD"] < merged_data["Close XPD"].shift(-look_ahead),"XPD Target"] = 1

#Drop any NA values
merged_data.dropna(inplace=True)


Análise Exploratória de Dados

Para visualizar nossos dados, primeiramente importaremos as bibliotecas necessárias.

#Explorartory data analysis
import seaborn as sns
import matplotlib.pyplot as plt

Exibindo as versões das bibliotecas.

#Display library version
print(f"Seaborn version: {sns.__version__}")
Versão do Seaborn: 0.13.2

Primeiro, vamos resetar o índice do nosso DataFrame mesclado.

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

Agora, vamos criar um mapa de calor de correlação. Como podemos ver, há níveis significativamente fortes de correlação entre os dois metais preciosos e o par USDCAD. Isso está em linha com nossa análise fundamentalista sobre o papel desempenhado por esses metais no Produto Interno Bruto de ambos os países. Infelizmente, isso não resultou em melhor desempenho ao tentar prever a taxa de câmbio do USDCAD.
#Correlation heatmap
fig , ax = plt.subplots(figsize=(7,7))
sns.heatmap(merged_data.loc[:,predictors].corr(),annot=True,ax=ax)

Mapa de calor de correlação

Fig 2: Nosso mapa de calor de correlação

Criamos dois gráficos categóricos resumindo todas as instâncias em que o preço do Ouro ou do Paládio subiu, coluna 1, ou caiu, coluna 2. A partir daí, colorimos cada ponto para indicar se a taxa de câmbio do USDCAD subiu, indicado pelos pontos laranja, ou caiu, indicado pelos pontos azuis. Como pode-se ver, temos uma mistura de ambos os resultados em ambas as colunas. Possivelmente sugerindo que as mudanças na taxa de câmbio do USDCAD são independentes das mudanças nos metais preciosos que escolhemos.

#Let's create categorical plots
sns.catplot(data=merged_data,x="XAU Target",y="Close",hue="Binary Target")


Fig 3: Gráfico categórico do preço do Ouro e do preço de fechamento do USDCAD

#Let's create categorical plots
sns.catplot(data=merged_data,x="XPD Target",y="Close",hue="Binary Target")

Fig 4: Gráfico categórico do preço do Paládio versus preço do USDCAD

Subsequentemente, criamos gráficos de dispersão para visualizar a variância entre o preço de fechamento do mercado de Paládio e o mercado de USDCAD. Infelizmente, isso não produziu nenhuma relação discernível que pudéssemos aproveitar. Colorimos cada ponto utilizando o mesmo mapa de cores laranja e azul descrito acima.

#Let's visualize scatter plots
sns.scatterplot(data=merged_data,x="Close XPD",y="Close",hue="Binary Target")

Fig 5: Gráfico de dispersão de XPDUSD versus USDCAD

Nenhuma melhoria foi observada quando substituímos o preço do Ouro pelo Paládio no gráfico de dispersão.

#Let's visualize scatter plots
sns.scatterplot(data=merged_data,x="Close XAU",y="Close",hue="Binary Target")

Fig 6: Gráfico de dispersão de XAUUSD versus o fechamento do USDCAD

Pensamos em testar uma relação entre os dois metais. No entanto, a relação ainda não era evidente para nós. Criamos um gráfico de dispersão do preço do Ouro contra o preço do Paládio e usamos a variação do par USDCAD para colorir cada ponto, mas isso não trouxe melhorias.

#Let's visualize scatter plots
sns.scatterplot(data=merged_data,x="Close XPD",y="Close XAU",hue="Binary Target")

Fig 7: Gráfico de dispersão de XAUUSD contra XPDUSD

Às vezes, as relações podem estar ocultas porque não estamos observando variáveis suficientes ao mesmo tempo para perceber os efeitos. Criamos um gráfico de dispersão 3D, utilizando os preços de fechamento do Paládio, do Ouro e do USDCAD. Infelizmente, o gráfico gerado destacou o que parecem ser agrupamentos nos dados com baixos níveis de separação, basicamente reafirmando o que já sabíamos até agora.

#Visualizing 3D data
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111,projection='3d')
colors = ['blue' if movement == 0 else 'orange' for movement in merged_data.loc[0:1100,"Binary Target"]]
ax.scatter(merged_data.loc[0:1100,"Close"],merged_data.loc[0:1100,"Close XAU"],merged_data.loc[0:1100,"Close XPD"],c=colors)

#Set labels
ax.set_xlabel('USDCAD')
ax.set_ylabel('XAUUSD')
ax.set_zlabel('XPDUSD')


Fig 8: Visualização dos nossos dados de mercado em 3D



Modelagem de Dados

Vamos nos preparar para começar a modelar nossos dados. Primeiramente, importaremos as bibliotecas padrão.

#Modelling the data
import sklearn
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import RobustScaler

Exibindo as versões da biblioteca.

#Print library version
print(f"Sklearn version {sklearn.__version__}")

Versão do Sklearn 1.4.1.post1

Antes de treinarmos qualquer modelo, precisamos escalar e padronizar os dados.

#Scale the data
scaled_data = pd.DataFrame(RobustScaler().fit_transform(merged_data.loc[:,predictors]),columns=predictors)

Agora vamos dividir os dados em duas metades, uma para treinamento e otimização, e a outra para validação e detecção de sobreajuste (overfitting).

#Split the data
train_X,test_X,train_y,test_y = train_test_split(scaled_data,merged_data.loc[:,"Target"],shuffle=False,test_size=0.5)

Agora, importe os modelos.

#Preparing to model the data
from sklearn.model_selection import TimeSeriesSplit
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor , GradientBoostingRegressor , BaggingRegressor
from sklearn.svm import LinearSVR
from sklearn.neighbors import KNeighborsRegressor
from sklearn.neural_network import MLPRegressor
from sklearn.metrics import root_mean_squared_error

Criar o objeto de divisão de séries temporais.

#Create the time series split object
tscv = TimeSeriesSplit(gap=look_ahead,n_splits=5)

Armazene os modelos em uma lista para que possamos iterar sobre eles.

#Create a list of models
models = [
        LinearRegression(),
        RandomForestRegressor(),
        GradientBoostingRegressor(),
        BaggingRegressor(),
        LinearSVR(),
        KNeighborsRegressor(),
        MLPRegressor(hidden_layer_sizes=(100,10))
]

Crie um DataFrame para armazenar nossos níveis de acurácia.

#List of models
columns = [
        "Linear Regression",
        "Random Forest",
        "Gradient Boost",
        "Bagging",
        "Linear SVR",
        "K-Neighbors",
        "Neural Network"
]

#Create a dataframe to store our error metrics
ohlc_error = pd.DataFrame(columns=columns,index=np.arange(0,5))
new_error = pd.DataFrame(columns=columns,index=np.arange(0,5))
all_error = pd.DataFrame(columns=columns,index=np.arange(0,5))

Defina os preditores a serem usados.

#Setting the current predictors
current_predictors = predictors

Faça a validação cruzada de cada modelo utilizando os preditores definidos acima.

#Perform cross validation
for j in np.arange(0,len(models)):
        model = models[j]
        for i,(train,test) in enumerate(tscv.split(train_X)):
        model.fit(train_X.loc[train[0]:train[-1],current_predictors],train_y.loc[train[0]:train[-1]])
        all_error.iloc[i,j] = root_mean_squared_error(train_y.loc[test[0]:test[-1]],model.predict(train_X.loc[test[0]:test[-1],current_predictors]))

Vamos observar nossos níveis de erro utilizando dados OHLC comuns. A partir de nossas visualizações e estatísticas resumidas, fica claro que o modelo de Regressão Linear teve o melhor desempenho nesta tarefa.

ohlc_error

Fig 9: Nossos níveis de erro ao prever com dados OHLC do USDCAD

Plotando os dados.

ohlc_error.plot()

Fig 10: Acurácia dos nossos modelos utilizando o primeiro conjunto de preditores

Criando gráficos de caixa (box-plots).

fig = plt.figure(figsize=(5,5))
plt.boxplot(ohlc_error)


Fig 11: Acurácia dos nossos modelos ao prever com o primeiro grupo de preditores

Vamos observar nossos níveis de erro ao utilizar os metais preciosos para prever a taxa de câmbio do USDCAD. A regressão linear ainda é o modelo com melhor desempenho, porém, já não com ampla vantagem.

new_error

Fig 12: Nossos novos níveis de erro

Plotando os novos níveis de erro.

new_error.plot()

Fig 13: Nossos novos níveis de erro


Criando um box-plot dos novos resultados.

fig = plt.figure(figsize=(5,5))
plt.boxplot(new_error)

Fig 14: Nossos níveis de erro ao usar dados do mercado de metais preciosos

Agora, vamos considerar nosso desempenho ao utilizar todos os dados disponíveis. Observamos que o modelo linear ainda tem desempenho superior a todos os demais modelos. Vamos tentar otimizar o segundo melhor modelo, o LinearSVR, para superar a Regressão Linear.

all_error


Fig 15: Nossa acurácia ao usar todos os dados disponíveis

Plotando os níveis de erro.

all_error.plot()

Fig 16: Gráficos de linhas da nossa acurácia usando todos os dados disponíveis

Criando um box-plot dos níveis de erro obtidos ao usar todos os dados disponíveis mostra que a regressão linear simples ainda é nossa melhor escolha.

fig = plt.figure(figsize=(5,5))
plt.boxplot(all_error)

Fig 17: Box-plots da nossa acurácia ao utilizar todos os dados disponíveis

Os níveis médios de erro dos nossos modelos ao longo dos 5 conjuntos de validação mostram claramente que o modelo linear é nossa melhor escolha até agora. Contudo, o LinearSVR não está muito atrás.

all_error.mean()

Regressão Linear 0.000523
Random Forest      0.001333
Gradient Boost       0.001227
Bagging                0.001343
Linear SVR             0.000653
K-Neighbors            0.001837
Rede Neural          0.114188
dtype: object


Importância das Variáveis

Antes de começarmos a otimizar nosso modelo, vamos primeiro avaliar quais variáveis parecem importantes. Esperamos que os dados relacionados ao mercado de metais preciosos mostrem seu valor neste teste. Vamos começar testando os níveis de informação mútua (MI). MI é uma medida de quanto conhecimento se ganha sobre o valor do alvo ao saber o valor de um dos preditores. MI está em escala logarítmica, portanto, um valor de MI acima de 2 é raro de se encontrar na prática.

Começamos importando as bibliotecas que precisamos.

#Mutual information score
from sklearn.feature_selection import mutual_info_regression

Agora, calculamos os valores de MI para cada um dos nossos preditores.

#Prepare the data for plotting
mi = mutual_info_regression(train_X,train_y)
mi = mi.reshape(1,12)
mi_scores = pd.DataFrame(mi,columns=predictors)

A visualização dos valores de MI sugere que os dados do mercado USDCAD podem ser mais informativos do que todos os dados do mercado de metais preciosos. Isso é validado por nosso teste de validação cruzada, no qual observamos que o Modelo Linear usando apenas as cotações do mercado USDCAD produziu o menor erro.

#Prepare the data for plotting
mi = mutual_info_regression(train_X,train_y)
mi = mi.reshape(1,12)
mi_scores = pd.DataFrame(mi,columns=predictors)
#Plot the scores
mi_scores.plot.bar()


Fig 18: Valores de informação mútua entre nossos 3 conjuntos de dados

Em seguida, vamos calcular os valores SHAP. Os valores SHAP são explicadores de caixa-preta que nos ajudam a identificar a importância global das variáveis em nossos modelos de machine learning.

Importe a biblioteca SHAP.

#The Linear SVR appears to be performing second best
import shap

Inicialize o modelo LSVR.

#Initialize the model
model = LinearSVR()
model.fit(train_X,train_y)

Calcule os valores SHAP.

#Compute SHAP values
explainer = shap.Explainer(model,train_X)
explanations = explainer(train_X)

Mostre a importância global das variáveis.

#Plot SHAP values
shap.plots.violin(explanations)


Fig 19: Níveis de importância segundo SHAP

Nossas explicações via SHAP discordam da análise de Informação Mútua. Exploramos esse problema de discordância extensivamente em artigos anteriores — o melhor que podemos dizer é que avaliar a importância das variáveis pode ser desafiador e deve ser feito com cautela. Contudo, ambas as explicações sugerem que há informações úteis contidas nos mercados de metais preciosos.


Ajuste de Hiperparâmetros

Ajustar nosso modelo pode nos permitir um desempenho melhor em dados não vistos do que ao utilizar o modelo com parâmetros padrão.

Para ajustar nosso modelo, primeiro vamos importar as bibliotecas necessárias.
#Parameter tuning
from sklearn.model_selection import RandomizedSearchCV

Agora inicialize o modelo.

#Reinitialize the model
model = LinearSVR()

Em seguida, definimos nossos parâmetros de ajuste. Passamos o modelo e um dicionário com os valores possíveis de parâmetros, seguido pelo número total de iterações que desejamos executar. Queremos realizar validação cruzada com 5 divisões para medir o erro quadrático médio negativo. Isso significa que selecionaremos o modelo com o menor erro de validação. Por fim, ao configurar n_jobs = -1, permitimos que a busca ocorra em paralelo em todos os núcleos disponíveis do processador.

#Define the tuner
tuner = RandomizedSearchCV(
        model,
        {
        "epsilon" : [0,10,100,1000],
        "tol":[0.01,0.001,0.0001,0.00001,0.0000001],
        "C":[1,10,100,1000,10000],
        "loss":['epsilon_insensitive', 'squared_epsilon_insensitive']  
        },
        n_iter=1000,
        cv=5,
        n_jobs=-1,
        scoring="neg_mean_squared_error"
)

Treine o otimizador (tuner).

#Let's fit the tuner
tuner_results = tuner.fit(train_X,train_y)

Os melhores parâmetros que encontramos.

#Let's see the best parameters we found
tuner_results.best_params_

{'tol': 1e-05, 'loss': 'squared_epsilon_insensitive', 'epsilon': 0, 'C': 10000}


Testando para Overfitting

Para testar o sobreajuste, primeiro precisamos inicializar nossos modelos.

#Testing for overfitting
benchmark =  LinearRegression()
default_lsvr = LinearSVR()
customized_lsvr = LinearSVR(tol=1e-05,loss='squared_epsilon_insensitive',epsilon=0,C=10000)

Agora, reinicialize os índices para que possamos executar a validação cruzada.

#Reset the indexes
test_y = test_y.reset_index()
test_X = test_X.reset_index()

Formate os dados.

#Format the data
test_y = test_y.loc[:,"Target"]
test_X = test_X.loc[:,predictors]

Criando um data frame para armazenar nossos níveis de erro.

#Create dataframes to store our error levels
test_error = pd.DataFrame(columns=["Linear Regression","LSVR","Customized LSVR"],index=[0,1,2,3,4])

Treine os modelos no conjunto de treinamento.

#Fit the models on the training set
benchmark.fit(train_X,train_y)
default_lsvr.fit(train_X,train_y)
customized_lsvr.fit(train_X,train_y)

Armazene os modelos em uma lista.

models = [benchmark,default_lsvr,customized_lsvr]

Validação cruzada de cada modelo no conjunto de teste.

for j in np.arange(0,len(models)):
        model = models[j]
        for i,(train,test) in enumerate(tscv.split(test_X)):
        model.fit(test_X.loc[train[0]:train[-1],:],test_y.loc[train[0]:train[-1]])
        test_error.iloc[i,j] = root_mean_squared_error(test_y.loc[test[0]:test[-1]],model.predict(test_X.loc[test[0]:test[-1],:]))

Nosso erro de teste.

test_error
Regressão Linear
 LSVR    
LSVR Personalizado     
0.000598
0.000542 0.000743
0.000472
0.000573
0.000722
0.000318
0.000451
0.000333
0.000341
0.000366 0.000499
0.00043
0.000839
0.00043

De acordo com nosso desempenho médio em todas as 5 dobras, nosso modelo linear ainda foi o modelo de melhor desempenho. No entanto, conseguimos superar o modelo padrão.

#Let's calculate our mean performances
test_error.mean()

Regressão Linear 0.000432
LSVR                          0.000554
LSVR Personalizado 0.000545
dtype: object

Visualizando nosso erro de teste.

#Let's visualize our error
test_error.plot()

Fig 20: Visualizando nosso erro de teste

Criando um box-plot das taxas de erro de teste.

#Create a boxplot of the error
sns.boxplot(data=test_error)

Fig 21: Visualizando nosso erro de teste


Preparando o Modelo para Exportação em ONNX

Antes de podermos exportar nosso modelo para o formato ONNX, precisamos primeiro escalar e padronizar os dados, subtraindo a média e dividindo pelo desvio padrão. A partir disso, gravaremos nossos fatores de escala em um arquivo CSV para podermos reproduzir o procedimento no MQL5.

Primeiro, crie o DataFrame para armazenar nossos fatores de escala.

#Let's scale our data
scaling_factors = pd.DataFrame(columns=predictors,index=['mean','standard deviation'])
X = merged_data.loc[:,predictors]
y = merged_data.loc[:,"Target"]

Depois armazene a média e o desvio padrão e, por fim, realize o procedimento de escalonamento.

#Let's fill each column
for i in np.arange(0,len(predictors)):
        scaling_factors.iloc[0,i] = X.iloc[:,i].mean()
        scaling_factors.iloc[1,i] = X.iloc[:,i].std()
        X.iloc[:,i] = ( ( X.iloc[:,i] - scaling_factors.iloc[0,i] ) / scaling_factors.iloc[1,i])

Salve os fatores de escala.

#Save the scaling factors as a CSV
scaling_factors.to_csv("/home/volatily/.wine/drive_c/Program Files/MetaTrader 5/MQL5/Files/usd_cad_xau_xpd_scaling_factors.csv")


Exportando o Modelo para ONNX

ONNX significa Open Neural Network Exchange (ONNX) e é um framework de machine learning de código aberto e interoperável que permite aos desenvolvedores criar, compartilhar e implementar modelos de aprendizado de máquina de forma independente da linguagem. Isso é possível ao representar cada modelo de machine learning como uma árvore de nós e grafos que pode ser remontada ao modelo original por qualquer linguagem que suporte a API ONNX.

Primeiro, vamos importar as bibliotecas necessárias.

#Let's prepare to export our model to ONNX format
import onnx
import netron
import skl2onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Exibindo as versões das bibliotecas.

#Display library versions
print(f"Onnx version: {onnx.__version__}")
print(f"Netron version: {netron.__version__}")
print(f"Skl2onnx version: {skl2onnx.__version__}")

Versão do Onnx: 1.15.0
Versão do Netron: 7.8.0
Versão do Skl2onnx: 1.16.0

Agora, vamos definir os tipos de entrada do nosso modelo.

#Define the input type
initial_types = [('float_input',FloatTensorType([1,12]))]

Vamos treinar o modelo com todos os dados que temos.

#Train the model on all the data we have
customized_lsvr = LinearSVR(tol=1e-05,loss='squared_epsilon_insensitive',epsilon=0,C=10000)
customized_lsvr.fit(X,y)

Converter o modelo para o formato ONNX.

#Covert the sklearn model
onnx_model = convert_sklearn(customized_lsvr,initial_types=initial_types)

Salvar o modelo ONNX.

#Save the onnx model
onnx_name = "USDCAD XAUUSD XPDUSD M1 Float.onnx"
onnx.save(onnx_model,onnx_name)

Visualizar o modelo ONNX.

#View the onnx model
netron.start(onnx_name)

Nosso modelo ONNX visualizado no Netron

Fig 22: Visualizando nosso modelo ONNX no Netron


Nossos parâmetros do modelo ONNX no Netron.

Fig 23: Os parâmetros do nosso modelo ONNX estão de acordo com nossas expectativas para entrada e saída do modelo



Implementação no MQL5

Para implementarmos um Expert Advisor com IA integrada em MQL5, precisamos primeiro carregar o modelo ONNX que exportamos anteriormente.

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

//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#resource  "\\Files\\USDCAD XAUUSD XPDUSD M1 Float.onnx" as const uchar onnx_buffer[];

Em seguida, devemos carregar a biblioteca de negociação para que possamos abrir e fechar posições.

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

Também devemos definir algumas variáveis globais que utilizaremos ao longo do programa.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;
double mean_values[12],std_values[12];
vectorf model_output = vectorf::Zeros(1);
int model_forecast,state;
double ask,bid;

Vamos definir funções auxiliares que utilizaremos em nosso programa. Precisamos de uma função responsável por carregar nosso modelo ONNX e definir as formas de entrada e saída. Faremos isso usando uma função que retorna true se for bem-sucedida, e false caso contrário. Nossa função primeiro cria o modelo a partir do buffer criado anteriormente, e então tenta definir e validar os formatos de entrada e saída.

//+------------------------------------------------------------------+
//| This function is responsible for loading our ONNX model          |
//+------------------------------------------------------------------+
bool load_onnx(void)
  {
//--- First we must create the model from the buffer
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);

//--- Now we shall define our I/O shape
   ulong input_shape [] = {1,12};
   ulong output_shape []  = {1,1};

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
        {
        Comment("Failed to set ONNX input shape!");
        return(false);
        }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
        {
        Comment("Failed to set ONNX output shape!");
        return(false);
        }

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

Esta função é responsável por ler o arquivo CSV com nossos valores de escalonamento e armazená-los nos arrays de escopo global que definimos.

//+------------------------------------------------------------------+
//| This function will read our scaling factors and store them       |
//+------------------------------------------------------------------+
bool load_scaling_factors(void)
  {
//--- Read in the file
   string file_name = "usd_cad_xau_xpd_scaling_factors.csv";

//--- Try open the file
   int result = FileOpen(file_name,FILE_READ|FILE_CSV|FILE_ANSI,","); //Strings of ANSI type (one byte symbols).

//--- Check the result
   if(result != INVALID_HANDLE)
        {
        Print("Opened the file");
        //--- Store the values of the file

        int counter = 0;
        string value = "";

        while(!FileIsEnding(result) && !IsStopped()) //read the entire csv file to the end
        {

        if(counter > 100)  //if you aim to read 10 values set a break point after 10 elements have been read
                break; //stop the reading progress

        value = FileReadString(result);
        Print("Trying to read string: ",value," count value: ",counter);

        //--- Check where we are
        if((counter >= 14) && (counter < 26))
        {
                mean_values[counter - 14] = (float) value;
        }
        //--- Check where we are
        if((counter >= 27) && (counter < 39))
        {
                std_values[counter - 27] = (float) value;
        }
        //--- Reading a new row
        if(FileIsLineEnding(result))
        {
                Print("row++");
        }

        counter++;
        }
        //---Close the file
        ArrayPrint(mean_values);
        ArrayPrint(std_values);
        FileClose(result);
        return(true);
        }
//--- We failed to find the file
   else
        {
        Comment("Failed to find the file containing scaling factors");
        return(false);
        }
  }

Também precisamos de uma função responsável por buscar as cotações de preços atualizadas do mercado.

//+------------------------------------------------------------------+
//| Update market prices                                             |
//+------------------------------------------------------------------+
void update_market_prices(void)
  {
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
  }

Por fim, precisamos de uma função para obter previsões do nosso modelo. Nossa função predict escalará os dados antes de passá-los ao modelo.

//+------------------------------------------------------------------+
//| Model predict                                                    |
//+------------------------------------------------------------------+
void model_predict(void)
  {
//--- First fetch the market data

   vectorf model_input  =
        {
        iOpen("USDCAD",PERIOD_CURRENT,0),iHigh("USDCAD",PERIOD_CURRENT,0),iLow("USDCAD",PERIOD_CURRENT,0),iClose("USDCAD",PERIOD_CURRENT,0),
        iOpen("XAUUSD",PERIOD_CURRENT,0),iHigh("XAUUSD",PERIOD_CURRENT,0),iLow("XAUUSD",PERIOD_CURRENT,0),iClose("XAUUSD",PERIOD_CURRENT,0),
        iOpen("XPDUSD",PERIOD_CURRENT,0),iHigh("XPDUSD",PERIOD_CURRENT,0),iLow("XPDUSD",PERIOD_CURRENT,0),iClose("XPDUSD",PERIOD_CURRENT,0)
        };

//--- Now standardize and scale the data
   for(int i =0; i < 12; i++)
        {
        model_input[i] = ((model_input[i] - mean_values[i]) / std_values[i]);
        }

//--- Now fetch a prediction from our model
   OnnxRun(onnx_model,ONNX_DEFAULT,model_input,model_output);

//--- Store our model's forecat
   if(model_output[0] > iClose("USDCAD",PERIOD_CURRENT,0))
        {
        model_forecast = 1;
        }
   else
        if(model_output[0] < iClose("USDCAD",PERIOD_CURRENT,0))
        {
        model_forecast = -1;
        }
  }

Ao inicializar nosso Expert Advisor, primeiro carregaremos o arquivo ONNX e depois leremos os valores de escalonamento. Se qualquer um desses procedimentos falhar, abortaremos todo o processo.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- This function will load our ONNX model
   if(!load_onnx())
        {
        return(INIT_FAILED);
        }

//--- This function will load our scaling factors
   if(!load_scaling_factors())
        {
        return(INIT_FAILED);
        }
//---
   return(INIT_SUCCEEDED);
  }

Sempre que nosso Expert Advisor for removido do gráfico, devemos liberar os recursos que não são mais necessários.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Free up the resources we do not need
   OnnxRelease(onnx_model);
   ExpertRemove();
  }

Finalmente, sempre que recebermos alterações nos preços de bid e ask, armazenaremos os preços atualizados na memória e obteremos uma nova previsão do nosso modelo de IA. Se não tivermos nenhuma posição aberta, abriremos a posição sugerida pelo modelo. Caso já exista uma posição aberta, verificaremos se a nova previsão do modelo não está indo contra nossa posição atual.

void OnTick()
  {
//--- Update the market prices
   update_market_prices();

//--- Fetch a forecast from our model
   model_predict();

//--- Find a trading oppurtunity
   if(PositionsTotal() == 0)
        {
        if(model_forecast == -1)
        {
        Trade.Sell(0.2,_Symbol,ask,0,0,"USDCAD AI");
        state = -1;
        }

        else
        if(model_forecast == 1)
        {
                Trade.Buy(0.2,_Symbol,ask,0,0,"USDCAD AI");
                state = 1;
        }
        }

//--- Check for reversals
   if(PositionsTotal() > 0)
        {
        if(state != model_forecast)
        {
        Alert("Reversal detected by our AI system, closing all positions now!");
        Trade.PositionClose(_Symbol);
        }
        }

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

Nosso sistema em ação

Fig 24: Nosso Expert Advisor em ação.

Nosso sistema em ação

Fig 25: Nosso sistema de IA detectou uma possível reversão

Conclusão

No artigo de hoje, demonstramos como você pode descobrir relações ocultas que podem existir entre mercados interrelacionados. No entanto, vale ressaltar que nossa análise empírica mostra que podemos obter melhores resultados utilizando apenas dados de mercado comuns e modelos lineares mais simples. Isso pode ser verdade porque, como todos sabemos, os dados de mercado podem ser ruidosos. E em casos de dados ruidosos, modelos mais simples tendem a superar modelos mais complexos, pois os modelos complexos são mais sensíveis às variações nos dados de entrada.


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15762

Últimos Comentários | Ir para discussão (3)
linfo2
linfo2 | 5 set. 2024 em 21:17
Mais uma vez, obrigado Gamuchirai, outro modelo excelentemente documentado. É muito útil ter exemplos práticos sobre como determinar a importância dos recursos, a correlação e visualizar o resultado usando os diferentes modelos de dados. À medida que lemos, aprendemos sobre Aprendizado de Máquina, obrigado. A lição de hoje para mim foi sobre os valores "SHAP". Mais uma vez, obrigado pelo trabalho, estou sempre me surpreendendo com as ferramentas que o Python tem à disposição
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 6 set. 2024 em 13:26
linfo2 #:
Mais uma vez, obrigado Gamuchirai, outro modelo excelentemente documentado. É muito útil ter exemplos práticos sobre como determinar a importância dos recursos, a correlação e visualizar o resultado usando os diferentes modelos de dados. À medida que lemos, aprendemos sobre Aprendizado de Máquina, obrigado. A lição de hoje para mim foi sobre os valores "SHAP". Mais uma vez, obrigado pelo trabalho, estou sempre me surpreendendo com as ferramentas que o Python tem à disposição
Obrigado, Neil.

É realmente incrível a diversidade do ecossistema Python, e o mais assustador é que estamos apenas começando a arranhar a superfície. Ainda há muito mais a ser explorado, é um momento empolgante para estar aqui.

Os valores de SHAP são realmente úteis e podem ser aplicados em quase todos os modelos, o que é, de fato, uma boa ferramenta.
Сергей Sergayustizanin
Сергей Sergayustizanin | 26 mar. 2025 em 13:56
Agora, neste momento, é possível ganhar ouro sem comprar grandes volumes desses cateteres.
Redes neurais e m trading: Aumento da eficiência do Transformer por meio da redução da nitidez (Conclusão) Redes neurais e m trading: Aumento da eficiência do Transformer por meio da redução da nitidez (Conclusão)
O SAMformer propõe uma solução para os principais problemas do Transformer na previsão de séries temporais de longo prazo, incluindo a complexidade do treinamento e a fraca capacidade de generalização em amostras pequenas. Sua arquitetura rasa e a otimização com consideração da nitidez garantem o desvio de mínimos locais ruins. Neste artigo, continuaremos a implementação das abordagens utilizando MQL5 e avaliaremos seu valor prático.
Técnicas do MQL5 Wizard que você precisa conhecer (Parte 36): Q-Learning com Cadeias de Markov Técnicas do MQL5 Wizard que você precisa conhecer (Parte 36): Q-Learning com Cadeias de Markov
Aprendizado por Reforço é um dos três pilares principais do aprendizado de máquina, ao lado do aprendizado supervisionado e do aprendizado não supervisionado. Portanto, ele está relacionado ao controle ótimo, ou seja, aprender a melhor política de longo prazo que melhor se adeque à função objetivo. É nesse contexto que exploramos seu possível papel no processo de aprendizado de uma MLP (rede neural de múltiplas camadas) de um Expert Advisor montado pelo assistente do MQL5 Wizard.
Do básico ao intermediário: Estruturas (VII) Do básico ao intermediário: Estruturas (VII)
Neste artigo, será mostrado como podemos lidar com problemas de forma a estruturar as coisas, a fim de criar uma solução mais fácil e atrativa. Apesar do conteúdo ser voltado para a didática, não representando assim um código real. Os conceitos e conhecimento vistos aqui, precisam de fato ser muito bem assimilados. Isto para que no futuro, você consiga acompanhar os códigos que iremos mostrar.
Formulando um EA Dinâmico de Múltiplos Pares (Parte 1): Correlação e Correlação Inversa entre Moedas Formulando um EA Dinâmico de Múltiplos Pares (Parte 1): Correlação e Correlação Inversa entre Moedas
O Expert Advisor dinâmico de múltiplos pares utiliza estratégias de correlação e correlação inversa para otimizar o desempenho nas negociações. Ao analisar dados de mercado em tempo real, ele identifica e explora as relações entre os pares de moedas.