English Русский 中文 Español Deutsch 日本語
preview
Ciência de Dados e ML (Parte 27): Redes Neurais Convolucionais (CNNs) em Bots de Trading no MetaTrader 5 — Vale a Pena?

Ciência de Dados e ML (Parte 27): Redes Neurais Convolucionais (CNNs) em Bots de Trading no MetaTrader 5 — Vale a Pena?

MetaTrader 5Experts |
222 2
Omega J Msigwa
Omega J Msigwa

A operação de pooling usada em redes neurais convolucionais é um grande erro, e o fato de que ela funciona tão bem é um desastre.

Geoffrey Hinton

Conteúdo


Um entendimento básico da linguagem de programação Python, Redes Neurais Artificiais, Aprendizado de Máquina e ONNX no MQL5 é necessário para compreender totalmente o conteúdo deste artigo.


O que são Redes Neurais Convolucionais (CNNs)?

As Redes Neurais Convolucionais (CNNs) são uma classe de algoritmos de aprendizado profundo projetados especificamente para processar dados estruturados em grade, como imagens, espectrogramas de áudio e séries temporais. Elas são particularmente adequadas para tarefas envolvendo dados visuais, pois podem aprender automaticamente e de forma adaptativa hierarquias espaciais de características a partir dos dados de entrada.

As CNNs são uma versão estendida das redes neurais artificiais (ANN). Elas são predominantemente usadas para extrair características de conjuntos de dados em formato de grade. Por exemplo, conjuntos de dados visuais como imagens ou vídeos, onde os padrões de dados desempenham um papel fundamental.

As redes neurais convolucionais possuem vários componentes-chave, como camadas convolucionais, funções de ativação, camadas de pooling, camadas totalmente conectadas e camadas dropout. Para entender as CNNs em profundidade, vamos dissecar cada componente e ver do que se trata.

Ilustração de rede neural convolucional


Camadas Convolucionais

Essas são as estruturas centrais das CNNs, onde ocorre a maior parte dos cálculos. As camadas convolucionais são responsáveis por detectar padrões locais nos dados de entrada, como bordas em imagens. Isso é alcançado por meio do uso de filtros (ou kernels) que percorrem os dados de entrada para produzir mapas de características.

Uma camada convolucional é uma camada oculta que contém várias unidades convolucionais em uma rede neural convolucional, sendo utilizada para a extração 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])))

Filters/Kernels

Filtros (ou kernels) são pequenas matrizes quadradas treináveis (geralmente de tamanho 3x3, 5x5, etc.) que percorrem os dados de entrada para detectar padrões locais.

Como eles funcionam?

Eles operam movendo-se através dos dados de entrada e realizando uma multiplicação elemento a elemento entre os valores do filtro e os valores de entrada dentro do campo receptivo atual do filtro, seguida da soma dos resultados. Essa operação é chamada de convolução.

Durante o treinamento, a rede aprende os valores ideais dos filtros. Nas camadas iniciais, os filtros geralmente aprendem a detectar características simples, como bordas e texturas, enquanto nas camadas mais profundas, os filtros podem detectar padrões mais complexos, como formas e objetos.

Considere um filtro simples de 3x3 e uma imagem de entrada de 5x5. O filtro percorre a imagem, computando a operação de convolução para produzir um mapa de características.

Kernel de rede convolucional



Stride

Este é outro recurso encontrado na camada de convolução. O stride é o tamanho do passo pelo qual o filtro se move sobre os dados de entrada. Ele determina o quanto o filtro se desloca a cada etapa durante o processo de convolução.

Como eles funcionam?

Stride de 1: O filtro se move um único pixel por vez, resultando em um mapa de características altamente sobreposto e detalhado. Isso gera um mapa de características de saída maior.

Stride de 2 ou mais: O filtro pula unidades, resultando em um mapa de características de saída menos detalhado, mas menor. Isso reduz as dimensões espaciais da saída, efetivamente diminuindo a resolução da entrada.

Por exemplo, se você tiver um filtro de 3x3 e uma imagem de entrada de 5x5 com um stride de 1, o filtro se moverá um pixel por vez, produzindo um mapa de características de saída de 3x3. Com um stride de 2, o filtro se moverá dois pixels por vez, produzindo um mapa de características de saída de 2x2.


Preenchimento

