English Русский 中文 Deutsch 日本語 Português
preview
Aprendizaje automático y Data Science (Parte 27): Redes neuronales convolucionales (CNN) en los robots comerciales de MetaTrader 5: ¿Merecen la pena?

Aprendizaje automático y Data Science (Parte 27): Redes neuronales convolucionales (CNN) en los robots comerciales de MetaTrader 5: ¿Merecen la pena?

MetaTrader 5Asesores Expertos | 10 enero 2025, 09:44
225 0
Omega J Msigwa
Omega J Msigwa

La operación de agrupamiento utilizada en las redes neuronales convolucionales es un gran error, y el hecho de que funcione tan bien es un desastre.

Geoffrey Hinton

Contenido


Se requiere un conocimiento básico de lenguaje de programación Python, Redes Neuronales Artificiales, Aprendizaje Automático y ONNX en MQL5 para entender completamente el contenido de este artículo.


¿Qué son las redes neuronales convolucionales (CNNS)?

Las redes neuronales convolucionales (CNN) son una clase de algoritmos de aprendizaje profundo diseñados específicamente para procesar datos estructurados en forma de cuadrícula, como imágenes, espectrogramas de audio y datos de series temporales. Son especialmente adecuados para tareas de datos visuales porque pueden aprender de forma automática y adaptativa jerarquías espaciales de características a partir de los datos de entrada.

Las CNN son la versión extendida de las redes neuronales artificiales (ANN). Se utilizan predominantemente para extraer características del conjunto de datos de matriz tipo cuadrícula. Por ejemplo, conjuntos de datos visuales como imágenes o vídeos donde los patrones de datos juegan un papel importante.

Las redes neuronales convolucionales tienen varios componentes clave como: capas convolucionales, funciones de activación, capas de agrupación, capas completamente conectadas y capas de abandono. Para entender las CNN en profundidad, analicemos cada componente y veamos de qué se trata.

Ilustración de red neuronal convolucional


Capas convolucionales

Estos son los componentes básicos de las CNN; es donde se produce la mayor parte de los cálculos. Las capas convolucionales son responsables de detectar patrones locales en los datos de entrada, como los bordes en las imágenes. Esto se puede lograr mediante el uso de filtros (o núcleos) que se deslizan sobre los datos de entrada para producir mapas de características.

Una capa convolucional es una capa oculta que contiene varias unidades de convolución en una red neuronal convolucional, que se utiliza para la extracción de características.

from tensorflow.keras.layers import Conv1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))

Filtros/Kernels

Los filtros (o kernels) son pequeñas matrices cuadradas que se pueden aprender (generalmente de tamaño 3x3, 5x5, etc.) y que se deslizan sobre los datos de entrada para detectar patrones locales.

¿Cómo funcionan?

Funcionan moviéndose a través de los datos de entrada y luego realizan una multiplicación elemento por elemento entre los valores del filtro y los valores de entrada dentro del campo receptivo actual del filtro, seguido de la suma de los resultados. Esta operación es lo que se llama convolución.

Durante el entrenamiento, la red aprende los valores óptimos de los filtros. En las primeras capas, los filtros generalmente aprenden a detectar características simples, como bordes y texturas, mientras que en capas más profundas, los filtros pueden detectar patrones más complejos, como formas y objetos.

Considere un filtro simple de 3x3 y una imagen de entrada de 5x5. El filtro se desliza sobre la imagen y calcula la operación de convolución para producir un mapa de características.

conv-net kernel



Zancada

Esta es otra característica que se encuentra en la capa de convolución. La zancada es el tamaño del paso en el que el filtro se mueve a través de los datos de entrada. Determina cuánto se desplaza el filtro en cada paso durante el proceso de convolución.

¿Cómo funcionan?

Zancada 1: el filtro se mueve una unidad a la vez, lo que da como resultado un mapa de características altamente superpuesto y detallado. Esto produce un mapa de características de salida más grande.

