English Русский Deutsch 日本語
preview
Aprendizaje automático y Data Science (Parte 32): Mantener actualizados los modelos de IA, aprendizaje en línea

Aprendizaje automático y Data Science (Parte 32): Mantener actualizados los modelos de IA, aprendizaje en línea

MetaTrader 5Estadística y análisis |
70 3
Omega J Msigwa
Omega J Msigwa

Contenido


¿Qué es el aprendizaje en línea?

El aprendizaje automático en línea es un método de aprendizaje automático en el que el modelo aprende de forma incremental a partir de un flujo de puntos de datos en tiempo real. Es un proceso dinámico que adapta su algoritmo predictivo a lo largo del tiempo, lo que permite que el modelo cambie a medida que llegan nuevos datos. Este método es increíblemente significativo en entornos ricos en datos que evolucionan rápidamente, como los datos comerciales, ya que puede proporcionar predicciones oportunas y precisas.

Al trabajar con datos comerciales, siempre es difícil determinar el momento adecuado para actualizar sus modelos y con qué frecuencia; por ejemplo, si tiene modelos de IA entrenados en Bitcoin durante el último año, la información reciente podría resultar ser valores atípicos para un modelo de aprendizaje automático considerando que esta criptomoneda alcanzó su nuevo precio más alto la semana pasada.

A diferencia de los instrumentos de divisas, que suelen subir y bajar dentro de rangos específicos históricamente, instrumentos como el NASDAQ 100, el S&P 500 y otros de su tipo, así como las acciones, suelen tender a aumentar y alcanzar nuevos valores máximos.

El aprendizaje en línea no solo se debe al temor de que la información de formación antigua quede obsoleta, sino también a la necesidad de mantener el modelo actualizado con información reciente que pueda tener algún impacto en lo que está sucediendo actualmente en el mercado.


Ventajas del aprendizaje en línea

  • Adaptabilidad

    Al igual que los ciclistas aprenden sobre la marcha, el aprendizaje automático en línea puede adaptarse a nuevos patrones en los datos, lo que puede mejorar su rendimiento con el tiempo.
  • Escalabilidad

    Algunos métodos de aprendizaje en línea para algunos modelos procesan los datos de uno en uno. Esto hace que esta técnica sea más segura para los recursos computacionales limitados con los que contamos la mayoría de nosotros, lo que finalmente puede ayudar a escalar modelos que dependen de big data.
  • Predicciones en tiempo real

    A diferencia del aprendizaje por lotes, que puede quedar obsoleto en el momento de su implementación, el aprendizaje en línea proporciona información en tiempo real que puede ser fundamental en muchas aplicaciones comerciales.
  • Eficiencia

    El aprendizaje automático incremental permite el aprendizaje y la actualización continuos de los modelos, lo que puede dar lugar a un proceso de formación más rápido y rentable.

Ahora que comprendemos varias ventajas de esta técnica, veamos la infraestructura necesaria para que el aprendizaje en línea en MetaTrader 5 sea eficaz.


Infraestructura de aprendizaje en línea para MetaTrader 5 

Infraestructura de aprendizaje en línea para MetaTrader 5

Infraestructura de aprendizaje en línea para MetaTrader 5


Paso 1: Cliente Python

Dentro de un cliente Python (script) es donde queremos crear modelos de IA basados en los datos de trading recibidos de MetaTrader 5.

Utilizando MetaTrader 5 (biblioteca Python), comenzamos inicializando la plataforma.

import pandas as pd
import numpy as np
import MetaTrader5 as mt5
from datetime import datetime

if not mt5.initialize(): # Initialize the MetaTrader 5 platform
    print("initialize() failed")
    mt5.shutdown()

Tras la inicialización de la plataforma MetaTrader 5, podemos obtener información comercial de ella utilizando el método copy_rates_from_pos

def getData(start = 1, bars = 1000):

    rates = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, start, bars)
  
   if len(rates) < bars: # if the received information is less than specified
        print("Failed to copy rates from MetaTrader 5, error = ",mt5.last_error())

    # create a pnadas DataFrame out of the obtained data

    df_rates = pd.DataFrame(rates)
                                                
    return df_rates

Podemos imprimir para ver la información obtenida.

print("Trading info:\n",getData(1, 100)) # get 100 bars starting at the recent closed bar

Salidas

           time     open     high      low    close  tick_volume  spread  real_volume
0   1731351600  1.06520  1.06564  1.06451  1.06491         1688       0            0
1   1731355200  1.06491  1.06519  1.06460  1.06505         1607       0            0
2   1731358800  1.06505  1.06573  1.06495  1.06512         1157       0            0
3   1731362400  1.06512  1.06564  1.06512  1.06557         1112       0            0
4   1731366000  1.06557  1.06579  1.06553  1.06557          776       0            0
..         ...      ...      ...      ...      ...          ...     ...          ...
95  1731693600  1.05354  1.05516  1.05333  1.05513         5125       0            0
96  1731697200  1.05513  1.05600  1.05472  1.05486         3966       0            0
97  1731700800  1.05487  1.05547  1.05386  1.05515         2919       0            0
98  1731704400  1.05515  1.05522  1.05359  1.05372         2651       0            0
99  1731708000  1.05372  1.05379  1.05164  1.05279         2977       0            0

[100 rows x 8 columns]