O preenchimento envolve adicionar pixels extras (geralmente zeros) ao redor da borda dos dados de entrada. Isso garante que o filtro se encaixe corretamente e controle as dimensões espaciais do mapa de características de saída.

Tipos de preenchimento

De acordo com Keras, existem três tipos de preenchimento. (sensível a maiúsculas e minúsculas)

  1. valid - nenhum preenchimento será aplicado.
  2. same - adiciona preenchimento à entrada para que o tamanho da saída corresponda ao tamanho da entrada quando strides=1.
  3. causal - usado para dados temporais para garantir que a saída no instante de tempo 𝑡 não dependa de entradas futuras.

O preenchimento ajuda a preservar as dimensões espaciais dos dados de entrada. Sem preenchimento, o mapa de características de saída diminui a cada camada convolucional, o que pode resultar na perda de informações importantes nas bordas.

Ao adicionar preenchimento, a rede pode aprender melhor as características das bordas e manter a resolução espacial da entrada.

Considere um filtro de 3x3 e uma imagem de entrada de 5x5. Com preenchimento válido (sem preenchimento), o mapa de características de saída será 3x3. Com o preenchimento "same", você pode adicionar uma borda de zeros ao redor da entrada, tornando-a 7x7. O mapa de características de saída será então 5x5, preservando as dimensões da entrada.

Abaixo está o código para uma camada de convolução em 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])
                )
         )


Funções de Ativação

Conforme discutido no artigo Redes Neurais Desmistificadas, uma função de ativação é uma função matemática que recebe uma entrada e processa uma saída.

A função de ativação é aplicada elemento a elemento para introduzir não-linearidade no modelo. As funções de ativação mais utilizadas em CNNs incluem ReLU (Rectified Linear Unit), Sigmoid e TanH.


Camadas de Pooling

Também conhecidas como camadas de amostragem, essas camadas são uma parte essencial das CNNs, pois são responsáveis por reduzir a dimensão espacial dos dados de entrada em termos de largura e altura, enquanto retêm as informações mais importantes.

Como eles funcionam?

Primeiramente, elas dividem os dados de entrada em regiões ou janelas sobrepostas e, em seguida, aplicam uma função de agregação, como Max pooling ou Average pooling, em cada janela para obter um único valor.

Max pooling seleciona o valor máximo de um conjunto de valores dentro da região do filtro. Isso reduz as dimensões espaciais dos dados, o que ajuda a diminuir a carga computacional e o 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 camada seleciona o valor máximo de cada janela de 2 elementos.

max pooling

Mais informações.

Average pooling calcula a média dos valores dentro da região do filtro. É menos utilizado do que o max pooling.

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 camada calcula a média de cada janela de 2 elementos.

average pooling

Mais informações.

Por que usar uma Camada Convolucional 1D?

Existem camadas Conv1D, Conv2D e Conv3D para CNNs. A camada de convolução 1D é a mais adequada para esse tipo de problema, pois é projetada para dados unidimensionais, tornando-a apropriada para dados sequenciais ou séries temporais. Outras camadas convolucionais, como Conv2D e Conv3D, são muito complexas para esse tipo de problema.


Camadas Totalmente Conectadas

Os neurônios em uma camada totalmente conectada têm conexões com todas as ativações da camada anterior. Essas camadas são normalmente utilizadas no final da rede para realizar classificação ou regressão com base nas características extraídas pelas camadas convolucionais e de pooling.

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

A Camada Flatten converte o mapa de características agrupado 1D em um vetor 1D, para que possa ser alimentado nas camadas totalmente conectadas (densas).

As Camadas Densas (Dense) são camadas totalmente conectadas usadas para tomar decisões finais com base nas características extraídas pelas camadas de convolução e pooling. As camadas densas são essencialmente o componente central das redes neurais artificiais (ANNs) tradicionais.

Camada densa em uma CNN destacada




Camadas Dropout

A camada Dropout age como uma máscara, eliminando a contribuição de alguns neurônios para a camada subsequente, enquanto mantém a funcionalidade de todos os outros neurônios. Se aplicarmos uma camada Dropout ao vetor de entrada, algumas de suas características são eliminadas. No entanto, se a aplicarmos a uma camada oculta, alguns neurônios ocultos serão eliminados.

