English Русский 中文 Español Deutsch 日本語
preview
Engenharia de Features com Python e MQL5 (Parte I): Previsão de Médias Móveis para Modelos de IA de Longo Alcance

Engenharia de Features com Python e MQL5 (Parte I): Previsão de Médias Móveis para Modelos de IA de Longo Alcance

MetaTrader 5Exemplos |
47 4
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Ao aplicar IA a qualquer tarefa, devemos nos esforçar para fornecer ao modelo o máximo possível de informações úteis sobre o mundo real. Para descrever diferentes propriedades do mercado aos nossos modelos de IA, precisamos manipular e transformar os dados de entrada — esse processo é chamado de engenharia de features. Esta série de artigos ensinará como transformar seus dados de mercado para reduzir os níveis de erro dos seus modelos. Hoje, vou focar em como usar médias móveis para aumentar o alcance de previsão dos seus modelos de IA de uma forma que ofereça controle completo e uma compreensão razoável da efetividade global da estratégia.


Visão Geral da Estratégia

Na última vez que falamos sobre previsão de médias móveis com IA, apresentei evidências que sugeriam que os valores das médias móveis são mais fáceis para nossos modelos de IA preverem do que os níveis futuros de preço. O link para esse artigo está disponível aqui. No entanto, para termos confiança de que nossas descobertas são significativas, treinei 2 modelos de IA idênticos em mais de 200 símbolos de mercado diferentes e comparei a precisão da previsão de preços com a precisão da previsão da média móvel. Os resultados parecem mostrar que nossos níveis de precisão caem, em média, 34% quando prevemos preços em vez de médias móveis.

Em média, podemos esperar 70% de precisão ao prever as médias móveis, em comparação com uma expectativa de 52% ao prever preços. Todos sabemos que, dependendo do período, o indicador de média móvel não acompanha os níveis de preço muito de perto. Por exemplo, o preço pode cair por 20 velas enquanto as médias móveis sobem no mesmo intervalo. Essa divergência é indesejável, porque é possível prever corretamente a direção futura da média móvel, mas o preço pode divergir. Notavelmente, observamos que a taxa de divergência permanece fixa em torno de 31% em todos os mercados, e nossa capacidade de prever divergências teve média de 68%.

Além disso, a variância da nossa capacidade de prever divergências e da ocorrência de divergência foi de 0.000041 e 0.000386, respectivamente. Isso mostra que nosso modelo é capaz de se corrigir com um nível confiável de habilidade. Membros da comunidade que desejam aplicar IA em estratégias de trading de longo prazo devem considerar essa abordagem alternativa em timeframes mais altos. Nossa discussão está limitada ao M1 por enquanto, porque esse timeframe garante que teremos dados suficientes em todos os 297 mercados para fazermos comparações justas.

Existem muitas possíveis razões pelas quais as médias móveis são mais fáceis de prever do que o próprio preço. Isso pode ser verdade porque prever médias móveis está mais alinhado com a ideia de regressão linear do que prever preços. A regressão linear assume que os dados são uma combinação linear (soma) de várias entradas: as médias móveis são uma soma de valores passados de preço, o que torna a suposição linear verdadeira. Já o preço não é uma simples soma de variáveis do mundo real, mas sim uma relação complexa entre muitas variáveis.


Primeiros Passos

Primeiro, precisaremos importar nossas bibliotecas padrão para computação científica em Python.

#Load the libraries we need
import pandas as pd
import numpy as np
import MetaTrader5 as mt5
from sklearn.model_selection import TimeSeriesSplit,cross_val_score
from sklearn.linear_model import LogisticRegression,LinearRegression
import matplotlib.pyplot as plt

Vamos inicializar nosso terminal MetaTrader 5.

#Initialize the terminal
mt5.initialize()
Verdadeiro

Quantos símbolos temos disponíveis?

#The total number of symbols we have
print(f"Total Symbols Available: ",mt5.symbols_total())
Total de Símbolos Disponíveis: 297

Obter os nomes de todos os símbolos.

#Get the names of all pairs
symbols = mt5.symbols_get()
idx = [s.name for s in symbols]

Criar um data frame para armazenar nossos níveis de precisão em todos os símbolos.

global_params = pd.DataFrame(index=idx,columns=["OHLC Error","MAR Error","Noise Levels","Divergence Error"])
global_params

Fig 1: Nosso data frame que armazenará nossos níveis de precisão em todos os mercados do terminal

Definir nosso objeto de divisão de séries temporais.

#Define the time series split object
tscv = TimeSeriesSplit(n_splits=5,gap=10)

Medir nossa precisão em todos os símbolos.

#Iterate over all symbols
for i in np.arange(global_params.dropna().shape[0],len(idx)):
    #Fetch M1 Data
    data = pd.DataFrame(mt5.copy_rates_from_pos(cols[i],mt5.TIMEFRAME_M1,0,50000))
    data.rename(columns={"open":"Open","high":"High","low":"Low","close":"Close"},inplace=True)
    #Define our period
    period = 10
    #Add the classical target
    data.loc[data["Close"].shift(-period) > data["Close"],"OHLC Target"] = 1
    #Calculate the returns
    data.loc[:,["Open","High","Low","Close"]] = data.loc[:,["Open","High","Low","Close"]].diff(period)
    data["RMA"] = data["Close"].rolling(period).mean()
    #Calculate our new target
    data.dropna(inplace=True)
    data.reset_index(inplace=True,drop=True)
    data.loc[data["RMA"].shift(-period) > data["RMA"],"New Target"] = 1
    data = data.iloc[0:-period,:]
    #Calculate the divergence target
    data.loc[data["OHLC Target"] != data["New Target"],"Divergence Target"] = 1
    #Noise ratio
    global_params.iloc[i,2] = data.loc[data["New Target"] != data["OHLC Target"]].shape[0] / data.shape[0]
    #Test our accuracy predicting the future close price
    score = cross_val_score(LogisticRegression(),data.loc[:,["Open","High","Low","Close"]],data["OHLC Target"],cv=tscv)
    global_params.iloc[i,0] = score.mean()
    #Test our accuracy predicting the moving average of future returns
    score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["New Target"],cv=tscv)
    global_params.iloc[i,1] = score.mean()
    #Test our accuracy predicting the future divergence between price and its moving average
    score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["Divergence Target"],cv=tscv)
    global_params.iloc[i,3] = score.mean()
    print(f"{((i/len(idx)) * 100)}% complete")

