English Русский 中文 Deutsch 日本語 Português
preview
Uso del algoritmo de aprendizaje automático PatchTST para predecir la acción del precio durante las próximas 24 horas

Uso del algoritmo de aprendizaje automático PatchTST para predecir la acción del precio durante las próximas 24 horas

MetaTrader 5Trading |
348 15
Shashank Rai
Shashank Rai

Introducción

Me encontré por primera vez con un algoritmo llamado PatchTST cuando comencé a investigar los avances de IA asociados con las predicciones de series de tiempo en Huggingface.co. Como sabrá cualquiera que haya trabajado con grandes modelos lingüísticos (LLM), la invención de los transformadores ha supuesto un cambio radical en el desarrollo de herramientas para el procesamiento del lenguaje natural, las imágenes y el vídeo. ¿Pero qué pasa con las series temporales? ¿Es algo que simplemente quedó atrás? ¿O la mayor parte de la investigación se realiza simplemente a puertas cerradas? Resulta que hay muchos modelos más nuevos que aplican transformadores con éxito para predecir series de tiempo. En este artículo, analizaremos una de esas implementaciones.

Lo impresionante de PatchTST es lo rápido que es entrenar un modelo y lo fácil que es usar el modelo entrenado con MQL. Admito abiertamente que soy nuevo en el concepto de redes neuronales. Pero al pasar por este proceso y abordar la implementación de PatchTST descrita en este artículo para MQL5, sentí que di un gran paso adelante en mi aprendizaje y comprensión de cómo se desarrollan, solucionan problemas, entrenan y utilizan estas redes neuronales complejas. Es como coger a un niño, que apenas está aprendiendo a andar, y ponerlo en un equipo de fútbol profesional, esperando que marque el gol de la victoria en la final de la Copa del Mundo. 


Descripción general de PatchTST