Utilizamos el método copy_rates_from_pos, ya que nos permite acceder a la barra cerrada recientemente situada en el índice 1, lo cual resulta muy útil en comparación con el acceso mediante fechas fijas.

Siempre podemos estar seguros de que al copiar desde la barra situada en el índice 1, obtendremos la información que comienza en la barra recientemente cerrada hasta el número específico de barras que deseemos.

Después de recibir esta información, podemos realizar las tareas típicas de aprendizaje automático para estos datos.

Creamos un archivo independiente para nuestro modelo. Al colocar cada modelo en su propio archivo, facilitamos la llamada a estos modelos en el archivo «main.py», donde se implementan todos los procesos y funciones clave.

Archivo catboost_models.py

from catboost import CatBoostClassifier
from sklearn.metrics import accuracy_score

from onnx.helper import get_attribute_value
from skl2onnx import convert_sklearn, update_registered_converter
from sklearn.pipeline import Pipeline
from skl2onnx.common.shape_calculator import (
    calculate_linear_classifier_output_shapes,
)  # noqa
from skl2onnx.common.data_types import (
    FloatTensorType,
    Int64TensorType,
    guess_tensor_type,
)
from skl2onnx._parse import _apply_zipmap, _get_sklearn_operator_name
from catboost.utils import convert_to_onnx_object

# Example initial data (X_initial, y_initial are your initial feature matrix and target)

class CatBoostClassifierModel():
    def __init__(self, X_train, X_test, y_train, y_test):

        self.X_train = X_train
        self.X_test = X_test
        self.y_train = y_train
        self.y_test = y_test
        self.model = None

    def train(self, iterations=100, depth=6, learning_rate=0.1, loss_function="CrossEntropy", use_best_model=True):
        # Initialize the CatBoost model

        params = {
            "iterations": iterations,
            "depth": depth,
            "learning_rate": learning_rate,
            "loss_function": loss_function,
            "use_best_model": use_best_model
        }

        self.model = Pipeline([ # wrap a catboost classifier in sklearn pipeline | good practice (not necessary tho :))
            ("catboost", CatBoostClassifier(**params))
        ])

        # Testing the model
        
        self.model.fit(X=self.X_train, y=self.y_train, catboost__eval_set=(self.X_test, self.y_test))

        y_pred = self.model.predict(self.X_test)
        print("Model's accuracy on out-of-sample data = ",accuracy_score(self.y_test, y_pred))

    # a function for saving the trained CatBoost model to ONNX format

    def to_onnx(self, model_name):

        update_registered_converter(
            CatBoostClassifier,
            "CatBoostCatBoostClassifier",
            calculate_linear_classifier_output_shapes,
            self.skl2onnx_convert_catboost,
            parser=self.skl2onnx_parser_castboost_classifier,
            options={"nocl": [True, False], "zipmap": [True, False, "columns"]},
        )

        model_onnx = convert_sklearn(
            self.model,
            "pipeline_catboost",
            [("input", FloatTensorType([None, self.X_train.shape[1]]))],
            target_opset={"": 12, "ai.onnx.ml": 2},
        )

        # And save.
        with open(model_name, "wb") as f:
            f.write(model_onnx.SerializeToString())

Para obtener más información sobre este modelo CatBoost implementado, consulte este artículo. He utilizado el modelo CatBoost como ejemplo, pero puede utilizar cualquiera de sus modelos preferidos.

Ahora que tenemos esta clase para ayudarnos con la inicialización, el entrenamiento y el almacenamiento del modelo CatBoost. Implementemos este modelo en el archivo «main.py».

Archivo main.py

Una vez más, comenzamos recibiendo los datos de la aplicación de escritorio MetaTrader 5.

data = getData(start=1, bars=1000)

Si observas detenidamente el modelo CatBoost, verás que se trata de un modelo clasificador. Aún no tenemos la variable objetivo para este clasificador, creemos una.

# Preparing the target variable

data["future_open"] = data["open"].shift(-1) # shift one bar into the future
data["future_close"] = data["close"].shift(-1)

target = []
for row in range(data.shape[0]):
    if data["future_close"].iloc[row] > data["future_open"].iloc[row]: # bullish signal
        target.append(1)
    else: # bearish signal
        target.append(0)

data["target"] = target # add the target variable to the dataframe

data = data.dropna() # drop empty rows

Podemos eliminar todas las variables futuras y otras características con muchos valores cero de la matriz X 2D, y asignar la variable «target» a la matriz y 1D.

X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
y = data["target"]

A continuación, dividimos la información en muestras de entrenamiento y validación, inicializamos el modelo CatBoost con los datos del mercado y lo entrenamos.

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=42)

catboost_model = catboost_models.CatBoostClassifierModel(X_train, X_test, y_train, y_test)
catboost_model.train()

Por último, guardamos este modelo en formato ONNX en un directorio común de MetaTrader 5.

Paso 2: Carpeta común (Common)

Utilizando MetaTrader 5 Python, podemos obtener la información en la ruta común.

terminal_info_dict = mt5.terminal_info()._asdict()
common_path = terminal_info_dict["commondata_path"]

Aquí es donde queremos guardar todos los modelos de IA entrenados de este cliente Python que tenemos.