Como as camadas Dropout evitam o overfitting dos dados de treinamento, elas são cruciais no treinamento de CNNs. Se estiverem ausentes, o primeiro conjunto de amostras de treinamento tem um impacto excessivo no aprendizado. Como resultado, características que aparecem apenas em amostras ou lotes posteriores não seriam aprendidas.

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 que usar Redes Neurais Convolucionais (CNNs) para Análise Financeira e Aplicações de Trading?

As CNNs são amplamente utilizadas em aplicações de processamento de imagens e vídeos, pois foram projetadas para isso. Se você observar as explicações acima, poderá perceber que me refiro ao uso de CNNs para classificação de imagens e outros tipos de dados visuais.

Usar Redes Neurais Convolucionais (CNNs) para dados tabulares, como análise financeira, pode parecer incomum em comparação com outros tipos de redes neurais, como Redes Neurais Feed Forward (FFNN), Redes Neurais Recorrentes (RNNs), Long Short-Term Memory (LSTMs) e Gated Recurrent Units (GRUs). No entanto, existem várias razões e benefícios potenciais destacados abaixo para o uso de CNNs nesse contexto.


01. CNNs são excelentes para extrair padrões locais automaticamente dos dados.

Os dados financeiros frequentemente exibem padrões temporais locais, como tendências e sazonalidades. Ao tratar os dados como uma série temporal, as CNNs podem aprender essas dependências locais e interações entre características, que podem ser ignoradas por métodos tradicionais.

02. Elas podem aprender hierarquias de características.

Múltiplas camadas em CNNs permitem que elas aprendam hierarquias complexas de características. Camadas iniciais podem detectar padrões simples, enquanto camadas mais profundas combinam esses padrões simples em representações mais complexas e abstratas. Esse aprendizado hierárquico pode ser benéfico na captura de padrões complexos em dados financeiros.

03. Elas são robustas contra ruídos e características redundantes.

Sabemos que conjuntos de dados financeiros contêm ruídos e dados redundantes. As camadas de pooling nas CNNs ajudam a tornar o modelo mais robusto a pequenas variações e ruídos, pois reduzem a influência de pequenas flutuações nos mapas de características.

04. CNNs lidam bem com séries temporais multivariadas.

Os dados financeiros frequentemente envolvem múltiplas séries temporais correlacionadas, como preços de diferentes ações e volumes de negociação. As CNNs podem capturar de forma eficaz as interações e dependências entre essas séries temporais, tornando-as adequadas para previsão de séries temporais multivariadas.

05. Elas são computacionalmente eficientes para dados de alta dimensão.

Os dados financeiros podem ter alta dimensionalidade (muitas características). Devido ao compartilhamento de pesos e conectividade local, as CNNs são mais eficientes computacionalmente do que redes neurais totalmente conectadas, tornando-as escaláveis para dados de alta dimensão.

06. CNNs permitem aprendizado de ponta a ponta.

As CNNs podem aprender diretamente a partir de dados brutos e gerar previsões sem a necessidade de engenharia de características manual. Esse aprendizado de ponta a ponta pode simplificar o processo de modelagem e potencialmente fornecer um melhor desempenho, permitindo que o modelo descubra as características mais relevantes.

07. CNNs aplicam operações de convolução que podem ser vantajosas para certos tipos de dados.

As operações de convolução podem detectar e aprimorar sinais importantes nos dados, como mudanças repentinas ou padrões específicos. Isso é particularmente útil na análise financeira, onde detectar mudanças repentinas no mercado ou padrões pode ser crítico.

Agora que temos razões válidas para usar CNNs em aplicações de trading, vamos criar uma e treiná-la. Em seguida, veremos como podemos usar uma CNN em um Expert Advisor (EA) no MetaTrader 5.


Criando uma Rede Neural Convolucional (CNN) em Python

Isso envolve várias etapas, que são:

  1. Coletando os dados
  2. Preparando os dados para um modelo de CNN
  3. Treinando um modelo de CNN
  4. Salvando um modelo de CNN no formato ONNX

01: Coletando os dados

Usando o conjunto de dados para previsão de séries temporais que utilizamos nos artigos anteriores.

Conjunto de dados para previsão de séries temporais

Agora que sabemos que as Redes Neurais Convolucionais (CNNs) são boas em detectar padrões dentro de dados de alta dimensão, podemos escolher algumas características que acredito conter muitos padrões que o modelo de CNN pode 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)

