English Русский 中文 Deutsch 日本語 Português
preview
Dominando ONNX: Un punto de inflexión para los tráders de MQL5

Dominando ONNX: Un punto de inflexión para los tráders de MQL5

MetaTrader 5Sistemas comerciales | 20 marzo 2024, 12:35
221 0
Omega J Msigwa
Omega J Msigwa

"La posibilidad de exportar e importar modelos de IA en formato ONNX simplifica el proceso de desarrollo, ahorrando tiempo y recursos al integrar la IA en ecosistemas escritos en diferentes idiomas".


Introducción

No se puede negar que vivimos en la era de la inteligencia artificial (IA) y el aprendizaje automático: cada día surgen nuevas tecnologías basadas en IA, que se aplican en las finanzas, el arte, los juegos, la educación y muchas otras esferas de la vida.

Para nosotros, los tráders, la IA resulta útil porque nos permite detectar patrones y relaciones que no podemos ver a simple vista.

A pesar de su aparente poder y magia, detrás de los modelos de IA hay operaciones matemáticas complejas cuya correcta comprensión e implementación requieren una enorme cantidad de trabajo y un alto grado de precisión. Afortunadamente, el código fuente abierto facilita mucho las cosas al eliminar la necesidad de implementar un modelo de aprendizaje automático desde cero.

Hoy en día, no resulta imprescindible ser un genio de las matemáticas y la programación para crear e implementar modelos de IA. Todo lo que necesitamos es una comprensión básica del lenguaje de programación específico o de las herramientas que deseamos utilizar en nuestros proyectos. A veces ni siquiera necesitamos tener una computadora. Gracias a servicios como Google Colab, podemos codificar, crear y ejecutar modelos de IA usando Python de forma gratuita.

La implementación de modelos de aprendizaje automático utilizando Python y otros lenguajes de programación populares y desarrollados resulta relativamente sencillo, lo cual no se puede decir de MQL5. A menos que deseemos reinventar la rueda creando modelos de aprendizaje automático en MQL5 desde cero, que es lo que estamos haciendo en esta serie de artículos, le recomiendo encarecidamente usar ONNX para integrar modelos de IA creados con Python. Afortunadamente, ONNX ahora es compatible con MQL5.

  onnx-mql5

Para comprender este artículo, necesitaremos conocimientos básicos de IA y aprendizaje automático. Encontrará información aquí y aquí


¿Qué es ONNX?

ONNX (Open Neural Network Exchange) es una biblioteca abierta para representar modelos de aprendizaje profundo y automático. Permite convertir modelos entrenados en un marco de aprendizaje profundo a un formato común que se puede usar en otros marcos, lo cual facilita el trabajo con modelos en diferentes plataformas y herramientas.

Esto significa que podemos crear modelos de aprendizaje automático utilizando cualquier lenguaje que lo admita, salvo MQL5, luego convertir este modelo al formato ONNX y usarlo dentro de nuestro programa MQL5.

En este artículo, usaré Python para implementar el aprendizaje automático tal como lo conozco. Según tengo entendido, podemos utilizar otros lenguajes, aunque no estoy seguro de esto. Por cierto, parece que toda la documentación de ONNX está basada en Python. Creo que ONNX está diseñado para Python en este momento. Esto tiene sentido porque no creo que exista ningún otro lenguaje aparte de Python con bibliotecas y herramientas avanzadas basadas en IA.


Conceptos básicos en ONNX

Antes de sumergirnos en ONNX, deberíamos familiarizarnos con algunos conceptos clave:

  • Modelo ONNX: El modelo ONNX es una representación de un modelo de aprendizaje automático. Consiste en un grafo de cálculo donde los nodos representan operaciones (por ejemplo, convolución, suma), mientras que las aristas representan el flujo de datos entre operaciones.
  • Nodos: Los nodos del gráfico ONNX representan operaciones o funciones aplicadas a los datos de entrada. Estos nodos pueden ser operaciones tales como convolución, suma u operaciones personalizadas.
  • Tensores: Los tensores son arrays multidimensionales que representan los datos transmitidos ​​entre los nodos en un grafo computacional. Pueden ser datos de entrada, de salida o intermedios.
  • Operadores: Los operadores son funciones aplicadas a tensores en ONNX. Cada operador representa una operación específica, como la multiplicación de matrices o la suma de elementos.

Creación de modelos en Python e implementación en MQL5 con ayuda de ONNX