Al acceder a la carpeta común mediante MQL5, normalmente se hace referencia a una subcarpeta «Files» que se encuentra dentro de la carpeta común (Common). Para facilitar el acceso a estos archivos desde MQL5, debemos guardar los modelos en esa subcarpeta.

# Save models in a specific location under the common parent folder

models_path = os.path.join(common_path, "Files")

if not os.path.exists(models_path): #if the folder exists
    os.makedirs(models_path) # Create the folder if it doesn't exist

catboost_model.to_onnx(model_name=os.path.join(models_path, "catboost.H1.onnx"))

Por último, tenemos que agrupar todas estas líneas de código en una sola función para que sea más fácil llevar a cabo todos estos procesos diferentes cuando queramos.

def trainAndSaveCatBoost():

    data = getData(start=1, bars=1000)

    # Check if we were able to receive some data

    if (len(data)<=0):
        print("Failed to obtain data from Metatrader5, error = ",mt5.last_error())
        mt5.shutdown()

    # Preparing the target variable

    data["future_open"] = data["open"].shift(-1) # shift one bar into the future
    data["future_close"] = data["close"].shift(-1)

    target = []
    for row in range(data.shape[0]):
        if data["future_close"].iloc[row] > data["future_open"].iloc[row]: # bullish signal
            target.append(1)
        else: # bearish signal
            target.append(0)

    data["target"] = target # add the target variable to the dataframe

    data = data.dropna() # drop empty rows

    X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
    y = data["target"]

    X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=42)

    catboost_model = catboost_models.CatBoostClassifierModel(X_train, X_test, y_train, y_test)
    catboost_model.train()

    # Save models in a specific location under the common parent folder

    models_path = os.path.join(common_path, "Files")

    if not os.path.exists(models_path): #if the folder exists
        os.makedirs(models_path) # Create the folder if it doesn't exist

    catboost_model.to_onnx(model_name=os.path.join(models_path, "catboost.H1.onnx"))

Llamemos a esta función y veamos qué hace.

trainAndSaveCatBoost()
exit() # stop the script

Resultado:

0:      learn: 0.6916088        test: 0.6934968 best: 0.6934968 (0)     total: 163ms    remaining: 16.1s
1:      learn: 0.6901684        test: 0.6936087 best: 0.6934968 (0)     total: 168ms    remaining: 8.22s
2:      learn: 0.6888965        test: 0.6931576 best: 0.6931576 (2)     total: 175ms    remaining: 5.65s
3:      learn: 0.6856524        test: 0.6927187 best: 0.6927187 (3)     total: 184ms    remaining: 4.41s
4:      learn: 0.6843646        test: 0.6927737 best: 0.6927187 (3)     total: 196ms    remaining: 3.72s
...
...
...
96:     learn: 0.5992419        test: 0.6995323 best: 0.6927187 (3)     total: 915ms    remaining: 28.3ms
97:     learn: 0.5985751        test: 0.7002011 best: 0.6927187 (3)     total: 924ms    remaining: 18.9ms
98:     learn: 0.5978617        test: 0.7003299 best: 0.6927187 (3)     total: 928ms    remaining: 9.37ms
99:     learn: 0.5968786        test: 0.7010596 best: 0.6927187 (3)     total: 932ms    remaining: 0us

bestTest = 0.6927187021
bestIteration = 3

Shrink model to first 4 iterations.
Model's accuracy on out-of-sample data =  0.5

El archivo .onnx se puede ver en Common\Files.

Paso 3: MetaTrader 5

Ahora, en MetaTrader 5, tenemos que cargar este modelo guardado en formato ONNX.

Comenzamos importando la librería que nos ayudará con esta tarea.

Dentro de "Online Learning Catboost.mq5":

#include <CatBoost.mqh>
CCatBoost *catboost;

input string model_name = "catboost.H1.onnx";
input string symbol = "EURUSD";
input ENUM_TIMEFRAMES timeframe = PERIOD_H1;

string common_path;

Lo primero que queremos hacer dentro de la función Oninit es comprobar si el archivo existe en la carpeta común. Si no existe, esto podría indicar que el modelo no se ha entrenado.

A continuación, inicializamos el modelo ONNX pasando el indicador ONNX_COMMON_FOLDER para cargar explícitamente el modelo desde la carpeta común «Common».

int OnInit()
  {
//--- Check if the model file exists
  
   if (!FileIsExist(model_name, FILE_COMMON))
     {
       printf("%s Onnx file doesn't exist",__FUNCTION__);
       return INIT_FAILED;
     }
     
//--- Initialize a catboost model
   
  catboost = new CCatBoost(); 
  if (!catboost.Init(model_name, ONNX_COMMON_FOLDER))
    {
      printf("%s failed to initialize the catboost model, error = %d",__FUNCTION__,GetLastError());      
      return INIT_FAILED;
    }
      
//---
}

Para utilizar este modelo cargado para realizar predicciones, podemos volver al script de Python y comprobar qué características se utilizaron para el entrenamiento después de que se eliminaran algunas.

Las mismas características y en el mismo orden deben recopilarse en MQL5.

Archivo de código Python «main.py».

X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
y = data["target"]

print(X.head())

 Resultado:

         time     open     high      low    close  tick_volume