#We are done
print("Done")
99.66329966329967% complete
Pronto!


Analisando os Resultados

Agora que buscamos nossos dados de mercado e avaliamos nosso modelo nos 2 alvos, vamos resumir os resultados do nosso teste em todos os mercados. Começaremos resumindo nossa precisão ao prever a variação do preço de fechamento futuro. A Fig 2 abaixo mostra o resumo da previsão da variação do preço de fechamento futuro. A linha horizontal vermelha representa o limite de 50% de precisão. Nossa precisão média usando esta técnica é denotada pela linha azul horizontal. Como podemos ver, nossa precisão média não está muito distante do limite de 50%, o que não é uma informação encorajadora.

No entanto, para sermos justos, também podemos observar que alguns mercados em particular estão bem acima da média, podendo ser previstos com mais de 65% de precisão. Isso é impressionante, mas também merece investigação adicional para determinar se os resultados são significativos ou se poderiam ter ocorrido por acaso.

global_params.iloc[:,0].plot()
plt.title("OHLC Accuracy")
plt.xlabel("Market")
plt.ylabel("5-fold Accuracy %")
plt.axhline(global_params.iloc[:,0].mean(),linestyle='--')
plt.axhline(0.5,linestyle='--',color='red')

Fig 2: Nossa precisão média prevendo preços

Agora vamos voltar nossa atenção para nossa precisão ao prever a variação das médias móveis. A Fig 3 abaixo resume os dados para nós. Novamente, a linha vermelha representa o limite de 50%, a linha dourada representa nossa precisão média ao prever mudanças nos preços e a linha azul é nossa precisão média ao prever mudanças nas médias móveis. Dizer simplesmente que nosso modelo é melhor em prever médias móveis é um eufemismo. Acho que não é mais um assunto para debate, mas simplesmente um fato: nossos modelos são melhores em prever certos indicadores do que em prever preços.

global_params.iloc[:,1].plot()
plt.title("Moving Average Returns Accuracy")
plt.xlabel("Market")
plt.ylabel("5-fold Accuracy %")
plt.axhline(global_params.iloc[:,1].mean(),linestyle='--')
plt.axhline(global_params.iloc[:,0].mean(),linestyle='--',color='orange')
plt.axhline(0.5,linestyle='--',color='red')

Fig 3: Nossa precisão prevendo a variação da média móvel

Agora vamos observar a taxa na qual o preço e a média móvel divergem. Níveis de divergência próximos de 50% são ruins, porque isso significa que não podemos ter certeza razoável se o preço e a média móvel vão se mover juntos ou em direções opostas. Felizmente, os níveis de ruído pareceram constantes em todos os mercados avaliados. Os níveis de ruído variaram entre 35% e 30%.

global_params.iloc[:,2].plot()
plt.title("Noise Level")
plt.xlabel("Market")
plt.ylabel("Percentage of Divergence:Price And Moving Average")
plt.axhline(global_params.iloc[:,2].mean(),linestyle='--')

Fig 4: Visualizando nossos níveis de ruído em todos os mercados

Se duas variáveis têm uma razão quase constante, isso pode ser um sinal de que existe uma relação que podemos modelar. Vamos observar quão bem podemos prever divergência entre preço e média móvel. Nosso raciocínio é simples: se nosso modelo prevê que a média móvel cairá, podemos prever de forma razoável se o preço seguirá na mesma direção ou se divergirá dela? Acontece que conseguimos prever divergência com um nível confiável de precisão, quase 70% em média.

global_params.iloc[:,3].plot()
plt.title("Divergence Accuracy")
plt.xlabel("Market")
plt.ylabel("5-fold Accuracy %")
plt.axhline(global_params.iloc[:,3].mean(),linestyle='--')

Fig 5: Nossa precisão prevendo divergência entre preço e média móvel

Também podemos resumir nossas descobertas em forma de tabela. Assim, podemos facilmente comparar nossos níveis de precisão entre os retornos do mercado e uma média móvel dos retornos. Note que, mesmo que nossa média móvel possa “atrasar” em relação ao preço, nossa precisão ao prever reversões ainda é significativamente maior do que ao prever o próprio preço.

Métrica
Precisão
Erro de Retornos
0.525353
Erro da Média Móvel dos Retornos
0.705468
Níveis de Ruído
0.317187
Erro de Divergência
0.682069

Vamos ver em quais mercados nosso modelo teve melhor desempenho.

global_params.sort_values("MAR Error",ascending=False)


Fig 6: Nossos mercados de melhor desempenho


Otimização para Nosso Melhor Mercado

Agora vamos projetar e ajustar nosso indicador de média móvel para um dos mercados em que nosso desempenho foi maior. Também faremos uma comparação visual entre nossa nova feature projetada e as features clássicas. Começaremos especificando o mercado selecionado.

symbol = "AUDJPY"

Certifique-se de que conseguimos acessar o terminal.

#Reach the terminal
mt5.initialize()
Verdadeiro

Agora, buscar os dados de mercado.

data = pd.DataFrame(mt5.copy_rates_from_pos(symbol,mt5.TIMEFRAME_D1,365*2,5000))

Importando as bibliotecas necessárias.

#Standard libraries
import seaborn                 as     sns
from   mpl_toolkits.mplot3d    import Axes3D
from   sklearn.linear_model    import LinearRegression
from   sklearn.neural_network  import MLPRegressor
from   sklearn.metrics         import mean_squared_error
from   sklearn.model_selection import cross_val_score,TimeSeriesSplit

Definir o ponto inicial e final para nosso cálculo de período e nosso horizonte de previsão. Certifique-se de que ambas as entradas sejam da mesma dimensão, caso contrário nosso código irá falhar.

#Define the input range
x_min , x_max = 2,100 #Look ahead
y_min , y_max = 2,100 #Period