Zancada 2 o más: el filtro omite unidades, lo que da como resultado un mapa de características de salida menos detallado pero más pequeño. De este modo, se reducen las dimensiones espaciales de la salida, con lo que se reduce el muestreo de la entrada.

Por ejemplo, si tiene un filtro de 3x3 y una imagen de entrada de 5x5 con un paso de 1, el filtro se moverá un píxel a la vez, produciendo un mapa de características de salida de 3x3. Con un paso de 2, el filtro se moverá dos píxeles a la vez, produciendo un mapa de características de salida de 2x2.


Relleno

El relleno implica agregar píxeles adicionales (generalmente ceros) alrededor del borde de los datos de entrada. Esto garantiza que el filtro se ajuste correctamente y controle las dimensiones espaciales del mapa de características de salida.

Tipos de relleno

Según Keras, existen tres tipos de relleno. (Con distinción entre mayúsculas y minúsculas)

  1. válido - no se aplicará ningún relleno,
  2. igual - rellena la entrada para que el tamaño de salida coincida con el tamaño de entrada cuando la zancadas es igual a 1.
  3. causal - usado para datos temporales para asegurar que la salida en el paso de tiempo 𝑡 no depende de entradas futuras.

El relleno ayuda a preservar las dimensiones espaciales de los datos de entrada. Sin relleno, el mapa de características de salida se contrae con cada capa convolucional, lo que podría provocar la pérdida de información importante sobre los bordes.

Al agregar relleno, la red puede aprender características de los bordes de manera efectiva y mantener la resolución espacial de la entrada.

Considere un filtro de 3x3 y una imagen de entrada de 5x5. Con un relleno válido (sin relleno), el mapa de características de salida será 3x3. Con el mismo relleno, puedes agregar un borde de ceros alrededor de la entrada, haciéndolo 7x7. El mapa de características de salida será entonces de 5x5, conservando las dimensiones de entrada.

A continuación se muestra el código para una capa de convolución en Python.

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv1D

model = Sequential()
model.add(Conv1D(filters=64, 
                 kernel_size=3, 
                 activation='relu', 
                 strides=2,
                 padding='causal',
                 input_shape=(window_size, X_train.shape[2])
                )
         )


Funciones de activación

Como se explica en el artículo Desmitificación de las redes neuronales, una función de activación es una función matemática que toma una entrada y procesa una salida.

La función de activación se aplica por elementos para introducir no linealidad en el modelo. Entre las funciones de activación utilizadas habitualmente en las CNN se encuentran ReLU (Unidad Lineal Rectificada), Sigmoide y TanH.


Agrupación de capas

También conocidas como capas de desmuestreo, estas capas son una parte esencial de las CNN, ya que se encargan de reducir la dimensión espacial de los datos de entrada en términos de anchura y altura, conservando al mismo tiempo la información más importante.

¿Cómo funcionan?

En primer lugar, dividen los datos de entrada en regiones o ventanas superpuestas y, a continuación, aplican una función de agregación como Max pooling o Average pooling en cada ventana para obtener un valor único.

Max pooling toma el valor máximo de un conjunto de valores dentro de una región de filtrado. Reduce las dimensiones espaciales de los datos, lo que ayuda a reducir la carga computacional y el número de parámetros.

Python

from tensorflow.keras.layers import Conv1D, MaxPooling1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
MaxPooling1D(pool_size=2)

Esta capa toma el valor máximo de cada ventana de 2 elementos.

Agrupamiento máximo

Más información

Average pooling toma el valor medio de un conjunto de valores dentro de una región de filtrado. Se utiliza con menos frecuencia que el agrupamiento máximo.

Python

from tensorflow.keras.layers import Conv1D, AveragePooling1D

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(AveragePooling1D(pool_size=2))
AveragePooling1D(pool_size=2)

Esta capa toma el valor promedio de cada ventana de 2 elementos.

Agrupación promedio

Más información

¿Por qué utilizar la capa convolucional 1D?

