
Dominando o ONNX: Ponto de virada para traders MQL5
"A capacidade de exportar e importar modelos de IA no formato ONNX simplifica o processo de desenvolvimento, economizando tempo e recursos ao integrar IA em ecossistemas escritos em diferentes linguagens".
Introdução
Não se pode negar que vivemos na era da inteligência artificial (IA) e do aprendizado de máquina: todos os dias, novas tecnologias baseadas em IA são aplicadas nas finanças, arte, jogos, educação e muitos outros aspectos da vida.
Para nós, traders, a IA é útil porque nos permite descobrir padrões e relações que não conseguimos ver a olho nu.
Apesar do aparente poder e magia, por trás dos modelos de IA existem operações matemáticas complexas que exigem um enorme volume de trabalho e um alto grau de precisão para sua correta compreensão e implementação. Felizmente, o código aberto simplifica significativamente a tarefa, eliminando a necessidade de desenvolver um modelo de aprendizado de máquina do zero.
Agora, não é necessário ser um gênio da matemática e da programação para criar e implementar modelos de IA. É necessário apenas um entendimento básico de uma determinada linguagem de programação ou das ferramentas que você deseja usar em seu projeto. Às vezes, nem é necessário ter um computador. Graças a serviços como o Google Colab, você pode codificar, criar e executar modelos de IA usando Python gratuitamente.
Implementar modelos de aprendizado de máquina usando Python e outras linguagens de programação populares e avançadas é relativamente simples, o que não se pode dizer sobre MQL5. A menos que você queira reinventar a roda, criando modelos de aprendizado de máquina em MQL5 do zero, o que estamos fazendo nesta série de artigos, eu recomendaria fortemente o uso do ONNX para integrar modelos de IA construídos com Python. Felizmente, o ONNX agora é suportado no MQL5.
Para entender o artigo, é necessário ter uma compreensão básica sobre IA e aprendizado de máquina. Você pode encontrar informações aqui e aqui.
O que é ONNX?
ONNX (Open Neural Network Exchange) é uma biblioteca aberta para representação de modelos de aprendizado de máquina e profundo. Ela permite converter modelos treinados em um framework de aprendizado profundo para um formato comum, que pode ser usado em outros frameworks, simplificando o trabalho com modelos em diferentes plataformas e ferramentas.
Isso significa que você pode criar modelos de aprendizado de máquina usando qualquer linguagem que o suporte, exceto MQL5, depois converter esse modelo para o formato ONNX e usá-lo dentro do seu programa MQL5.
Neste artigo, estarei usando Python para criar aprendizado de máquina, pois estou familiarizado com ele. De fato, é possível usar outras linguagens, embora eu não esteja seguro disso. Aliás, parece que toda a documentação do ONNX é baseada em Python. Acredito que, no momento, o ONNX foi criado para Python. Isso faz sentido, pois não creio que exista outra linguagem, além do Python, com bibliotecas e ferramentas avançadas baseadas em IA.
Conceitos fundamentais no ONNX
Antes de mergulhar no ONNX, você deve estar familiarizado com alguns conceitos-chave:
- Modelo ONNX: Um modelo ONNX é uma representação de um modelo de aprendizado de máquina. Consiste em um grafo de computação, onde os nós representam operações (por exemplo, convolução, adição), e as arestas representam o fluxo de dados entre as operações.
- Nós: Os nós do grafo ONNX representam operações ou funções aplicadas aos dados de entrada. Esses nós podem ser operações como convolução, adição ou operações personalizadas.
- Tensores: Os tensores são arrays multidimensionais que representam os dados transmitidos entre os nós do grafo computacional. Eles podem ser dados de entrada, saída ou intermediários.
- Operadores: Os operadores são funções aplicadas aos tensores no ONNX. Cada operador representa uma operação específica, como multiplicação de matrizes ou adição elemento a elemento.
Criando modelos em Python e implementando em MQL5 usando ONNX
Para construir com sucesso um modelo de aprendizado de máquina em Python e implementá-lo dentro do seu Expert Advisor, indicador ou script em MQL5, é necessário mais do que apenas código Python. Aqui estão os passos importantes que você deve seguir para acabar não só com um modelo ONNX, mas também com um modelo que realmente forneça as previsões precisas de que você precisa:
- Coleta de dados
- Normalização de dados do lado do MQL5
- Construção de modelos em Python
- Incorporação do modelo ONNX em MQL5
- Execução do modelo em tempo real
01: Coleta de dados
A coleta de dados é o primeiro passo a ser feito diretamente dentro do seu programa MQL5. Acredito que é melhor coletar todos os dados dentro do seu programa para que eles correspondam à maneira como coletamos dados de treinamento e os dados usados durante a negociação real ou a execução do modelo em tempo real. Lembre-se de que a coleta de dados pode variar dependendo da natureza do problema que você está tentando resolver. Neste artigo, estaremos abordando um problema de regressão. Usaremos dados OHLC (Open, High, Low, Close) como nosso principal conjunto de dados, onde Open, High e Low serão usados como variáveis independentes, e os valores de Close serão usados como a variável alvo.
Inside ONNX get data.mq5
matrixf GetTrainData(uint start, uint total) { matrixf return_matrix(total, 3); ulong last_col; OPEN.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_LOW, start, total); CLOSE.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_CLOSE, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); csv_name_ = Symbol()+"."+EnumToString(Period())+"."+string(total_bars); x_vars = "OPEN,HIGH,LOW"; return_matrix.Resize(total, 4); //if we are collecting the train data collect the target variable also last_col = return_matrix.Cols()-1; //Column located at the last index is the last column return_matrix.Col(CLOSE, last_col); //put the close price information in the last column of a matrix csv_name_ +=".targ=CLOSE"; csv_header = x_vars + ",CLOSE"; if (!WriteCsv("ONNX Datafolder\\"+csv_name_+".csv", return_matrix, csv_header)) Print("Failed to Write to a csv file"); else Print("Data saved to a csv file successfully"); return return_matrix; }
Esta função coleta as variáveis independentes OHL e a variável alvo CLOSE. No aprendizado supervisionado, é necessário especificar e passar a variável alvo para o modelo, para que ele possa aprender com ela e entender os padrões entre a variável alvo e as outras variáveis. No nosso caso, o modelo tentará entender como esses indicadores levam a um movimento altista ou baixista.
Ao implementar o modelo, precisamos coletar os dados da mesma maneira, exceto que desta vez coletaremos os dados sem a variável alvo, porque queremos que nosso modelo treinado descubra isso. Basicamente, estamos falando de previsão.
É por isso que existe uma função separada GetLiveData para carregar novos dados para previsões rápidas no mercado.
Inside ONNX mt5.mq5
matrixf GetLiveData(uint start, uint total) { matrixf return_matrix(total, 3); OPEN.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_LOW, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); return return_matrix; }
Coleta de dados de treinamento
matrixf dataset = GetTrainData(start_bar, total_bars); Print("Train data\n",dataset);
Resultados
DK 0 23:10:54.837 ONNX get data (EURUSD,H1) Train data PR 0 23:10:54.837 ONNX get data (EURUSD,H1) [[1.4243405,1.4130603,1.4215617,1.11194] HF 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3976599,1.3894916,1.4053394,1.11189] RK 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.402994,1.3919021,1.397626,1.11123] PM 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3848507,1.3761013,1.3718294,1.11022] FF 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3597701,1.3447646,1.3545419,1.1097701] CH 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3461626,1.3522644,1.3433729,1.1106] NL 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3683074,1.3525325,1.3582669,1.10996]
Recuperando dados reais
Assim sãon obtidos os dados OHL atuais por barra.
matrixf live_data = GetLiveData(0,1); Print("Live data\n",live_data);
Resultados
MN 0 23:15:47.167 ONNX mt5 (EURUSD,H1) Live data KS 0 23:15:47.167 ONNX mt5 (EURUSD,H1) [[-0.21183228,-0.23540309,-0.20334835]]
A maneira de carregar dados em tempo real pode variar um pouco ao preparar dados para modelos de previsão de séries temporais, como RNN, GRU e LSTM.
error 2023.09.18 18:03:53.212 ONNX: invalid parameter size, expected 1044480 bytes instead of 32640
02: Normalização de dados do lado do MQL5
A normalização de dados é uma das coisas mais importantes que precisam ser feitas corretamente para o conjunto de dados que será usado pelo modelo de aprendizado de máquina.
Lembre-se de que o método de normalização usado para preparar os dados de treinamento deve ser o mesmo usado para preparar os dados de teste e reais. Isso significa que ao usar o método MinMaxScaler, os valores de min e max, que são variáveis fundamentais na equação do MinMaxScaler usada na preparação dos dados de treinamento, devem ser usados para continuar a normalização dos novos dados que serão processados pelo modelo em outro lugar. Para alcançar tal consistência, precisamos salvar as variáveis para cada método de normalização em um arquivo csv:
A normalização de dados destina-se apenas às variáveis independentes. Independentemente do problema que você está tentando resolver, não há necessidade de normalizar a variável alvo.
Vamos usar a classe Preprocessing, apresentada aqui.
Inside ONNX get data.mq5 script
//--- Saving the normalization prameters switch(NORM) { case NORM_MEAN_NORM: //--- saving the mean norm_params.Assign(norm_x.mean_norm_scaler.mean); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.mean.csv",norm_params,x_vars); //--- saving the min norm_params.Assign(norm_x.mean_norm_scaler.min); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.mean_norm_scaler.max); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.max.csv",norm_params,x_vars); break; case NORM_MIN_MAX_SCALER: //--- saving the min norm_params.Assign(norm_x.min_max_scaler.min); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.min_max_scaler.max); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.max.csv",norm_params,x_vars); break; case NORM_STANDARDIZATION: //--- saving the mean norm_params.Assign(norm_x.standardization_scaler.mean); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.mean.csv",norm_params,x_vars); //--- saving the std norm_params.Assign(norm_x.standardization_scaler.std); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.std.csv",norm_params,x_vars); break; }
Resultados
Quando o Standardization Scaler foi usado dentro dos arquivos csv, os parâmetros apareceram da seguinte forma:
Note que a normalização também está integrada dentro da função GetData. Como a normalização é muito importante, cada matriz de dados retornada por ambas as funções responsáveis pela coleta de dados deve ser uma matriz com valores de preços normalizados.
Inside ONNX get data.mq5 script
matrixf GetTrainData(uint start, uint total) { matrixf return_matrix(total, 3); ulong last_col; OPEN.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_LOW, start, total); CLOSE.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_CLOSE, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); matrixf norm_params = {}; csv_name_ = Symbol()+"."+EnumToString(Period())+"."+string(total_bars); x_vars = "OPEN,HIGH,LOW"; while (CheckPointer(norm_x) != POINTER_INVALID) delete (norm_x); norm_x = new CPreprocessing<vectorf, matrixf>(return_matrix, NORM); //--- Saving the normalization prameters switch(NORM) { case NORM_MEAN_NORM: //--- saving the mean norm_params.Assign(norm_x.mean_norm_scaler.mean); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.mean.csv",norm_params,x_vars); //--- saving the min norm_params.Assign(norm_x.mean_norm_scaler.min); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.mean_norm_scaler.max); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.max.csv",norm_params,x_vars); break; case NORM_MIN_MAX_SCALER: //--- saving the min norm_params.Assign(norm_x.min_max_scaler.min); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.min_max_scaler.max); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.max.csv",norm_params,x_vars); break; case NORM_STANDARDIZATION: //--- saving the mean norm_params.Assign(norm_x.standardization_scaler.mean); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.mean.csv",norm_params,x_vars); //--- saving the std norm_params.Assign(norm_x.standardization_scaler.std); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.std.csv",norm_params,x_vars); break; } return_matrix.Resize(total, 4); //if we are collecting the train data collect the target variable also last_col = return_matrix.Cols()-1; //Column located at the last index is the last column return_matrix.Col(CLOSE, last_col); //put the close price information in the last column of a matrix csv_name_ +=".targ=CLOSE"; csv_header = x_vars + ",CLOSE"; if (!WriteCsv("ONNX Datafolder\\"+csv_name_+".csv", return_matrix, csv_header)) Print("Failed to Write to a csv file"); else Print("Data saved to a csv file successfully"); return return_matrix; }
Finalmente, os dados foram salvos em formato CSV para que pudessem ser utilizados em conjunto com o código Python.
03: Construção de modelos em Python
Eu vou construir uma rede neural de perceptron multicamadas, mas você pode construir qualquer modelo de sua escolha. Você não está limitado a este tipo específico de modelo. Instale o Python, se você ainda não o tem. Em seguida, instale o virtualenv, executando os seguintes comandos do Windows CMD (não confunda com o Powershell!!)
$ pip3 install virtualenv
Depois de iniciado,
$ virtualenv venv
Isso criará um ambiente virtual Python para o seu computador com Windows. Este processo pode variar um pouco para usuários de Mac e Linux. Depois, inicie o ambiente virtual executando este comando:
$ venv\Scripts\activate
Em seguida, instale todas as dependências usadas neste guia, executando
É sempre importante isolar o projeto criando um ambiente virtual para evitar conflitos entre módulos e versões do Python e facilitar o compartilhamento do projeto.
Importação e inicialização do MT5
import MetaTrader5 as mt5 if not mt5.initialize(): #This will open MT5 app in your pc print("initialize() failed, error code =",mt5.last_error()) quit() # program logic and ML code will be here mt5.shutdown() #This closes the program # Getting the data we stored in the Files path on Metaeditor data_path = terminal_info.data_path dataset_path = data_path + "\\MQL5\\Files\\ONNX Datafolder"
Precisamos verificar se o caminho existe. Se não existir, isso significa que não coletamos dados do MetaTrader 5.
import os if not os.path.exists(dataset_path): print("Dataset folder doesn't exist | Be sure you are referring to the correct path and the data is collected from MT5 side of things") quit()
Criação de uma rede neural perceptron de múltiplas camadas(MLP)
Vamos encapsular a NN MLP dentro de uma classe para tornar nosso código em seções legíveis;
01: Inicialização da classe
Os dados são coletados e divididos em conjuntos de treino e teste, enquanto variáveis importantes são declaradas disponíveis para uso por toda a classe,
class NeuralNetworkClass(): def __init__(self, csv_name, target_column, batch_size=32): # Loading the dataset and storing to a variable Array self.data = pd.read_csv(dataset_path+"\\"+csv_name) if self.data.empty: print(f"No such dataset or Empty dataset csv = {csv_name}") quit() # quit the program print(self.data.head()) # Print 5 first rows of a given data self.target_column = target_column # spliting the data into training and testing samples X = self.data.drop(columns=self.target_column).to_numpy() # droping the targeted column, the rest is x variables Y = self.data[self.target_column].to_numpy() # We convert data arrays to numpy arrays compartible with sklearn and tensorflow self.train_x, self.test_x, self.train_y, self.test_y = train_test_split(X, Y, test_size=0.3, random_state=42) # splitting the data into training and testing samples print(f"train x shape {self.train_x.shape}\ntest x shape {self.test_x.shape}") self.input_size = self.train_x.shape[-1] # obtaining the number of columns in x variable as our inputs self.output_size = 1 # We are solving for a regression problem we need to have a single output neuron self.batch_size = batch_size self.model = None # Object to store the model self.plots_directory = "Plots" self.models_directory = "Models"
Resultados
02: Construção da rede neural
Nossa rede neural de uma camada é definida pelo número especificado de neurônios.
def BuildNeuralNetwork(self, activation_function='relu', neurons = 10): # Create a Feedforward Neural Network model self.model = keras.Sequential([ keras.layers.Input(shape=(self.input_size,)), # Input layer keras.layers.Dense(units=neurons, activation=activation_function, activity_regularizer=l2(0.01), kernel_initializer="he_uniform"), # Hidden layer with an activation function keras.layers.Dense(units=self.output_size, activation='linear', activity_regularizer=l2(0.01), kernel_initializer="he_uniform") ]) # Print a summary of the model's architecture. self.model.summary()
Resultados
03: Treinamento e teste do modelo da rede neural
def train_network(self, epochs=100, learning_rate=0.001, loss='mean_squared_error'): early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) # Early stoppage mechanism | stop training when there is no major change in loss in the last to epochs, defined by the variable patience adam = optimizers.Adam(learning_rate=learning_rate) # Adam optimizer >> https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/ # Compile the model: Specify the loss function, optimizer, and evaluation metrics. self.model.compile(loss=loss, optimizer=adam, metrics=['mae']) # One hot encode the validation and train target variables validation_y = self.test_y y = self.train_y history = self.model.fit(self.train_x, y, epochs=epochs, batch_size=self.batch_size, validation_data=(self.test_x, validation_y), callbacks=[early_stopping], verbose=2) if not os.path.exists(self.plots_directory): #create plots path if it doesn't exist for saving the train-test plots os.makedirs(self.plots_directory) # save the loss and validation loss plot plt.figure(figsize=(12, 6)) plt.plot(history.history['loss'], label='Training Loss') plt.plot(history.history['val_loss'], label='Validation Loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() title = 'Training and Validation Loss Curves' plt.title(title) plt.savefig(fname=f"{self.plots_directory}\\"+title) # use the trained model to make predictions on the trained data pred = self.model.predict(self.train_x) acc = metrics.r2_score(self.train_y, pred) # Plot actual & pred count = [i*0.1 for i in range(len(self.train_y))] title = f'MLP {self.target_column} - Train' # Saving the plot containing information about predictions and actual values plt.figure(figsize=(7, 5)) plt.plot(count, self.train_y, label = "Actual") plt.plot(count, pred, label = "forecast") plt.xlabel('Actuals') plt.ylabel('Preds') plt.title(title+f" | Train acc={acc}") plt.legend() plt.savefig(fname=f"{self.plots_directory}\\"+title) self.model.save(f"Models\\lstm-pat.{self.target_column}.h5") #saving the model in h5 format, this will help us to easily convert this model to onnx later def test_network(self): # Plot actual & pred count = [i*0.1 for i in range(len(self.test_y))] title = f'MLP {self.target_column} - Test' pred = self.model.predict(self.test_x) acc = metrics.r2_score(self.test_y, pred) # Saving the plot containing information about predictions and actual values plt.figure(figsize=(7, 5)) plt.plot(count, self.test_y, label = "Actual") plt.plot(count, pred, label = "forecast") plt.xlabel('Actuals') plt.ylabel('Preds') plt.title(title+f" | Train acc={acc}") plt.legend() plt.savefig(fname=f"{self.plots_directory}\\"+title) if not os.path.exists(self.plots_directory): #create plots path if it doesn't exist for saving the train-test plots os.makedirs(self.plots_directory) plt.savefig(fname=f"{self.plots_directory}\\"+title) return acc
Resultados
Epoch 1/50 219/219 - 2s - loss: 1.2771 - mae: 0.3826 - val_loss: 0.1153 - val_mae: 0.0309 - 2s/epoch - 8ms/step Epoch 2/50 219/219 - 1s - loss: 0.0836 - mae: 0.0305 - val_loss: 0.0582 - val_mae: 0.0291 - 504ms/epoch - 2ms/step Epoch 3/50 219/219 - 1s - loss: 0.0433 - mae: 0.0283 - val_loss: 0.0323 - val_mae: 0.0284 - 515ms/epoch - 2ms/step Epoch 4/50 219/219 - 0s - loss: 0.0262 - mae: 0.0272 - val_loss: 0.0218 - val_mae: 0.0270 - 482ms/epoch - 2ms/step Epoch 5/50 ... ... Epoch 48/50 219/219 - 0s - loss: 0.0112 - mae: 0.0106 - val_loss: 0.0112 - val_mae: 0.0121 - 490ms/epoch - 2ms/step Epoch 49/50 219/219 - 0s - loss: 0.0112 - mae: 0.0106 - val_loss: 0.0112 - val_mae: 0.0109 - 486ms/epoch - 2ms/step Epoch 50/50 219/219 - 1s - loss: 0.0112 - mae: 0.0106 - val_loss: 0.0112 - val_mae: 0.0097 - 501ms/epoch - 2ms/step 219/219 [==============================] - 0s 2ms/step C:\Users\Omega Joctan\OneDrive\Documents\onnx article\ONNX python\venv\Lib\site-packages\keras\src\engine\training.py:3079: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`. saving_api.save_model( 94/94 [==============================] - 0s 2ms/step Test accuracy = 0.9336617822086006
O modelo da rede neural teve uma acurácia de 93% durante o treinamento e cerca de 95% durante o teste. Há um risco de fitting, mas continuaremos a trabalhar com ele.
04: Salvando o modelo ONNX
É recomendável salvar o modelo após a conclusão bem-sucedida do treinamento e depois de verificar o desempenho do modelo tanto no treinamento quanto na validação fora da amostra. Precisamos adicionar o código de tempo de execução do ONNX para salvar o modelo durante a função train_network na nossa classe. Primeiro, precisamos instalar duas bibliotecas - onnx e tf2onnx
def train_network(self, epochs=100, learning_rate=0.001, loss='mean_squared_error'): # at the end of this function # .... self.model.save(f"Models\\MLP.REG.{self.target_column}.{self.data.shape[0]}.h5") #saving the model in h5 format, this will help us to easily convert this model to onnx later self.saveONNXModel() def saveONNXModel(self, folder="ONNX Models"): path = data_path + "\\MQL5\\Files\\" + folder if not os.path.exists(path): # create this path if it doesn't exist os.makedirs(path) onnx_model_name = f"MLP.REG.{self.target_column}.{self.data.shape[0]}.onnx" path += "\\" + onnx_model_name loaded_keras_model = load_model(f"Models\\MLP.REG.{self.target_column}.{self.data.shape[0]}.h5") onnx_model, _ = tf2onnx.convert.from_keras(loaded_keras_model, output_path=path) onnx.save(onnx_model, path ) print(f'Saved model to {path}')
Resultados
Você pode ter notado que escolhi salvar o modelo ONNX no diretório pai Files. Por que lá? Isso é porque o arquivo ONNX é mais fácil de ser incorporado em nosso programa MQL5 como um recurso, como um Expert Advisor ou indicador.
04: Incorporação do modelo ONNX em MQL5
#resource "\\Files\\ONNX Models\\MLP.REG.CLOSE.10000.onnx" as uchar RNNModel[]
O comando importa o modelo ONNX e o salva dentro de um array RNNModel uchar.
A próxima coisa que precisamos fazer é definir um handle ONNX como uma variável global e criar um handle dentro da função OnInit.
Inside ONNX mt5.mq5 EA
long mlp_onnxhandle; #include <MALE5\preprocessing.mqh> CPreprocessing<vectorf, matrixf> *norm_x; int inputs[], outputs[]; vectorf OPEN, HIGH, LOW; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if (!LoadNormParams()) //Load the normalization parameters we saved once { Print("Normalization parameters csv files couldn't be found \nEnsure you are collecting data and Normalizing them using [ONNX get data.ex5] Script \nTrain the Python model again if necessary"); return INIT_FAILED; } //--- ONNX SETTINGS mlp_onnxhandle = OnnxCreateFromBuffer(RNNModel, MQLInfoInteger(MQL_DEBUG) ? ONNX_DEBUG_LOGS : ONNX_DEFAULT); //creating onnx handle buffer | rUN DEGUG MODE during debug mode if (mlp_onnxhandle == INVALID_HANDLE) { Print("OnnxCreateFromBuffer Error = ",GetLastError()); return INIT_FAILED; } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (only Close) OnnxTypeInfo type_info; //Getting onnx information for Reference In case you forgot what the loaded ONNX is all about long input_count=OnnxGetInputCount(mlp_onnxhandle); Print("model has ",input_count," input(s)"); for(long i=0; i<input_count; i++) { string input_name=OnnxGetInputName(mlp_onnxhandle,i); Print(i," input name is ",input_name); if(OnnxGetInputTypeInfo(mlp_onnxhandle,i,type_info)) { PrintTypeInfo(i,"input",type_info); ArrayCopy(inputs, type_info.tensor.dimensions); } } long output_count=OnnxGetOutputCount(mlp_onnxhandle); Print("model has ",output_count," output(s)"); for(long i=0; i<output_count; i++) { string output_name=OnnxGetOutputName(mlp_onnxhandle,i); Print(i," output name is ",output_name); if(OnnxGetOutputTypeInfo(mlp_onnxhandle,i,type_info)) { PrintTypeInfo(i,"output",type_info); ArrayCopy(outputs, type_info.tensor.dimensions); } } //--- if (MQLInfoInteger(MQL_DEBUG)) { Print("Inputs & Outputs"); ArrayPrint(inputs); ArrayPrint(outputs); } const long input_shape[] = {batch_size, 3}; if (!OnnxSetInputShape(mlp_onnxhandle, 0, input_shape)) //Giving the Onnx handle the input shape { printf("Failed to set the input shape Err=%d",GetLastError()); return INIT_FAILED; } const long output_shape[] = {batch_size, 1}; if (!OnnxSetOutputShape(mlp_onnxhandle, 0, output_shape)) //giving the onnx handle the output shape { printf("Failed to set the input shape Err=%d",GetLastError()); return INIT_FAILED; } //--- return(INIT_SUCCEEDED); }
Resultados
PR 0 18:57:10.265 ONNX mt5 (EURUSD,H1) ONNX: Creating and using per session threadpools since use_per_session_threads_ is true CN 0 18:57:10.265 ONNX mt5 (EURUSD,H1) ONNX: Dynamic block base set to 0 EE 0 18:57:10.266 ONNX mt5 (EURUSD,H1) ONNX: Initializing session. IM 0 18:57:10.266 ONNX mt5 (EURUSD,H1) ONNX: Adding default CPU execution provider. JN 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Use DeviceBasedPartition as default QK 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Saving initialized tensors. GR 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Done saving initialized tensors RI 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Session successfully initialized. JF 0 18:57:10.269 ONNX mt5 (EURUSD,H1) model has 1 input(s) QR 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 input name is input_1 NF 0 18:57:10.269 ONNX mt5 (EURUSD,H1) type ONNX_TYPE_TENSOR PM 0 18:57:10.269 ONNX mt5 (EURUSD,H1) data type ONNX_TYPE_TENSOR HI 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape [-1, 3] FS 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 input shape must be defined explicitly before model inference NE 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape of input data can be reduced to [3] if undefined dimension set to 1 GD 0 18:57:10.269 ONNX mt5 (EURUSD,H1) model has 1 output(s) GQ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 output name is dense_1 LJ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) type ONNX_TYPE_TENSOR NQ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) data type ONNX_TYPE_TENSOR LF 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape [-1, 1] KQ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 output shape must be defined explicitly before model inference CO 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape of output data can be reduced to [1] if undefined dimension set to 1 GR 0 18:57:10.269 ONNX mt5 (EURUSD,H1) Inputs & Outputs IE 0 18:57:10.269 ONNX mt5 (EURUSD,H1) -1 3 CK 0 18:57:10.269 ONNX mt5 (EURUSD,H1) -1 1
Recuperando dados reais
Como eu mencionei anteriormente, os dados ao vivo devem ser obtidos do mercado e normalizados da mesma forma que foram normalizados ao coletar dados para treinamento.
Inside ONNX mt5.mq5 EA
matrixf GetLiveData(uint start, uint total) { matrixf return_matrix(total, 3); OPEN.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_LOW, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); if (!norm_x.Normalization(return_matrix)) Print("Failed to Normalize"); return return_matrix; }
Uma instância de trabalho da classe norm_x foi declarada dentro da função LoadNormParams() em OnInit. A função carrega os parâmetros de normalização salvos do respectivo arquivo CSV.
Inside ONNX mt5.mq5 EA
bool LoadNormParams() { vectorf min = {}, max ={}, mean={} , std = {}; csv_name_ = Symbol()+"."+EnumToString(Period())+"."+string(total_bars); switch(NORM) { case NORM_MEAN_NORM: mean = ReadCsvVector(normparams_folder+csv_name_+".mean_norm_scaler.mean.csv"); //--- Loading the mean min = ReadCsvVector(normparams_folder+csv_name_+".mean_norm_scaler.min.csv"); //--- Loading the min max = ReadCsvVector(normparams_folder+csv_name_+".mean_norm_scaler.max.csv"); //--- Loading the max if (MQLInfoInteger(MQL_DEBUG)) Print(EnumToString(NORM),"\nMean ",mean,"\nMin ",min,"\nMax ",max); norm_x = new CPreprocessing<vectorf,matrixf>(max, mean, min); if (mean.Sum()<=0 && min.Sum()<=0 && max.Sum() <=0) return false; break; case NORM_MIN_MAX_SCALER: min = ReadCsvVector(normparams_folder+csv_name_+".min_max_scaler.min.csv"); //--- Loading the min max = ReadCsvVector(normparams_folder+csv_name_+".min_max_scaler.max.csv"); //--- Loading the max if (MQLInfoInteger(MQL_DEBUG)) Print(EnumToString(NORM),"\nMin ",min,"\nMax ",max); norm_x = new CPreprocessing<vectorf,matrixf>(max, min); if (min.Sum()<=0 && max.Sum() <=0) return false; break; case NORM_STANDARDIZATION: mean = ReadCsvVector(normparams_folder+csv_name_+".standardization_scaler.mean.csv"); //--- Loading the mean std = ReadCsvVector(normparams_folder+csv_name_+".standardization_scaler.std.csv"); //--- Loading the std if (MQLInfoInteger(MQL_DEBUG)) Print(EnumToString(NORM),"\nMean ",mean,"\nStd ",std); norm_x = new CPreprocessing<vectorf,matrixf>(mean, std, NORM_STANDARDIZATION); if (mean.Sum()<=0 && std.Sum() <=0) return false; break; } return true; }
05: Executando o modelo em tempo real
Para usar o modelo dentro da função OnTick, é necessário chamar a função OnnxRun e passar a ela o handle ONNX, o vetor ou a matriz de valores float tanto para dados de entrada quanto para previsões.
Inside ONNX mt5.mq5 EA
void OnTick() { //--- matrixf input_data = GetLiveData(0,1); vectorf output_data(1); //It is very crucial to resize this vector or matrix if (!OnnxRun(mlp_onnxhandle, ONNX_NO_CONVERSION, input_data, output_data)) { Print("Failed to Get the Predictions Err=",GetLastError()); ExpertRemove(); return; } Comment("inputs_data\n",input_data,"\npredictions\n",output_data); }
O vetor de saída ou matriz de float precisa ser ajustado para evitar o erro 5805 (ERR_ONNX_INVALID_PARAMETER). Como eu tenho apenas uma saída na rede neural, eu ajustei o tamanho desse vetor para ter tamanho 1. Se eu estivesse usando uma matriz, teria que ajustar seu tamanho para 1 linha e 1 coluna.
Resultados
Tudo está funcionando normalmente. Agora estamos usando um modelo de rede neural, criado e treinado usando Python dentro do MetaTrader5. O procedimento não é tão complicado.
Vantagens de usar ONNX em MQL5
- Interoperabilidade: ONNX fornece um formato comum para representar modelos de aprendizado profundo. Este formato permite que modelos treinados em um ambiente de aprendizado profundo (como TensorFlow, PyTorch ou scikit-learn) sejam usados em MQL5 sem a necessidade de uma extensa re-implementação do modelo. Isso pode economizar muito tempo, já que não precisaremos mais codificar modelos do zero para fazê-los funcionar em MQL5.
- Flexibilidade: ONNX suporta uma ampla gama de tipos de modelos de aprendizado profundo: desde redes neurais tradicionais de feedforward até modelos mais complexos, como redes neurais recorrentes (RNN) e redes neurais convolucionais (CNN). Essa flexibilidade o torna adequado para uma variedade de aplicações.
- Eficiência: modelos ONNX podem ser otimizados para implantação eficiente em diferentes equipamentos e plataformas. Isso significa que você pode implantar modelos em dispositivos periféricos, dispositivos móveis, servidores na nuvem e até em aceleradores de hardware especializados.
- Suporte da comunidade: ONNX conta com um suporte significativo da comunidade. Plataformas de aprendizado profundo líderes, como TensorFlow, PyTorch e scikit-learn, suportam a exportação de modelos para o formato ONNX, enquanto várias engines de execução, como ONNX Runtime, facilitam a implantação de modelos ONNX.
- Ampla ecossistema: ONNX está integrado em diversos pacotes de software, existindo um extenso conjunto de ferramentas para trabalhar com modelos ONNX. Com essas ferramentas, você pode converter, otimizar e executar modelos no formato ONNX.
- Compatibilidade entre plataformas: Modelos exportados no formato ONNX podem ser executados em diferentes sistemas operacionais e hardware sem modificações.
- Evolução do Modelo: ONNX suporta o gerenciamento de versões e a evolução do modelo. Você pode melhorar e expandir seus modelos ao longo do tempo, mantendo a compatibilidade com versões anteriores.
- Padronização: ONNX está se tornando o padrão de facto para interação entre diferentes plataformas de aprendizado profundo, simplificando para a comunidade a troca de modelos e ferramentas.
Considerações finais
ONNX é particularmente valioso em cenários onde você precisa usar modelos em diferentes plataformas, implantar modelos em diversas plataformas ou colaborar com outras pessoas que podem estar usando diferentes ferramentas de aprendizado profundo. Ele simplifica o processo de trabalhar com modelos de aprendizado profundo e, à medida que o ecossistema continua a crescer, os benefícios do ONNX se tornam ainda mais significativos. Neste artigo, examinamos cinco etapas importantes que você precisa realizar para começar com um modelo operacional. Pelo menos, você pode expandir esse código de acordo com suas necessidades. Além disso, para que o programa funcione com o testador de estratégias, os arquivos CSV de normalização precisam ser lidos dentro do testador. Isso não foi abordado no artigo atual.
Obrigado pela atenção!
Arquivo | Aplicação |
---|---|
neuralnet.py | Arquivo principal do script Python. Contém toda a implementação da rede neural em Python. |
ONNX mt5.mq5 | Expert Advisor que demonstra como usar o modelo ONNX na negociação. |
ONNX get data.mq5 | Script para coletar e preparar dados para uso conjunto com o script Python. |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13394





- 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