Amostre nosso domínio de entrada em passos de 5 para que nossos cálculos sejam detalhados e pouco custosos de obter.

#Sample input range uniformly
x_axis = np.arange(x_min,x_max,2) #Look ahead
y_axis = np.arange(y_min,y_max,2) #Period

Crie uma mesh-grid usando nosso x_axis e y_axis. A mesh-grid é composta de 2 matrizes bidimensionais que definem todas as combinações possíveis de períodos e horizontes de previsão que desejamos avaliar.

#Create a meshgrid
x , y = np.meshgrid(x_axis,y_axis)

Em seguida, precisamos de uma função que buscará nossos dados de mercado e os rotulará para avaliação.

def clean_data(look_ahead,period):
    #Fetch the data from our terminal and clean it up 
    data = pd.DataFrame(mt5.copy_rates_from_pos('AUDJPY',mt5.TIMEFRAME_D1,365*2,5000))
    data['time'] = pd.to_datetime(data['time'],unit='s')
    data['MA'] = data['close'].rolling(period).mean()
    #Transform the data
    #Target
    data['Target'] = data['MA'].shift(-look_ahead) - data['MA']
    #Change in price
    data['close']  = data['close'] - data['close'].shift(period)
    #Change in MA
    data['MA']  = data['MA'] - data['MA'].shift(period)
    data.dropna(inplace=True)
    data.reset_index(drop=True,inplace=True)
    return(data)

A função a seguir executará uma validação cruzada de 5 folds em nosso modelo de IA.

#Evaluate the objective function
def evaluate(look_ahead,period):
    #Define the model
    model = LinearRegression()
    #Define our time series split
    tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead)
    temp = clean_data(look_ahead,period)
    score = np.mean(cross_val_score(model,temp.loc[:,["Open","High","Low","Close"]],temp["Target"],cv=tscv))
    return(score)

Finalmente, nossa função objetivo. Nossa função objetivo é simplesmente o erro de validação em 5 folds do nosso modelo sob as novas configurações que desejamos avaliar. Lembre-se: estamos tentando encontrar a distância ótima no futuro que nosso modelo deve prever e, adicionalmente, localizar o período com o qual devemos calcular as variações no preço.

#Define the objective
def objective(x,y):
    #Define the output matrix
    results = np.zeros([x.shape[0],y.shape[0]])
    #Fill in the output matrix
    for i in np.arange(0,x.shape[0]):
        #Select the rows
        look_ahead = x[i]
        period     = y[i]
        for j in np.arange(0,y.shape[0]):
            results[i,j] = evaluate(look_ahead[j],period[j])
    return(results)

Avaliaremos a relação do nosso modelo com o mercado ao tentarmos prever diretamente a variação nos níveis de preço. A Fig 7 mostra a relação entre nosso modelo e a variação do preço, enquanto a Fig 8 mostra a relação entre nosso modelo e a variação da média móvel. O ponto branco em ambos os gráficos simboliza a combinação de entradas que resultou no menor erro para nós. 

res = objective(x,y)
res = np.abs(res)

Plotando o melhor desempenho do nosso modelo ao prever o retorno diário do AUDJPY. Os dados revelam que, ao prevermos as variações futuras do preço, o melhor que conseguimos é prever 1 passo à frente. Traders humanos não olham apenas 1 passo à frente quando fazem suas operações. Portanto, os resultados que obtivemos ao prever retornos de mercado diretamente limitam nossa abordagem e tornam nossos modelos fixados na próxima vela.

plt.contourf(x,y,res,100,cmap="jet")
plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white')
plt.ylabel("Differencing Period")
plt.xlabel("Forecast Horizon")
plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Return")

Fig 7: Visualizando a capacidade do nosso modelo em prever níveis futuros de preço

Assim que começamos a prever a variação da média móvel em vez da variação do preço, podemos observar que nosso horizonte de previsão ótimo se desloca para a direita. A Fig 8 abaixo mostra que, ao prevermos a variação das médias móveis, podemos prever de forma confiável 22 passos no futuro, em comparação a apenas 1 passo no futuro ao prevermos a variação do preço.

plt.contourf(x,y,res,100,cmap="jet")
plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white')
plt.ylabel("Differencing Period")
plt.xlabel("Forecast Horizon")
plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Moving Average Return")


Fig 8: Visualizando a capacidade do nosso modelo em prever níveis futuros da média móvel

O que é ainda mais impressionante é que, nos pontos ótimos, nossos níveis de erro nos dois alvos foram idênticos. Em outras palavras, é tão fácil para nosso modelo prever a variação da média móvel 40 passos no futuro quanto prever a variação do preço 1 passo no futuro. Portanto, a previsão da média móvel nos dá maior alcance, sem aumentar o erro de nossas previsões.

Ao visualizarmos os resultados do nosso teste em 3D, a diferença entre os 2 alvos fica clara. A Fig 9 abaixo mostra a relação entre as variações nos níveis de preço e os parâmetros de previsão para nosso modelo. Há uma tendência clara nos dados: à medida que tentamos prever mais longe no futuro, nossos resultados pioram. Portanto, quando projetamos nossos modelos de IA dessa forma, eles ficam de certa forma “míopes” e não conseguem prever intervalos maiores que 20 de forma razoável.

A Fig 10 foi criada a partir da relação entre nosso modelo e seu erro ao prever as variações nas médias móveis. O gráfico de superfície mostra propriedades desejáveis. Podemos ver claramente que, à medida que prevemos mais longe no futuro e aumentamos o período de cálculo da variação da média móvel, nossas taxas de erro caem suavemente até um mínimo e depois começam a subir novamente. Essa demonstração visual mostra o quanto é mais fácil para nosso modelo prever a média móvel do que prever o preço.

#Create a surface plot
fig , ax = plt.subplots(subplot_kw={"projection":"3d"})
fig.set_size_inches(8,8)
ax.plot_surface(x,y,optimal_nn_res,cmap="jet")


Fig 9: Visualizando a relação entre nosso modelo e as variações no preço diário do AUDJPY.


Fig 10: A relação entre nosso modelo e as variações no valor da média móvel no par AUDJPY.


Transformações Não Lineares: Denoising com Wavelet

