English 中文 Español Deutsch 日本語 Português
preview
Использование алгоритма машинного обучения PatchTST для прогноза ценовых движений на следующие 24 часа

Использование алгоритма машинного обучения PatchTST для прогноза ценовых движений на следующие 24 часа

MetaTrader 5Трейдинг |
1 200 15
Shashank Rai
Shashank Rai

Введение

Впервые я столкнулся с алгоритмом под названием PatchTST, когда начал изучать достижения ИИ, связанные с прогнозами временных рядов на сайте Huggingface.co. Как известно всякому, кто работал с большими языковыми моделями (Large Language Model, LLM), изобретение трансформеров кардинально изменило правила игры в разработке инструментов для обработки естественного языка, изображений и видеоматериалов. А как насчет временных рядов? Разве о них просто забыли? Или большинство исследований просто проводят за закрытыми дверями? Оказывается, есть множество более новых моделей, успешно применяющих трансформеры для прогнозирования временных рядов. В этой статье мы рассмотрим одну из таких реализаций.

Что впечатляет в алгоритме PatchTST, так это быстрота, с которой он обучает модель, и простота использования обученной модели с MQL. Не скрою, я новичок в том, что касается теории нейросетей. Но пройдя через этот процесс и взяв на себя труд реализовать PatchTST на MQL5, как описано в этой статье, я почувствовал, что сделал огромный скачок вперед в изучении и понимании способов разработки, устранения неисправностей, обучения и использования этих сложных нейросетей. Это как взять едва научившегося ходить ребенка и отправить его в профессиональную футбольную команду, ожидая, что он забьет решающий гол в финале Чемпионата мира. 


Обзор алгоритма PatchTST