0  1726772400  1.11469  1.11584  1.11453  1.11556         3315
1  1726776000  1.11556  1.11615  1.11525  1.11606         2812
2  1726779600  1.11606  1.11680  1.11606  1.11656         2309
3  1726783200  1.11656  1.11668  1.11590  1.11622         2667
4  1726786800  1.11622  1.11644  1.11605  1.11615         1166

Ahora, obtengamos esta información dentro de la función OnTick y llamemos a la función predict_bin, que predice clases.

Esta función predecirá dos clases que se observaron en la variable objetivo que preparamos en el cliente Python. 0 (alcista), 1 (bajista).

void OnTick()
  {
//---
     MqlRates rates[];
     CopyRates(symbol, timeframe, 1, 1, rates); //copy the recent closed bar information
     
     vector x = {
                 (double)rates[0].time, 
                 rates[0].open, 
                 rates[0].high, 
                 rates[0].low, 
                 rates[0].close, 
                 (double)rates[0].tick_volume};
     
     Comment(TimeCurrent(),"\nPredicted signal: ",catboost.predict_bin(x)==0?"Bearish":"Bullish");// if the predicted signal is 0 it means a bearish signal, otherwise it is a bullish signal
  }

Resultado:


Automatización del proceso de formación y despliegue

Hemos podido entrenar e implementar el modelo en MetaTrader 5, pero esto no es lo que queremos, nuestro objetivo principal es automatizar todo el proceso. 

Dentro del entorno virtual Python, tenemos que instalar la biblioteca schedule.

$ pip install schedule

Este pequeño módulo puede ayudar a programar cuándo queremos que se ejecute una función específica. Como ya hemos agrupado el código para recopilar datos, entrenar y guardar el modelo en una sola función, programemos esta función para que se ejecute cada minuto.

schedule.every(1).minute.do(trainAndSaveCatBoost) #schedule catboost training

# Keep the script running to execute the scheduled tasks
while True:
    schedule.run_pending()
    time.sleep(60)  # Wait for 1 minute before checking again

Esta función de programación funciona de maravilla. :-)

En nuestro asesor experto principal, también programamos cuándo y con qué frecuencia nuestro EA debe cargar el modelo desde el directorio común, con lo que actualizamos eficazmente el modelo para nuestro robot de trading.

Podemos usar la función OnTimer, que también funciona a las mil maravillas.

int OnInit()
  {
//--- Check if the model file exists
  
  ....
     
//--- Initialize a catboost model
   
....
      
//---

   if (!EventSetTimer(60)) //Execute the OnTimer function after every 60 seconds
     {
       printf("%s failed to set the event timer, error = %d",__FUNCTION__,GetLastError());
       return INIT_FAILED;
     }
    
    
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
    if (CheckPointer(catboost) != POINTER_INVALID)
      delete catboost;
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
     ....
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
    if (CheckPointer(catboost) != POINTER_INVALID)
      delete catboost; 
      
//--- Load the new model after deleting the prior one from memory

     catboost = new CCatBoost(); 
     if (!catboost.Init(model_name, ONNX_COMMON_FOLDER))
       {
         printf("%s failed to initialize the catboost model, error = %d",__FUNCTION__,GetLastError());      
         return;
       }
       
     printf("%s New model loaded",TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
  }

Resultado:

HO      0       13:14:00.648    Online Learning Catboost (EURUSD,D1)    2024.11.18 12:14 New model loaded
FK      0       13:15:55.388    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:15 New model loaded
JG      0       13:16:55.380    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:16 New model loaded
MP      0       13:17:55.376    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:17 New model loaded
JM      0       13:18:55.377    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:18 New model loaded
PF      0       13:19:55.368    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:19 New model loaded
CR      0       13:20:55.387    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:20 New model loaded
NO      0       13:21:55.377    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:21 New model loaded
LH      0       13:22:55.379    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:22 New model loaded

Ahora que hemos visto cómo se puede programar el proceso de entrenamiento y mantener los nuevos modelos sincronizados con el Asesor Experto en MetaTrader 5. Aunque el proceso es fácil de implementar para la mayoría de las técnicas de aprendizaje automático, puede resultar complicado cuando se trabaja con modelos de aprendizaje profundo, como las redes neuronales recurrentes (RNN), que no pueden incluirse en el proceso pipeline de Sklearn, lo que nos facilita el trabajo cuando utilizamos diversos modelos de aprendizaje automático.

Veamos cómo se puede aplicar esta técnica al trabajar con Gated Recurrent Unit (GRU), que es una forma especial de red neuronal recurrente.


Aprendizaje en línea para modelos de IA de aprendizaje profundo

En el cliente Python

Aplicamos los conceptos típicos del aprendizaje automático dentro de la clase GRUClassifier. Para obtener más información sobre GRU, consulte este artículo.

Después de entrenar el modelo, lo guardamos en ONNX, esta vez también guardamos la información del escalador estándar en archivos binarios, lo que nos ayudará más adelante a normalizar de forma similar los nuevos datos en MQL5 tal y como están actualmente en Python.

Archivo gru_models.py

import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import GRU, Dense, Input, Dropout
from keras.callbacks import EarlyStopping
from keras.optimizers import Adam
import tf2onnx


class GRUClassifier():
    def __init__(self, time_step, X_train, X_test, y_train, y_test):

        self.X_train = X_train
        self.X_test = X_test
        self.y_train = y_train
        self.y_test = y_test
        self.model = None
        self.time_step = time_step
        self.classes_in_y = np.unique(self.y_train)


    def train(self, learning_rate=0.001, layers=2, neurons = 50, activation="relu", batch_size=32, epochs=100, loss="binary_crossentropy", verbose=0):

        self.model = Sequential()
        self.model.add(Input(shape=(self.time_step, self.X_train.shape[2]))) 
        self.model.add(GRU(units=neurons, activation=activation)) # input layer


        for layer in range(layers): # dynamically adjusting the number of hidden layers

            self.model.add(Dense(units=neurons, activation=activation))
            self.model.add(Dropout(0.5))

        self.model.add(Dense(units=len(self.classes_in_y), activation='softmax', name='output_layer')) # the output layer

        # Compile the model
        adam_optimizer = Adam(learning_rate=learning_rate)
        self.model.compile(optimizer=adam_optimizer, loss=loss, metrics=['accuracy'])
        

        early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)

        history = self.model.fit(self.X_train, self.y_train, epochs=epochs, batch_size=batch_size,
                                 validation_data=(self.X_test, self.y_test),
                                 callbacks=[early_stopping], verbose=verbose)

        val_loss, val_accuracy = self.model.evaluate(self.X_test, self.y_test, verbose=verbose)

        print("Gru accuracy on validation sample = ",val_accuracy)

            
    def to_onnx(self, model_name, standard_scaler):

        # Convert the Keras model to ONNX
        spec = (tf.TensorSpec((None, self.time_step, self.X_train.shape[2]), tf.float16, name="input"),)
        self.model.output_names = ['outputs']

        onnx_model, _ = tf2onnx.convert.from_keras(self.model, input_signature=spec, opset=13)

        # Save the ONNX model to a file
        with open(model_name, "wb") as f:
            f.write(onnx_model.SerializeToString())

        # Save the mean and scale parameters to binary files
        standard_scaler.mean_.tofile(f"{model_name.replace('.onnx','')}.standard_scaler_mean.bin")
        standard_scaler.scale_.tofile(f"{model_name.replace('.onnx','')}.standard_scaler_scale.bin")