Para crear con éxito un modelo de aprendizaje automático en Python, implementaremos este modelo dentro de nuestro asesor, indicador o script MQL5. El modelo requiere algo más que código Python. A continuación analizaremos los pasos más importantes que debemos seguir para asegurarnos de obtener no solo un modelo ONNX, sino también un modelo que realmente produzca los pronósticos precisos que necesitamos;

  1. Recopilación de datos
  2. Normalización de datos en el lado de MQL5 
  3. Construcción de modelos en Python
  4. Obtención del modelo ONNX integrado en MQL5
  5. Ejecución del modelo en tiempo real.


01: Recopilación de datos

La recopilación de datos es lo primero que debemos hacer dentro de nuestro programa MQL5. Creo que resulta mejor recopilar todos los datos dentro de nuestro programa para que coincidan con la forma en que recopilamos datos de entrenamiento y los datos que se utilizan durante el comercio real o la ejecución de un modelo en tiempo real. Tenga en cuenta que la recopilación de datos puede variar según la naturaleza del problema que intentamos resolver. En este artículo trataremos de resolver un problema de regresión. Usaremos datos OHLC (Open, High, Low, Close) como nuestro conjunto de datos principal, donde Open, High y Low se utilizarán como variables independientes, mientras que los valores de Close se usarán como variable objetivo.

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 función recopilará las variables independientes OHL y la variable objetivo CLOSE. En el aprendizaje automático supervisado, la variable objetivo debe especificarse y transmitirse al modelo para que pueda aprender de ella y comprender los patrones entre la variable objetivo y el resto de variables. En nuestro caso, el modelo intentará comprender cómo las lecturas de estos indicadores provocan un movimiento alcista o bajista. 

Cuando implementamos el modelo, necesitamos recopilar datos de la misma forma, salvo que esta vez recopilaremos datos sin la variable objetivo porque queremos que nuestro modelo entrenado resuelva esto. Básicamente, hablamos de previsiones.

Precisamente por eso existe otra función GetLiveData para descargar nuevos datos y realizar pronósticos de mercado rápidos.

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;
 }

Recopilación de los datos de entrenamiento

    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]

Obtención de datos reales

Obtención de los datos OHL actuales de una 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]]

La forma de cargar datos en tiempo real puede resultar ligeramente distinta al preparar datos para modelos de pronóstico de series temporales como RNN, GRU y LSTM

¡Tenga en cuenta que he utilizado matrixf, que indica una matriz de punto flotante en lugar de matrix normal! Esto es necesario para garantizar que sean compatibles los tipos de datos entre MQL5 y Python. Asegúrese de que el tipo de datos de entrada sea compatible con el tipo de datos esperado por el modelo ONNX. Si su modelo ONNX espera datos de entrada float32, asegúrese de que su entrada también sea float32. ONNX es compatible con float32 y float64. La vulneración de la compatibilidad produce los siguientes errores;
error 2023.09.18 18:03:53.212   ONNX: invalid parameter size, expected 1044480 bytes instead of 32640


02: Normalización de datos en el lado de MQL5

La normalización de datos es una de las cosas más importantes que se deben hacer correctamente para un conjunto de datos que será usado por un modelo de aprendizaje automático.

Tenga en cuenta que el método de normalización usado para preparar los datos de entrenamiento deberá ser el mismo que el utilizado para preparar la prueba y los datos reales. Esto significa que cuando se usa el método MinMaxScaler, los valores min y max, que son variables fundamentales en la ecuación de MinMaxScaler utilizada para preparar los datos de entrenamiento, deberán usarse para continuar normalizando los nuevos datos que serán procesados ​​por el modelo en otro lugar. Para lograr esta coherencia, deberemos guardar las variables para cada método de normalización en un archivo csv:

La normalización de datos está destinada únicamente a variables independientes. No importa qué problema intentemos resolver, no necesitaremos normalizar la variable de destino.

Usaremos la clase Preprocessing, presentada aquí.

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

parámetros de normalización

Al usar Standardization Scaler dentro de archivos csv, las opciones se veían así:


Tenga en cuenta que la normalización también está integrada dentro de la función GetData. Como la normalización es tan importante, cada matriz de datos retornada por ambas funciones responsables de recopilar los datos deberá ser una matriz con valores de precios 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, los datos se han guardado en formato CSV para poder compartirlos con código Python.


03: Construcción de modelos en Python

Construiremos una red neuronal de perceptrón multicapa, pero usted puede construir cualquier modelo que elija, sin limitarse a este tipo de modelo en particular. Instale Python si aún no lo tiene. Luego instale virtualenv ejecutando los siguientes comandos desde Windows CMD (¡no se confunda con Powershell!)

$ pip3 install virtualenv

Después del inicio,

$ virtualenv venv