Después de descubrir PatchTST, comencé a investigar el artículo que explica su diseño: "A Time Series is Worth 64 Words: Long-term Forecasting with Transformers". El título era interesante. A medida que comencé a leer más sobre el documento, pensé: “¡Vaya! Esta parece una estructura fascinante: tiene muchos elementos que siempre quise aprender”. Naturalmente, quise probarlo y ver cómo funcionan las predicciones. Esto es lo que me hizo interesarme aún más en este algoritmo:

  • Puede predecir la apertura, el máximo, el mínimo y el cierre utilizando PatchTST. Con PatchTST, sentí que podía alimentarlo con todos los datos a medida que llegaban: apertura, máximo, mínimo, cierre e incluso volumen. Se puede esperar que encuentre patrones en los datos porque todos los datos se convierten en algo llamado "parches". Más adelante en este artículo hablaremos más sobre qué son estos parches. Por ahora, solo es importante saber que los parches son atractivos y ayudan a mejorar las predicciones.
  • Requisitos mínimos de preprocesamiento de datos con PatchTST. Cuando comencé a profundizar en el algoritmo, me di cuenta de que los autores utilizan algo llamado "RevIn", que es la normalización de instancia inversa. RevIn proviene de un artículo titulado: "REVERSIBLE INSTANCE NORMALIZATION FOR ACCURATE TIME-SERIES FORECASTING AGAINST DISTRIBUTION SHIFT". RevIn intenta abordar el problema del cambio de distribución en la previsión de series temporales. Como traders algorítmicos, estamos muy familiarizados con la sensación que sentimos cuando nuestro EA entrenado ya no parece predecir el mercado y nos vemos obligados a volver a optimizar y actualizar nuestros parámetros. Considere RevIn como una forma de hacer lo mismo. 

  • Este método básicamente toma los datos que se le pasan y los normaliza utilizando la siguiente fórmula:

    x = (x - mean) / std

    Luego, cuando el modelo tiene que hacer una predicción, desnormaliza los datos utilizando la propiedad opuesta:

    x = x * std + mean

    RevIn también tiene otra propiedad llamada affine_bias. En los términos más sencillos posibles, se trata de un parámetro que se puede aprender y que se ocupa de la asimetría, la curtosis, etc. que pueda haber en el conjunto de datos. 

    x = x * affine_weight + affine_bias

    La estructura de PatchTST puede resumirse como sigue: 

    Datos de entrada -> RevIn -> Descomposición de series -> Componente de tendencia -> PatchTST Backbone -> TSTi Encoder -> Flatten_Head -> Pronosticador de tendencia -> Componente residual -> Añadir tendencia y residual -> Predicción final

    Entendemos que nuestros datos se extraerán mediante MT5. También hemos discutido cómo funciona RevIn.

    Así es como funciona PatchTST: digamos que extraes 80.000 barras de datos EURUSD para el período de tiempo H1. Eso equivale aproximadamente a 13 años de datos. Con PatchTST, segmentas los datos en algo llamado “parches”. A modo de analogía, piense en los parches como algo similar a cómo funcionan los Transformadores de Visión (Vision Transformers,ViTs) para imágenes, pero adaptados para datos de series de tiempo. Entonces, por ejemplo, si la longitud del parche es 16, cada parche contendría 16 valores de precios consecutivos. Esto es como mirar pequeños fragmentos de la serie temporal a la vez, lo que ayuda al modelo a centrarse en los patrones locales antes de considerar el patrón global.

    A continuación, los parches incluyen codificación posicional para preservar el orden de la secuencia, lo que ayuda al modelo a recordar la posición de cada parche en la secuencia.

    El transformador pasa los parches normalizados y codificados a través de una pila de capas de codificador. Cada capa del codificador contiene una capa de atención de múltiples cabezales y una capa de avance. La capa de atención de múltiples cabezales permite que el modelo preste atención a diferentes partes de la secuencia de entrada, mientras que la capa de avance permite que el modelo aprenda transformaciones no lineales complejas de los datos.

    Por último, tenemos los componentes de tendencia y residual. Las mismas capas de parcheo, normalización, codificación posicional y transformación se aplican tanto al componente de tendencia como al componente residual. Luego, sumamos los resultados de los componentes de tendencia y residuales para producir el pronóstico final.


    Problemas con el repositorio oficial de PatchTST

    El repositorio oficial de PatchTST se puede encontrar en GitHub en el siguiente enlace: PatchTST (ICLR 2023). Hay dos versiones diferentes disponibles: supervisada y no supervisada. Para este artículo, utilizaremos el enfoque de aprendizaje supervisado. Como sabemos, para utilizar cualquier modelo con MQL5, necesitamos una forma de convertirlo al formato ONNX. Sin embargo, los autores de PatchTST no tuvieron esto en cuenta. Tuve que hacer las siguientes modificaciones a su código base para que el modelo funcionara con MQL5: 

    Código original: 

    class PatchTST_backbone(nn.Module):
        def __init__(self, c_in:int, context_window:int, target_window:int, patch_len:int, stride:int, max_seq_len:Optional[int]=1024, 
                     n_layers:int=3, d_model=128, n_heads=16, d_k:Optional[int]=None, d_v:Optional[int]=None,
                     d_ff:int=256, norm:str='BatchNorm', attn_dropout:float=0., dropout:float=0., act:str="gelu", key_padding_mask:bool='auto',
                     padding_var:Optional[int]=None, attn_mask:Optional[Tensor]=None, res_attention:bool=True, pre_norm:bool=False,                          
                     store_attn:bool=False,
                     pe:str='zeros', learn_pe:bool=True, fc_dropout:float=0., head_dropout = 0, padding_patch = None,
                     pretrain_head:bool=False, head_type = 'flatten', individual = False, revin = True, affine = True, subtract_last = False,
                     verbose:bool=False, **kwargs):
            
            super().__init__()
            
            # RevIn
            self.revin = revin
            if self.revin: self.revin_layer = RevIN(c_in, affine=affine, subtract_last=subtract_last)
            
            # Patching
            self.patch_len = patch_len
            self.stride = stride
            self.padding_patch = padding_patch
            patch_num = int((context_window - patch_len)/stride + 1)
            if padding_patch == 'end': # can be modified to general case
                self.padding_patch_layer = nn.ReplicationPad1d((0, stride)) 
                patch_num += 1
            
            # Backbone 
            self.backbone = TSTiEncoder(c_in, patch_num=patch_num, patch_len=patch_len, max_seq_len=max_seq_len,
                                    n_layers=n_layers, d_model=d_model, n_heads=n_heads, d_k=d_k, d_v=d_v, d_ff=d_ff,
                                    attn_dropout=attn_dropout, dropout=dropout, act=act, key_padding_mask=key_padding_mask, padding_var=padding_var,
                                    attn_mask=attn_mask, res_attention=res_attention, pre_norm=pre_norm, store_attn=store_attn,
                                    pe=pe, learn_pe=learn_pe, verbose=verbose, **kwargs)
    
            # Head
            self.head_nf = d_model * patch_num
            self.n_vars = c_in
            self.pretrain_head = pretrain_head
            self.head_type = head_type
            self.individual = individual
    
            if self.pretrain_head: 
                self.head = self.create_pretrain_head(self.head_nf, c_in, fc_dropout) # custom head passed as a partial func with all its kwargs
            elif head_type == 'flatten': 
                self.head = Flatten_Head(self.individual, self.n_vars, self.head_nf, target_window, head_dropout=head_dropout)
            
        
        def forward(self, z):                                                                   # z: [bs x nvars x seq_len]
            # norm
            if self.revin: 
                z = z.permute(0,2,1)
                z = self.revin_layer(z, 'norm')
                z = z.permute(0,2,1)
                
            # do patching
            if self.padding_patch == 'end':
                z = self.padding_patch_layer(z)
            z = z.unfold(dimension=-1, size=self.patch_len, step=self.stride)                   # z: [bs x nvars x patch_num x patch_len]
            z = z.permute(0,1,3,2)                                                              # z: [bs x nvars x patch_len x patch_num]
            
            # model
            z = self.backbone(z)                                                                # z: [bs x nvars x d_model x patch_num]
            z = self.head(z)                                                                    # z: [bs x nvars x target_window] 
            
            # denorm
            if self.revin: 
                z = z.permute(0,2,1)
                z = self.revin_layer(z, 'denorm')
                z = z.permute(0,2,1)
            return z

    El código anterior es la columna vertebral principal. Como puedes ver, el código utiliza una función llamada Unfold en la línea: 

     z = z.unfold(dimension=-1, size=self.patch_len, step=self.stride)                   # z: [bs x nvars x patch_num x patch_len]

    ONNX no admite la conversión de Unfold. Recibirás un error como el siguiente: 

    Unsupported: ONNX export of operator Unfold, input size not accessible. Please feel free to request support or submit a pull request on PyTorch GitHub: https://github.com/pytorch/pytorch/issues

    Entonces tuve que reemplazar esta sección del código con: 

    # Manually unfold the input tensor
        batch_size, n_vars, seq_len = z.size()
        patches = []
        for i in range(0, seq_len - self.patch_len + 1, self.stride):
            patches.append(z[:, :, i:i+self.patch_len])

    Tenga en cuenta que el reemplazo anterior es un poco menos eficiente porque utiliza un bucle for para entrenar una red neuronal. Las ineficiencias pueden acumularse a lo largo de muchas épocas y en grandes conjuntos de datos. Pero esto es necesario porque de lo contrario, el modelo simplemente no podrá convertirse y no podremos usarlo con MQL5. 

    He abordado específicamente esta cuestión. Hacer esto tomó el mayor tiempo. Luego puse todo junto en un archivo llamado patchTST.py, que se puede encontrar en el archivo ZIP adjunto a este artículo. Este es el archivo que usaremos para el entrenamiento de nuestro modelo. 


    Requisitos para trabajar con PatchTST en Python

    En esta sección, te daré los requisitos para trabajar con PatchTST en Python. Estos requisitos se pueden resumir a continuación:

    Crear un entorno virtual: 

    python -m venv myenv

    Activar el entorno virtual (Windows)

    .\myenv\Scripts\activate
    

    Instale el archivo requirements.txt incluido en el archivo ZIP adjunto a este artículo: 

    pip install -r requirements.txt

    En concreto, los requisitos para ejecutar este proyecto son: 

    MetaTrader5
    pandas
    numpy
    torch
    plotly
    datetime


    Desarrollo de código de entrenamiento modelo paso a paso

    Para el siguiente código, puedes seguirme usando un cuaderno Jupyter que he incluido en el archivo ZIP: PatchTST Step-By-Step.ipynb. A continuación resumiremos los pasos: 

    1. Importar las bibliotecas necesarias: Importar las bibliotecas necesarias, incluidas MetaTrader 5, Pandas, Numpy, Torch y el modelo PatchTST.

      # Step 1: Import necessary libraries
      import MetaTrader5 as mt5
      import pandas as pd
      import numpy as np
      import torch
      from torch.utils.data import TensorDataset, DataLoader
      from patchTST import Model as PatchTST
    2. Inicializar y recuperar datos de MetaTrader 5: La función fetch_mt5_data inicializa MT5, obtiene los datos para el símbolo, el período de tiempo y el número de barras dados, luego devuelve un marco de datos con las columnas de apertura, máximo, mínimo y cierre.

      # Step 2: Initialize and fetch data from MetaTrader 5
      def fetch_mt5_data(symbol, timeframe, bars):
          if not mt5.initialize():
              print("MT5 initialization failed")
              return None
      
          timeframe_dict = {
              'M1': mt5.TIMEFRAME_M1,
              'M5': mt5.TIMEFRAME_M5,
              'M15': mt5.TIMEFRAME_M15,
              'H1': mt5.TIMEFRAME_H1,
              'D1': mt5.TIMEFRAME_D1
          }
      
          rates = mt5.copy_rates_from_pos(symbol, timeframe_dict[timeframe], 0, bars)
          mt5.shutdown()
      
          df = pd.DataFrame(rates)
          df['time'] = pd.to_datetime(df['time'], unit='s')
          df.set_index('time', inplace=True)
          return df[['open', 'high', 'low', 'close']]
      
      # Fetch data
      data = fetch_mt5_data('EURUSD', 'H1', 80000)
    3. Preparar datos de pronóstico utilizando una ventana deslizante: La función prepare_forecasting_data crea el conjunto de datos utilizando un enfoque de ventana deslizante, generando secuencias de datos históricos (X) y los datos futuros correspondientes (y).

      # Step 3: Prepare forecasting data using sliding window
      def prepare_forecasting_data(data, seq_length, pred_length):
          X, y = [], []
          for i in range(len(data) - seq_length - pred_length):
              X.append(data.iloc[i:(i + seq_length)].values)
              y.append(data.iloc[(i + seq_length):(i + seq_length + pred_length)].values)
          return np.array(X), np.array(y)
      
      seq_length = 168  # 1 week of hourly data
      pred_length = 24  # Predict next 24 hours
      
      X, y = prepare_forecasting_data(data, seq_length, pred_length)
    4. Dividir los datos en conjuntos de entrenamiento y prueba: Dividir los datos en conjuntos de entrenamiento y prueba, con un 80 % para entrenamiento y un 20 % para prueba. 

      # Step 4: Split data into training and testing sets
      split = int(len(X) * 0.8)
      X_train, X_test = X[:split], X[split:]
      y_train, y_test = y[:split], y[split:]
      
    5. Convertir datos en tensores de PyTorch: Conversión de las matrices NumPy en tensores PyTorch, que son necesarios para el entrenamiento con PyTorch. Establece una semilla manual para la antorcha, para la reproducibilidad de los resultados. 

      # Step 5: Convert data to PyTorch tensors
      X_train = torch.tensor(X_train, dtype=torch.float32)
      y_train = torch.tensor(y_train, dtype=torch.float32)
      X_test = torch.tensor(X_test, dtype=torch.float32)
      y_test = torch.tensor(y_test, dtype=torch.float32)
      
      torch.manual_seed(42)
    6. Configurar el dispositivo para cálculo: Configurar el dispositivo en CUDA si está disponible, de lo contrario, utilizar la CPU. Esto es esencial para aprovechar la aceleración de la GPU durante el entrenamiento, especialmente si está disponible.

      # Step 6: Set device for computation
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
      print(f"Using device: {device}")
    7. Crear un cargador de datos para los datos de entrenamiento: Creación de un cargador de datos para gestionar el procesamiento por lotes y la mezcla de los datos de entrenamiento.

      # Step 7: Create DataLoader for training data
      train_dataset = TensorDataset(X_train, y_train)
      train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
    8. Definir la clase de configuración para el modelo: Definir una clase de configuración Config para almacenar todos los hiperparámetros y configuraciones necesarios para el modelo PatchTST.

      # Step 8: Define the configuration class for the model
      class Config:
          def __init__(self):
              self.enc_in = 4  # Adjusted for 4 columns (open, high, low, close)
              self.seq_len = seq_length
              self.pred_len = pred_length
              self.e_layers = 3
              self.n_heads = 4
              self.d_model = 64
              self.d_ff = 256
              self.dropout = 0.1
              self.fc_dropout = 0.1
              self.head_dropout = 0.1
              self.individual = False
              self.patch_len = 24
              self.stride = 24
              self.padding_patch = True
              self.revin = True
              self.affine = False
              self.subtract_last = False
              self.decomposition = True
              self.kernel_size = 25
      
      configs = Config()
    9. Inicializar el modelo PatchTST: Inicializa el modelo PatchTST con la configuración definida y lo mueve al dispositivo seleccionado.

      # Step 9: Initialize the PatchTST model
      model = PatchTST(
          configs=configs,
          max_seq_len=1024,
          d_k=None,
          d_v=None,
          norm='BatchNorm',
          attn_dropout=0.1,
          act="gelu",
          key_padding_mask='auto',
          padding_var=None,
          attn_mask=None,
          res_attention=True,
          pre_norm=False,
          store_attn=False,
          pe='zeros',
          learn_pe=True,
          pretrain_head=False,
          head_type='flatten',
          verbose=False
      ).to(device)
    10. Definir optimizador y función de pérdida: Configuración del optimizador (Adam) y la función de pérdida (error cuadrático medio) para entrenar el modelo.

      # Step 10: Define optimizer and loss function
      optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
      loss_fn = torch.nn.MSELoss()
      num_epochs = 100
    11. Entrenar el modelo: Entrenamiento del modelo durante el número especificado de épocas. Para cada lote de datos, el modelo realiza un pase hacia adelante, calcula la pérdida, realiza un pase hacia atrás para calcular gradientes y actualiza los parámetros del modelo.

      # Step 11: Train the model
      for epoch in range(num_epochs):
          model.train()
          total_loss = 0
          for batch_X, batch_y in train_loader:
              optimizer.zero_grad()
              batch_X = batch_X.to(device)
              batch_y = batch_y.to(device)
      
              outputs = model(batch_X)
              outputs = outputs[:, -pred_length:, :4]
      
              loss = loss_fn(outputs, batch_y)
      
              loss.backward()
              optimizer.step()
              total_loss += loss.item()
      
          print(f"Epoch {epoch+1}/{num_epochs}, Loss: {total_loss/len(train_loader):.10f}")
    12. Guardar el modelo en formato PyTorch: Guardar el diccionario de estados del modelo entrenado en un archivo. Podemos usar este archivo para hacer predicciones directamente en Python. 

      # Step 12: Save the model in PyTorch format
      torch.save(model.state_dict(), 'patchtst_model.pth')
    13. Preparar una entrada ficticia para la exportación ONNX: Creación de un tensor de entrada ficticio para usar para exportar el modelo al formato ONNX. 

      # Step 13: Prepara una entrada ficticia para exportar ONNX
      dummy_input = torch.randn(1, seq_length, 4).to(device)
    14. Exportar el modelo al formato ONNX: Exportar el modelo entrenado al formato ONNX. Necesitaremos este fichero para realizar predicciones con MQL5. 

      # Step 14: Export the model to ONNX format
      torch.onnx.export(model, dummy_input, "patchtst_model.onnx",
                        opset_version=13,
                        input_names=['input'],
                        output_names=['output'],
                        dynamic_axes={'input': {0: 'batch_size'},
                                      'output': {0: 'batch_size'}})
      
      print("Model trained and saved in PyTorch and ONNX formats.")


    Resultados del entrenamiento de modelos

    Estos son los resultados que he obtenido al entrenar el modelo. 

    Epoch 1/100, Loss: 0.0000283705
    Epoch 2/100, Loss: 0.0000263274
    Epoch 3/100, Loss: 0.0000256321
    Epoch 4/100, Loss: 0.0000252389
    Epoch 5/100, Loss: 0.0000249340
    Epoch 6/100, Loss: 0.0000246715
    Epoch 7/100, Loss: 0.0000244293
    Epoch 8/100, Loss: 0.0000241942
    Epoch 9/100, Loss: 0.0000240157
    Epoch 10/100, Loss: 0.0000236776
    Epoch 11/100, Loss: 0.0000233954
    Epoch 12/100, Loss: 0.0000230437
    Epoch 13/100, Loss: 0.0000226635
    Epoch 14/100, Loss: 0.0000221875
    Epoch 15/100, Loss: 0.0000216960
    Epoch 16/100, Loss: 0.0000213242
    Epoch 17/100, Loss: 0.0000208693
    Epoch 18/100, Loss: 0.0000204956
    Epoch 19/100, Loss: 0.0000200573
    Epoch 20/100, Loss: 0.0000197222
    Epoch 21/100, Loss: 0.0000193516
    Epoch 22/100, Loss: 0.0000189223
    Epoch 23/100, Loss: 0.0000186635
    Epoch 24/100, Loss: 0.0000184025
    Epoch 25/100, Loss: 0.0000180468
    Epoch 26/100, Loss: 0.0000177854
    Epoch 27/100, Loss: 0.0000174621
    Epoch 28/100, Loss: 0.0000173247
    Epoch 29/100, Loss: 0.0000170032
    Epoch 30/100, Loss: 0.0000168594
    Epoch 31/100, Loss: 0.0000166609
    Epoch 32/100, Loss: 0.0000164818
    Epoch 33/100, Loss: 0.0000162424
    Epoch 34/100, Loss: 0.0000161265
    Epoch 35/100, Loss: 0.0000159775
    Epoch 36/100, Loss: 0.0000158510
    Epoch 37/100, Loss: 0.0000156571
    Epoch 38/100, Loss: 0.0000155327
    Epoch 39/100, Loss: 0.0000154742
    Epoch 40/100, Loss: 0.0000152778
    Epoch 41/100, Loss: 0.0000151757
    Epoch 42/100, Loss: 0.0000151083
    Epoch 43/100, Loss: 0.0000150182
    Epoch 44/100, Loss: 0.0000149140
    Epoch 45/100, Loss: 0.0000148057
    Epoch 46/100, Loss: 0.0000147672
    Epoch 47/100, Loss: 0.0000146499
    Epoch 48/100, Loss: 0.0000145281
    Epoch 49/100, Loss: 0.0000145298
    Epoch 50/100, Loss: 0.0000144795
    Epoch 51/100, Loss: 0.0000143969
    Epoch 52/100, Loss: 0.0000142840
    Epoch 53/100, Loss: 0.0000142294
    Epoch 54/100, Loss: 0.0000142159
    Epoch 55/100, Loss: 0.0000140837
    Epoch 56/100, Loss: 0.0000140005
    Epoch 57/100, Loss: 0.0000139986
    Epoch 58/100, Loss: 0.0000139122
    Epoch 59/100, Loss: 0.0000139010
    Epoch 60/100, Loss: 0.0000138351
    Epoch 61/100, Loss: 0.0000138050
    Epoch 62/100, Loss: 0.0000137636
    Epoch 63/100, Loss: 0.0000136853
    Epoch 64/100, Loss: 0.0000136191
    Epoch 65/100, Loss: 0.0000136272
    Epoch 66/100, Loss: 0.0000135552
    Epoch 67/100, Loss: 0.0000135439
    Epoch 68/100, Loss: 0.0000135200
    Epoch 69/100, Loss: 0.0000134461
    Epoch 70/100, Loss: 0.0000133950
    Epoch 71/100, Loss: 0.0000133979
    Epoch 72/100, Loss: 0.0000133059
    Epoch 73/100, Loss: 0.0000133242
    Epoch 74/100, Loss: 0.0000132816
    Epoch 75/100, Loss: 0.0000132145
    Epoch 76/100, Loss: 0.0000132803
    Epoch 77/100, Loss: 0.0000131212
    Epoch 78/100, Loss: 0.0000131809
    Epoch 79/100, Loss: 0.0000131538
    Epoch 80/100, Loss: 0.0000130786
    Epoch 81/100, Loss: 0.0000130651
    Epoch 82/100, Loss: 0.0000130255
    Epoch 83/100, Loss: 0.0000129917
    Epoch 84/100, Loss: 0.0000129804
    Epoch 85/100, Loss: 0.0000130086
    Epoch 86/100, Loss: 0.0000130156
    Epoch 87/100, Loss: 0.0000129557
    Epoch 88/100, Loss: 0.0000129013
    Epoch 89/100, Loss: 0.0000129018
    Epoch 90/100, Loss: 0.0000128864
    Epoch 91/100, Loss: 0.0000128663
    Epoch 92/100, Loss: 0.0000128411
    Epoch 93/100, Loss: 0.0000128514
    Epoch 94/100, Loss: 0.0000127915
    Epoch 95/100, Loss: 0.0000127778
    Epoch 96/100, Loss: 0.0000127787
    Epoch 97/100, Loss: 0.0000127623
    Epoch 98/100, Loss: 0.0000127452
    Epoch 99/100, Loss: 0.0000127141
    Epoch 100/100, Loss: 0.0000127229

    Los resultados se pueden visualizar de la siguiente manera: 

    Progreso del entrenamiento a lo largo de 100 épocas

    También obtenemos el siguiente resultado sin errores ni advertencias, lo que indica que nuestro modelo se ha convertido correctamente al formato ONNX. 

    Model trained and saved in PyTorch and ONNX formats.
    


    Generación de predicciones con Python paso a paso

    Ahora veamos el código de predicción: 

    1. Paso 1. Importar bibliotecas necesarias: Comenzamos importando todas las librerías necesarias.
      # Import required libraries
      import MetaTrader5 as mt5
      import pandas as pd
      import numpy as np
      import torch
      from datetime import datetime, timedelta
      import plotly.graph_objects as go
      from plotly.subplots import make_subplots
      from patchTST import Model as PatchTST
    2. Paso 2. Obtener datos de MetaTrader 5: Definimos una función para obtener datos de MetaTrader 5 y convertirlos en un DataFrame. Obtenemos 168 barras anteriores porque eso es lo que se requiere para obtener una predicción con nuestro modelo. 
      # Function to fetch data from MetaTrader 5
      def fetch_mt5_data(symbol, timeframe, bars):
          if not mt5.initialize():
              print("MT5 initialization failed")
              return None
      
          timeframe_dict = {
              'M1': mt5.TIMEFRAME_M1,
              'M5': mt5.TIMEFRAME_M5,
              'M15': mt5.TIMEFRAME_M15,
              'H1': mt5.TIMEFRAME_H1,
              'D1': mt5.TIMEFRAME_D1
          }
      
          rates = mt5.copy_rates_from_pos(symbol, timeframe_dict[timeframe], 0, bars)
          mt5.shutdown()
      
          df = pd.DataFrame(rates)
          df['time'] = pd.to_datetime(df['time'], unit='s')
          df.set_index('time', inplace=True)
          return df[['open', 'high', 'low', 'close']]
      
      # Fetch the latest week of data
      historical_data = fetch_mt5_data('EURUSD', 'H1', 168)
    3. Paso 3. Preparar datos de entrada: Definimos una función para preparar los datos de entrada para el modelo tomando las últimas filas de datos seq_length. Al extraer datos, solo necesitamos las últimas 168 horas de datos de 1 hora para hacer predicciones para las próximas 24 horas. Esto se debe a que así es como entrenamos el modelo. 
      # Function to prepare input data
      def prepare_input_data(data, seq_length):
          X = []
          X.append(data.iloc[-seq_length:].values)
          return np.array(X)
      
      # Prepare the input data
      seq_length = 168  # 1 week of hourly data
      input_data = prepare_input_data(historical_data, seq_length)
      
    4. Paso 4. Definir configuración: Definimos una clase de configuración para establecer los parámetros para el modelo. Estas configuraciones son las mismas que usamos para entrenar el modelo. 
      # Define the configuration class
      class Config:
          def __init__(self):
              self.enc_in = 4  # Adjusted for 4 columns (open, high, low, close)
              self.seq_len = seq_length
              self.pred_len = 24  # Predict next 24 hours
              self.e_layers = 3
              self.n_heads = 4
              self.d_model = 64
              self.d_ff = 256
              self.dropout = 0.1
              self.fc_dropout = 0.1
              self.head_dropout = 0.1
              self.individual = False
              self.patch_len = 24
              self.stride = 24
              self.padding_patch = True
              self.revin = True
              self.affine = False
              self.subtract_last = False
              self.decomposition = True
              self.kernel_size = 25
      
      # Initialize the configuration
      config = Config()
    5. Paso 5. Cargar el modelo entrenado: Definimos una función para cargar el modelo PatchTST entrenado. Éstas son las mismas configuraciones que usamos para entrenar el modelo. 
      # Function to load the trained model
      def load_model(model_path, config):
          model = PatchTST(
              configs=config,
              max_seq_len=1024,
              d_k=None,
              d_v=None,
              norm='BatchNorm',
              attn_dropout=0.1,
              act="gelu",
              key_padding_mask='auto',
              padding_var=None,
              attn_mask=None,
              res_attention=True,
              pre_norm=False,
              store_attn=False,
              pe='zeros',
              learn_pe=True,
              pretrain_head=False,
              head_type='flatten',
              verbose=False
          )
          model.load_state_dict(torch.load(model_path))
          model.eval()
          return model
      
      # Load the trained model
      model_path = 'patchtst_model.pth'
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
      model = load_model(model_path, config).to(device)
      
    6. Paso 6. Hacer predicciones: Definimos una función para realizar predicciones utilizando el modelo cargado y los datos de entrada.
      # Function to make predictions
      def predict(model, input_data, device):
          with torch.no_grad():
              input_data = torch.tensor(input_data, dtype=torch.float32).to(device)
              output = model(input_data)
          return output.cpu().numpy()
      
      # Make predictions
      predictions = predict(model, input_data, device)
      
    7. Paso 7. Posprocesamiento y visualización: Procesamos las predicciones, creamos un marco de datos y visualizamos los datos históricos y predichos utilizando Plotly.
      # Ensure predictions have the correct shape
      if predictions.shape[2] != 4:
          predictions = predictions[:, :, :4]  # Adjust based on actual number of columns required
      
      # Check the shape of predictions
      print("Shape of predictions:", predictions.shape)
      
      # Create a DataFrame for predictions
      pred_index = pd.date_range(start=historical_data.index[-1] + pd.Timedelta(hours=1), periods=24, freq='H')
      pred_df = pd.DataFrame(predictions[0], columns=['open', 'high', 'low', 'close'], index=pred_index)
      
      # Combine historical data and predictions
      combined_df = pd.concat([historical_data, pred_df])
      
      # Create the plot
      fig = make_subplots(rows=1, cols=1, shared_xaxes=True, vertical_spacing=0.03, subplot_titles=('EURUSD OHLC'))
      
      # Add historical candlestick
      fig.add_trace(go.Candlestick(x=historical_data.index,
                                   open=historical_data['open'],
                                   high=historical_data['high'],
                                   low=historical_data['low'],
                                   close=historical_data['close'],
                                   name='Historical'))
      
      # Add predicted candlestick
      fig.add_trace(go.Candlestick(x=pred_df.index,
                                   open=pred_df['open'],
                                   high=pred_df['high'],
                                   low=pred_df['low'],
                                   close=pred_df['close'],
                                   name='Predicted'))
      
      # Add a vertical line to separate historical data from predictions
      fig.add_vline(x=historical_data.index[-1], line_dash="dash", line_color="gray")
      
      # Update layout
      fig.update_layout(title='EURUSD OHLC Chart with Predictions',
                        yaxis_title='Price',
                        xaxis_rangeslider_visible=False)
      
      # Show the plot
      fig.show()
      
      # Print predictions (optional)
      print("Predicted prices for the next 24 hours:", predictions)
      


    Código de entrenamiento y predicción en Python

    Si no está interesado en ejecutar la base de código en un cuaderno Jupyter, he proporcionado un par de archivos que puede ejecutar directamente en los archivos adjuntos: 

    • model_training.py
    • model_prediction.py

    Puede configurar el modelo como desee y ejecutarlo sin usar Jupyter. 


    Resultados de la predicción

    Después de entrenar el modelo y ejecutar el código de predicción en Python, obtuve el siguiente gráfico. Las predicciones se crearon aproximadamente a las 12:30 a. m. (CEST + 3) del 8/7/2024. Esto es justo en la apertura del domingo por la noche/lunes por la mañana. Podemos ver un hueco (gap) en el gráfico porque el EURUSD abrió con un hueco. El modelo predice que el EURUSD debería experimentar una tendencia alcista durante la mayor parte del tiempo, posiblemente llenando este hueco (gap). Una vez que se haya llenado el hueco (gap), el precio de la acción debería volverse hacia abajo cerca del final del día. 

    Predicciones de Python

    También imprimimos el valor bruto de los resultados, que se puede ver a continuación: 

    Predicted prices for the next 24 hours: [[[1.0789319 1.08056   1.0789403 1.0800443]
      [1.0791171 1.080738  1.0791024 1.0802013]
      [1.0792702 1.0807946 1.0792127 1.0802455]
      [1.0794896 1.0809869 1.07939   1.0804181]
      [1.0795166 1.0809793 1.0793561 1.0803629]
      [1.0796498 1.0810834 1.079427  1.0804263]
      [1.0798903 1.0813211 1.0795883 1.0805805]
      [1.0800778 1.081464  1.0796818 1.0806502]
      [1.0801392 1.0815498 1.0796598 1.0806476]
      [1.0802988 1.0817037 1.0797216 1.0807337]
      [1.080521  1.0819166 1.079835  1.08086  ]
      [1.0804708 1.0818571 1.079683  1.0807351]
      [1.0805807 1.0819991 1.079669  1.0807738]
      [1.0806456 1.0820425 1.0796478 1.0807805]
      [1.080733  1.0821087 1.0796758 1.0808226]
      [1.0807986 1.0822101 1.0796862 1.08086  ]
      [1.0808219 1.0821983 1.0796905 1.0808747]
      [1.0808604 1.082247  1.0797052 1.0808727]
      [1.0808146 1.082188  1.0796149 1.0807893]
      [1.0809066 1.0822624 1.0796828 1.0808471]
      [1.0809724 1.0822903 1.0797662 1.0808889]
      [1.0810378 1.0823163 1.0797914 1.0809084]
      [1.0810691 1.0823379 1.0798224 1.0809308]
      [1.0810966 1.0822875 1.0797993 1.0808865]]]


    Incorporación del modelo preentrenado a MQL5

    En esta sección, crearemos un precursor de un indicador que nos ayudará a visualizar la acción del precio prevista en nuestros gráficos. He hecho deliberadamente el guión rudimentario y abierto porque nuestros lectores pueden tener diferentes objetivos y diferentes estrategias sobre cómo utilizar estas complejas redes neuronales. El indicador está desarrollado en el formato de asesor experto MQL5. Aquí está el script completo: 

    //+------------------------------------------------------------------+
    //|                                            PatchTST Predictor    |
    //|                                                   Copyright 2024 |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2024"
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #resource "\\PatchTST\\patchtst_model.onnx" as uchar PatchTSTModel[]
    
    #define SEQ_LENGTH 168
    #define PRED_LENGTH 24
    #define INPUT_FEATURES 4
    
    long ModelHandle = INVALID_HANDLE;
    datetime ExtNextBar = 0;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
    {
       // Load the ONNX model
       ModelHandle = OnnxCreateFromBuffer(PatchTSTModel, ONNX_DEFAULT);
       if (ModelHandle == INVALID_HANDLE)
       {
          Print("Error creating ONNX model: ", GetLastError());
          return(INIT_FAILED);
       }
    
       // Set input shape
       const long input_shape[] = {1, SEQ_LENGTH, INPUT_FEATURES};
       if (!OnnxSetInputShape(ModelHandle, ONNX_DEFAULT, input_shape))
       {
          Print("Error setting input shape: ", GetLastError());
          return(INIT_FAILED);
       }
    
       // Set output shape
       const long output_shape[] = {1, PRED_LENGTH, INPUT_FEATURES};
       if (!OnnxSetOutputShape(ModelHandle, 0, output_shape))
       {
          Print("Error setting output shape: ", GetLastError());
          return(INIT_FAILED);
       }
    
       return(INIT_SUCCEEDED);
    }
    
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
       if (ModelHandle != INVALID_HANDLE)
       {
          OnnxRelease(ModelHandle);
          ModelHandle = INVALID_HANDLE;
       }
    }
    
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
    {
       if (TimeCurrent() < ExtNextBar)
          return;
    
       ExtNextBar = TimeCurrent();
       ExtNextBar -= ExtNextBar % PeriodSeconds();
       ExtNextBar += PeriodSeconds();
    
       // Prepare input data
       float input_data[];
       if (!PrepareInputData(input_data))
       {
          Print("Error preparing input data");
          return;
       }
    
       // Make prediction
       float predictions[];
       if (!MakePrediction(input_data, predictions))
       {
          Print("Error making prediction");
          return;
       }
    
       // Draw hypothetical future bars
       DrawFutureBars(predictions);
    }
    
    //+------------------------------------------------------------------+
    //| Prepare input data for the model                                 |
    //+------------------------------------------------------------------+
    bool PrepareInputData(float &input_data[])
    {
       MqlRates rates[];
       ArraySetAsSeries(rates, true);
       int copied = CopyRates(_Symbol, PERIOD_H1, 0, SEQ_LENGTH, rates);
       
       if (copied != SEQ_LENGTH)
       {
          Print("Failed to copy rates data. Copied: ", copied);
          return false;
       }
    
       ArrayResize(input_data, SEQ_LENGTH * INPUT_FEATURES);
       for (int i = 0; i < SEQ_LENGTH; i++)
       {
          input_data[i * INPUT_FEATURES + 0] = (float)rates[SEQ_LENGTH - 1 - i].open;
          input_data[i * INPUT_FEATURES + 1] = (float)rates[SEQ_LENGTH - 1 - i].high;
          input_data[i * INPUT_FEATURES + 2] = (float)rates[SEQ_LENGTH - 1 - i].low;
          input_data[i * INPUT_FEATURES + 3] = (float)rates[SEQ_LENGTH - 1 - i].close;
       }
    
       return true;
    }
    
    //+------------------------------------------------------------------+
    //| Make prediction using the ONNX model                             |
    //+------------------------------------------------------------------+
    bool MakePrediction(const float &input_data[], float &output_data[])
    {
       ArrayResize(output_data, PRED_LENGTH * INPUT_FEATURES);
    
       if (!OnnxRun(ModelHandle, ONNX_NO_CONVERSION, input_data, output_data))
       {
          Print("Error running ONNX model: ", GetLastError());
          return false;
       }
    
       return true;
    }
    
    //+------------------------------------------------------------------+
    //| Draw hypothetical future bars                                    |
    //+------------------------------------------------------------------+
    void DrawFutureBars(const float &predictions[])
    {
       datetime current_time = TimeCurrent();
       for (int i = 0; i < PRED_LENGTH; i++)
       {
          datetime bar_time = current_time + PeriodSeconds(PERIOD_H1) * (i + 1);
          double open = predictions[i * INPUT_FEATURES + 0];
          double high = predictions[i * INPUT_FEATURES + 1];
          double low = predictions[i * INPUT_FEATURES + 2];
          double close = predictions[i * INPUT_FEATURES + 3];
    
          string obj_name = "FutureBar_" + IntegerToString(i);
          ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, bar_time, low, bar_time + PeriodSeconds(PERIOD_H1), high);
          ObjectSetInteger(0, obj_name, OBJPROP_COLOR, close > open ? clrGreen : clrRed);
          ObjectSetInteger(0, obj_name, OBJPROP_FILL, true);
          ObjectSetInteger(0, obj_name, OBJPROP_BACK, true);
       }
    
       ChartRedraw();
    }

    Para ejecutar el script anterior, tenga en cuenta cómo se define la siguiente línea: 

    #resource "\\PatchTST\\patchtst_model.onnx" as uchar PatchTSTModel[]

    Esto significa que dentro de la carpeta del Asesor Experto, necesitaremos crear una subcarpeta titulada PatchTST. Dentro de la subcarpeta PatchTST, necesitaremos guardar el archivo ONNX del entrenamiento del modelo. Sin embargo, el EA principal se almacenará en la propia carpeta raíz. 

    Los parámetros que usamos para entrenar nuestro modelo también están definidos en la parte superior del script:

    #define SEQ_LENGTH 168
    #define PRED_LENGTH 24
    #define INPUT_FEATURES 4

    En nuestro caso, queremos utilizar 168 barras anteriores, introducirlas en el modelo ONNX y obtener una predicción para las próximas 24 barras en el futuro. Tenemos 4 características de entrada: open, high, low, and close. 

    Además, tenga en cuenta el siguiente código dentro de la función OnTick(): 

    if (TimeCurrent() < ExtNextBar)
       return;
    
    ExtNextBar = TimeCurrent();
    ExtNextBar -= ExtNextBar % PeriodSeconds();
    ExtNextBar += PeriodSeconds();

    Dado que los modelos ONNX consumen mucha potencia de procesamiento de una computadora, este código garantizará que solo se genere una nueva predicción una vez por barra. En nuestro caso, como trabajamos con barras horarias, las predicciones se actualizarán una vez por hora. 

    Finalmente, en este código, dibujaremos las barras de futuros en la pantalla, mediante el uso de las funciones de dibujo de MQL5: 

    void DrawFutureBars(const float &predictions[])
    {
       datetime current_time = TimeCurrent();
       for (int i = 0; i < PRED_LENGTH; i++)
       {
          datetime bar_time = current_time + PeriodSeconds(PERIOD_H1) * (i + 1);
          double open = predictions[i * INPUT_FEATURES + 0];
          double high = predictions[i * INPUT_FEATURES + 1];
          double low = predictions[i * INPUT_FEATURES + 2];
          double close = predictions[i * INPUT_FEATURES + 3];
    
          string obj_name = "FutureBar_" + IntegerToString(i);
          ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, bar_time, low, bar_time + PeriodSeconds(PERIOD_H1), high);
          ObjectSetInteger(0, obj_name, OBJPROP_COLOR, close > open ? clrGreen : clrRed);
          ObjectSetInteger(0, obj_name, OBJPROP_FILL, true);
          ObjectSetInteger(0, obj_name, OBJPROP_BACK, true);
       }
    
       ChartRedraw();
    }

    Después de implementar este código en MQL5, compilar el modelo y colocar el EA resultante en el marco temporal H1, debería ver algunas barras adicionales agregadas en el futuro en su gráfico. En mi caso se ve así: 


    Tenga en cuenta que si no ve las barras recién dibujadas a la derecha, es posible que deba hacer clic en el botón "Desplazamiento del gráfico desde el borde derecho".

    Conclusión

    En este artículo, adoptamos un enfoque paso a paso para entrenar el modelo PatchTST, que se introdujo en 2023. Obtuvimos una idea general de cómo funciona el algoritmo PatchTST. El código base tenía algunos problemas relacionados con la conversión de ONNX. En concreto, el operador "Unfold" no es compatible, por lo que resolvimos este problema para que el código sea más compatible con ONNX. También mantuvimos el propósito del artículo amigable para el comerciante al enfocarnos en los conceptos básicos del modelo, extraer los datos, entrenar el modelo y obtener una predicción para las próximas 24 horas. Luego implementamos la predicción en MQL5, para que podamos usar el modelo completamente entrenado con nuestros indicadores y asesores expertos favoritos. Estoy encantado de compartir todo mi código con la comunidad MQL5 en el archivo ZIP adjunto. Si tiene alguna pregunta o comentario, no dude en hacérmelo saber. 


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

    Archivos adjuntos |
    PatchTST.zip (4876.35 KB)
    Thomas Sawyer
    Thomas Sawyer | 20 ene 2025 en 04:46
    A menudo me encuentro con que los resultados predichos de este modelo no son del todo coherentes con la situación real. No he hecho ningún cambio en el código de este modelo. ¿Podría orientarme? Muchas gracias.
    Shashank Rai
    Shashank Rai | 20 ene 2025 en 09:56
    Thomas Sawyer #:
    A menudo me encuentro con que los resultados predichos de este modelo no son del todo coherentes con la situación real. No he hecho ningún cambio en el código de este modelo. ¿Podría orientarme? Muchas gracias.

    Gracias por compartir su experiencia con el modelo. Ha planteado una cuestión válida sobre la coherencia de las predicciones. El modelo PatchTST funciona mejor cuando se integra en un enfoque de negociación global que tenga en cuenta múltiples factores del mercado. Le recomiendo que utilice las predicciones del modelo de manera más eficaz:

    1. Optimización de la ventana temporal:
    • Concéntrese en operar durante las horas pico (de 6:00 AM a 10:00 AM hora central de EE.UU.).
    • Utilice las predicciones del modelo principalmente durante estas horas, cuando los movimientos del mercado son más predecibles.
    • Preste especial atención a las desviaciones en torno a los máximos y mínimos diarios y semanales anteriores. Estas son sus principales zonas de oferta y demanda.
    1. Estrategia de integración del modelo:
    • Utilice las predicciones como parte de un análisis más amplio, no como señales independientes.
    • Busque Gaps de Valor Justo (GVF) en los rangos de precios pronosticados. He dado el código para un indicador que utilizo para FVGs en MQL5 abajo.
    • Combine las predicciones con patrones técnicos como banderas, cuñas y consolidaciones horizontales.
    • Considere las predicciones en el contexto de los precios diarios, semanales y mensuales.
    1. Gestión del riesgo:
    • Implemente stops más amplios (por ejemplo, stop-loss de 10 puntos o 100 pips para el Oro, 50 pips para el EURUSD, 65 pips del USDJPY, 60 pips del GBPUSD, 30 pips para el AUDUSD/NZDUSD, 40 pips para el USDCAD, 0,80 puntos para el Petróleo, 25 puntos para el US500, 75 puntos para el NQ, 200 puntos para el US30).
    • Utilice beneficios modestos para los parciales (es decir, recompensa por riesgo 1:1 para el primer parcial (70% de la posición inicial), deje un corredor (30% de la posición inicial)
    • Escale las posiciones en función de las condiciones del mercado - buenas operaciones con mucha confluencia, 2 - 3 veces el tamaño de la posición base.
    • Evite operar durante acontecimientos de gran impacto
    1. Creación de contexto:
    • Analice la estructura del mercado en múltiples marcos temporales: utilice 5 minutos y 15 minutos - es probable que haya una vela/barra/precio de cierre óptimo para entrar dentro de la hora en la que el modelo predice la entrada en su dirección de negociación.
    • Tenga en cuenta el estado actual del mercado (tendencia/ranging/consolidación/choppy/reversión): utilice esta información para planificar de antemano su hora óptima de negociación. Si lo que anticipa no se cumple, busque la siguiente oportunidad disponible que le ofrezca el modelo.
    • Busque confirmaciones de patrones en los rangos de precios previstos.
    • Concéntrese en la tendencia direccional y en los principales niveles de soporte/resistencia.
    1. Refinamiento de la entrada:
    • Espere confirmaciones estructurales antes de entrar en operaciones. Estructuras que hay que conocer mejor: dobles máximos/mínimos, cuñas, banderas alcistas/bajistas, máximos/mínimos MTR, clímax, especialmente en torno a zonas de soporte y resistencia/FVG clave.
    • No entre si anticipa un canal de tendencia. Los canales de tendencia son sus peores enemigos. Incluso si encuentra un canal de tendencia en un marco temporal superior, como 4 horas o un día, ¡NO CONTRATIENDA!
    • Busque patrones de consolidación dentro de los rangos pronosticados - juegue cada reversión. Este modelo realmente brilla con los retrocesos.
    • Considere la posibilidad de escalar en las posiciones en lugar de tomar entradas de tamaño completo. Tamaño inicial del 25% - escale a medida que el precio vaya a su favor hasta el tamaño total de la posición. No escale en contra de su posición, es decir, si la posición va en su contra.

    Algunas observaciones personales adicionales:

    • Hay que estar atento y anticipar ciertas cosas con este modelo: Los retrocesos en torno a áreas clave son los más rentables.
      • Así que digamos que el modelo predice un cambio de color para una barra o marco de tiempo en particular. Ese es el momento de empezar a prestar atención. Busque 1 confluencia extra antes de entrar. Si usted no consigue esa confluencia, espere hasta que la consiga, incluso si el color ha cambiado, el comercio todavía puede funcionar. Mi punto es, este es un buen modelo de "tiempo" desde el punto de vista de "cuando" hay que empezar a preocuparse y prestar atención.
    • Este modelo da muchos falsos positivos, pero unas pocas grandes victorias enjugan todas las malas pérdidas.
    • Comience con 1 par y añada otro par cada semana. Escala tus modelos hasta 10 pares en total.
    • Obtendrá de 2 a 3 grandes operaciones cada semana.
    • Anticipe una ganancia semanal de entre el 0,5% y el 1,5%. Está arriesgando entre un 1,0% y un 2,5% cada semana en todos los pares. En otras palabras, usted negocia poco, se mantiene diversificado en múltiples instrumentos no correlacionados y se centra en intervalos de tiempo específicos. Conseguirás acertar con entre 3 y 6 instrumentos de forma consistente y eso hará que ganes la mayor parte de tu dinero. Por lo tanto, no se obsesione con una sola operación ni analice en exceso un solo punto de datos.
    • Si el cambio pronosticado se produce en torno a un acontecimiento noticioso, sáltelo y céntrese en otro par o espere a la siguiente señal de cambio de color.
    • Los canales de tendencia son su peor enemigo: si sospecha de ellos, olvídese del par ese día. Alternativamente, haga lo contrario de lo que el modelo le está diciendo, porque de lo contrario se verá atrapado en contra-tendencia perder.
    • Operar con este modelo es "incómodo" - realmente nunca confías en él y siempre sientes que "esta operación nunca funcionará, es tan contraintuitiva", pero ahí reside la belleza. Te hace hacer lo que no quieres hacer, que es lo correcto (lo que no quieres hacer, pero deberías hacer porque es lo correcto en esa situación).
    • Concepto de Inversiones Ocurre ~20 - 30% del tiempo: esto es cuando el modelo le está dando un color (rojo o verde), pero el mercado parece estar siempre haciendo lo contrario. Este es un escenario de inversión - nada está mal con el modelo y nada está mal con su comercio tampoco. Sólo tiene que darse cuenta de que se ha producido una inversión y empezar a hacer lo contrario o saltarse el par por completo hasta que las expectativas vuelvan a alinearse. Esto generalmente toma alrededor de 6 - 10 sesiones (alrededor de 2 - 4 días) para que una inversión se arregle. La forma más fácil de identificar una inversión con este modelo es utilizar el script python que te di para las predicciones del modelo - extraer los datos de forma secuencial para las últimas 6 sesiones (por lo que no tire de los últimos datos - hacer una mirada hacia atrás). Comprueba si las predicciones coinciden con los datos reales. Si no es así, se ha producido una inversión.

    Las predicciones del modelo deben utilizarse como un componente de su análisis y no como el único elemento decisorio. Si incorpora estos elementos, podrá mejorar potencialmente la coherencia de los resultados de sus operaciones cuando utilice el modelo PatchTST.

    Espero que le sirva de ayuda.

    Fair Value Gap (FVG) Script que he mencionado (estos gaps funcionan de forma muy parecida a las zonas de oferta y demanda, según mi experiencia):

    #property copyright "© ShashankRai1"
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property strict
    #property indicator_chart_window
    
    input bool ShowMidpoint = false; // Mostrar línea de punto medio
    input color UpFVGColor = clrGreen;  // Up FVG Color
    input color DownFVGColor = clrRed;  // Abajo FVG Color
    
    int OnInit()
    {
        IndicatorSetString(INDICATOR_SHORTNAME, "Show FVG");
        return(INIT_SUCCEEDED);
    }
    
    void OnDeinit(const int reason)
    {
        ObjectsDeleteAll(0, "FVG_");
    }
    
    int OnCalculate(const int rates_total,
                    const int prev_calculated,
                    const datetime &time[],
                    const double &open[],
                    const double &high[],
                    const double &low[],
                    const double &close[],
                    const long &tick_volume[],
                    const long &volume[],
                    const int &spread[])
    {
        int start;
        if(prev_calculated == 0)
        {
            start = rates_total - 1;  // Procesar todos los datos disponibles
        }
        else
        {
            start = prev_calculated - 1;  // Procesar sólo las barras nuevas
        }
    
        for (int i = start; i >= 2; i--)  // Asegúrese de que tenemos al menos 3 barras
        {
            drawFVG(i, rates_total, time, open, high, low, close);
        }
    
        return(rates_total);
    }
    
    void drawFVG(int index, int total, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[])
    {
        if (index < 2 || index >= total) return; // Asegurarnos de que tenemos suficientes barras
    
        if (close[index - 1] > open[index - 1] && high[index - 2] < low[index])
        {
            // Condición de vela cerrada al alza y existe gap
            string boxName = StringFormat("FVG_Box_Up_%d", index);
            if(ObjectCreate(0, boxName, OBJ_RECTANGLE, 0, time[index - 2], high[index - 2], time[index], low[index]))
            {
                ObjectSetInteger(0, boxName, OBJPROP_COLOR, UpFVGColor);
                ObjectSetInteger(0, boxName, OBJPROP_BGCOLOR, UpFVGColor);
                ObjectSetInteger(0, boxName, OBJPROP_BACK, true);
                ObjectSetInteger(0, boxName, OBJPROP_WIDTH, 1);
                ObjectSetInteger(0, boxName, OBJPROP_FILL, true);
                Print("Created Up FVG box at index ", index);
            }
            else
            {
                Print("Failed to create Up FVG box at index ", index, ". Error: ", GetLastError());
            }
    
            if (ShowMidpoint)
            {
                string lineName = StringFormat("FVG_Line_Up_%d", index);
                double midpoint = (high[index - 2] + low[index]) / 2;
                if(ObjectCreate(0, lineName, OBJ_TREND, 0, time[index - 2], midpoint, time[index], midpoint))
                {
                    ObjectSetInteger(0, lineName, OBJPROP_COLOR, UpFVGColor);
                    ObjectSetInteger(0, lineName, OBJPROP_STYLE, STYLE_DASH);
                    ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 1);
                    Print("Created Up FVG midline at index ", index);
                }
                else
                {
                    Print("Failed to create Up FVG midline at index ", index, ". Error: ", GetLastError());
                }
            }
        }
        else if (close[index - 1] < open[index - 1] && low[index - 2] > high[index])
        {
            // Condición de vela cerrada a la baja y existe gap
            string boxName = StringFormat("FVG_Box_Down_%d", index);
            if(ObjectCreate(0, boxName, OBJ_RECTANGLE, 0, time[index - 2], low[index - 2], time[index], high[index]))
            {
                ObjectSetInteger(0, boxName, OBJPROP_COLOR, DownFVGColor);
                ObjectSetInteger(0, boxName, OBJPROP_BGCOLOR, DownFVGColor);
                ObjectSetInteger(0, boxName, OBJPROP_BACK, true);
                ObjectSetInteger(0, boxName, OBJPROP_WIDTH, 1);
                ObjectSetInteger(0, boxName, OBJPROP_FILL, true);
                Print("Created Down FVG box at index ", index);
            }
            else
            {
                Print("Failed to create Down FVG box at index ", index, ". Error: ", GetLastError());
            }
    
            if (ShowMidpoint)
            {
                string lineName = StringFormat("FVG_Line_Down_%d", index);
                double midpoint = (high[index] + low[index - 2]) / 2;
                if(ObjectCreate(0, lineName, OBJ_TREND, 0, time[index - 2], midpoint, time[index], midpoint))
                {
                    ObjectSetInteger(0, lineName, OBJPROP_COLOR, DownFVGColor);
                    ObjectSetInteger(0, lineName, OBJPROP_STYLE, STYLE_DASH);
                    ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 1);
                    Print("Created Down FVG midline at index ", index);
                }
                else
                {
                    Print("Failed to create Down FVG midline at index ", index, ". Error: ", GetLastError());
                }
            }
        }
    }
    ceejay1962
    ceejay1962 | 20 ene 2025 en 11:29
    Shashank Rai #:

    Gracias por su interés. Sí, esos cambios en los parámetros funcionarían en principio, pero hay algunas consideraciones importantes al cambiar a datos M1:

    1. Volumen de datos: Entrenar con 10080 minutos (1 semana) de datos M1 significa manejar significativamente más puntos de datos que con H1. Esto supondrá:

    • Aumentará sustancialmente el tiempo de entrenamiento
    • Requerirá más memoria
    • Potencialmente necesitará aceleración GPU para un entrenamiento eficiente.

    2. Ajustes en la arquitectura del modelo: En el paso 8 del entrenamiento del modelo y el paso 4 del código de predicción, es posible que desee ajustar otros parámetros para acomodar la secuencia de entrada más grande:

    3. 3. Calidad de la predicción: Aunque obtendrá predicciones más detalladas, tenga en cuenta que los datos M1 suelen contener más ruido. Es posible que desee experimentar con diferentes longitudes de secuencia y ventanas de predicción para encontrar el equilibrio óptimo.

    Gracias por la información. Mi ordenador es razonablemente capaz, con 256 GB y 64 núcleos físicos. Sin embargo, no le vendría mal una GPU mejor.

    En cuanto actualice la GPU, probaré los ajustes de configuración actualizados.

    Thomas Sawyer
    Thomas Sawyer | 20 ene 2025 en 13:55
    Shashank Rai #:

    Gracias por compartir su experiencia con el modelo. Usted plantea un punto válido sobre la consistencia de la predicción. El modelo PatchTST funciona mejor cuando se integra en un enfoque de negociación global que tenga en cuenta múltiples factores del mercado. Le recomiendo que utilice las predicciones del modelo de manera más eficaz:

    1. Optimización de la ventana temporal:
    • Concéntrese en operar durante las horas pico (de 6:00 AM a 10:00 AM hora central de EE.UU.).
    • Utilice las predicciones del modelo principalmente durante estas horas, cuando los movimientos del mercado son más predecibles.
    • Preste especial atención a las desviaciones en torno a los máximos y mínimos diarios y semanales anteriores. Estas son sus principales zonas de oferta y demanda.
    1. Estrategia de integración del modelo:
    • Utilice las predicciones como parte de un análisis más amplio, no como señales independientes.
    • Busque Fair Value Gaps (FVGs) en los rangos de precios pronosticados. He dado el código de un indicador que utilizo para FVGs en MQL5 a continuación.
    • Combine las predicciones con patrones técnicos como banderas, cuñas y consolidaciones horizontales.
    • Considere las predicciones en el contexto de los precios diarios, semanales y mensuales.
    1. Gestión del riesgo:
    • Implemente stops más amplios (por ejemplo, stop-loss de 10 puntos o 100 pips para el oro, 50 pips para el EURUSD, 65 pips del USDJPY, 60 pips del GBPUSD, 30 pips para el AUDUSD/NZDUSD, 40 pips para el USDCAD, 0,80 puntos para el petróleo, 25 puntos para el US500, 75 puntos para el NQ, 200 puntos para el US30).
    • Utilice beneficios modestos para los parciales (es decir, recompensa por riesgo 1:1 para el primer parcial (70% de la posición inicial), deje un corredor (30% de la posición inicial)
    • Escale las posiciones en función de las condiciones del mercado - buenas operaciones con mucha confluencia, 2 - 3 veces el tamaño de la posición base.
    • Evite operar durante acontecimientos de gran impacto
    1. Creación de contexto:
    • Analice la estructura del mercado a través de múltiples marcos temporales: utilice 5 minutos y 15 minutos - es probable que haya una vela/barra/precio de cierre óptimo para entrar dentro de la hora en la que el modelo predice la entrada en su dirección de negociación.
    • Tenga en cuenta el estado actual del mercado (tendencia/ranging/consolidación/choppy/reversión): utilice esta información para planificar de antemano su hora óptima de negociación. Si lo que anticipa no se cumple, busque la siguiente oportunidad disponible que le ofrezca el modelo.
    • Busque confirmaciones de patrones en los rangos de precios previstos.
    • Céntrese en la tendencia direccional y en los principales niveles de soporte/resistencia.
    1. Refinamiento de la entrada:
    • Espere confirmaciones estructurales antes de entrar en operaciones. Estructuras que hay que conocer mejor: dobles máximos/mínimos, cuñas, banderas alcistas/bajistas, máximos/mínimos MTR, clímax, especialmente en torno a zonas de soporte y resistencia/FVG clave.
    • No entre si anticipa un canal de tendencia. Los canales de tendencia son sus peores enemigos. Incluso si encuentra un canal de tendencia en un marco temporal superior, como 4 horas o un día, ¡NO CONTRATIENDA!
    • Busque patrones de consolidación dentro de los rangos pronosticados - juegue cada reversión. Este modelo realmente brilla con los retrocesos.
    • Considere la posibilidad de escalar en las posiciones en lugar de tomar entradas de tamaño completo. Tamaño inicial del 25% - escale a medida que el precio vaya a su favor hasta el tamaño total de la posición. No escale en contra de su posición, es decir, si la posición va en su contra.

    Algunas observaciones personales adicionales:

    • Hay que estar atento y anticipar ciertas cosas con este modelo: Los retrocesos en torno a áreas clave son los más rentables.
      • Así que digamos que el modelo predice un cambio de color para una barra o marco de tiempo en particular. Ese es el momento de empezar a prestar atención. Busque 1 confluencia extra antes de entrar. Si usted no consigue esa confluencia, espere hasta que la consiga, incluso si el color ha cambiado, el comercio todavía puede funcionar. Mi punto es, este es un buen modelo de "tiempo" desde el punto de vista de "cuando" hay que empezar a preocuparse y prestar atención.
    • Este modelo da muchos falsos positivos, pero unas pocas grandes victorias enjugan todas las malas pérdidas.
    • Comience con 1 par y añada otro par cada semana. Escala tus modelos hasta 10 pares en total.
    • Obtendrá de 2 a 3 grandes operaciones cada semana.
    • Anticipe una ganancia semanal de entre el 0,5% y el 1,5%. Está arriesgando entre un 1,0% y un 2,5% cada semana en todos los pares. En otras palabras, usted negocia poco, se mantiene diversificado en múltiples instrumentos no correlacionados y se centra en intervalos de tiempo específicos. Conseguirás acertar con entre 3 y 6 instrumentos de forma consistente y eso hará que ganes la mayor parte de tu dinero. Por lo tanto, no se obsesione con una sola operación ni analice en exceso un solo punto de datos.
    • Si el cambio pronosticado se produce en torno a un acontecimiento noticioso, sáltelo y céntrese en otro par o espere a la siguiente señal de cambio de color.
    • Los canales de tendencia son su peor enemigo: si sospecha de ellos, olvídese del par ese día. Alternativamente, haga lo contrario de lo que el modelo le está diciendo, porque de lo contrario se verá atrapado en contra-tendencia perder.
    • Operar con este modelo es "incómodo" - realmente nunca confías en él y siempre sientes que "esta operación nunca funcionará, es tan contraintuitiva", pero ahí reside la belleza. Te hace hacer lo que no quieres hacer, que es lo correcto (lo que no quieres hacer, pero deberías hacer porque es lo correcto en esa situación).
    • Concepto de Inversiones Ocurre ~20 - 30% del tiempo: esto es cuando el modelo le está dando un color (rojo o verde), pero el mercado parece estar siempre haciendo lo contrario. Este es un escenario de inversión - nada está mal con el modelo y nada está mal con su comercio tampoco. Sólo tiene que darse cuenta de que se ha producido una inversión y empezar a hacer lo contrario o saltarse el par por completo hasta que las expectativas vuelvan a alinearse. Esto generalmente toma alrededor de 6 - 10 sesiones (alrededor de 2 - 4 días) para que una inversión se arregle. La forma más fácil de identificar una inversión con este modelo es utilizar el script python que te di para las predicciones del modelo - extraer los datos de forma secuencial para las últimas 6 sesiones (por lo que no tire de los últimos datos - hacer una mirada hacia atrás). Comprueba si las predicciones coinciden con los datos reales. Si no es así, se ha producido una inversión.

    Las predicciones del modelo deben utilizarse como un componente de su análisis y no como el único elemento decisorio. Al incorporar estos elementos, puede mejorar potencialmente la coherencia de los resultados de sus operaciones cuando utilice el modelo PatchTST.

    Espero que le sirva de ayuda.

    Fair Value Gap (FVG) Script que he mencionado (estos gaps funcionan de forma muy parecida a las zonas de oferta y demanda, según mi experiencia):

    Muchas gracias por tu paciente respuesta y desinteresado compartir. Nunca antes había visto respuestas tan detalladas y profesionales. Leeré su artículo repetidamente. Estos conocimientos son especialmente valiosos para mí. Mis mejores deseos para usted.
    Shashank Rai
    Shashank Rai | 20 ene 2025 en 16:48
    Thomas Sawyer #:
    Muchas gracias por su paciente respuesta y por compartirla desinteresadamente. Nunca había visto respuestas tan detalladas y profesionales. Leeré su artículo repetidamente. Estos conocimientos son especialmente valiosos para mí. Mis mejores deseos para usted.

    Muchas gracias. ¡¡Sus amables palabras significan mucho!! Si necesita más ayuda, no dude en ponerse en contacto con nosotros.

    Redes neuronales: así de sencillo (Parte 89): Transformador de descomposición de la frecuencia de señal (FEDformer) Redes neuronales: así de sencillo (Parte 89): Transformador de descomposición de la frecuencia de señal (FEDformer)
    Todos los modelos de los que hemos hablado anteriormente analizan el estado del entorno como una secuencia temporal. Sin embargo, las propias series temporales también pueden representarse como características de frecuencia. En este artículo, presentaremos un algoritmo que utiliza las características de frecuencia de una secuencia temporal para predecir los estados futuros.
    Gestor de riesgos para el trading algorítmico Gestor de riesgos para el trading algorítmico
    Los objetivos de este artículo son: demostrar por qué el uso del gestor de riesgos es algo imprescindible, adaptar los principios del riesgo controlado en el trading algorítmico en una clase aparte, de modo que todo el mundo pueda comprobar de forma independiente la eficacia del enfoque de racionamiento del riesgo en el trading intradía y la inversión en los mercados financieros. En este artículo, detallaremos la escritura de una clase de gestor de riesgos para el trading algorítmico como continuación del artículo anterior sobre la escritura de un gestor de riesgos para el trading manual.
    Algoritmo de cola de cometa (Comet Tail Algorithm, CTA) Algoritmo de cola de cometa (Comet Tail Algorithm, CTA)
    En este artículo, analizaremos un nuevo algoritmo de optimización de autor, el CTA (Comet Tail Algorithm), que se inspira en objetos espaciales únicos: los cometas y sus impresionantes colas que se forman al acercarse al Sol. Este algoritmo se basa en el concepto del movimiento de los cometas y sus colas, y está diseñado para encontrar soluciones óptimas en problemas de optimización.
    Características del Wizard MQL5 que debe conocer (Parte 25): Pruebas y operaciones en múltiples marcos temporales Características del Wizard MQL5 que debe conocer (Parte 25): Pruebas y operaciones en múltiples marcos temporales
    Las estrategias que se basan en múltiples marcos de tiempo no se pueden probar en los Asesores Expertos ensamblados por defecto debido a la arquitectura de código MQL5 utilizada en las clases de ensamblaje. Exploramos una posible solución a esta limitación para las estrategias que buscan utilizar múltiples marcos temporales en un estudio de caso con la media móvil cuadrática.