Dentro del archivo «main.py», creamos una función responsable de todo lo que queremos que suceda con el modelo GRU. 

def trainAndSaveGRU():

    data = getData(start=1, bars=1000)

    # Preparing the target variable

    data["future_open"] = data["open"].shift(-1)
    data["future_close"] = data["close"].shift(-1)

    target = []
    for row in range(data.shape[0]):
        if data["future_close"].iloc[row] > data["future_open"].iloc[row]:
            target.append(1)
        else:
            target.append(0)

    data["target"] = target

    data = data.dropna()

    # Check if we were able to receive some data

    if (len(data)<=0):
        print("Failed to obtain data from Metatrader5, error = ",mt5.last_error())
        mt5.shutdown()

    X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
    y = data["target"]

    X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=False)

    ########### Preparing data for timeseries forecasting ###############

    time_step = 10 

    scaler = StandardScaler()

    X_train = scaler.fit_transform(X_train)
    X_test = scaler.transform(X_test)

    x_train_seq, y_train_seq = create_sequences(X_train, y_train, time_step)
    x_test_seq, y_test_seq = create_sequences(X_test, y_test, time_step)

    ###### One HOt encoding #######

    y_train_encoded = to_categorical(y_train_seq)
    y_test_encoded = to_categorical(y_test_seq)

    gru = gru_models.GRUClassifier(time_step=time_step,
                                    X_train= x_train_seq, 
                                    y_train= y_train_encoded, 
                                    X_test= x_test_seq, 
                                    y_test= y_test_encoded
                                    )

    gru.train(
        batch_size=64, 
        learning_rate=0.001, 
        activation = "relu",
        epochs=1000,
        loss="binary_crossentropy",
        layers = 2,
        neurons = 50,
        verbose=1
        )
    
    # Save models in a specific location under the common parent folder

    models_path = os.path.join(common_path, "Files")

    if not os.path.exists(models_path): #if the folder exists
        os.makedirs(models_path) # Create the folder if it doesn't exist

    gru.to_onnx(model_name=os.path.join(models_path, "gru.H1.onnx"), standard_scaler=scaler)

Por último, podemos programar la frecuencia con la que se debe ejecutar la función trainAndSaveGRU, de forma similar a como lo hicimos con la función CatBoost.

schedule.every(1).minute.do(trainAndSaveGRU) #scheduled GRU training

Resultado:

Epoch 1/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 7s 87ms/step - accuracy: 0.4930 - loss: 0.6985 - val_accuracy: 0.5000 - val_loss: 0.6958
Epoch 2/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4847 - loss: 0.6957 - val_accuracy: 0.4931 - val_loss: 0.6936
Epoch 3/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.5500 - loss: 0.6915 - val_accuracy: 0.4897 - val_loss: 0.6934
Epoch 4/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4910 - loss: 0.6923 - val_accuracy: 0.4690 - val_loss: 0.6938
Epoch 5/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.5538 - loss: 0.6910 - val_accuracy: 0.4897 - val_loss: 0.6935
Epoch 6/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.5037 - loss: 0.6953 - val_accuracy: 0.4931 - val_loss: 0.6937
Epoch 7/1000
...
...
...
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 22ms/step - accuracy: 0.4964 - loss: 0.6952 - val_accuracy: 0.4793 - val_loss: 0.6940
Epoch 20/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 19ms/step - accuracy: 0.5285 - loss: 0.6914 - val_accuracy: 0.4793 - val_loss: 0.6949
Epoch 21/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.5224 - loss: 0.6935 - val_accuracy: 0.4966 - val_loss: 0.6942
Epoch 22/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 21ms/step - accuracy: 0.5009 - loss: 0.6936 - val_accuracy: 0.5103 - val_loss: 0.6933
10/10 ━━━━━━━━━━━━━━━━━━━━ 0s 19ms/step - accuracy: 0.4925 - loss: 0.6938
Gru accuracy on validation sample =  0.5103448033332825