Esto creará un entorno virtual de Python para su computadora con Windows. Este proceso puede resultar ligeramente diferente para usuarios de Mac y Linux. Después de eso, inicie el entorno virtual ejecutando este comando:

$ venv\Scripts\activate

Una vez hecho esto, instale todas las dependencias usadas en este tutorial ejecutando

$ pip install -r requirements.txt #This txt file is found at https://github.com/MegaJoctan/ONNX-MQL5/blob/main/requirements.txt

Siempre resulta importante aislar el proyecto creando un entorno virtual para evitar conflictos entre módulos y versiones de Python y hacer que el proyecto sea más fácil de compartir.

Importar e inicializar 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"

Necesitamos comprobar si la ruta existe. Si no existe, significará que no hemos recopilado los datos de 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()

Construcción de una red neuronal multicapa del perceptrón (MLP)

Vamos a incluir un NN MLP dentro de una clase para convertir nuestro código en secciones legibles;

01: Inicializando una clase

Los datos se recopilarán y dividirán en conjuntos de entrenamiento y prueba, con variables importantes declaradas disponibles para uso de toda la clase.

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

pd head

02: Construcción de una red neuronal

Nuestra red neuronal de una sola capa estará definida por un número determinado de neuronas.

    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

descripción coherente del modelo

03: Entrenamiento y prueba de un modelo de red neuronal.

    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

precisión del entrenamiento

El modelo de red neuronal ha logrado una precisión del 93% durante el entrenamiento y alrededor del 95% durante las pruebas. Existe cierto riesgo de ajuste, pero seguiremos trabajando con ello.

04: Almacenamiento del modelo ONNX

Le recomendamos guardar el modelo después de que el entrenamiento se haya completado exitosamente y después de haber verificado el rendimiento del modelo tanto en el entrenamiento como en las pruebas fuera de muestra. Necesitamos añadir el código de tiempo de ejecución ONNX para conservar el modelo durante la función train_network en nuestra clase. En primer lugar, necesitaremos instalar dos bibliotecas: onnx y 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

onnx guardado

Quizás haya notado que hemos elegido almacenar el modelo ONNX en el directorio principal Files. ¿Y por qué ahí? Esto se debe a que el archivo ONNX resulta más fácil de incluir en nuestro programa MQL5 como recurso, como por ejemplo un asesor o un indicador.


04: Obtención del modelo ONNX integrado en MQL5

#resource "\\Files\\ONNX Models\\MLP.REG.CLOSE.10000.onnx" as uchar RNNModel[]

El comando importa el modelo ONNX y lo guarda dentro del array RNNModel uchar

Lo siguiente que deberemos hacer es definir el identificador ONNX como una variable global y crear el identificador dentro de la función 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

Obtención de datos reales

Como hemos dicho antes, los datos en vivo deben obtenerse del mercado y normalizarse de la misma forma que se normalizó al recopilar datos de entrenamiento.

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;
 }

El ejemplar funcional de la clase norm_x se ha declarado dentro de la función LoadNormParams()en OnInit. La función cargará los parámetros de normalización guardados desde el archivo CSV correspondiente.

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: Ejecución del modelo en tiempo real.

Para usar el modelo dentro de la función OnTick, deberemos llamar a la función OnnxRun y ​​transmitirle el manejador ONNX, un vector o una matriz de valores float tanto para los datos de entrada como para las predicciones.

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

El vector de salida o la matriz float se deberán modificar para evitar el error 5805 (ERR_ONNX_INVALID_PARAMETER). Como solo tenemos una salida en la red neuronal, hemos cambiado el tamaño de este vector para que sea de tamaño1. Si luego usáramos una matriz, deberíamos cambiar su tamaño a 1 fila y 1 columna.

Resultados

gráfico

Todo funciona como es debido. Actualmente estamos usando un modelo de red neuronal creado y entrenado con Python dentro de MetaTrader5. El procedimiento no es tan complicado.