Até agora, aplicamos apenas transformações lineares aos nossos dados. Podemos explorar ainda mais e aplicar transformações não lineares aos dados de entrada do modelo. A engenharia de features é, às vezes, simplesmente um processo de tentativa e erro. Não temos garantia de obter melhores resultados. Portanto, aplicamos essas transformações de maneira ad-hoc — não temos uma fórmula precisa que nos mostre a “melhor” transformação a aplicar em determinado momento.

A transformação wavelet é uma ferramenta matemática usada para criar uma representação de frequência e tempo dos dados. Ela é comumente usada em tarefas de processamento de sinais e imagens para separar o ruído do sinal que estamos tentando processar. Depois que a transformação é aplicada, nossos dados estarão no domínio da frequência. A ideia é que o ruído em nossos dados venha dos pequenos valores de frequência identificados pela transformação. Todos os valores abaixo de um certo limite são reduzidos a 0 de duas maneiras possíveis. O resultado é uma representação esparsa dos dados originais.

O wavelet denoising tem várias vantagens sobre outras técnicas populares, como a Transformada Rápida de Fourier (FFT). Para leitores que possam não estar familiarizados, a Transformada de Fourier representa qualquer sinal como uma soma de ondas seno e cosseno. Infelizmente, a Transformada de Fourier filtra valores de alta frequência. Isso pode não ser sempre desejável, especialmente para dados onde o sinal está no domínio de alta frequência. Como não sabemos se nosso sinal está no domínio de alta ou baixa frequência, precisamos de uma transformação flexível o suficiente para realizar essa tarefa de forma não supervisionada. A transformada wavelet preservará o sinal nos dados, enquanto filtra o máximo possível do ruído.

Para acompanhar, certifique-se de que você tenha instalado o scikit-learn image e sua dependência PyWavelets. Para leitores que desejam criar um aplicativo completo de trading em MQL5, implementar e depurar a transformação do zero pode ser trabalhoso demais. É mais fácil para nós prosseguir sem ela. E para leitores que desejam interagir com o terminal usando a biblioteca Python, a transformação é uma ferramenta que vale a pena incluir em seu arsenal.

Podemos comparar a mudança na precisão de validação para ver se a transformação está ajudando nosso modelo — e está. Observe: aplicamos a transformação apenas nas entradas do modelo e não no alvo, que é preservado. Observe que nossa precisão de validação de fato caiu. Estamos usando a transformação wavelet com um limiar rígido (hard threshold), que define todos os coeficientes de ruído como 0. Alternativamente, poderíamos usar um limiar suave (soft threshold), que direcionaria os coeficientes de ruído para 0, mas sem necessariamente zerá-los completamente.

#Benchmark Score
np.mean(cross_val_score(LinearRegression(),data.loc[:,["MA"]],data["Target"]))
0.9935846835797412

#Wavelet denoising
data["Denoised"] = denoise_wavelet(
    data["MA"],
    method='BayesShrink',
    mode='hard',
    rescale_sigma=True,
    wavelet_levels = 3,
    wavelet='sym5'
    )
np.mean(cross_val_score(LinearRegression(),np.sqrt(np.log(data.loc[:,["Denoised"]])),data["Target"]))
0.9082244556297641


Construindo Modelos de IA Personalizados

Agora que sabemos os parâmetros ideais para até onde devemos prever no futuro, e nosso período ótimo da média móvel. Vamos buscar nossos dados de mercado diretamente do Terminal MetaTrader 5 para garantir que nossos modelos de IA estejam sendo treinados usando os mesmos valores de indicadores que eles observarão durante o trading real. Queremos emular a experiência do trading real o máximo possível.

Nosso período da média móvel, no script, corresponderá ao período ótimo que calculamos acima. Além disso, também buscaremos leituras do RSI em nosso terminal para estabilizar o comportamento do nosso robô de trading com IA. Ao depender das previsões feitas por 2 indicadores separados em vez de apenas 1, nosso modelo de IA pode se tornar mais estável ao longo do tempo.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| Script Inputs                                                    |
//+------------------------------------------------------------------+
input int size = 100000; //How much data should we fetch?

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,rsi_handler;
double ma_reading[],rsi_reading[];

//+------------------------------------------------------------------+
//| On start function                                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Load indicator
ma_handler  = iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_CLOSE);
rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE);
   
//--- Load the indicator values
CopyBuffer(ma_handler,0,0,size,ma_reading);
CopyBuffer(rsi_handler,0,0,size,rsi_reading);

ArraySetAsSeries(ma_reading,true);
ArraySetAsSeries(rsi_reading,true);

//--- File name
   string file_name = "Market Data " + Symbol() +" MA RSI " +  " As Series.csv";