En MetaTrader 5

Comenzamos cargando las bibliotecas que nos ayudarán con la tarea de cargar el modelo GRU y el escalador estándar.

#include <preprocessing.mqh>
#include <GRU.mqh>

CGRU *gru;
StandardizationScaler *scaler;

//--- Arrays for temporary storage of the scaler values
double scaler_mean[], scaler_std[];

input string model_name = "gru.H1.onnx";

string mean_file;
string std_file;

Lo primero que queremos hacer en la función OnInit es obtener los nombres de los archivos binarios del escalador, aplicamos este mismo principio al crear estos archivos.

 string base_name__ = model_name;
   
 if (StringReplace(base_name__,".onnx","")<0)
   {
     printf("%s Failed to obtain the parent name for the scaler files, error = %d",__FUNCTION__,GetLastError());
     return INIT_FAILED;
   }
  
  mean_file = base_name__ + ".standard_scaler_mean.bin";
  std_file = base_name__ + ".standard_scaler_scale.bin";

Por último, procedemos a cargar el modelo GRU en formato ONNX desde la carpeta común, también leemos los archivos escaladores en formato binario asignando sus valores en las matrices scaler_mean y scaler_std.

int OnInit()
  {
   
   string base_name__ = model_name;
   
   if (StringReplace(base_name__,".onnx","")<0) //we followed this same file patterns while saving the binary files in python client
     {
       printf("%s Failed to obtain the parent name for the scaler files, error = %d",__FUNCTION__,GetLastError());
       return INIT_FAILED;
     }
        
   mean_file = base_name__ + ".standard_scaler_mean.bin";
   std_file = base_name__ + ".standard_scaler_scale.bin";
   
//--- Check if the model file exists

   if (!FileIsExist(model_name, FILE_COMMON))
     {
       printf("%s Onnx file doesn't exist",__FUNCTION__);
       return INIT_FAILED;
     }
  
//--- Initialize the GRU model from the common folder

     gru = new CGRU(); 
     if (!gru.Init(model_name, ONNX_COMMON_FOLDER))
       {
         printf("%s failed to initialize the gru model, error = %d",__FUNCTION__,GetLastError());      
         return INIT_FAILED;
       }

//--- Read the scaler files
   
   if (!readArray(mean_file, scaler_mean) || !readArray(std_file, scaler_std))
     {
       printf("%s failed to read scaler information",__FUNCTION__);
       return INIT_FAILED;
     }  
      
   scaler = new StandardizationScaler(scaler_mean, scaler_std); //Load the scaler class by populating it with values
   
//--- Set the timer

   if (!EventSetTimer(60))
     {
       printf("%s failed to set the event timer, error = %d",__FUNCTION__,GetLastError());
       return INIT_FAILED;
     }
    
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
    if (CheckPointer(gru) != POINTER_INVALID)
      delete gru;
    if (CheckPointer(scaler) != POINTER_INVALID)
      delete scaler;
  }

Programamos el proceso de lectura de los archivos de escalador y modelo de la carpeta común en la función OnTimer.