Existen capas Conv1D, Conv2D y Conv3D para las CNN. La capa de convolución 1D es una adecuada para este tipo de problemas ya que está diseñada para datos unidimensionales, lo que la hace adecuada para datos secuenciales o series temporales. Otras capas convolucionales como la Conv2D y la Conv3D son demasiado complejas para este tipo de problema.


Capas totalmente conectadas

Las neuronas de una capa totalmente conectada tienen conexiones con todas las activaciones de la capa anterior. Estas capas suelen utilizarse hacia el final de la red para realizar una clasificación o regresión basada en las características extraídas por las capas convolucionales y de agrupación.

from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='sigmoid'))  # For binary classification (e.g., buy/sell signal)

model.summary()

La capa aplanada (Flatten) convierte el mapa de características 1D agrupado en un vector 1D, de modo que pueda introducirse en las capas totalmente conectadas (densas).

Las capas densas (Dense) son capas totalmente conectadas que se utilizan para tomar decisiones finales basadas en las características extraídas por las capas de convolución y agrupación. Las capas densas son esencialmente el componente central de las redes neuronales artificiales (RNA) tradicionales.

Capa densa en una CNN resaltada




Capas de abandono

La capa de abandono (Dropout) actúa como una máscara, eliminando las contribuciones de algunas neuronas a la capa siguiente, pero manteniendo la funcionalidad de todas las demás neuronas. Si aplicamos una capa Dropout al vector de entrada, se eliminan algunas de sus características; sin embargo, si la aplicamos a una capa oculta, se eliminan algunas neuronas ocultas.

Dado que evitan el sobreajuste de los datos de entrenamiento, las capas de abandono son cruciales en el entrenamiento de las CNN. Si están ausentes, el primer conjunto de muestras de entrenamiento tiene un impacto excesivamente grande en el aprendizaje. En consecuencia, no se aprenderían rasgos que sólo aparecen en muestras o lotes posteriores.

from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout

model = Sequential()
model.add(Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=(window_size, X_train.shape[2])))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='sigmoid'))  # For binary classification (e.g., buy/sell signal)

model.summary()


¿Por qué utilizar redes neuronales convolucionales (CNN) para aplicaciones de análisis financiero y trading?

Las CNN se utilizan ampliamente en aplicaciones de procesamiento de imágenes y vídeo, ya que para eso están diseñadas. Si echas un vistazo a las explicaciones anteriores, podrás darte cuenta de que me refiero al uso de CNNs cuando trabajo con clasificaciones de imágenes y esas cosas.

El uso de redes neuronales convolucionales (CNN) para datos tabulares, como el análisis financiero, puede parecer poco convencional en comparación con el uso de otros tipos de redes neuronales, como las redes neuronales de alimentación directa (FFNN), las redes neuronales recurrentes (RNNN) , la memoria a corto plazo de larga duración (LSTM) y las unidades recurrentes controladas (GRU) . Sin embargo, existen varias razones y ventajas potenciales que se exponen a continuación para emplear las CNN en este contexto.


01. Las CNN son excelentes para extraer automáticamente patrones locales de los datos.

Los datos financieros a menudo muestran patrones temporales locales, como tendencias y estacionalidades. Tratando los datos como una serie temporal. Las CNN pueden aprender estas dependencias locales e interacciones entre características, que podrían pasar desapercibidas con los métodos tradicionales.

02. Pueden aprender características jerárquicas.

Las capas múltiples en las CNN les permiten aprender jerarquías de características complejas. Las primeras capas pueden detectar patrones simples, mientras que las capas más profundas pueden combinar estos patrones simples en representaciones más complejas y abstractas. Este aprendizaje de características jerárquicas puede ser beneficioso para capturar patrones complejos en datos financieros.

03. Pueden ser resistentes al ruido y a las características redundantes.

Como sabemos, los conjuntos de datos financieros contienen datos ruidosos y redundantes. Las capas de agrupamiento en las CNN ayudan a que el modelo sea resistente a ligeras variaciones y ruido, ya que reducen la muestra de los mapas de características y la influencia de fluctuaciones menores.