Logo após preparar a variável alvo com base em TARGET_OPEN e TARGET_CLOSE, que são respectivamente os valores de abertura e fechamento coletados um período à frente. Criamos uma versão reduzida do conjunto de dados chamada new_df, que continha apenas 4 variáveis independentes: valores de OPEN, HIGH e LOW, além de uma variável dependente chamada TARGET_VAR.


02: Preparando Dados para um Modelo de CNN

Primeiramente, precisamos pré-processar os dados de entrada, reorganizando e alinhando-os em janelas. Isso é extremamente importante ao trabalhar com dados tabulares em CNNs, e aqui está o porquê.

Como os dados de trading são sequenciais, os padrões geralmente surgem ao longo de uma série de etapas temporais, em vez de um único ponto no tempo. Ao criar janelas sobrepostas de dados, podemos capturar dependências temporais e fornecer contexto ao modelo de CNN.

Além disso, as CNNs esperam que os dados de entrada estejam em um formato específico. Para camadas convolucionais 1D, o formato de entrada normalmente precisa ser (número de janelas, tamanho da janela, número de características). Esse formato se assemelha ao que usamos na análise de séries temporais utilizando Redes Neurais Recorrentes (RNNs) no artigo anterior. O procedimento de pré-processamento que faremos agora garante que os dados estejam nesse formato, tornando-os adequados para serem usados como entrada de um modelo de 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}")

Saídas:

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

Como nossos dados foram coletados em uma escala de tempo diária, o tamanho da janela de 10 indica que estaremos treinando o modelo de CNN para entender padrões dentro de um período de 10 dias.


Em seguida, precisamos dividir os dados em amostras de treinamento e teste.

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

Saídas:

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

y_train (792,) y_test (198,)


Por fim, precisamos aplicar one-hot encoding à variável alvo para esta tarefa de classificação.

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

Saídas:

One hot encoded

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


03: Treinando um Modelo de CNN

Aqui é onde a maior parte do trabalho acontece.

# 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()

Saídas:

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 │
└─────────────────────────────────┴────────────────────────┴───────────────┘

O treinamento parou na 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 perda de treinamento e validação da CNN

O modelo foi aproximadamente 57% preciso nas previsões fora da amostra.

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

Saídas:

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

Nosso modelo de CNN é suficientemente bom para um Expert Advisor. Mas, antes de começarmos a codificar um EA, precisamos salvar o modelo de CNN treinado no formato ONNX.


04: Salvando um modelo de CNN no formato ONNX

O processo é bastante simples. Precisamos salvar o modelo da CNN no formato .onnx e os parâmetros da técnica de normalização em arquivos binários.

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


Criando um Robô de Trading baseado em Rede Neural Convolucional (CNN)

Dentro de um Expert Advisor, a primeira coisa que precisamos fazer é incluir o modelo no formato ONNX e os arquivos binários do Standard Scaler como recursos.

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[]

Precisamos inicializar tanto o scaler quanto o 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);
  }

Isso é o suficiente para fazer o modelo funcionar. Agora, vamos criar a função para extrair os dados de forma semelhante à usada durante o treinamento. Usamos quatro variáveis OHLC dos últimos 10 períodos anteriores, que representam o tamanho da janela. O período de tempo deve ser mantido (gráfico diário).

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

Agora que temos uma função para coletar as variáveis independentes, podemos finalizar nossa estratégia de trading.

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

A estratégia é simples. Ao receber um determinado sinal, como um sinal de compra, abrimos uma posição de compra sem definir stop loss ou take profit e fechamos a posição oposta. O mesmo vale para sinais de venda.

Testei essa estratégia no símbolo para o qual foi treinada, EURUSD, por dez anos. De 01/01/2014 a 27/05/2024 em um gráfico de 4 horas usando preços de abertura de cada barra.

Resultados do teste da estratégia ConvNet EA

Os resultados obtidos no testador de estratégia foram excelentes.

Relatório do testador de estratégia do ConvNet EA

Gráfico do testador do ConvNet EA

O EA fez previsões precisas 58% do tempo, resultando em um lucro líquido de $503.


A linha de fundo

Apesar de terem sido projetadas especificamente para o processamento de imagens e vídeos, quando adaptadas para lidar com dados tabulares, como os dados do mercado forex que utilizamos, As Redes Neurais Convolucionais (CNNs) podem desempenhar um bom papel na detecção de padrões e no uso desses padrões para fazer previsões no mercado forex.