void OnTimer(void)
  {
//--- Delete the existing pointers in memory as the new ones are about to be created

    if (CheckPointer(gru) != POINTER_INVALID)
      delete gru;
    if (CheckPointer(scaler) != POINTER_INVALID)
      delete scaler;
      
//---
      
   if (!readArray(mean_file, scaler_mean) || !readArray(std_file, scaler_std))
     {
       printf("%s failed to read scaler information",__FUNCTION__);
       return;
     }  
      
   scaler = new StandardizationScaler(scaler_mean, scaler_std);
   
     gru = new CGRU(); 
     if (!gru.Init(model_name, ONNX_COMMON_FOLDER))
       {
         printf("%s failed to initialize the gru model, error = %d",__FUNCTION__,GetLastError());      
         return;
         
       }
     printf("%s New model loaded",TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
  }

Resultado:

II      0       14:49:35.920    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:49 New model loaded
QP      0       14:50:35.886    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
MF      0       14:50:35.919    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
IJ      0       14:50:35.919    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:50 New model loaded
EN      0       14:51:35.894    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
JD      0       14:51:35.913    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
EL      0       14:51:35.913    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:51 New model loaded
NM      0       14:52:35.885    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
KK      0       14:52:35.915    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
QQ      0       14:52:35.915    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:52 New model loaded
DK      0       14:53:35.899    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
HI      0       14:53:35.935    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
MS      0       14:53:35.935    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:53 New model loaded
DI      0       14:54:35.885    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
IL      0       14:54:35.908    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
QE      0       14:54:35.908    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:54 New model loaded

Para recibir las predicciones del modelo GRU, debemos tener en cuenta el valor del intervalo de tiempo, que ayuda a las redes neuronales recurrentes (RNN) a comprender las dependencias temporales en los datos.

Utilizamos un valor de intervalo de tiempo de diez (10) dentro de la función «trainAndSaveGRU».

def trainAndSaveGRU():

    data = getData(start=1, bars=1000)

     ....
     ....

    time_step = 10 

Recopilemos las últimas 10 barras (intervalos de tiempo) del historial a partir de la barra recientemente cerrada en MQL5. (así es como debe ser)

input int time_step = 10;
void OnTick()
  {
//---
     MqlRates rates[];
     CopyRates(symbol, timeframe, 1, time_step, rates); //copy the recent closed bar information
     
     vector classes = {0,1}; //Beware of how classes are organized in the target variable. use numpy.unique(y) to determine this array
     
     matrix X = matrix::Zeros(time_step, 6); // 6 columns
     for (int i=0; i<time_step; i++)
       {         
         vector row = {
                 (double)rates[i].time, 
                 rates[i].open, 
                 rates[i].high, 
                 rates[i].low, 
                 rates[i].close, 
                 (double)rates[i].tick_volume};
         
         X.Row(row, i);
       }     
     
     X = scaler.transform(X); //it's important to normalize the data  
     Comment(TimeCurrent(),"\nPredicted signal: ",gru.predict_bin(X, classes)==0?"Bearish":"Bullish");// if the predicted signal is 0 it means a bearish signal, otherwise it is a bullish signal
  }

Resultado:


Aprendizaje automático incremental

Algunos modelos son más eficaces y robustos que otros en lo que respecta a los métodos de entrenamiento. Cuando se busca «aprendizaje automático en línea» en Internet, la mayoría de la gente dice que es un proceso mediante el cual se vuelven a entrenar pequeños lotes de datos en el modelo con el fin de alcanzar un objetivo de entrenamiento mayor.

El problema es que muchos modelos no son compatibles o no funcionan bien cuando se les proporciona una muestra pequeña de datos.

Las técnicas modernas de aprendizaje automático, como CatBoost, se basan en el aprendizaje incremental. Este método de entrenamiento se puede utilizar para el aprendizaje en línea y puede ayudar a ahorrar mucha memoria cuando se trabaja con grandes volúmenes de datos, ya que estos se pueden dividir en pequeños fragmentos que se pueden volver a entrenar en el modelo inicial.

def getData(start = 1, bars = 1000):

    rates = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, start, bars)

    df_rates = pd.DataFrame(rates)
                                                
    return df_rates

def trainIncrementally():

    # CatBoost model
    clf = CatBoostClassifier(
        task_type="CPU",
        iterations=2000,
        learning_rate=0.2,
        max_depth=1,
        verbose=0,
    )
    
    # Get big data
    big_data = getData(1, 10000)

    # Split into chunks of 1000 samples
    chunk_size = 1000
    chunks = [big_data[i:i + chunk_size].copy() for i in range(0, len(big_data), chunk_size)]  # Use .copy() here    

    for i, chunk in enumerate(chunks):
            
        # Preparing the target variable

        chunk["future_open"] = chunk["open"].shift(-1)
        chunk["future_close"] = chunk["close"].shift(-1)

        target = []
        for row in range(chunk.shape[0]):
            if chunk["future_close"].iloc[row] > chunk["future_open"].iloc[row]:
                target.append(1)
            else:
                target.append(0)

        chunk["target"] = target

        chunk = chunk.dropna()

        # Check if we were able to receive some data

        if (len(chunk)<=0):
            print("Failed to obtain chunk from Metatrader5, error = ",mt5.last_error())
            mt5.shutdown()

        X = chunk.drop(columns = ["spread","real_volume","future_close","future_open","target"])
        y = chunk["target"]

        X_train, X_val, y_train, y_val = train_test_split(X, y, train_size=0.8, random_state=42)

        if i == 0:
            # Initial training, training the model for the first time
            clf.fit(X_train, y_train, eval_set=(X_val, y_val))

            y_pred = clf.predict(X_val)
            print(f"---> Acc score: {accuracy_score(y_pred=y_pred, y_true=y_val)}")
        else:
            # Incremental training by using the intial trained model
            clf.fit(X_train, y_train, init_model="model.cbm", eval_set=(X_val, y_val))

            y_pred = clf.predict(X_val)
            print(f"---> Acc score: {accuracy_score(y_pred=y_pred, y_true=y_val)}")
        
        # Save the model
        clf.save_model("model.cbm")
        print(f"Chunk {i + 1}/{len(chunks)} processed and model saved.")

Resultado:

---> Acc score: 0.555
Chunk 1/10 processed and model saved.
---> Acc score: 0.505
Chunk 2/10 processed and model saved.
---> Acc score: 0.55
Chunk 3/10 processed and model saved.
---> Acc score: 0.565
Chunk 4/10 processed and model saved.
---> Acc score: 0.495
Chunk 5/10 processed and model saved.
---> Acc score: 0.55
Chunk 6/10 processed and model saved.
---> Acc score: 0.555
Chunk 7/10 processed and model saved.
---> Acc score: 0.52
Chunk 8/10 processed and model saved.
---> Acc score: 0.455
Chunk 9/10 processed and model saved.
---> Acc score: 0.535
Chunk 10/10 processed and model saved.