04: Las CNN pueden manejar bien series temporales multivariadas.

Los datos financieros a menudo involucran múltiples series de tiempo correlacionadas, como diferentes precios de acciones y volúmenes de negociación. Las CNN pueden capturar eficazmente las interacciones y dependencias entre estas múltiples series de tiempo, lo que las hace adecuadas para análisis multivariados.pronóstico de series de tiempo.

05. Son computacionalmente eficientes para datos de alta dimensión.

Los datos financieros pueden tener una alta dimensionalidad (muchas características). Gracias a la distribución de peso y la conectividad local, las CNN son computacionalmente más eficientes que las redes neuronales totalmente conectadas, lo que las hace escalables a datos de alta dimensión.

06. Las CNN permiten el aprendizaje de extremo a extremo

Las CNN pueden aprender directamente de los datos sin procesar y producir predicciones sin necesidad de intervención manual.Ingeniería de características. Este enfoque de aprendizaje de extremo a extremo puede simplificar el proceso de modelado y potencialmente producir un mejor rendimiento al permitir que el modelo descubra las características más relevantes.

07. Las CNN aplican operaciones de convolución que pueden resultar ventajosas para ciertos tipos de datos.

Las operaciones de convolución pueden detectar y mejorar señales importantes dentro de los datos, como cambios repentinos o patrones específicos. Esto es particularmente útil en el análisis financiero, donde detectar cambios o patrones repentinos del mercado puede ser fundamental.

Ahora que tenemos razones válidas para usar CNN en aplicaciones comerciales, creemos una y entrenémosla, luego veremos cómo podemos usar una CNN en un Asesor Experto (EA) de Meta Trader 5.


Creación de una red neuronal convolucional (CNN) en Python

Esto implica varios pasos que son:

  1. Recopilación de datos
  2. Preparación de datos para un modelo CNN
  3. Entrenamiento de un modelo CNN
  4. Guardar un modelo CNN en formato ONNX

01: Recopilación de datos

Utilizando los datos realizados para la previsión de series temporales que utilizamos en los artículos anteriores.

Conjunto de datos de previsión de series temporales

Ahora que sabemos que las redes neuronales convolucionales (CNN) son buenas detectando patrones dentro de datos de alta dimensión, sin complicar el modelo podemos elegir algunas de las características que creo que pueden tener muchos patrones que el modelo CNN puede detectar.

Código Python

open_price = df['TARGET_OPEN']
close_price = df['TARGET_CLOSE']

# making the target variable

target_var = []
for i in range(len(open_price)):
    if close_price[i] > open_price[i]: # if the price closed above where it opened
        target_var.append(1) # bullish signal
    else:
        target_var.append(0) # bearish signal

        
new_df = pd.DataFrame({
    'OPEN': df['OPEN'],
    'HIGH': df['HIGH'],
    'LOW': df['LOW'],
    'CLOSE': df['CLOSE'],
    'TARGET_VAR': target_var
})

print(new_df.shape)

Poco después de preparar la variable objetivo basada en TARGET_OPEN y TARGET_CLOSE, que son los valores de apertura y cierre respectivamente, recogidos una barra hacia adelante. Creamos una versión del minijuego de datos llamada nuevo_df que sólo tenía 4 variables independientes valores ABIERTO, ALTO y BAJO, y una variable dependiente llamada TARGET_VAR.


02: Preparación de datos para un modelo CNN

En primer lugar, tenemos que preprocesar los datos de entrada reformulándolos y alineándolos en ventanas. Esto es muy crucial cuando se trabaja con datos tabulares en CNN, aquí explicamos por qué.

Dado que los datos comerciales son secuenciales, los patrones a menudo surgen a lo largo de una serie de pasos de tiempo en lugar de en un único punto en el tiempo. Al crear ventanas superpuestas de datos podemos, captar dependencias temporales y proporcionar contexto al modelo CNN..