//--- Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i= size;i>=0;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","Open","High","Low","Close","MA","RSI");
        }

      else
        {
         FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                   iOpen(Symbol(),PERIOD_CURRENT,i),
                   iHigh(Symbol(),PERIOD_CURRENT,i),
                   iLow(Symbol(),PERIOD_CURRENT,i),
                   iClose(Symbol(),PERIOD_CURRENT,i),
                   ma_reading[i],
                   rsi_reading[i]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+

Agora que criamos nosso script, podemos simplesmente arrastá-lo e soltá-lo no mercado desejado e, em seguida, começar a trabalhar com os dados de mercado. Para que nossos backtests sejam significativos, removemos os últimos 2 anos de dados de mercado do arquivo CSV que geramos. Dessa forma, quando testarmos nossa estratégia de 2023 a 2024, os resultados que observaremos serão um reflexo fiel do desempenho do nosso modelo em dados que ele nunca viu antes.

#Read in the data
data = pd.read_csv("Market Data AUDJPY MA RSI As Series.csv")
#Let's drop the last two years of data. We'll use that to validate our model in the back test
data = data.iloc[365:-(365 * 2),:]
data

Fig 11: Treinando nosso modelo com 22 anos de dados de mercado, excluindo o período de 2023-2024.

Agora vamos rotular nossos dados para machine learning. Queremos ajudar nosso modelo a aprender as variações de preço dadas as variações em nossos indicadores técnicos. Para ajudar o modelo a aprender essa relação, transformaremos nossas entradas para denotar o estado atual do indicador. Por exemplo, nosso indicador RSI terá 3 possíveis estados:

  1. Acima de 70.
  2. Abaixo de 30.
  3. Entre 70 e 30.
Se a leitura atual do RSI estiver acima de 70, a primeira coluna RSI 1 será definida como 1 e as colunas restantes serão definidas como 0. Faremos o mesmo para nossas médias móveis, mas elas terão apenas 2 estados, para indicar se estão subindo ou caindo. Ao aplicar essas transformações, estamos ajudando nosso modelo a focar nas mudanças críticas do indicador, como um trader humano faria.
#MA States
data["MA 1"] = 0
data["MA 2"] = 0
data.loc[data["MA"] > data["MA"].shift(40),"MA 1"] = 1
data.loc[data["MA"] <= data["MA"].shift(40),"MA 2"] = 1

#RSI States
data["RSI 1"] = 0
data["RSI 2"] = 0
data["RSI 3"] = 0
data.loc[data["RSI"] < 30,"RSI 1"] = 1
data.loc[data["RSI"] > 70,"RSI 2"] = 1
data.loc[(data["RSI"] >= 30) & (data["RSI"] <= 70),"RSI 3"] = 1

#Target
data["Target"]    = data["Close"].shift(-22) - data["Close"]
data["MA Target"] = data["MA"].shift(-22) - data["MA"]

#Clean up the data
data = data.dropna()
data = data.iloc[40:,:]
data = data.reset_index(drop=True)

Agora podemos começar a medir nossa precisão.

from sklearn.linear_model import Ridge
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

Ao aplicar essas transformações, conseguimos observar a variação média do preço à medida que o RSI passa pelas 3 zonas especificadas. Os coeficientes do nosso modelo linear podem ser interpretados como a variação média de preço associada a cada uma das 3 zonas do RSI. Essas descobertas às vezes podem ir contra os ensinamentos clássicos de como usar o indicador. Por exemplo, nosso modelo Ridge aprendeu que quando a leitura do RSI ultrapassa 70, os preços tendem a cair; caso contrário, quando o RSI está abaixo de 70, os níveis futuros de preço tendem a subir.

#Our model can suggest optimal ways of using the RSI indicator
#Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises
model = Ridge()

model.fit(data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"])

model.coef_
array([-0.19297857,  0.14816216,  0.04481641])

Nosso modelo Ridge consegue prever bem os preços futuros apenas considerando o estado atual do RSI.

#RSI state
np.mean(cross_val_score(Ridge(),data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"],cv=tscv))
-0.025569914370219736

Da mesma forma, nosso modelo também aprendeu suas próprias regras de trading a partir das variações nos indicadores de média móvel. O primeiro coeficiente do modelo é negativo, o que significa que, quando a média móvel sobe durante 40 velas, ela tende a cair em seguida. E o segundo coeficiente é positivo. Portanto, com base nos dados históricos que buscamos em nosso Terminal, quando a média móvel diária de 40 períodos do AUDJPY cai durante 40 velas, tende a subir logo depois. Nosso modelo aprendeu uma estratégia de reversão à média a partir dos dados.

#Our model can suggest optimal ways of using the RSI indicator
#Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises
model = Ridge()

model.fit(data.loc[:,["MA 1","MA 2"]] , data["Target"])

model.coef_
array([-0.15572796,  0.15572796])

Nosso modelo tem desempenho ainda melhor quando fornecemos a ele o estado atual do indicador de média móvel.

#MA state
np.mean(cross_val_score(Ridge(),data.loc[:,["MA 1","MA 2"]] , data["Target"],cv=tscv))
-0.009645886983465935


Convertendo para ONNX

Agora que encontramos os parâmetros ideais de entrada para nossa previsão de Média Móvel, vamos preparar a conversão do nosso modelo para o formato ONNX. Open Neural Network Exchange (ONNX) nos permite construir modelos de machine learning em uma estrutura independente de linguagem. O protocolo ONNX é uma iniciativa de código aberto para criar uma representação padrão universal para modelos de machine learning, permitindo que possamos construir e implantar modelos em qualquer linguagem que adote totalmente a API do ONNX.

Primeiro, vamos buscar os dados de acordo com as melhores entradas que encontramos.

#Fetch clean data
new_data = clean_data(140,130)

Importar as bibliotecas necessárias.

import onnx
from   skl2onnx import convert_sklearn
from   skl2onnx.common.data_types import FloatTensorType

Ajustar o modelo RSI em todos os dados disponíveis.

#First we will export the RSI model
rsi_model = Ridge()
rsi_model.fit(data.loc[:,['RSI 1','RSI 2','RSI 3']],data.loc[:,'Target'])

Ajustar o modelo da média móvel em todos os dados disponíveis.

#Finally we will export the MA model
ma_model = Ridge()
ma_model.fit(data.loc[:,['MA 1','MA 2']],data.loc[:,'MA Target'])

Definir o formato de entrada do nosso modelo e salvá-lo no disco.

initial_types = [('float_input', FloatTensorType([1, 3]))]
onnx.save(convert_sklearn(rsi_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 RSI AI F22 P40.onnx")

initial_types = [('float_input', FloatTensorType([1, 2]))]
onnx.save(convert_sklearn(ma_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 MA AI F22 P40.onnx")


Implementando em MQL5

Agora estamos prontos para começar a construir nossa aplicação de trading em MQL5. Queremos criar uma aplicação de trading capaz de abrir e fechar posições usando nossos novos insights sobre as médias móveis. Além disso, guiaremos nosso modelo usando indicadores mais lentos, que geram sinais de maneira menos agressiva. Vamos tentar emular a forma como traders humanos não estão sempre forçando uma posição no mercado.

Adicionalmente, também implementaremos trailing stop losses para garantir uma boa gestão de risco. Usaremos o indicador Average True Range (ATR) para definir dinamicamente nossos níveis de stop loss e take profit. Nossa estratégia é baseada principalmente em canais de média móvel.

Nossa estratégia prevê as médias móveis 40 passos à frente para nos dar confirmação antes de abrir qualquer posição. Realizamos um backtest dessa estratégia em 1 ano de dados históricos que não foram mostrados ao modelo durante o treinamento.

Primeiro, começaremos importando o arquivo ONNX que acabamos de criar.

//+------------------------------------------------------------------+
//|                                                    GBPUSD AI.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Load our resources                                               |
//+------------------------------------------------------------------+
#resource  "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[];
#resource  "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[];

Vamos importar as bibliotecas necessárias.

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;
#include <Trade\OrderInfo.mqh>
class COrderInfo;

Agora definiremos as variáveis globais necessárias.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long     onnx_model;
int      ma_handler,state;
double   bid,ask,vol;
vectorf  model_forecast   = vectorf::Zeros(1);
vectorf  rsi_model_output = vectorf::Zeros(1);
double   min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity;
double   take_profit = 0;
double   close_price[3],atr_reading[],ma_buffer[];
long     min_distance,login;
int      atr,close_average,ticket_1,ticket_2;
bool     authorized = false;
double   margin,lot_step;
string   currency,server;
bool     all_closed =true;
int      rsi_handler;
long     rsi_onnx_model;
double   indicator_reading[];
ENUM_ACCOUNT_TRADE_MODE account_type;
const double  stop_percent = 1;

Definamos nossas variáveis de entrada.

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
input group "Money Management"
input int    lot_multiple     = 10; // How big should the lot size be?
input double profit_target = 0;     // Profit Target
input double loss_target   = 0;     // Max Loss Allowed

input group "Money Management"
const int    atr_period = 200;      //ATR Period
input double atr_multiple =2.5;     //ATR Multiple

Agora devemos definir exatamente como nossa aplicação de trading deve ser inicializada. Primeiro, verificaremos se o usuário concedeu permissão para a aplicação operar. Se tivermos permissão para continuar, então carregaremos nossos indicadores técnicos e modelos ONNX.

int OnInit()
  {
//Authorization
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program");
      return(INIT_FAILED);
     }

   else
      if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
        {
         Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading");
         return(INIT_FAILED);
        }

      else
        {
         Comment("This License is Genuine");
         setup();
        }
//Everything was okay
   return(INIT_SUCCEEDED);
  }

Sempre que nossa aplicação de trading não estiver mais em uso, devemos liberar os recursos que não estamos mais utilizando, para garantir que o usuário final tenha uma boa experiência com a aplicação.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   OnnxRelease(onnx_model);
   OnnxRelease(rsi_onnx_model);
   IndicatorRelease(atr)
  }

Sempre que recebermos novas cotações de preços, atualizaremos nossas variáveis e verificaremos novas oportunidades de trading. Caso contrário, se já tivermos operações abertas, atualizaremos nosso trailing stop loss.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//Update technical data
   update();

   if(PositionsTotal() == 0)
     {
      check_setup();
     }

   if(PositionsTotal() > 0)
     {
      check_atr_stop();
     }
  }

Para obter uma previsão do nosso modelo, temos que definir os estados atuais do RSI e do indicador de média móvel.

//+------------------------------------------------------------------+
//| Get a prediction from our model                                  |
//+------------------------------------------------------------------+
int model_predict(void)
  {
//MA Forecast
   vectorf  model_inputs = vectorf::Zeros(2);
   vectorf  rsi_model_inputs = vectorf::Zeros(3);
   CopyBuffer(ma_handler,0,0,40,ma_buffer);

   if(ma_buffer[0] > ma_buffer[39])
     {
      model_inputs[0] = 1;
      model_inputs[1] = 0;
     }

   else
      if(ma_buffer[0] < ma_buffer[39])
        {
         model_inputs[1] = 1;
         model_inputs[0] = 0;
        }

//RSI Forecast
   CopyBuffer(rsi_handler,0,0,1,indicator_reading);

   if(indicator_reading[0] < 30)
     {
      rsi_model_inputs[0] = 1;
      rsi_model_inputs[1] = 0;
      rsi_model_inputs[2] = 0;
     }


   else
      if(indicator_reading[0] >70)
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 1;
         rsi_model_inputs[2] = 0;
        }

      else
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 0;
         rsi_model_inputs[2] = 1;
        }

//Model predictions
   OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast);
   OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output);


//Evaluate model output for buy setup
   if(((rsi_model_output[0] > 0)  && (model_forecast[0] > 0)))
     {
      //AI Models forecast
      Comment("AI Forecast: UP");
      return(1);
     }

//Evaluate model output for a sell setup
   if((rsi_model_output[0] < 0) && (model_forecast[0] < 0))
     {
      Comment("AI Forecast: DOWN");
      return(-1);
     }

//Otherwise no position was found
   return(0);
  }

Atualize nossas variáveis globais. É mais limpo realizar essas atualizações em uma única chamada de função, em vez de ter todo o código sendo executado diretamente no manipulador OnTick().

//+------------------------------------------------------------------+
//| Update our market data                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   buy_stop_loss = 0;
   sell_stop_loss = 0;
   static datetime time_stamp;
   datetime time = iTime(_Symbol,PERIOD_CURRENT,0);
   check_price(3);
   CopyBuffer(atr,0,0,1,atr_reading);
   CopyBuffer(ma_handler,0,0,1,ma_buffer);
   ArraySetAsSeries(atr_reading,true);
   atr_stop = ((min_volume + atr_reading[0]) * atr_multiple);
//On Every Candle
   if(time_stamp != time)
     {

      //Mark the candle
      time_stamp = time;
      OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin);
     }
  }