Puede seguir la misma arquitectura de aprendizaje en línea mientras construye el modelo de forma incremental y guardar el modelo final en formato ONNX en la carpeta «Common» (Común), para su uso en MetaTrader 5.


Reflexiones finales

El aprendizaje en línea es un enfoque excelente para mantener los modelos continuamente actualizados con una intervención manual mínima. Al implementar esta infraestructura, puede estar seguro de que sus modelos se mantendrán alineados con las últimas tendencias del mercado y se adaptarán rápidamente a la nueva información. Sin embargo, es importante señalar que el aprendizaje en línea a veces puede hacer que los modelos sean muy sensibles al orden en que se procesan los datos, por lo que a menudo puede ser necesaria la supervisión humana para verificar que el modelo y la información de entrenamiento tengan sentido lógico desde una perspectiva humana.
Es necesario encontrar el equilibrio adecuado entre la automatización del proceso de aprendizaje y la evaluación periódica de los modelos para garantizar que todo funcione según lo previsto.


Tabla de archivos adjuntos


Infraestructura (Carpetas)

Archivos Descripción y uso

 Cliente Python


-  catboost_models.py
-  gru_models.py
-  main.py
-  incremental_learning.py


- En este archivo se puede encontrar un modelo CatBoost.
- En este archivo se puede encontrar un modelo GRU.
- El archivo Python principal para unir todo.
- El aprendizaje incremental para el modelo CatBoost se implementa en este archivo.


Carpeta común (Common)



- catboost.H1.onnx
- gru.H1.onnx
- gru.H1.standard_scaler_mean.bin
- gru.H1.standard_scaler_scale.bin

 Todos los modelos de IA en formato ONNX y los archivos escaladores en formatos binarios se pueden encontrar en esta carpeta.
 
MetaTrader 5 (MQL5)


 - Experts\Online Learning Catboost.mq5
 - Experts\Online Learning GRU.mq5
 - Include\CatBoost.mqh
 - Include\GRU.mqh
 - Include\preprocessing.mqh
 
- Implementa un modelo CatBoost en MQL5.
- Implementa un modelo GRU en MQL5.
- Un archivo de biblioteca para inicializar e implementar un modelo CatBoost en formato ONNX.
- Un archivo de biblioteca para inicializar e implementar un modelo GRU en formato ONNX.
- Un archivo de biblioteca que contiene el escalador estándar para normalizar los datos para el uso del modelo ML.

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

Archivos adjuntos |
Attachments.zip (475 KB)
Phuc Hung
Phuc Hung | 14 mar 2025 en 08:49

Hola Omega J Msigwa

Te pregunte que versión de python estas usando para este articulo lo instale y hay un conflicto de librerías.

El conflicto es causado por:

El usuario pidió protobuf==3.20.3

onnx 1.17.0 depende de protobuf>=3.20.2

onnxconverter-common 1.14.0 depende de protobuf==3.20.2


Luego edité la versión como se sugiere y obtuve otro error de instalación.


Para solucionarlo podrías intentar

1. aflojar el rango de versiones de paquetes que ha especificado

2. eliminar versiones de paquetes para permitir que pip intente resolver el conflicto de dependencias


El conflicto es causado por:

El usuario solicitó protobuf==3.20.2

onnx 1.17.0 depende de protobuf>=3.20.2

onnxconverter-common 1.14.0 depende de protobuf==3.20.2

tensorboard 2.18.0 depende de protobuf!=4.24.0 y >=3.19.6

tensorflow-intel 2.18.0 depende de protobuf!=4.21.0, !=4.21.1, !=4.21.2, !=4.21.3, !=4.21.4, !=4.21.5, <6.0.0dev y >=3.20.3


Para solucionar esto podrías intentar

1. aflojar el rango de versiones de paquetes que ha especificado

2. eliminar versiones de paquetes para permitir que pip intente resolver el conflicto de dependencias



Por favor, proporcione más instrucciones

panovq
panovq | 13 ago 2025 en 15:48
¿Puedo aclarar qué es exactamente lo que da esto?
Miguel Angel Vico Alba
Miguel Angel Vico Alba | 13 ago 2025 en 19:02
panovq #¿Puedo aclarar qué es exactamente lo que da esto?
No dude en hacerlo.
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Redes neuronales en el trading: Aprendizaje contextual aumentado por memoria (MacroHFT) Redes neuronales en el trading: Aprendizaje contextual aumentado por memoria (MacroHFT)
Hoy le propongo familiarizarse con el framework MacroHFT, que aplica el aprendizaje por refuerzo dependiente del contexto y la memoria para mejorar las decisiones en el comercio de criptodivisas de alta frecuencia utilizando datos macroeconómicos y agentes adaptativos.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Cómo empezar a trabajar con MQL5 Algo Forge Cómo empezar a trabajar con MQL5 Algo Forge
Le presentamos MQL5 Algo Forge, un portal especial para desarrolladores de algoritmos comerciales. El portal combina las características de Git con una interfaz fácil de usar para mantener y organizar proyectos dentro del ecosistema MQL5. Aquí podrá suscribirse a autores que le resulten interesantes, crear equipos y llevar a cabo proyectos conjuntos sobre trading algorítmico.