Además, las CNN esperan que los datos de entrada tengan una forma específica. Para las capas convolucionales 1D, la forma de entrada normalmente debe ser (número de ventanas, tamaño de ventana, número de características). Esta forma se asemeja a la que utilizamos en el análisis de series temporales mediante Redes Neuronales Recurrentes (RNNs) en el artículo anterior. El procedimiento de preprocesamiento que vamos a realizar garantiza que los datos tengan este formato, lo que los hace adecuados para la entrada de un modelo CNN.

# Example data preprocessing function

def preprocess_data(df, window_size):
    X, y = [], []
    for i in range(len(df) - window_size):
        X.append(df.iloc[i:i+window_size, :-1].values)
        y.append(df.iloc[i+window_size, -1])
    return np.array(X), np.array(y)


window_size = 10


X, y = preprocess_data(new_df, window_size)
print(f"x_shape = {X.shape}\ny_shape = {y.shape}")

Salidas

x_shape = (990, 10, 4)
y_shape = (990,)

Dado que nuestros datos se recopilaron en un período de tiempo diario, el tamaño de ventana de 10 indica que entrenaremos el modelo CNN para comprender patrones dentro de 10 días.


Luego tenemos que dividir los datos en muestras de entrenamiento y de prueba.

# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)

# Standardize the data
scaler = StandardScaler()

X_train = scaler.fit_transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape)
X_test = scaler.transform(X_test.reshape(-1, X_test.shape[-1])).reshape(X_test.shape)

print(f"x_train\n{X_train.shape}\nx_test\n{X_test.shape}\n\ny_train {y_train.shape} y_test {y_test.shape}")

Salidas

x_train
(792, 10, 4)
x_test
(198, 10, 4)

y_train (792,) y_test (198,)


Por último tenemos que codificar en caliente la variable objetivo para esta tarea del problema de clasificación.

from tensorflow.keras.utils import to_categorical

y_train_encoded = to_categorical(y_train)
y_test_encoded = to_categorical(y_test)

print(f"One hot encoded\n\ny_train {y_train_encoded.shape}\ny_test {y_test_encoded.shape}")

Salidas

One hot encoded

y_train (792, 2)
y_test (198, 2)


03: Entrenamiento de un modelo CNN

Aquí es donde se hace la mayor parte del trabajo.

# Defining the CNN model

model = Sequential()
model.add(Conv1D(filters=64, 
                 kernel_size=3, 
                 activation='relu', 
                 strides=2,
                 padding='causal',
                 input_shape=(window_size, X_train.shape[2])
                )
         )

model.add(MaxPooling1D(pool_size=2))
model.add(Flatten()) 
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=len(np.unique(y)), activation='softmax'))  # For binary classification (buy/sell signal)

model.summary()

# Compiling the model

optimizer = Adam(learning_rate=0.001)

model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])

# Training the model

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

history = model.fit(X_train, y_train_encoded, epochs=100, batch_size=16, validation_split=0.2, callbacks=[early_stopping])