Carregue os recursos de que precisamos, como nossos indicadores técnicos, informações de conta, informações de mercado e outros dados dessa natureza.

//+------------------------------------------------------------------+
//| Load resources                                                   |
//+------------------------------------------------------------------+
bool setup(void)
  {
//Account Info
   currency = AccountInfoString(ACCOUNT_CURRENCY);
   server = AccountInfoString(ACCOUNT_SERVER);
   login = AccountInfoInteger(ACCOUNT_LOGIN);

//Indicators
   atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);

//Setup technical indicators
   ma_handler   =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW);
   vol          = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple;
   rsi_handler  = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE);

//Market Information
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);

//Define our ONNX model
   ulong ma_input_shape [] = {1,2};
   ulong rsi_input_shape [] = {1,3};
   ulong output_shape [] = {1,1};

//Create the model
   onnx_model     = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
   rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT);

   if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE))
     {
      Comment("[ERROR] Failed to load AI module correctly");
      return(false);
     }

//Validate I/O
   if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape)))
     {
      Comment("[ERROR] Failed to set input shape correctly: ",GetLastError());
      return(false);
     }

   if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape)))
     {
      Comment("[ERROR] Failed to load AI module correctly: ",GetLastError());
      return(false);
     }
//Everything went fine
   return(true);
  }