Conforme pode ser visto no relatório do testador de estratégia, O EA baseado em CNN fez previsões razoavelmente precisas. Aposto que muitos modelos tradicionais projetados para dados tabulares, como Regressão Linear, Support Vector Machine e Naive Bayes, não conseguiriam alcançar essa precisão preditiva, considerando que o modelo de CNN foi treinado com apenas 4 variáveis independentes (OHLC). Na minha experiência, poucos modelos conseguem alcançar esse desempenho com um número tão reduzido de variáveis.

Atenciosamente.


Acompanhe o desenvolvimento de modelos de machine learning e muito mais discutido nesta série de artigos no repositório do GitHub.

Tabela de Anexos

Nome do Arquivo
Tipo de Arquivo Descrição|Uso
 ConvNet EA.mq5
 Consultor Especialista          Robô de trading para carregar o modelo CNN no formato ONNX e testar a estratégia final de negociação no MetaTrader 5.
 cnn.EURUSD.D1.onnx
 ONNX  CNN model in ONNX format.
 cnn.EURUSD.D1.standard_scaler_mean.bin  
 cnn.EURUSD.D1.standard_scaler_scale.bin
 Arquivos Binários   Arquivos binários para o Scaler de Padronização
 preprocessing.mqh
 Um arquivo de inclusão
 Uma biblioteca que consiste no Scaler de Padronização
 ConvNet.mqh
 Um arquivo de inclusão   Uma biblioteca para carregar e implantar um modelo CNN no formato ONNX. 
 cnn-for-trading-applications-tutorial.ipynb
 r   Consiste em todo o código Python discutido neste artigo 


Fontes e Referências:


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15259

Arquivos anexados |
Attachments.zip (132.38 KB)
Últimos Comentários | Ir para discussão (2)
npats2007
npats2007 | 29 jan. 2025 em 16:47

5,5 negociações por ano no H4 não são suficientes. Muito pouco.

Silk Road Trading LLC
Ryan L Johnson | 29 jan. 2025 em 21:31

Esta é a explicação mais concisa da CNN aplicada à negociação que já vi e, em sua maior parte, em linguagem simples e diagramas. Em seguida, ela é reduzida para o código MQL5. Observe que o código não está limitado ao período de tempo H4.

Muito bem, senhor!👍

Redes neurais em trading: Transformer vetorial hierárquico (HiVT) Redes neurais em trading: Transformer vetorial hierárquico (HiVT)
Apresentamos o método Transformer Vetorial Hierárquico (HiVT), desenvolvido para a previsão rápida e precisa de séries temporais multimodais.
Combine Estratégias de Análise Fundamental e Técnica no MQL5 Para Iniciantes Combine Estratégias de Análise Fundamental e Técnica no MQL5 Para Iniciantes
Neste artigo, discutiremos como integrar princípios de seguimento de tendência e análise fundamental em um único Expert Advisor para construir uma estratégia mais robusta. Este artigo demonstrará como qualquer pessoa pode facilmente começar a construir algoritmos de trading personalizados usando MQL5.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 28): GANs revisitados com uma introdução às taxas de aprendizado Técnicas do MQL5 Wizard que você deve conhecer (Parte 28): GANs revisitados com uma introdução às taxas de aprendizado
A Taxa de Aprendizado é um tamanho de passo em direção a um objetivo de treinamento nos processos de treinamento de muitos algoritmos de aprendizado de máquina. Examinamos o impacto que seus diversos cronogramas e formatos podem ter no desempenho de uma Rede Generativa Adversária, um tipo de rede neural que já havíamos analisado em um artigo anterior.
Otimização por Quimiotaxia Bacteriana (BCO) Otimização por Quimiotaxia Bacteriana (BCO)
Este artigo apresenta a versão original do algoritmo de otimização por quimiotaxia bacteriana (Bacterial Chemotaxis Optimization, BCO) e sua variante modificada. Examinaremos detalhadamente todas as diferenças, com foco especial na nova versão BCOm, que simplifica o mecanismo de movimento das bactérias, reduz a dependência do histórico de mudanças de posição e emprega operações matemáticas mais simples em comparação com a versão original, que possui um alto custo computacional. Além disso, serão realizados testes e apresentadas conclusões.