Ventajas de usar ONNX en MQL5

  1. Interacción: ONNX ofrece un formato común para representar modelos de aprendizaje profundo. Este formato permite modelos entrenados en un único marco de aprendizaje profundo (como TensorFlow, PyTorch o scikit-learn); se puede utilizar en MQL5 sin necesidad de una reimplementación exhaustiva del modelo. Esto puede ahorrarnos mucho tiempo, ya que ya no tendremos que codificar modelos desde cero para que funcionen en MQL5.
  2. Flexibilidad: ONNX admite una amplia gama de tipos de modelos de aprendizaje profundo, desde redes neuronales tradicionales hasta modelos más complejos, como las redes neuronales recurrentes (RNN) y las redes neuronales convolucionales (CNN). Esta flexibilidad lo hace adecuado para una amplia variedad de aplicaciones.
  3. Eficiencia: los modelos ONNX se pueden optimizar para una implementación eficiente en toda una variedad de hardware y plataformas. Esto significa que podemos implementar modelos en dispositivos perimetrales, dispositivos móviles, servidores en la nube e incluso aceleradores de hardware dedicados.
  4. Soporte comunitario: ONNX cuenta con un importante apoyo comunitario. Los principales marcos de aprendizaje profundo, como TensorFlow, PyTorch y scikit-learn, admiten la exportación de modelos al formato ONNX, y varios motores de ejecución, como ONNX Runtime, facilitan la implementación de modelos ONNX.
  5. Amplio ecosistema: ONNX está integrado en varios paquetes de software; además, existe un amplio conjunto de herramientas para trabajar con modelos ONNX. Con estas herramientas podremos convertir, optimizar y ejecutar modelos en formato ONNX.
  6. Compatibilidad multiplataforma: Los modelos exportados en formato ONNX pueden ejecutarse en distintos sistemas operativos y hardware sin modificaciones. 
  7. Evolución del modelo: ONNX admite versiones y evolución de modelos. Podremos mejorar y ampliar nuestros modelos con el tiempo manteniendo la compatibilidad con versiones anteriores.
  8. Estandarización: ONNX se está convirtiendo en el estándar de facto para la interoperabilidad entre diferentes plataformas de aprendizaje profundo, lo cual facilita que la comunidad comparta modelos y herramientas. 


Conclusión

ONNX resulta especialmente valioso en escenarios en los que necesitamos utilizar modelos en diferentes plataformas, implementar modelos en plataformas distintas o colaborar con otras personas que puedan estar usando diferentes herramientas de aprendizaje profundo. Simplifica el proceso de trabajo con modelos de aprendizaje profundo y, a medida que el ecosistema continúa creciendo, los beneficios de ONNX se vuelven aún más significativos. En este artículo, hemos abarcado cinco pasos importantes que debemos seguir para comenzar con un modelo funcional. Como mínimo, podemos ampliar este código para adaptarlo a nuestras necesidades. Además, para que el programa funcione con el simulador de estrategias, los archivos CSV de normalización deberán leerse dentro del simulador. No hemos analizado esto en el artículo actual. 

¡Gracias por su atención!

Archivo Aplicación
neuralnet.py  Archivo del script principal de Python. Contiene la implementación completa de la red neuronal en Python.
ONNX mt5.mq5  Asesor que muestra cómo utilizar el modelo ONNX en el trading
ONNX get data.mq5  Script para recopilar y preparar datos para el uso conjunto con un script de Python


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13394

Archivos adjuntos |
MQL5-CODE.zip (9.14 KB)
Trabajamos con fechas y horas en MQL5 Trabajamos con fechas y horas en MQL5
Resulta esencial que los tráders y desarrolladores de herramientas comerciales comprendan cómo manejar las fechas y horas de manera adecuada y eficaz. En este artículo, veremos cómo podemos manejar fechas y horas al crear herramientas comerciales efectivas.
Modelos de clasificación de la biblioteca Scikit-learn y su exportación a ONNX Modelos de clasificación de la biblioteca Scikit-learn y su exportación a ONNX
En este artículo, analizaremos el uso de todos los modelos de clasificación del paquete Scikit-learn para resolver el problema de la clasificación de los iris de Fisher; asimismo, intentaremos convertir estos al formato ONNX y usar los modelos resultantes en programas MQL5. También compararemos la precisión de los modelos originales y sus versiones ONNX en el conjunto de datos completo Iris dataset.
Redes neuronales: así de sencillo (Parte 60): Online Decision Transformer (ODT) Redes neuronales: así de sencillo (Parte 60): Online Decision Transformer (ODT)
En los 2 últimos artículos nos hemos centrado en el método Decision Transformer, que modela las secuencias de acciones en el contexto de un modelo autorregresivo de recompensas deseadas. En el artículo de hoy, analizaremos otro algoritmo para optimizar este método.
Redes neuronales: así de sencillo (Parte 59): Dicotomía de control (DoC) Redes neuronales: así de sencillo (Parte 59): Dicotomía de control (DoC)
En el artículo anterior nos familiarizamos con el transformador de decisión. Sin embargo, el complejo entorno estocástico del mercado de divisas no nos permitió aprovechar plenamente el potencial del método presentado. Hoy veremos un algoritmo que tiene como objetivo mejorar el rendimiento de los algoritmos en entornos estocásticos.