Reunindo tudo, é assim que nossa aplicação de trading se parece.

//+------------------------------------------------------------------+
//|                                                    GBPUSD AI.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Load our resources                                               |
//+------------------------------------------------------------------+
#resource  "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[];
#resource  "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[];

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;
#include <Trade\OrderInfo.mqh>
class COrderInfo;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long     onnx_model;
int      ma_handler,state;
double   bid,ask,vol;
vectorf  model_forecast   = vectorf::Zeros(1);
vectorf  rsi_model_output = vectorf::Zeros(1);
double   min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity;
double   take_profit = 0;
double   close_price[3],atr_reading[],ma_buffer[];
long     min_distance,login;
int      atr,close_average,ticket_1,ticket_2;
bool     authorized = false;
double   margin,lot_step;
string   currency,server;
bool     all_closed =true;
int      rsi_handler;
long     rsi_onnx_model;
double   indicator_reading[];
ENUM_ACCOUNT_TRADE_MODE account_type;
const double  stop_percent = 1;

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
input group "Money Management"
input int    lot_multiple     = 10; // How big should the lot size be?
input double profit_target = 0;     // Profit Target
input double loss_target   = 0;     // Max Loss Allowed

input group "Money Management"
input int    bb_period = 36;        //Bollinger band period
input int    ma_period = 4;         //Moving average period
const int    atr_period = 200;      //ATR Period
input double atr_multiple =2.5;      //ATR Multiple

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//Authorization
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program");
      return(INIT_FAILED);
     }

   else
      if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
        {
         Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading");
         return(INIT_FAILED);
        }

      else
        {
         Comment("This License is Genuine");
         setup();
        }
//--- Everything was okay
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   OnnxRelease(onnx_model);
   OnnxRelease(rsi_onnx_model);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update technical data
   update();

   if(PositionsTotal() == 0)
     {
      check_setup();
     }

   if(PositionsTotal() > 0)
     {
      check_atr_stop();
     }
  }
//+------------------------------------------------------------------+
//| Get a prediction from our model                                  |
//+------------------------------------------------------------------+
int model_predict(void)
  {
//MA Forecast
   vectorf  model_inputs = vectorf::Zeros(2);
   vectorf  rsi_model_inputs = vectorf::Zeros(3);
   CopyBuffer(ma_handler,0,0,40,ma_buffer);

   if(ma_buffer[0] > ma_buffer[39])
     {
      model_inputs[0] = 1;
      model_inputs[1] = 0;
     }

   else
      if(ma_buffer[0] < ma_buffer[39])
        {
         model_inputs[1] = 1;
         model_inputs[0] = 0;
        }

//RSI Forecast
   CopyBuffer(rsi_handler,0,0,1,indicator_reading);

   if(indicator_reading[0] < 30)
     {
      rsi_model_inputs[0] = 1;
      rsi_model_inputs[1] = 0;
      rsi_model_inputs[2] = 0;
     }


   else
      if(indicator_reading[0] >70)
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 1;
         rsi_model_inputs[2] = 0;
        }

      else
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 0;
         rsi_model_inputs[2] = 1;
        }

//Model predictions
   OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast);
   OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output);


//Evaluate model output for buy setup
   if(((rsi_model_output[0] > 0)  && (model_forecast[0] > 0)))
     {
      //AI Models forecast
      Comment("AI Forecast: UP");
      return(1);
     }

//Evaluate model output for a sell setup
   if((rsi_model_output[0] < 0) && (model_forecast[0] < 0))
     {
      Comment("AI Forecast: DOWN");
      return(-1);
     }

//Otherwise no position was found
   return(0);
  }

//+------------------------------------------------------------------+
//| Check for valid trade setups                                     |
//+------------------------------------------------------------------+
void check_setup(void)
  {
   int res = model_predict();

   if(res == -1)
     {
      Trade.Sell(vol,Symbol(),bid,0,0,"VD V75 AI");
      state = -1;
     }

   else
      if(res == 1)
        {
         Trade.Buy(vol,Symbol(),ask,0,0,"VD V75 AI");
         state = 1;
        }
  }

//+------------------------------------------------------------------+
//| Update our market data                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   buy_stop_loss = 0;
   sell_stop_loss = 0;
   static datetime time_stamp;
   datetime time = iTime(_Symbol,PERIOD_CURRENT,0);
   check_price(3);
   CopyBuffer(atr,0,0,1,atr_reading);
   CopyBuffer(ma_handler,0,0,1,ma_buffer);
   ArraySetAsSeries(atr_reading,true);
   atr_stop = ((min_volume + atr_reading[0]) * atr_multiple);
//On Every Candle
   if(time_stamp != time)
     {

      //Mark the candle
      time_stamp = time;
      OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin);
     }
  }

//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Load resources                                                   |
//+------------------------------------------------------------------+
bool setup(void)
  {
//Account Info
   currency = AccountInfoString(ACCOUNT_CURRENCY);
   server = AccountInfoString(ACCOUNT_SERVER);
   login = AccountInfoInteger(ACCOUNT_LOGIN);

//Indicators
   atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);

//Setup technical indicators
   ma_handler   =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW);
   vol          = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple;
   rsi_handler  = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE);

//Market Information
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);

//Define our ONNX model
   ulong ma_input_shape [] = {1,2};
   ulong rsi_input_shape [] = {1,3};
   ulong output_shape [] = {1,1};

//Create the model
   onnx_model     = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
   rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT);

   if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE))
     {
      Comment("[ERROR] Failed to load AI module correctly");
      return(false);
     }