plt.figure(figsize=(7.5, 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.title('Training Loss Curve')
plt.legend()
plt.savefig("training loss cuver-cnn-clf.png")
plt.show()

Salidas

Model: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv1d_2 (Conv1D)               │ (None, 5, 64)          │           832 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling1d_2 (MaxPooling1D)  │ (None, 2, 64)          │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_2 (Flatten)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_4 (Dense)                 │ (None, 100)            │        12,900 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_2 (Dropout)             │ (None, 100)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_5 (Dense)                 │ (None, 2)              │           202 │
└─────────────────────────────────┴────────────────────────┴───────────────┘

El entrenamiento se detuvo en la 34ª época.

40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5105 - loss: 0.6875 - val_accuracy: 0.4843 - val_loss: 0.6955
Epoch 32/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5099 - loss: 0.6888 - val_accuracy: 0.5283 - val_loss: 0.6933
Epoch 33/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.4636 - loss: 0.6933 - val_accuracy: 0.5283 - val_loss: 0.6926
Epoch 34/100
40/40 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - accuracy: 0.5070 - loss: 0.6876 - val_accuracy: 0.5346 - val_loss: 0.6963

Curva de pérdida de validación y entrenamiento de CNN

El modelo tuvo una precisión de aproximadamente el 57% del tiempo en las predicciones fuera de la muestra.

y_pred = model.predict(X_test) 

classes_in_y = np.unique(y)
y_pred_binary = classes_in_y[np.argmax(y_pred, axis=1)]

# Confusion Matrix
cm = confusion_matrix(y_test, y_pred_binary)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.savefig("confusion-matrix CNN")  # Display the heatmap


print("Classification Report\n",
      classification_report(y_test, y_pred_binary))

Salidas

7/7 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
Classification Report
               precision    recall  f1-score   support

           0       0.53      0.24      0.33        88
           1       0.58      0.83      0.68       110

    accuracy                           0.57       198
   macro avg       0.55      0.53      0.50       198
weighted avg       0.55      0.57      0.52       198

Nuestro modelo CNN es lo suficientemente bueno para un Asesor Experto. Pero, antes de que podamos comenzar a codificar un EA, guardemos el modelo CNN que hemos entrenado en formato ONNX.


04: Guardar un modelo CNN en formato ONNX.

El proceso es bastante sencillo, tenemos que guardar el modelo CNN en formato .onnx, y los parámetros de la técnica de escalado en archivos binarios.

import tf2onnx

onnx_file_name = "cnn.EURUSD.D1.onnx"

spec = (tf.TensorSpec((None, window_size, X_train.shape[2]), tf.float16, name="input"),)
model.output_names = ['outputs']

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

# Save the ONNX model to a file
with open(onnx_file_name, "wb") as f:
    f.write(onnx_model.SerializeToString())
    
    
# Save the mean and scale parameters to binary files
scaler.mean_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_mean.bin")
scaler.scale_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_scale.bin")


Creación de un robot comercial basado en una red neuronal convolucional (CNN)

Dentro de un Asesor Experto, lo primero que tenemos que hacer es incluir como recursos el modelo con formato ONNX y los ficheros binarios Standard Scaler.

MQL5 | ConvNet EA.mq5

#resource "\\Files\\cnn.EURUSD.D1.onnx" as uchar onnx_model[]
#resource "\\Files\\cnn.EURUSD.D1.standard_scaler_scale.bin" as double scaler_stddev[]
#resource "\\Files\\cnn.EURUSD.D1.standard_scaler_mean.bin" as double scaler_mean[]

Tenemos que inicializar ambos, el escalador y el modelo ONNX.

#include <MALE5\Convolutioal Neural Networks(CNNs)\Convnet.mqh>
#include <MALE5\preprocessing.mqh>

CConvNet cnn;
StandardizationScaler scaler;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

input group "cnn";
input uint cnn_data_window = 10; 
//this value must be the same as the one used during training in a python script

vector classes_in_y = {0,1}; //we have to assign the classes manually | it is essential that their order is preserved as they can be seen in python code, HINT: They are usually in ascending order
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
   if (!cnn.Init(onnx_model)) //Initialize the ONNX model
     return INIT_FAILED;
     
//--- Initializing the scaler with values loaded from binary files 

   scaler = new StandardizationScaler(scaler_mean, scaler_stddev); //load the scaler
      
   return(INIT_SUCCEEDED);
  }

Eso es suficiente para poner en marcha el modelo. Hagamos que la función extraiga los datos de forma similar a como se utilizaron las variables independientes durante el entrenamiento. Utilizamos cuatro variables valores OHLC desde la barra cerrada anterior hasta 10 barras anteriores que era el tamaño de ventana que debe conservar el timeframe (Un marco temporal diario).

input group "cnn";
input uint cnn_data_window = 10; 
//this value must be the same as the one used during training in a python script

input ENUM_TIMEFRAMES timeframe = PERIOD_D1;
input int magic_number = 1945;
input int slippage = 50;
matrix GetXVars(int bars, int start_bar=1)
 {
   vector open(bars), 
          high(bars),
          low(bars), 
          close(bars);

//--- Getting OHLC values
   
   open.CopyRates(Symbol(), timeframe, COPY_RATES_OPEN, start_bar, bars);
   high.CopyRates(Symbol(), timeframe, COPY_RATES_HIGH, start_bar, bars);
   low.CopyRates(Symbol(), timeframe, COPY_RATES_LOW, start_bar, bars);
   close.CopyRates(Symbol(), timeframe, COPY_RATES_CLOSE, start_bar, bars);
   
//---

   matrix data(bars, 4); //we have 10 inputs from cnn | this value is fixed
   
//--- adding the features into a data matrix
   
   data.Col(open, 0);
   data.Col(high, 1);
   data.Col(low, 2);
   data.Col(close, 3);
   
   return data;
 }

Ahora que tenemos una función para recoger las variables independientes, podemos finalizar nuestra estrategia de negociación.

void OnTick()
  {
//---
   
   if (NewBar()) //Trade at the opening of a new candle
    {
      matrix input_data_matrix = GetXVars(cnn_data_window); //get data for the past 10 days(default)
      input_data_matrix = scaler.transform(input_data_matrix); //applying StandardSCaler to the input data
      
      int signal = cnn.predict_bin(input_data_matrix, classes_in_y); //getting trade signal from the RNN model
     
      Comment("Signal==",signal);
     
   //---
     
      MqlTick ticks;
      SymbolInfoTick(Symbol(), ticks);
      
      if (signal==1) //if the signal is bullish
       {
          if (!PosExists(POSITION_TYPE_BUY)) //There are no buy positions
           {
             if (!m_trade.Buy(lotsize, Symbol(), ticks.ask, 0, 0)) //Open a buy trade
               printf("Failed to open a buy position err=%d",GetLastError());

             ClosePosition(POSITION_TYPE_SELL); //close opposite trade
           }
       }
      else if (signal==0) //Bearish signal
        {
          if (!PosExists(POSITION_TYPE_SELL)) //There are no Sell positions
            if (!m_trade.Sell(lotsize, Symbol(), ticks.bid, 0, 0)) //open a sell trade
               printf("Failed to open a sell position err=%d",GetLastError());
               
               ClosePosition(POSITION_TYPE_BUY); 
        }
      else //There was an error
        return;
    }
  }

La estrategia es sencilla. Al recibir una señal en particular, digamos una señal de compra, abrimos una operación de compra sin valores de stop loss y take profit, luego cerramos la señal opuesta y viceversa para una señal de venta.

Finalmente, probé esta estrategia en un símbolo en el que fue entrenada que es EURUSD, durante diez años. Desde 2014.01.01 hasta 2024.05.27 en un gráfico de 4 Horas sobre precios de apertura de cada barra.

convNet EA

Los resultados del probador de estrategia fueron sobresalientes.

ConvNet EA

ConvNet EA

El EA realizó predicciones precisas el 58% de las veces y, como resultado, el EA basado en CNN obtuvo una ganancia neta de $503.


El resultado final

A pesar de estar hecho específicamente para el procesamiento de imágenes y vídeos, cuando se adopta para manejar datos tabulares como los datos de forex que le proporcionamos. Las redes neuronales convolucionales (CNN) pueden hacer un buen trabajo detectando patrones y usarlos para hacer predicciones en el mercado de divisas.

Como se puede ver en el informe del probador de estrategia. El EA basado en CNN ha realizado predicciones decentes; apuesto a que muchos modelos tradicionales diseñados para datos tabulares como regresión lineal, máquina de vectores de soporte, Bayes naive, etc. no pueden lograr esta precisión predictiva considerando que al modelo CNN se le dieron solo 4 variables independientes (OHLC). En mi experiencia, no muchos modelos pueden llegar a ser tan buenos dadas unas pocas variables.

¡Saludos!


Siga el desarrollo de modelos de aprendizaje automático y mucho más discutido en esta serie de artículos en este repositorio de GitHub.

Tabla de archivos adjuntos

Nombre del archivo
Tipo de archivo Descripción y uso
 ConvNet EA.mq5
 Asesor experto          Robot comercial para cargar el modelo CNN en formato ONNX y probar la estrategia comercial final en MetaTrader 5.
 cnn.EURUSD.D1.onnx
 ONNX  Modelo CNN en formato ONNX.
 cnn.EURUSD.D1.standard_scaler_mean.bin  
 cnn.EURUSD.D1.standard_scaler_scale.bin
 Archivos binarios   Archivos binarios para el escalador de estandarización
 preprocessing.mqh
 Archivo de inclusión
 Una librería que consta del escalador de estandarización
 ConvNet.mqh
 Archivo de inclusión   Una librería para cargar e implementar modelos CNN en formato ONNX 
 cnn-for-trading-applications-tutorial.ipynb
 Python Script/Jupyter Notebook   Contiene todo el código Python analizado en este artículo. 


Fuentes y referencias


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

Archivos adjuntos |
Attachments.zip (132.38 KB)
Del básico al intermedio: Directiva Include Del básico al intermedio: Directiva Include
En este artículo, hablaremos de una directiva de compilación ampliamente utilizada en los diversos códigos que puedes encontrar en MQL5. Aunque esta directiva de compilación se explicará aquí de manera bastante básica y superficial, es importante comenzar a entender cómo usarla, ya que pronto será indispensable para avanzar hacia un nivel de programación superior. El contenido expuesto aquí tiene un propósito puramente didáctico. En ningún caso debe considerarse una aplicación cuya finalidad no sea el aprendizaje y el estudio de los conceptos presentados.
Del básico al intermedio: Comandos BREAK y CONTINUE Del básico al intermedio: Comandos BREAK y CONTINUE
En este artículo veremos cómo usar los comandos RETURN, BREAK y CONTINUE dentro de un bucle. Entender lo que cada uno de estos comandos hace en el flujo de ejecución de un bucle es algo muy importante para que puedas trabajar con aplicaciones más elaboradas. El contenido expuesto aquí tiene un propósito puramente didáctico. En ningún caso debe considerarse una aplicación cuya finalidad no sea el aprendizaje y el estudio de los conceptos presentados.
Desarrollo de un sistema comercial basado en el libro de órdenes (Parte I): el indicador Desarrollo de un sistema comercial basado en el libro de órdenes (Parte I): el indicador
El libro de órdenes —Depth of Market— es, sin duda, un elemento muy relevante para la ejecución de operaciones rápidas, especialmente en algoritmos de alta frecuencia (HFT). En esta serie de artículos, exploraremos este tipo de evento comercial que podemos obtener a través del bróker en muchos de los símbolos negociados. Empezaremos con un indicador en el que se pueden configurar la paleta de colores, la posición y el tamaño del histograma que se mostrará directamente en el gráfico. También veremos cómo generar eventos BookEvent para probar el indicador en condiciones específicas. Otros posibles temas que trataremos en artículos futuros son el almacenamiento de estas distribuciones de precios y las formas de utilizarlas en el simulador de estrategias.
Del básico al intermedio: Comando WHILE y DO WHILE Del básico al intermedio: Comando WHILE y DO WHILE
En este artículo veremos de manera práctica y bastante didáctica el primer comando de bucle. A pesar de que muchos principiantes sienten temor al enfrentarse a la necesidad de crear bucles, saber cómo hacerlo de manera adecuada y segura, es algo que solo la experiencia y la práctica pueden proporcionar. Pero, ¿quién sabe? Tal vez pueda ayudarte a reducir las dificultades y el sufrimiento, mostrándote los principales problemas y precauciones que debes tener al utilizar bucles en tus códigos. El contenido expuesto aquí tiene como objetivo exclusivamente la enseñanza didáctica. En ningún caso debe considerarse como una aplicación destinada a otro fin que no sea el aprendizaje y estudio de los conceptos presentados.