Узнав о PatchTST, я стал изучать статью, объясняющую, как он устроен: "A Time Series is Worth 64 Words: Long-term Forecasting with Transformers" ("Лучше временной ряд, чем 64 слова: долгосрочное прогнозирование с помощью трансформеров"). Название было интересным. Вчитавшись, я подумал: "Ух ты, как увлекательно написано, — и здесь много элементов, о которых я всегда хотел узнать". Само собой, мне захотелось попробовать эти прогнозы на практике. Вот что еще больше заинтересовало меня в этом алгоритме:

  • С помощью PatchTST можно прогнозировать цены открытия, максимумы, минимумы и цены закрытия. Благодаря PatchTST я решил, что такому алгоритму можно передавать все данные по мере их поступления — цены открытия и закрытия, максимумы и минимумы, даже объем. Можно ожидать, что он найдет закономерности в данных, так как все они преобразуются в так называемые "патчи" (сегменты). Подробнее о том, что такое собой патчи, расскажу ниже в этой статье. Пока важно только понимать, что патчи привлекательны и помогают делать более точные прогнозы.
  • Минимальные требования к предварительной обработке данных в алгоритме PatchTST. Погрузившись в дальнейшее изучение этого алгоритма, я узнал, что авторы используют метод под названием RevIn (reverse instance normalization — обратимая инстантная нормализация). RevIn взят из статьи под названием: "REVERSIBLE INSTANCE NORMALIZATION FOR ACCURATE TIME-SERIES FORECASTING AGAINST DISTRIBUTION SHIFT" ("ОБРАТИМАЯ ИНСТАНТНАЯ НОРМАЛИЗАЦИЯ ДЛЯ ТОЧНОГО ПРОГНОЗИРОВАНИЯ ВРЕМЕННЫХ РЯДОВ ОТНОСИТЕЛЬНО СДВИГА РАСПРЕДЕЛЕНИЯ"). RevIn пытается решить задачу сдвига распределения при прогнозировании временных рядов. Нам, алгоритмическим трейдерам, слишком хорошо знакомо чувство, когда обученный советник перестает предсказывать рынок, и мы вынуждены заново оптимизировать и обновлять свои параметры. Считайте, что RevIn — способ сделать то же самое. 

  • По сути, этот метод берет переданные в него данные и нормализует их с помощью следующей формулы:

    x = (x - mean) / std

    Затем, когда модели нужно сделать прогноз, она выполняет денормализацию данных с помощью противоположного свойства:

    x = x * std + mean

    Кроме того, у RevIn есть другое свойство, которое называется affine_bias. Проще говоря, это обучаемый параметр, учитывающий асимметрию, эксцесс и прочих элементов, которые могут присутствовать в наборе данных. 

    x = x * affine_weight + affine_bias

    Обобщить структуру PatchTST можно так: 

    Input Data -> RevIn -> Series Decomposition -> Trend Component -> PatchTST Backbone -> TSTiEncoder -> Flatten_Head -> Trend Forecaster -> Residual Component -> Add Trend and Residual -> Final Forecast

    Мы понимаем, что данные будут извлечены с помощью MT5. Кроме того, мы обсудили, как работает RevIn.

    Вот как работает PatchTST: предположим, вы извлекаете 80 000 баров данных по EURUSD на таймфрейме H1. И это данные всего за 13 лет. С помощью PatchTST вы сегментируете данные в так называемые "патчи". В качестве аналогии представьте, что патчи похожи на работу Vision Transformers (ViTs) для изображений, но адаптированы для данных временных рядов. Так, например, если длина патча 16, то в каждом патче будет 16 последовательных значений цены. Это похоже на просмотр небольших фрагментов временного ряда за раз, что помогает модели сфокусироваться на локальных закономерностях, прежде чем рассматривать глобальную.

    Далее, патчи включают позиционное кодирование для сохранения порядка последовательности, благодаря чему модель лучше запоминает положение каждого патча в последовательности.

    Трансформер передает нормализованные и закодированные патчи через стек слоев кодировщика. Каждый слой кодировщика содержит слой многопоточного механизма внимания и слой с прямой связью. Слой многопоточного внимания позволяет модели уделять внимание различным компонентов входной последовательности, а слой с прямой связью позволяет ей осваивать сложные нелинейные преобразования данных.

    Наконец, у нас есть тренд и остаточные компоненты. Создание патчей, нормализация, позиционное кодирование и слои трансформеров применяются как к компоненту тренда, так и к остаточному компоненту. Затем сложим выходные данные компонентов тренда и остаточных компонентов, чтобы получить окончательный прогноз.


    Проблемы PatchTST с официальным репозиторием

    Официальный репозиторий PatchTST можно найти на сайте GitHub по ссылке: PatchTST (ICLR 2023). Доступны две различные версии — контролируемая и неконтролируемая. Для данной статьи используем подход с контролируемым обучением. Как известно, для использования любой модели с MQL5 нам нужен способ преобразовать ее в формат ONNX. Однако авторы PatchTST не приняли это во внимание. Для работы модели с MQL5 мне пришлось внести в их базовый код следующие модификации: 

    Оригинальный код: 

    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

    Вышеприведенный код — это основа. Как видите, код использует функцию под названием Unfold в строке: 

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

    ONNX не поддерживает преобразование функции Unfold. Вы получите ошибку типа: 

    Unsupported: ONNX export of operator Unfold, input size not accessible. Не стесняйтесь обращаться за поддержкой или отправлять запрос на включение изменений на PyTorch GitHub по адресу: https://github.com/pytorch/pytorch/issues

    Поэтому мне пришлось заменить этот участок кода на: 

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

    Обратите внимание, что вышеприведенная замена немного менее эффективна, так как пользуется циклом for для обучения нейросети. Неэффективность может копиться на протяжении многих эпох и на больших наборах данных. Но это необходимо, поскольку иначе модель просто не сможет преобразоваться и мы не сможем использовать ее с MQL5. 

    Я специально занялся решением этой проблемы. Это заняло больше всего времени. Затем я все собрал в файле patchTST.py, который можно найти в прикрепленном к этой статье zip-архиве. Вот файл, который мы используем для обучения своей модели. 


    Требования к работе с PatchTST на языке Python

    В этом разделе я расскажу о требованиях к работе с PatchTST на языке Python. Их можно обобщить ниже: 

    Создание виртуальной среды: 

    python -m venv myenv

    Активация виртуальной среды (Windows)

    .\myenv\Scripts\activate
    

    Установка файла requirements.txt из архива, прикрепленного к этой статье: 

    pip install -r requirements.txt

    В частности, требования к запуску этого проекта: 

    MetaTrader5
    pandas
    numpy
    torch
    plotly
    datetime


    Пошаговая разработка кода обучения модели

    Для следующего кода вы можете повторить за мной, используя блокнот Jupyter, который я включил в zip-файл: PatchTST Step-By-Step.ipynb. Ниже кратко опишем шаги: 

    1. Импорт необходимых библиотек. Импорт нужных библиотек, в том числе MetaTrader 5, Pandas, Numpy, Torch, и модели 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. Инициализация и извлечение данных из MetaTrader 5. Функция fetch_mt5_data инициализирует MT5, извлекает данные для заданного символа, таймфрейма и количества баров, а затем возвращает датафрейм со столбцами для цены открытия, максимума, минимума и цены закрытия.

      # 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. Подготовка данных прогнозирования с помощью скользящего окна. Функция prepare_forecasting_data создает набор данных с помощью метода скользящего окна, генерируя последовательности исторических данных (X) и соответствующих будущих данных (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. Разделение данных на обучающие и тестовые. Разбиение данных на наборы — 80% для обучения и 20% для тестирования. 

      # 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. Преобразование данных в тензоры PyTorch. Преобразование массивов NumPy в тензоры PyTorch, необходимые для обучения с помощью PyTorch. Устанавливает вручную начальное значение (сид) для модели torch для воспроизводимости результатов. 

      # 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. Установка устройства для расчетов. Настройка элемента на CUDA при наличии, в противном случае использование ЦП. Это важно для применения ускорения графического процессора во время обучения, особенно если он доступен.

      # Step 6: Set device for computation
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
      print(f"Using device: {device}")
    7. Создание загрузчика для обучающих данных. Создание загрузчика данных для обработки пакетирования и перемешивания обучающих данных.

      # 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. Определение класса конфигурации для модели. Определение класса Config для хранения всех гиперпараметров и настроек, необходимых для модели 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. Инициализация модели PatchTST. Инициализация модели PatchTST с определенной конфигурацией и перемещение ее на выбранное устройство.

      # 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. Описание оптимизатора и функции потерь. Настройка оптимизатора (Adam) и функции потерь (Mean Squared Error — среднеквадратическая ошибка) для обучения модели.

      # 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. Обучение модели. Обучение модели на протяжении указанного количества эпох. Для каждого пакета данных модель выполняет прямой проход, вычисляет потери, выполняет обратный проход для расчета градиентов и обновляет параметры модели.

      # 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. Сохранение модели в формате PyTorch. Сохранение словаря состояний обученной модели в файл. Можем использовать этот файл для создания прогнозов непосредственно на языке Python. 

      # Step 12: Save the model in PyTorch format
      torch.save(model.state_dict(), 'patchtst_model.pth')
    13. Подготовка фиктивных входных данных для экспорта ONNX. Создание фиктивного входного тензора для использования при экспорте модели в формат ONNX. 

      # Step 13: Prepare a dummy input for ONNX export
      dummy_input = torch.randn(1, seq_length, 4).to(device)
    14. Экспорт модели в формат ONNX. Экспорт обученной модели в формат ONNX. Нам понадобится этот файл для прогнозирования с помощью 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.")


    Результаты обучения модели

    Вот результаты, которые я получил в результате обучения модели. 

    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

    Их можно визуализировать следующим образом: 

    Прогресс в обучении за 100 эпох

    Кроме того, мы получаем следующий вывод без ошибок и предупреждений, что указывает на успешное преобразование нашей модели в формат ONNX. 

    Модель, обученная и сохраненная в форматах PyTorch и ONNX.
    


    Пошаговая генерация прогнозов с помощью Python

    Теперь посмотрим на код прогноза: 

    1. Шаг 1. Импорт библиотек: начнем с импорта всех необходимых библиотек.
      # 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. Шаг 2. Извлечение даннных из MetaTrader 5:  определим функцию для извлечения данных из MetaTrader 5 и преобразуем их в DataFrame. Извлечем 168 предыдущих баров, потому что именно это требуется для получения прогноза с помощью нашей модели. 
      # 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. Шаг 3. Подготовка входных данных:  определим функцию для подготовки входных данных для модели, взяв последние строки данных seq_length. При извлечении данных нам требуются только последние 168 часов из 1-часовых данных для прогнозов на последующие 24 часа. Это потому, что мы обучили модель именно так. 
      # 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. Шаг 4. Определение конфигурации: определим класс конфигурации для настройки параметров модели. Это те же настройки, что мы используем для обучения модели. 
      # 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. Шаг 5. Загрузка обученной модели: определим функцию для загрузки обученной модели PatchTST. Это те же настройки, что мы используем для обучения модели. 
      # 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. Шаг 6. Составление прогнозов: определим функцию для составления прогнозов с помощью загруженной модели и входных данных.
      # 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. Шаг 7. Постобработка и визуализация: обрабатываем прогнозы, создаем датафреймы и визуализируем исторические и прогнозируемые данные при помощи 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)
      


    Код обучения и прогнозирования на языке Python

    Если вас не интересует запуск базы исходного кода в блокноте Jupyter, во вложениях я предоставил пару файлов, которые можно запустить напрямую: 

    • model_training.py
    • model_prediction.py

    Вы можете конфигурировать модель по своему усмотрению и запускать ее без использования Jupyter. 


    Результаты прогнозирования

    После обучения модели и запуска кода прогнозирования на Python я получил следующий график. Прогнозы были созданы примерно в 00:30 (CEST+3) 8 июля 2024 года. Это как раз во время открытия в воскресенье ночью (понедельник утром). Мы видим на графике разрыв, потому что открытие EURUSD произошло с разрывом. Модель прогнозирует для EURUSD восходящий тренд для большей части заполнения этого разрыва. После заполнения разрыва движение цены должно пойти вниз ближе к концу дня. 

    Прогнозы от Python

    Кроме того, мы распечатали необработанные значения результатов, которые можно видеть ниже: 

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


    Перенос предварительно обученной модели в MQL5

    В этом разделе создадим предшественник индикатора, который поможет визуализировать прогнозируемое движение цен на наших графиках. Я намеренно сделал скрипт элементарным и расширяемым, потому что у наших читателей могут быть разные цели и разные стратегии использования этих сложных нейросетей. Индикатор разработан в формате советника на языке MQL5. Вот полный код скрипта: 

    //+------------------------------------------------------------------+
    //|                                            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();
    }

    Для запуска этого скрипта обратите внимание на определение следующей строки: 

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

    Это значит, что нам нужно будет создать внутри папки Expert Advisor подпапку с названием PatchTST. Внутри подпапки PatchTST нам нужно будет сохранить файл ONNX из обучения модели. Однако основной EA будет храниться в корневой папке. 

    Кроме того, в верхней части скрипта определены параметры, которые мы использовали для обучения модели:

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

    В нашем случае нужно использовать 168 предшествующих баров, ввести их в модель ONNX и получить прогноз на следующие 24 бара в будущем. У нас 4 входных характеристики: Open, High, Low и Close. 

    Кроме того, обратите внимание на следующий код внутри функции OnTick(): 

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

    Поскольку модели ONNX требуют больших вычислительных мощностей компьютера, этот код обеспечит генерацию нового прогноза только один раз для каждого бара. В нашем случае, поскольку мы работаем с часовыми барами, прогнозы будут обновляться один раз в час. 

    Наконец, в этом коде будем рисовать на экране фьючерсные бары, используя функции рисования 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();
    }

    После реализации этого кода на MQL5, компиляции модели и размещения полученного EA на таймфрейме H1 вы должны увидеть несколько новых баров, добавленных в будущее на графике. В моем случае это выглядит так: 


    Обратите внимание: если вы не видите вновь нарисованных баров справа, вам может понадобиться щелкнуть на кнопке "Отодвинуть конец графика от правой границы" ("Shift end of chart from right border").

    Заключение

    В этой статье мы применили пошаговый подход к обучению модели PatchTST, представленной в 2023 году. Получили общее представление о принципах работы алгоритма PatchTST. В базовом коде было несколько проблем, связанных с преобразованием ONNX. В частности, не поддерживается оператор "Unfold", поэтому мы решили эту проблему, адаптировав код к ONNX. Кроме того, мы сохранили ориентацию цели статьи на трейдеров, сосредоточившись на основах модели, извлечении данных, обучении модели и получении прогноза на ближайшие 24 часа. Затем мы реализовали прогнозирование на языке MQL5, чтобы иметь возможность использовать полностью обученную модель с любимыми индикаторами и советниками. Рад поделиться своим кодом в прикрепленном архивном файле с сообществом MQL. Дайте мне знать, пожалуйста, если у вас появятся вопросы или комментарии. 


    Перевод с английского произведен MetaQuotes Ltd.
    Оригинальная статья: https://www.mql5.com/en/articles/15198

    Прикрепленные файлы |
    PatchTST.zip (4876.35 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (15)
    Thomas Sawyer
    Thomas Sawyer | 20 янв. 2025 в 04:46
    Я часто обнаруживаю, что предсказанные результаты этой модели не вполне соответствуют реальной ситуации. Я не вносил никаких изменений в код этой модели. Не могли бы вы дать мне какие-нибудь рекомендации? Спасибо.
    Shashank Rai
    Shashank Rai | 20 янв. 2025 в 09:56
    Thomas Sawyer #:
    Я часто обнаруживаю, что предсказанные результаты этой модели не вполне соответствуют реальной ситуации. Я не вносил никаких изменений в код этой модели. Не могли бы вы дать мне какие-нибудь рекомендации? Спасибо.

    Спасибо, что поделились своим опытом работы с моделью. Вы подняли важный вопрос о согласованности прогнозов. Модель PatchTST лучше всего работает, когда она интегрирована в комплексный торговый подход, учитывающий множество рыночных факторов. Вот как я рекомендую использовать прогнозы модели более эффективно:

    1. Оптимизация временного окна:
    • Сосредоточьтесь на торговле в часы пик (с 6:00 утра до 10:00 утра по центральному времени США).
    • Используйте прогнозы модели преимущественно в эти часы, когда движения рынка более предсказуемы.
    • Обращайте особое внимание на отклонения вокруг предыдущих дневных и недельных максимумов/минимумов. Это ваши основные зоны спроса и предложения.
    1. Стратегия интеграции модели:
    • Используйте прогнозы как часть более широкого анализа, а не как самостоятельные сигналы
    • Ищите разрывы справедливой стоимости (FVG) в прогнозируемых ценовых диапазонах. Ниже я привел код индикатора, который я использую для поиска FVG в MQL5.
    • Сочетайте прогнозы с техническими паттернами, такими как флаги, клинья и горизонтальные консолидации.
    • Рассматривайте прогнозы в контексте дневного, недельного и месячного расположения цен.
    1. Управление рисками:
    • Используйте более широкие стопы (например, 10-пунктовый или 100-пунктовый стоп-лосс для золота, 50 пунктов для EURUSD, 65 пунктов для USDJPY, 60 пунктов для GBPUSD, 30 пунктов для AUDUSD/NZDUSD, 40 пунктов для USDCAD, 0,80 пункта для нефти, 25 пунктов для US500, 75 пунктов для NQ, 200 пунктов для US30).
    • Используйте скромные тейк-профиты для частичных (т.е. 1:1 вознаграждение за риск для первой части (70% от начальной позиции), оставьте бегунок (30% от начальной позиции))
    • Масштабируйте позиции в зависимости от рыночных условий - хорошие сделки с большим количеством слияний, 2 - 3x базовый размер позиции.
    • Избегайте торговли во время высокозначимых новостных событий.
    1. Построение контекста:
    • Анализируйте структуру рынка на нескольких таймфреймах: используйте 5 минут и 15 минут - вероятно, в течение 1 часа существует оптимальная свеча/бар/цена закрытия для входа в рынок, где модель предсказывает вход в вашем направлении торговли.
    • Учитывайте текущее состояние рынка (тренд/ранжирование/консолидация/колебания/разворот) - используйте эту информацию, чтобы заранее спланировать оптимальный торговый час. Если то, что вы ожидаете, не сбывается, ищите следующую доступную возможность, которую дает вам модель.
    • Ищите подтверждения моделей в прогнозируемых ценовых диапазонах.
    • Ориентируйтесь на направленность и основные уровни поддержки/сопротивления.
    1. Уточнение входа:
    • Дождитесь подтверждения структур, прежде чем входить в сделку. Структуры, которые лучше всего знать: двойные вершины/низы, клинья, бычьи/медвежьи флаги, вершины/низы MTR, кульминационные точки, особенно вблизи ключевых областей поддержки и сопротивления/FVG.
    • Не входите в рынок, если вы предвидите трендовый канал. Трендовые каналы - ваши злейшие враги. Даже если вы обнаружили трендовый канал на более высоком таймфрейме, например на 4 часах или одном дне, НЕ ВХОДИТЕ!
    • Ищите модели консолидации в пределах прогнозируемых диапазонов - играйте на каждом развороте. Эта модель действительно сияет на разворотах.
    • Рассмотрите возможность масштабирования позиций вместо полноразмерных входов. Начальный размер 25% - масштабирование по мере того, как цена идет в вашу пользу, вплоть до полного размера позиции. Не масштабируйте против своей позиции, т. е. если позиция идет против вас.

    Некоторые дополнительные личные наблюдения:

    • При работе с этой моделью нужно быть внимательным и предугадывать определенные вещи: Развороты вокруг ключевых областей являются наиболее прибыльными.
      • Скажем, модель предсказывает изменение цвета для определенного бара или таймфрейма. Это время, чтобы начать обращать внимание. Ищите еще 1 дополнительное слияние перед входом. Если вы не получаете это стечение, подождите, пока не получите его, даже если цвет сменился, сделка все равно может сработать. Я хочу сказать, что это хорошая "временная" модель с точки зрения того, "когда" вам нужно начать проявлять заботу и внимание.
    • Эта модель дает много ложных срабатываний, но несколько крупных выигрышей сводят на нет все убытки.
    • Начните с 1 пары и добавляйте еще одну пару каждую неделю. Масштабируйте свои модели до 10 пар.
    • Каждую неделю вы будете получать 2 - 3 крупные сделки.
    • Ожидайте еженедельную прибыль в размере 0,5 - 1,5 %. Вы рискуете примерно 1,0 - 2,5 % каждую неделю по всем парам. Другими словами, вы торгуете небольшими суммами, сохраняете диверсификацию по нескольким некоррелированным инструментам и фокусируетесь на определенных временных диапазонах. Вы будете постоянно правильно торговать примерно 3-6 инструментами, и это принесет вам большую часть денег. Поэтому не зацикливайтесь на одной сделке и не анализируйте одну точку данных.
    • Если прогнозируемое изменение связано с новостным событием, пропустите его и сосредоточьтесь на другой паре или дождитесь следующего сигнала о смене цвета.
    • Трендовые каналы - ваш злейший враг: если вы даже подозреваете о них - забудьте об этой паре на этот день. Как вариант, делайте противоположное тому, что говорит вам модель, потому что иначе вы попадете в контртренд и проиграете.
    • Торговля по этой модели "неудобна" - вы действительно никогда не доверяете ей, и вам постоянно кажется, что "эта сделка никогда не сработает, она настолько контринтуитивна", но в этом и заключается ее прелесть. Она заставляет вас делать то, что вы не хотите делать, но это правильное действие (то, что вы не хотите делать, но должны сделать, потому что это правильное действие в данной ситуации).
    • Концепция инверсий встречается ~20-30 % времени: это когда модель дает вам один цвет (красный или зеленый), но рынок, кажется, всегда делает противоположное. Это сценарий инверсии - в модели нет ничего плохого, и в вашей торговле тоже нет ничего плохого. Вы просто должны осознать, что произошла инверсия, и начать действовать в противоположном направлении или вообще пропустить пару, пока ожидания не выровняются. Обычно для исправления инверсии требуется 6 - 10 сессий (около 2 - 4 дней). Самый простой способ выявить инверсию с помощью этой модели - использовать скрипт python, который я дал вам для прогнозов модели, - последовательно взять данные за последние 6 сессий (поэтому не берите самые последние данные - сделайте обратный просмотр). Посмотрите, совпадают ли прогнозы с фактическими данными. Если нет - произошла инверсия.

    Прогнозы модели должны использоваться как один из компонентов вашего анализа, а не как единственное средство принятия решений. Приняв во внимание эти элементы, вы сможете потенциально улучшить согласованность результатов торговли при использовании модели PatchTST.

    Надеюсь, это поможет.

    Скрипт гэпа справедливой стоимости (FVG), о котором я упоминал (по моему опыту, эти гэпы работают очень похоже на зоны спроса и предложения):

    #property copyright "© ShashankRai1"
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property strict
    #property indicator_chart_window
    
    input bool ShowMidpoint = false; // Show Midpoint Line
    input color UpFVGColor = clrGreen;  // Up FVG Color
    input color DownFVGColor = clrRed;  // Down 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;  // Process all available data
        }
        else
        {
            start = prev_calculated - 1;  // Process only new bars
        }
    
        for (int i = start; i >= 2; i--)  // Ensure we have at least 3 bars
        {
            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; // Ensure we have enough bars
    
        if (close[index - 1] > open[index - 1] && high[index - 2] < low[index])
        {
            // Up close candle condition and gap exists
            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])
        {
            // Down close candle condition and gap exists
            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 янв. 2025 в 11:29
    Shashank Rai #:

    Спасибо за интерес! Да, эти изменения параметров в принципе будут работать, но есть несколько важных соображений при переходе на данные M1:

    1. Объем данных: Обучение на данных M1 длительностью 10080 минут (1 неделя) означает обработку значительно большего количества точек данных, чем в случае с H1. Это приведет к:

    • Значительно увеличит время обучения
    • Потребуется больше памяти
    • Потенциально необходимо GPU-ускорение для эффективного обучения

    2. Корректировка архитектуры модели: На шаге 8 обучения модели и шаге 4 кода предсказания, возможно, потребуется настроить другие параметры, чтобы учесть большую входную последовательность:

    3. Качество предсказания: Хотя вы получите более детальные предсказания, имейте в виду, что данные M1 обычно содержат больше шума. Возможно, вам захочется поэкспериментировать с различными длинами последовательностей и окнами предсказаний, чтобы найти оптимальный баланс.

    Спасибо за понимание. Мой компьютер достаточно мощный, с 256 ГБ и 64 физическими ядрами. Однако ему не помешал бы лучший GPU.

    Как только я обновлю GPU, я попробую обновленные настройки конфигурации.

    Thomas Sawyer
    Thomas Sawyer | 20 янв. 2025 в 13:55
    Shashank Rai #:

    Спасибо, что поделились своим опытом работы с моделью. Вы подняли важный вопрос о согласованности прогнозов. Модель PatchTST лучше всего работает, когда она интегрирована в комплексный торговый подход, учитывающий множество рыночных факторов. Вот как я рекомендую использовать прогнозы модели более эффективно:

    1. Оптимизация временных окон:
    • Сосредоточьтесь на торговле в часы пик (с 6:00 до 10:00 по центральному времени США).
    • Используйте прогнозы модели преимущественно в эти часы, когда движения рынка более предсказуемы.
    • Обращайте особое внимание на отклонения вокруг предыдущих дневных и недельных максимумов/минимумов. Это ваши основные зоны спроса и предложения.
    1. Стратегия интеграции модели:
    • Используйте прогнозы как часть более широкого анализа, а не как самостоятельные сигналы.
    • Ищите разрывы справедливой стоимости (FVG) в прогнозируемых ценовых диапазонах. Ниже я привел код индикатора, который я использую для поиска FVG в MQL5.
    • Сочетайте прогнозы с техническими паттернами, такими как флаги, клинья и горизонтальные консолидации.
    • Рассматривайте прогнозы в контексте дневных, недельных и месячных ценовых диапазонов
    1. Управление рисками:
    • Используйте более широкие стопы (например, 10-пунктовый или 100-пунктовый стоп-лосс для золота, 50 пунктов для EURUSD, 65 пунктов для USDJPY, 60 пунктов для GBPUSD, 30 пунктов для AUDUSD/NZDUSD, 40 пунктов для USDCAD, 0,80 пункта для нефти, 25 пунктов для US500, 75 пунктов для NQ, 200 пунктов для US30).
    • Используйте скромные тейк-профиты для частичных (т.е. 1:1 вознаграждение за риск для первой части (70% от начальной позиции), оставьте бегунок (30% от начальной позиции))
    • Масштабируйте позиции в зависимости от рыночных условий - хорошие сделки с большим количеством слияний, 2 - 3x базовый размер позиции.
    • Избегайте торговли во время высокозначимых новостных событий
    1. Построение контекста:
    • Анализируйте структуру рынка на нескольких таймфреймах: используйте 5 минут и 15 минут - вероятно, в течение 1 часа существует оптимальная свеча/бар/цена закрытия для входа в рынок, где модель предсказывает вход в вашем направлении торговли.
    • Учитывайте текущее состояние рынка (тренд/развитие/консолидация/колебания/разворот) - используйте эту информацию, чтобы заранее спланировать оптимальный торговый час. Если то, что вы ожидаете, не сбывается, ищите следующую доступную возможность, которую дает вам модель.
    • Ищите подтверждения моделей в прогнозируемых ценовых диапазонах.
    • Ориентируйтесь на направленность и основные уровни поддержки/сопротивления
    1. Уточнение входа:
    • Дождитесь структурных подтверждений перед входом в сделку. Структуры, которые лучше всего знать: двойные вершины/низы, клинья, бычьи/медвежьи флаги, вершины/низы MTR, кульминационные точки, особенно вблизи ключевых областей поддержки и сопротивления/FVG.
    • Не входите в рынок, если вы предвидите трендовый канал. Трендовые каналы - ваши злейшие враги. Даже если вы обнаружили трендовый канал на более высоком таймфрейме, например на 4 часах или одном дне, НЕ ВХОДИТЕ!
    • Ищите модели консолидации в пределах прогнозируемых диапазонов - играйте на каждом развороте. Эта модель действительно сияет на разворотах.
    • Рассмотрите возможность масштабирования позиций вместо полноразмерных входов. Начальный размер 25% - масштабирование по мере того, как цена идет в вашу пользу, вплоть до полного размера позиции. Не масштабируйте против своей позиции, т. е. если позиция идет против вас.

    Некоторые дополнительные личные наблюдения:

    • При работе с этой моделью нужно быть внимательным и предугадывать определенные вещи: Развороты вокруг ключевых областей являются наиболее прибыльными.
      • Скажем, модель предсказывает изменение цвета для определенного бара или таймфрейма. Это время, чтобы начать обращать внимание. Ищите еще 1 дополнительное слияние перед входом. Если вы не получаете это стечение, подождите, пока не получите его, даже если цвет сменился, сделка все равно может сработать. Я хочу сказать, что это хорошая "временная" модель с точки зрения того, "когда" вам нужно начать проявлять заботу и внимание.
    • Эта модель дает много ложных срабатываний, но несколько крупных выигрышей сводят на нет все убытки.
    • Начните с 1 пары и добавляйте еще одну пару каждую неделю. Масштабируйте свои модели до 10 пар.
    • Каждую неделю вы будете получать 2 - 3 крупные сделки.
    • Ожидайте еженедельную прибыль в размере 0,5 - 1,5 %. Вы рискуете примерно 1,0 - 2,5 % каждую неделю по всем парам. Другими словами, вы торгуете небольшими суммами, сохраняете диверсификацию по нескольким некоррелированным инструментам и фокусируетесь на определенных временных диапазонах. Вы будете постоянно правильно торговать примерно 3-6 инструментами, и это принесет вам большую часть денег. Поэтому не зацикливайтесь на одной сделке и не анализируйте одну точку данных.
    • Если прогнозируемое изменение связано с новостным событием, пропустите его и сосредоточьтесь на другой паре или дождитесь следующего сигнала о смене цвета.
    • Трендовые каналы - ваш злейший враг: если вы даже подозреваете о них - забудьте об этой паре на этот день. Как вариант, делайте противоположное тому, что говорит вам модель, потому что иначе вы попадете в контртренд и проиграете.
    • Торговля по этой модели "неудобна" - вы действительно никогда не доверяете ей, и вам постоянно кажется, что "эта сделка никогда не сработает, она настолько контринтуитивна", но в этом и заключается ее прелесть. Она заставляет вас делать то, что вы не хотите делать, но это правильное действие (то, что вы не хотите делать, но должны сделать, потому что это правильное действие в данной ситуации).
    • Концепция инверсий встречается ~20-30 % времени: это когда модель дает вам один цвет (красный или зеленый), но рынок, кажется, всегда делает противоположное. Это сценарий инверсии - в модели нет ничего плохого, и в вашей торговле тоже нет ничего плохого. Вы просто должны осознать, что произошла инверсия, и начать действовать в противоположном направлении или вообще пропустить пару, пока ожидания не выровняются. Обычно для исправления инверсии требуется 6 - 10 сессий (около 2 - 4 дней). Самый простой способ выявить инверсию с помощью этой модели - использовать скрипт python, который я дал вам для прогнозов модели, - последовательно взять данные за последние 6 сессий (поэтому не берите самые последние данные - сделайте обратный просмотр). Посмотрите, совпадают ли прогнозы с фактическими данными. Если нет - произошла инверсия.

    Прогнозы модели должны использоваться как один из компонентов вашего анализа, а не как единственное средство принятия решений. Учет этих элементов позволит вам улучшить согласованность результатов торговли при использовании модели PatchTST.

    Надеюсь, это поможет.

    Скрипт гэпа справедливой стоимости (FVG), о котором я упоминал (по моему опыту, эти гэпы работают очень похоже на зоны спроса и предложения):

    Большое спасибо за ваш терпеливый ответ и бескорыстное участие. Я никогда раньше не встречал таких подробных и профессиональных ответов. Я буду перечитывать вашу статью неоднократно. Эти знания особенно ценны для меня. С наилучшими пожеланиями.
    Shashank Rai
    Shashank Rai | 20 янв. 2025 в 16:48
    Thomas Sawyer #:
    Большое спасибо за ваш терпеливый ответ и бескорыстное участие. Я никогда раньше не встречала таких подробных и профессиональных ответов. Я буду перечитывать вашу статью неоднократно. Эти знания особенно ценны для меня. С наилучшими пожеланиями.

    Спасибо. Ваши добрые слова очень много значат! Пожалуйста, обращайтесь, если вам понадобится дополнительная помощь!

    Нейросети в трейдинге: Агент с многоуровневой памятью Нейросети в трейдинге: Агент с многоуровневой памятью
    Подходы многоуровневой памяти, имитирующие когнитивные процессы человека, позволяют обрабатывать сложные финансовые данные и адаптироваться к новым сигналам, что способствует повышению эффективности инвестиционных решений в условиях динамичных рынков.
    Нейросети в трейдинге: Модели с использованием вейвлет-преобразования и многозадачного внимания (Окончание) Нейросети в трейдинге: Модели с использованием вейвлет-преобразования и многозадачного внимания (Окончание)
    В предыдущей статье мы рассмотрели теоретические основы и приступили к реализации подходов фреймворка Multitask-Stockformer, объединяющего вейвлет-преобразование и многозадачную модель Self-Attention. Продолжаем реализацию алгоритмов указанного фреймворка и оценим их эффективность на реальных исторических данных.
    Анализ сентимента (рыночных настроений) и глубокое обучение для торговли советником и тестирование на истории с помощью Python Анализ сентимента (рыночных настроений) и глубокое обучение для торговли советником и тестирование на истории с помощью Python
    В этой статье познакомим вас с анализом сентимента и моделями ONNX на языке Python для использования в советнике. Один скрипт запускает обученную модель ONNX из TensorFlow для прогнозов на основе глубокого обучения, а другой извлекает заголовки новостей и дает количественную оценку настроений при помощи ИИ.
    Разработка интерактивного графического пользовательского интерфейса на MQL5 (Часть 1): Создание панели Разработка интерактивного графического пользовательского интерфейса на MQL5 (Часть 1): Создание панели
    В статье рассматриваются основные этапы создания и реализации панели графического пользовательского интерфейса (Graphical User Interface, GUI) с помощью языка MetaQuotes Language 5 (MQL5). Пользовательские панели утилит повышают качество взаимодействия с системой при торговле, упрощая типовые задачи и визуализируя важную торговую информацию. Создавая пользовательские панели, трейдеры могут оптимизировать рабочий процесс и сэкономить время при торговых операциях.