//--- Validate I/O
   if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape)))
     {
      Comment("[ERROR] Failed to set input shape correctly: ",GetLastError());
      return(false);
     }

   if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape)))
     {
      Comment("[ERROR] Failed to load AI module correctly: ",GetLastError());
      return(false);
     }
//--- Everything went fine
   return(true);
  }

//+------------------------------------------------------------------+
//| Close all our open positions                                     |
//+------------------------------------------------------------------+
void close_all()
  {
   if(PositionsTotal() > 0)
     {
      ulong ticket;
      for(int i =0;i < PositionsTotal();i++)
        {
         ticket = PositionGetTicket(i);
         Trade.PositionClose(ticket);
        }
     }
  }

//+------------------------------------------------------------------+
//| Update our trailing ATR stop                                     |
//+------------------------------------------------------------------+
void check_atr_stop()
  {

   for(int i = PositionsTotal() -1; i >= 0; i--)
     {

      string symbol = PositionGetSymbol(i);
      if(_Symbol == symbol)
        {

         ulong ticket = PositionGetInteger(POSITION_TICKET);
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN);
         double type = PositionGetInteger(POSITION_TYPE);
         double current_stop_loss = PositionGetDouble(POSITION_SL);

         if(type == POSITION_TYPE_BUY)
           {
            double atr_stop_loss = (ask - (atr_stop));
            double atr_take_profit = (ask + (atr_stop));

            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
              }
           }

         else
            if(type == POSITION_TYPE_SELL)
              {
               double atr_stop_loss = (bid + (atr_stop));
               double atr_take_profit = (bid - (atr_stop));
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                 }
              }
        }
     }
  }

//+------------------------------------------------------------------+
//| Close our open buy positions                                     |
//+------------------------------------------------------------------+
void close_buy()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_BUY)
              {
               Trade.PositionClose(ticket);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Close our open sell positions                                    |
//+------------------------------------------------------------------+
void close_sell()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_SELL)
              {
               Trade.PositionClose(ticket);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Get the most recent price values                                 |
//+------------------------------------------------------------------+
void check_price(int candles)
  {
   for(int i = 0; i < candles;i++)
     {
      close_price[i] = iClose(_Symbol,PERIOD_CURRENT,i);
     }
  }
//+------------------------------------------------------------------+

Agora vamos fazer o backtest do nosso algoritmo de trading usando dados que não foram apresentados ao algoritmo durante o treinamento. O período selecionado vai do início de janeiro de 2023 até 28 de junho de 2024, com cotações diárias no par AUDJPY. Definiremos o parâmetro "Forward" como "No", porque garantimos que as datas selecionadas não foram observadas durante o treinamento do modelo.


Fig 12: O Símbolo e Time Frame que usaremos para avaliar nossa estratégia de trading.

Além disso, vamos simular condições reais de trading definindo primeiro o parâmetro "Delays" como "Random delay". Esse parâmetro controla a latência entre o momento em que nossas ordens são enviadas e quando são executadas. Defini-lo como aleatório é semelhante ao que ocorre no trading real: nossa latência não é constante o tempo todo. Além disso, instruiremos nosso Terminal a modelar o mercado usando real ticks. Essa configuração deixará nosso backtest um pouco mais lento porque o terminal precisará buscar dados detalhados de mercado de nosso corretor pela internet.

Os últimos parâmetros, que controlam o depósito da conta e a alavancagem, devem ser ajustados para se adequar à sua configuração de trading. Assumindo que buscamos com sucesso todos os dados solicitados, nosso backtest começará.

Fig 13: Os parâmetros que usaremos para nosso backtest.

Fig 14: O desempenho da nossa estratégia em dados nos quais o modelo não foi treinado.

Fig 12: Mais detalhes do nosso backtest em dados de mercado não vistos.


Conclusão

Os amplos dados de mercado que analisamos hoje mostram claramente que, se você deseja prever intervalos menores que 40 passos no futuro, provavelmente é melhor prever o preço diretamente. No entanto, se você deseja prever mais de 40 passos no futuro, é mais provável que seja melhor prever a variação da média móvel em vez da variação do preço. Sempre existem mais melhorias esperando para serem observadas e verificarmos a diferença que elas trazem. Podemos ver claramente que qualquer tempo gasto transformando as entradas dos dados vale a pena, pois nos permite expor os relacionamentos subjacentes aos nossos modelos de uma forma mais significativa.


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

Últimos Comentários | Ir para discussão (4)
Too Chee Ng
Too Chee Ng | 27 mar. 2025 em 10:43

O resultado mostrado parece promissor; vou experimentar.

Mais desse tipo, por favor.

Obrigado.

npats2007
npats2007 | 13 jun. 2025 em 15:03

Algumas das imagens não estão aparecendo...

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 10 jul. 2025 em 09:31
Too Chee Ng # :

Os resultados parecem promissores.

Por favor, mais coisas como essa.

Mais coisas como essa, por favor. Obrigado.

De nada, Too Che Ng.

Definitivamente, ainda há muito mais que pode ser dito, considerando um início tão forte.


Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 10 jul. 2025 em 09:33
npats2007 # :

Algumas imagens não são exibidas...

Sinto muito em ouvir isso. Tenho certeza de que os moderadores resolverão o problema, pois eles já têm muito o que fazer.

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Criação de uma estratégia de retorno à média com base em aprendizado de máquina Criação de uma estratégia de retorno à média com base em aprendizado de máquina
Neste artigo, é proposto um novo método para criar sistemas de trading baseados em aprendizado de máquina, utilizando clusterização e anotação de trades para estratégias de retorno à média.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Algoritmo de Otimização de Bilhar — Billiards Optimization Algorithm (BOA) Algoritmo de Otimização de Bilhar — Billiards Optimization Algorithm (BOA)
Inspirado no jogo clássico de bilhar, o método BOA modela o processo de busca por soluções ótimas como uma partida em que as bolas tentam cair nas caçapas, que simbolizam os melhores resultados. Neste artigo, analisaremos os fundamentos do funcionamento do BOA, seu modelo matemático e sua eficácia na resolução de diferentes problemas de otimização.