English Русский 中文 Español Deutsch 日本語
preview
Construindo Expert Advisors Auto-otimizantes Com MQL5 E Python (Parte II): Ajustando Redes Neurais Profundas

Construindo Expert Advisors Auto-otimizantes Com MQL5 E Python (Parte II): Ajustando Redes Neurais Profundas

MetaTrader 5Exemplos |
167 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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.

Roger Mead

Fig 1: Roger Mead


John Nelder

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.

Pesquise os dados que você precisa.

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")
Concluído em 312.29402351379395 segundos

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")

Nosso índice de erro

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
array([0.75747551, 0.34066536, 0.26214705])

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")
Tarefa concluída em 1332.9911317825317 segundos

Agora vamos interpretar o resultado da otimização.

Resultado:
mensagem: O número máximo de avaliações de funções foi excedido.
       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)

Visualizando o desempenho do nosso modelo

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}")
1. Nome: float_input, Tipo de Dados: tensor(float), Forma: [1, 4]

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}")
1. Nome: variable, Tipo de Dados: tensor(float), Forma: [1, 1]

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)

Visualização do Netron

Fig 6: A representação ONNX da nossa rede neural network


Detalhes do arquivo ONNX

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.

Diagrama esquemático da nossa aplicação 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();
     }
  }
//+------------------------------------------------------------------+

Testando nosso EA

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

Ganhe uma Vantagem Sobre Qualquer Mercado (Parte III): Índice de Gastos com Cartões Visa Ganhe uma Vantagem Sobre Qualquer Mercado (Parte III): Índice de Gastos com Cartões Visa
No mundo dos big data, existem milhões de conjuntos de dados alternativos que têm o potencial de aprimorar nossas estratégias de negociação. Nesta série de artigos, vamos ajudá-lo a identificar os conjuntos de dados públicos mais informativos.
Implementando uma Estratégia de Negociação com Bandas de Bollinger usando MQL5: Um Guia Passo a Passo Implementando uma Estratégia de Negociação com Bandas de Bollinger usando MQL5: Um Guia Passo a Passo
Um guia passo a passo para implementar um algoritmo de negociação automatizado em MQL5 baseado na estratégia de Bandas de Bollinger. Um tutorial detalhado sobre a criação de um Expert Advisor que pode ser útil para traders.
Métodos de otimização da biblioteca ALGLIB (Parte I) Métodos de otimização da biblioteca ALGLIB (Parte I)
Neste artigo, vamos conhecer os métodos de otimização da biblioteca ALGLIB para MQL5. O artigo inclui exemplos simples e visuais de aplicação da ALGLIB para resolver tarefas de otimização, o que tornará o processo de aprendizado dos métodos o mais acessível possível. Analisaremos detalhadamente a integração de algoritmos como BLEIC, L-BFGS e NS, e com base neles resolveremos uma tarefa de teste simples.
Redes neurais em trading: Análise da situação do mercado usando o transformador de padrões Redes neurais em trading: Análise da situação do mercado usando o transformador de padrões
Ao analisarmos a situação do mercado com nossos modelos, o elemento-chave é a vela. No entanto, sabe-se há muito tempo que os padrões de velas podem ajudar a prever movimentos futuros de preço. Neste artigo, apresentaremos um método que permite integrar essas duas abordagens.