
Construindo Expert Advisors Auto-otimizantes Com MQL5 E Python (Parte II): Ajustando Redes Neurais Profundas
Introdução
Membros da nossa comunidade estão ansiosos para integrar IA em suas estratégias de trading, o que exige ajustar modelos de IA para mercados específicos. Cada modelo de IA possui parâmetros ajustáveis que influenciam significativamente seu desempenho; configurações ótimas para um mercado podem não funcionar para outro. Este artigo mostrará como personalizar modelos de IA para superar as configurações padrão utilizando algoritmos de otimização, especificamente o algoritmo Nelder-Mead. Aplicaremos esse algoritmo para afinar uma rede neural profunda usando dados do terminal MetaTrader5 e, em seguida, exportaremos o modelo otimizado no formato ONNX para uso em um Expert Advisor. Para aqueles que não estão familiarizados com esses conceitos, forneceremos explicações detalhadas ao longo do artigo.Algoritmo de Otimização Nelder-Mead
O algoritmo Nelder-Mead é uma escolha popular para problemas de otimização multimodais, não diferenciáveis e com ruído. Nomeado em homenagem aos seus inventores John Nelder e Roger Mead, o algoritmo foi introduzido no artigo de 1965 intitulado "A Simplex Method for Function Minimization". Ele pode ser usado tanto para problemas de otimização univariados quanto multivariados.
O algoritmo Nelder-Mead não depende de informações derivadas; em vez disso, é um algoritmo de busca de padrões para otimização. Ele exige que o usuário forneça um ponto de partida. Dependendo do ponto de partida escolhido, o algoritmo pode ficar preso em um ótimo local enganoso. Portanto, pode ser benéfico realizar a otimização várias vezes com diferentes pontos de partida para melhorar as chances de encontrar um ótimo global.
O algoritmo funciona usando uma forma geométrica chamada simplex. O simplex tem um vértice para cada variável de entrada, mais um vértice adicional. Os pontos (vértices) do simplex são avaliados, e regras simples são usadas para mover os pontos com base em suas avaliações. O algoritmo tem certas condições de parada, como atingir o número máximo de iterações ou alcançar uma mudança mínima nos valores de avaliação. Se nenhuma melhoria for feita ou se o número permitido de iterações for excedido, o procedimento de otimização para.
Fig 1: Roger Mead
Fig 2: John Nelder
Vamos começar então.
Começamos pegando os dados de que precisamos do nosso terminal MetaTrader 5. Primeiro, abrimos nosso Terminal MetaTrader 5 e clicamos no ícone do Símbolo no menu de contexto. A partir daí, selecionamos barras e procuramos pelo símbolo que você gostaria de usar. Uma vez que você tenha solicitado os dados, basta clicar em exportar e os dados estarão disponíveis para você no formato CSV.
Fig 3: Pesquise os dados que você precisa
Agora que nossos dados estão prontos, podemos começar importando as bibliotecas de que precisamos.
#import libraries we need import pandas as pd import numpy as np from numpy.random import randn,rand import seaborn as sns
Em seguida, lemos os dados que preparamos.
#Read in our market data brent = pd.read_csv("/home/volatily/market_data/Market Data UK Brent Oil.csv", sep="\t")
Precisamos rotular nossos dados.
#Preparing to label the data look_ahead = 20 #Defining the target brent["Target"] = brent["Close"].shift(-look_ahead) #Drop missing values brent.dropna(inplace=True)
Vamos importar as bibliotecas que precisamos para otimização.
#In this article we will cover some techniques for hyper-parameter tuning from scipy.optimize import minimize from sklearn.neural_network import MLPRegressor from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import root_mean_squared_error import time
Agora criaremos nosso objeto de validação cruzada de séries temporais.
#Define the time series split parameters splits = 5 gap = look_ahead #Create the time series split object tscv = TimeSeriesSplit(n_splits=splits,gap=gap) #Create a dataframe to store our accuracy current_error_rate = pd.DataFrame(index = np.arange(0,splits),columns=["Current Error"])
Vamos definir os preditores e alvos para o nosso modelo.
#Define the predictors and the target predictors = ["Open","High","Low","Close"] target = "Target"
Agora definimos a função que queremos minimizar: o erro da validação cruzada do modelo. Observe que isso é apenas para fins de demonstração. Idealmente, dividiríamos o conjunto de dados ao meio, realizando a otimização em uma metade e medindo a precisão na outra metade. No entanto, para esta demonstração, estamos otimizando o modelo e medindo sua precisão usando o mesmo conjunto de dados.
#Define the objective function def objective(x): #The parameter x represents a new value for our neural network's settings #In order to find optimal settings, we will perform 10 fold cross validation using the new setting #And return the average RMSE from all 10 tests #We will first turn the model's Alpha parameter, which controls the amount of L2 regularization model = MLPRegressor(hidden_layer_sizes=(5,2),alpha=x[0],early_stopping=True,shuffle=False,learning_rate_init=x[1],tol=x[2]) #Now we will cross validate the model for i,(train,test) in enumerate(tscv.split(brent)): #The data X_train = brent.loc[train[0]:train[-1],predictors] y_train = brent.loc[train[0]:train[-1],target] X_test = brent.loc[test[0]:test[-1],predictors] y_test = brent.loc[test[0]:test[-1],target] #Train the model model.fit(X_train,y_train) #Measure the RMSE current_error_rate.iloc[i,0] = root_mean_squared_error(y_test,model.predict(X_test)) #Return the Mean CV RMSE return(current_error_rate.iloc[:,0].mean())
Lembre-se de que o algoritmo Nelder-Mead requer um ponto de partida inicial. Para encontrar um bom ponto de partida, realizaremos uma busca em linha sobre os parâmetros em questão. Usaremos um loop for para medir nossa precisão com os parâmetros configurados para 0.1, depois 0.01 e assim por diante. Isso nos ajudará a identificar um ponto de partida potencialmente bom para o algoritmo.
#Let us measure how much time this takes. start = time.time() #Create a dataframe to measure the error rates starting_point_error = pd.DataFrame(index=np.arange(0,21),columns=["Average CV RMSE"]) starting_point_error["Iteration"] = np.arange(0,21) #Let us first find a good starting point for our optimization algorithm for i in np.arange(0,21): #Set a new starting point new_starting_point = (10.0 ** -i) #Store error rates starting_point_error.iloc[i,0] = objective([new_starting_point,new_starting_point,new_starting_point]) #Record the time stamp at the end stop = time.time() #Report the amount of time taken print(f"Completed in {stop - start} seconds")
Agora vamos observar nossos níveis de erro.
Média CV RMSE | Iteração |
---|---|
0.91546 | 0 |
0.267167 | 1 |
14.846035 | 2 |
15.763264 | 3 |
56.820397 | 4 |
75.202923 | 5 |
72.562681 | 6 |
64.33746 | 7 |
88.980977 | 8 |
83.791834 | 9 |
82.871215 | 10 |
88.031151 | 11 |
65.532539 | 12 |
78.177191 | 13 |
85.063947 | 14 |
88.631589 | 15 |
74.369735 | 16 |
86.133656 | 17 |
90.482654 | 18 |
102.803612 | 19 |
74.636781 | 20 |
Como podemos ver, parece que passamos por uma região ótima entre as iterações 0 e 2. A partir daí, nosso erro continuou aumentando. Podemos observar a mesma informação visualmente.
sns.lineplot(data=starting_point_error,x="Iteration",y="Average CV RMSE")
Fig 4: Visualizando os resultados da nossa busca em linha
Agora que temos uma ideia de qual pode ser um bom ponto de partida, vamos definir uma função para nos dar pontos aleatórios dentro do intervalo em que acreditamos que o ótimo possa estar.
pt = abs(((10 ** -1) + rand(3) * ((10 ** 0) - (10 ** -1)))) pt
Observe que estamos buscando um array de 3 valores aleatórios porque estamos otimizando 3 parâmetros diferentes na nossa rede neural. Agora, vamos realizar o ajuste dos hiperparâmetros.
start = time.time() result = minimize(objective,pt,method="nelder-mead") stop = time.time() print(f"Task completed in {stop - start} seconds")
Agora vamos interpretar o resultado da otimização.
Resultado:
success: False
status: 1
fun: 0.12022686955703668
x: [ 7.575e-01 3.577e-01 2.621e-01]
nit: 225
nfev: 600
final_simplex: (array([[ 7.575e-01, 3.577e-01, 2.621e-01],
[ 7.575e-01, 3.577e-01, 2.621e-01],
[ 7.575e-01, 3.577e-01, 2.621e-01],
[ 7.575e-01, 3.577e-01, 2.621e-01]]), array([ 1.202e-01, 2.393e-01, 2.625e-01, 8.978e-01])
Primeiro, observe a mensagem amigável exibida no topo. A mensagem indica que o algoritmo ultrapassou o número máximo de avaliações de função. Lembre-se das condições que especificamos anteriormente sobre os cenários que causariam a interrupção da otimização. Embora possamos tentar aumentar o número de iterações permitidas, isso não garante um desempenho melhor.
Podemos ver a chave 'fun', que indica a saída ótima que o algoritmo alcançou a partir da função. A seguir, temos a chave 'x', que mostra os valores de x que resultaram na saída ótima.
Também podemos observar a chave 'nit', que nos diz o número de iterações que a função executou. Por último, a chave 'nfev' indica o número de vezes que o algoritmo chamou a função objetivo para avaliar sua saída. Lembre-se de que nossa função objetivo realizou validação cruzada de 5 dobras e retornou a taxa média de erro. Isso significa que cada vez que chamamos a função uma vez, ajustamos nossa rede neural 5 vezes. Portanto, 600 avaliações de função significam que ajustamos nossa rede neural 3000 vezes!
Agora vamos comparar o modelo padrão e o modelo personalizado que construímos.
#Let us compare our customised model and the defualt model custom_model = MLPRegressor(hidden_layer_sizes=(5,2),alpha=result.x[0],early_stopping=True,shuffle=False,learning_rate_init=result.x[1],tol=result.x[2]) #Default model default_model = MLPRegressor(hidden_layer_sizes=(5,2))
Vamos preparar o objeto de divisão de séries temporais.
#Define the time series split parameters splits = 10 gap = look_ahead #Create the time series split object tscv = TimeSeriesSplit(n_splits=splits,gap=gap) #Create a dataframe to store our accuracy model_error_rate = pd.DataFrame(index = np.arange(0,splits),columns=["Default Model","Custom Model"])
Agora faremos a validação cruzada de cada modelo.
#Now we will cross validate the model for i,(train,test) in enumerate(tscv.split(brent)): #The data X_train = brent.loc[train[0]:train[-1],predictors] y_train = brent.loc[train[0]:train[-1],target] X_test = brent.loc[test[0]:test[-1],predictors] y_test = brent.loc[test[0]:test[-1],target] #Our model model = MLPRegressor(hidden_layer_sizes=(5,2),alpha=result.x[0],early_stopping=True,shuffle=False,learning_rate_init=result.x[1],tol=result.x[2]) #Train the model model.fit(X_train,y_train) #Measure the RMSE model_error_rate.iloc[i,1] = root_mean_squared_error(y_test,model.predict(X_test))
Vamos observar nossas métricas de erro.
taxa_de_erro_do_modelo
Modelo Padrão | Modelo Personalizado |
---|---|
0.153904 | 0.550214 |
0.113818 | 0.501043 |
82.188345 | 0.52897 |
0.114108 | 0.117466 |
0.114718 | 0.112892 |
77.508403 | 0.258558 |
0.109191 | 0.304262 |
0.142143 | 0.363774 |
0.163161 | 0.153202 |
0.120068 | 2.20102 |
Vamos também visualizar os resultados.
model_error_rate["Default Model"].plot(legend=True) model_error_rate["Custom Model"].plot(legend=True)
Fig 5: Visualizando o desempenho do nosso modelo personalizado
Como podemos observar, o modelo personalizado superou o modelo padrão. No entanto, nosso teste teria sido mais convincente se tivéssemos usado conjuntos de dados separados para treinar os modelos e avaliar sua precisão. Usar o mesmo conjunto de dados para ambos os fins não é o procedimento ideal.
Em seguida, vamos nos preparar para converter nossa rede neural profunda para sua representação ONNX. ONNX, que significa Open Neural Network Exchange, é um formato padronizado que permite que modelos de IA treinados em qualquer framework compatível sejam usados em diferentes programas. Por exemplo, o ONNX nos permite treinar um modelo de IA em Python e depois usá-lo em MQL5 ou até mesmo em um programa Java (desde que a API Java suporte ONNX).
Primeiro, importamos as bibliotecas necessárias.
#Now we will prepare to export our neural network into ONNX format from skl2onnx.common.data_types import FloatTensorType from skl2onnx import convert_sklearn import onnxruntime as ort import netron
Vamos definir a forma de entrada para o nosso modelo, lembre-se de que nosso modelo recebe 4 entradas.
#Define the input types initial_type = [("float_input",FloatTensorType([1,4]))]
Ajustando nosso modelo personalizado.
#Fit our custom model custom_model.fit(brent.loc[:,["Open","High","Low","Close"]],brent.loc[:,"Target"])
Criar a representação ONNX da nossa rede neural profunda é fácil graças à biblioteca skl2onnx.
#Create the onnx represantation onnx = convert_sklearn(custom_model,initial_types=initial_type,target_opset=12)
Definir o nome do nosso arquivo ONNX.
#The name of our ONNX file onnx_filename = "Brent_M1.onnx"
Agora vamos escrever o arquivo ONNX.
#Write out the ONNX file with open(onnx_filename,"wb") as f: f.write(onnx.SerializeToString())
Vamos inspecionar os parâmetros do nosso modelo ONNX.
#Now let us inspect our ONNX model onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[0].name output_name = onnx_session.get_outputs()[0].name
Vamos ver a forma de entrada.
for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")
Observe a forma de saída do nosso modelo.
for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")
Agora podemos visualizar nosso modelo usando o netron. Esses passos nos ajudam a garantir que as formas de entrada e saída do nosso ONNX estejam de acordo com as nossas expectativas.
#Nós também podemos inspecionar nosso modelo visualmente usando o netron.
netron.start(onnx_filename)
Fig 6: A representação ONNX da nossa rede neural network
Fig 7: Metadados do nosso modelo ONNX
Netron é uma biblioteca Python open-source que nos permite inspecionar visualmente modelos ONNX, verificar seus parâmetros e revisar os metadados. Para aqueles interessados em aprender mais sobre o uso de modelos ONNX no MetaTrader 5, existem muitos artigos bem escritos disponíveis. Um dos meus autores favoritos sobre o assunto é o Omega.
Implementação no MQL5
Com a configuração do nosso modelo ONNX finalizada, podemos começar a construir nosso Expert Advisor em MQL5.
Fig 8: Um plano esquemático do nosso Expert Advisor
Nosso Expert Advisor usará o modelo ONNX personalizado para gerar sinais de entrada. No entanto, todos os bons traders exercem cautela ao não executar todos os sinais de entrada que recebem. Para incutir essa disciplina no nosso Expert Advisor, o programaremos para esperar pela confirmação de indicadores técnicos antes de abrir uma posição.
Esses indicadores técnicos nos ajudarão a sincronizar nossas entradas de forma eficaz. Uma vez que as posições sejam abertas, os níveis de stop loss definidos pelo usuário serão responsáveis por fechá-las. O primeiro passo é especificar o modelo ONNX como um recurso para nossa aplicação.
//+------------------------------------------------------------------+ //| Custom Deep Neural Network.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Load the ONNX model | //+------------------------------------------------------------------+ #resource "\\Files\\Brent_M1.onnx" as const uchar ModelBuffer[];
Em seguida, carregaremos a biblioteca de negociação, que é essencial para gerenciar nossas posições.
//+------------------------------------------------------------------+ //| Libraries we need | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade;
Agora podemos avançar para criar variáveis globais para o nosso programa.
//+------------------------------------------------------------------+ //| Gloabal variables | //+------------------------------------------------------------------+ long model; //The handler for our ONNX model vector forecast = vector::Zeros(1); //Our model's forecast const int states = 3; //The total number of states the system can be in vector state = vector::Zeros(states); //The state of our system int mfi_handler,wpr_handler; //Handlers for our technical indicators vector mfi_reading,wpr_reading; //The values of our indicators will be kept in vectors double minimum_volume, trading_volume; //Smallest lot size allowed & our calculated lotsize double ask_price, bid_price; //Market rates
Vamos definir as entradas do usuário que nos permitirão modificar o comportamento do Expert Advisor.
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input int mfi_period = 20; //Money Flow Index Period input int wpr_period = 30; //Williams Percent Range Period input int lot_multiple = 20; //How big should our lot sizes be? input double sl_width = 2; //How tight should the stop loss be? input double max_profit = 10; //Close the position when this profit level is reached. input double max_loss = 10; //Close the position when this loss level is reached.
Nossa aplicação requer funções auxiliares para realizar rotinas específicas. Começamos definindo uma função que gerencia o estado da aplicação. Essa aplicação terá três estados: o estado 0 implica que não temos posições, enquanto os estados 1 e 2 indicam uma posição de compra ou venda, respectivamente.
Dependendo do estado atual, a aplicação terá acesso a diferentes funções.
//+------------------------------------------------------------------+ //| This function is responsible for updating the system state | //+------------------------------------------------------------------+ void update_state(int index) { //--- Reset the system state state = vector::Zeros(states); //--- Now update the current state state[index] = 1; }
Em seguida, precisamos de uma função responsável por validar as entradas do usuário ao iniciar a aplicação. Por exemplo, essa função garantirá que todos os períodos dos indicadores técnicos sejam maiores que 0.
//+------------------------------------------------------------------+ //| This function will ensure that user inputs are valid | //+------------------------------------------------------------------+ bool valid_inputs(void) { //--- Let us validate the inputs the user passed return((mfi_period > 0)&&(wpr_period > 0) && (max_profit >= 0) && (max_loss >= 0) && (lot_multiple >= 0) && (sl_width >= 0)); }
Nosso Expert Advisor verificará continuamente se os níveis de lucro atendem às entradas especificadas pelo usuário. Por exemplo, se o usuário definir uma meta de lucro máximo de $1, a posição será fechada automaticamente assim que atingir um lucro de $1, mesmo que ainda não tenha atingido o nível de take profit. A mesma lógica se aplica ao stop loss: a posição será fechada com base no primeiro limite atingido, seja o nível de stop loss ou o nível máximo de perda. Esse recurso foi projetado para oferecer flexibilidade na definição de níveis de risco aceitáveis.
//+------------------------------------------------------------------+ //| This function will check our profit levels | //+------------------------------------------------------------------+ void check_profit_level(void) { //--- Let us check if the user set a max profit/loss limit if(max_loss > 0 || max_profit > 0) { //--- If true, let us inspect whether we have passed the limit. if((PositionGetDouble(POSITION_PROFIT) > max_profit) || (PositionGetDouble(POSITION_PROFIT) < (max_loss * -1))) { //--- Close the position Trade.PositionClose(Symbol()); } } }
Como temos um sistema baseado em IA, vamos construir uma função para verificar se nosso modelo prevê um movimento de mercado que pode ser adverso à nossa posição aberta. Esses sinais podem servir como indicações precoces de mudanças no sentimento do mercado.
//+------------------------------------------------------------------+ //| If we predict a reversal, let's close our positions | //+------------------------------------------------------------------+ void find_reversal(void) { //--- We have a position if(((state[1] == 1) && (forecast[0] < iClose(Symbol(),PERIOD_CURRENT,0))) || ((state[2] == 1) && (forecast[0] > iClose(Symbol(),PERIOD_CURRENT,0)))) { Trade.PositionClose(Symbol()); } }
Em seguida, definiremos uma função para verificar sinais de entrada válidos. Um sinal de entrada é considerado válido se atender a duas condições: primeiro, ele deve ser apoiado por mudanças no nível de preço em períodos de tempo mais altos; segundo, nosso modelo de IA deve prever um movimento de preço alinhado com essa tendência maior. Se ambas as condições forem satisfeitas, então verificaremos nossos indicadores técnicos para o nível final de confirmação.
//+------------------------------------------------------------------+ //| This function will determine if we have a valid entry | //+------------------------------------------------------------------+ void find_entry(void) { //--- First we want to know if the higher timeframes are moving in the same direction we want to go double higher_time_frame_trend = iClose(Symbol(),PERIOD_W1,16) - iClose(Symbol(),PERIOD_W1,0); //--- If price levels appreciated, the difference will be negative if(higher_time_frame_trend < 0) { //--- We may be better off only taking buy opportunities //--- Buy opportunities are triggered when the model's prediction is greater than the current price if(forecast[0] > iClose(Symbol(),PERIOD_CURRENT,0)) { //--- We will use technical indicators to time our entries bullish_sentiment(); } } //--- If price levels depreciated, the difference will be positive if(higher_time_frame_trend > 0) { //--- We may be better off only taking sell opportunities //--- Sell opportunities are triggered when the model's prediction is less than the current price if(forecast[0] < iClose(Symbol(),PERIOD_CURRENT,0)) { //--- We will use technical indicators to time our entries bearish_sentiment(); } } }
Agora, chegamos à função responsável por interpretar nossos indicadores técnicos. Existem várias maneiras de interpretar esses indicadores; no entanto, eu prefiro centralizá-los em torno de 50. Fazendo isso, valores superiores a 50 confirmam o sentimento de alta, enquanto valores abaixo de 50 indicam o sentimento de baixa. Usaremos o Índice de Fluxo de Dinheiro (MFI) como nosso indicador de volume e o Williams Percent Range (WPR) como nosso indicador de força da tendência.
//+------------------------------------------------------------------+ //| This function will interpret our indicators for buy signals | //+------------------------------------------------------------------+ void bullish_sentiment(void) { //--- For bullish entries we want strong volume readings from our MFI //--- And confirmation from our WPR indicator wpr_reading.CopyIndicatorBuffer(wpr_handler,0,0,1); mfi_reading.CopyIndicatorBuffer(mfi_handler,0,0,1); if((wpr_reading[0] > -50) && (mfi_reading[0] > 50)) { //--- Get the ask price ask_price = SymbolInfoDouble(Symbol(),SYMBOL_ASK); //--- Make sure we have no open positions if(PositionsTotal() == 0) Trade.Buy(trading_volume,Symbol(),ask_price,(ask_price - sl_width),(ask_price + sl_width),"Custom Deep Neural Network"); update_state(1); } } //+------------------------------------------------------------------+ //| This function will interpret our indicators for sell signals | //+------------------------------------------------------------------+ void bearish_sentiment(void) { //--- For bearish entries we want strong volume readings from our MFI //--- And confirmation from our WPR indicator wpr_reading.CopyIndicatorBuffer(wpr_handler,0,0,1); mfi_reading.CopyIndicatorBuffer(mfi_handler,0,0,1); if((wpr_reading[0] < -50) && (mfi_reading[0] < 50)) { //--- Get the bid price bid_price = SymbolInfoDouble(Symbol(),SYMBOL_BID); if(PositionsTotal() == 0) Trade.Sell(trading_volume,Symbol(),bid_price,(bid_price + sl_width),(bid_price - sl_width),"Custom Deep Neural Network"); //--- Update the state update_state(2); } }
Em seguida, focamos em obter previsões do nosso modelo ONNX. Lembre-se, nosso modelo espera entradas com forma [1,4] e retorna saídas com forma [1,1]. Definimos vetores para armazenar as entradas e saídas de acordo, e depois usamos a função OnnxRun para obter a previsão do modelo.
//+------------------------------------------------------------------+ //| This function will fetch forecasts from our model | //+------------------------------------------------------------------+ void model_predict(void) { //--- First we get the input data ready vector input_data = {iOpen(_Symbol,PERIOD_CURRENT,0),iHigh(_Symbol,PERIOD_CURRENT,0),iLow(_Symbol,PERIOD_CURRENT,0),iClose(_Symbol,PERIOD_CURRENT,0)}; //--- Now we need to perform inferencing if(!OnnxRun(model,ONNX_DATA_TYPE_FLOAT,input_data,forecast)) { Comment("Failed to obtain a forecast from the model: ",GetLastError()); forecast[0] = 0; return; } //--- We succeded! Comment("Model forecast: ",forecast[0]); }
Agora podemos começar a construir o manipulador de eventos para nossa aplicação, que será invocado quando o Expert Advisor for inicializado. Nosso procedimento validará primeiro as entradas do usuário, depois definirá as formas de entrada e saída do nosso modelo ONNX. Em seguida, configuraremos nossos indicadores técnicos, buscaremos dados de mercado e finalmente configuraremos o estado do nosso sistema para 0.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Make sure user inputs are valid if(!valid_inputs()) { Comment("Invalid inputs were passed to the application."); return(INIT_FAILED); } //--- Create the ONNX model from the buffer model = OnnxCreateFromBuffer(ModelBuffer,ONNX_DEFAULT); //--- Check if we were succesfull if(model == INVALID_HANDLE) { Comment("[ERROR] Failed to create the ONNX model from the buffer: ",GetLastError()); return(INIT_FAILED); } //--- Set the input shape of the model ulong input_shape[] = {1,4}; //--- Check if we were succesfull if(!OnnxSetInputShape(model,0,input_shape)) { Comment("[ERROR] Failed to set the ONNX model input shape: ",GetLastError()); return(INIT_FAILED); } //--- Set the output shape of the model ulong output_shape[] = {1,1}; //--- Check if we were succesfull if(!OnnxSetOutputShape(model,0,output_shape)) { Comment("[ERROR] Failed to set the ONNX model output shape: ",GetLastError()); return(INIT_FAILED); } //--- Setup the technical indicators wpr_handler = iWPR(Symbol(),PERIOD_CURRENT,wpr_period); mfi_handler = iMFI(Symbol(),PERIOD_CURRENT,mfi_period,VOLUME_TICK); //--- Fetch market data minimum_volume = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); trading_volume = minimum_volume * lot_multiple; //--- Set the system to state 0, indicating we have no open positions update_state(0); //--- Everything went fine return(INIT_SUCCEEDED); }
Uma parte crucial da nossa aplicação é o procedimento de desinicialização. Nesse manipulador de eventos, liberaremos quaisquer recursos que não sejam mais necessários quando o Expert Advisor não estiver em uso.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Free the onnx resources OnnxRelease(model); //--- Free the indicator resources IndicatorRelease(wpr_handler); IndicatorRelease(mfi_handler); //--- Detach the expert advisor ExpertRemove(); }
Por fim, precisamos definir nosso manipulador de eventos OnTick. As ações tomadas dependerão do estado do sistema. Se não tivermos posições abertas (estado 0), nossa prioridade será obter uma previsão do nosso modelo e identificar uma entrada potencial. Se tivermos uma posição aberta (estado 1 para longo ou estado 2 para curto), nosso foco será mudar para o gerenciamento da posição. Isso inclui monitorar reversões potenciais e verificar níveis de risco, metas de lucro e níveis máximos de lucro.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Which state is the system in? if(state[0] == 1) { //--- Being in this state means we have no open positions, let's analyse the market to try find one model_predict(); find_entry(); } if((state[1] == 1) || (state[2] == 1)) { //--- Being in this state means we have an position open, if our model forecasts a reversal move we will close model_predict(); find_reversal(); check_profit_level(); } } //+------------------------------------------------------------------+
Fig 9: Testando nosso Expert Advisor
Conclusão
Este artigo forneceu uma introdução simples ao uso de algoritmos de otimização para seleção de hiperparâmetros do modelo. Em artigos futuros, adotaremos uma metodologia mais robusta, utilizando dois conjuntos de dados dedicados: um para otimizar o modelo e outro para validação cruzada e comparação de seu desempenho contra um modelo usando configurações padrão.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15413
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso