English Русский Español Deutsch 日本語 Português
preview
使用PatchTST机器学习算法预测未来24小时的价格走势

使用PatchTST机器学习算法预测未来24小时的价格走势

MetaTrader 5交易 |
595 15
Shashank Rai
Shashank Rai

引言

当我开始深入研究与时间序列预测相关的AI进展时,我在Huggingface.co上首次接触到了一个名为PatchTST的算法。对于任何使用过大型语言模型(LLMs)的人来说,Transformer的发明无疑是自然语言、图像和视频处理工具开发领域的一场革命。但时间序列呢?它是不是被遗忘了?还是说大部分相关研究都只是秘而不宣?事实证明,有许多新的模型成功地将Transformer应用于时间序列预测。在本文中,我们将探讨其中的一种实现。

PatchTST令人印象深刻之处在于其训练模型的快速性以及使用训练好的模型与MQL的便捷性。我坦诚地说,我对神经网络的概念还很陌生。但是,通过这个过程,并解决了本文中为MQL5概述的PatchTST的实施问题,我感觉自己在学习和理解这些复杂神经网络的开发、故障排除、训练和使用的道路上迈出了巨大的一步。这就像把一个刚学会走路的孩子放到一支职业足球队里,期望他在世界杯决赛中踢进致胜一球一样。虽然挑战巨大,但正是这种挑战促使我不断学习和成长。 


PatchTST 概览

在发现PatchTST之后,我开始查阅阐释其设计原理的论文:《时间序列的64个金词:基于Transformer的长期预测》。标题很有趣。当我开始更深入地阅读这篇论文时,我心想,哇,这看起来是一种迷人的结构——它包含了许多我一直想学习的元素。因此,我自然而然地想要尝试一下,看看它的预测效果如何。以下是这个算法让我更加感兴趣的原因:

  • 你可以使用PatchTST预测开盘价、最高价、最低价和收盘价。使用PatchTST时,我感觉你可以将整个数据(包括开盘价、最高价、最低价、收盘价,甚至交易量)一股脑儿地输入进去。你可以期待它能从数据中找出规律,因为所有数据都被转换成了一种称为“补丁(patches)”的东西。关于补丁的更多信息,稍后在本文中会详细介绍。现在,你只需要知道补丁很有吸引力,并且有助于提升预测效果。
  • PatchTST的最小数据预处理要求。当我开始进一步深入研究这个算法时,我意识到作者使用了一种称为“RevIn”的技术,即反向实例归一化。RevIn来源于一篇题为《针对分布偏移的准确时间序列预测的可逆实例归一化》的论文。RevIn试图解决时间序列预测中的分布偏移问题。作为算法交易者,我们都太熟悉这种感觉了:我们训练好的交易执行算法(EA)似乎不再能预测市场走势,我们不得不重新优化和更新参数。可以把RevIn看作是实现同样目标的一种方法。 

  • 这种方法基本上会接收传入的数据,并使用以下公式对其进行归一化处理:

    x = (x - mean) / std

    然后,当模型需要进行预测时,它会使用相反的属性来对数据进行反归一化处理:

    x = x * std + mean

    RevIn 的另一个属性affine_bias。简而言之,affine_bias 是一个可学习的参数,它负责处理数据集中可能存在的偏度、峰度等问题。 

    x = x * affine_weight + affine_bias

    PatchTST 的结构可以概括为以下步骤: 

    输入数据 -> RevIn -> 序列分解 -> 趋势分量 -> PatchTST 主干 -> TSTiEncoder -> Flatten_Head -> 趋势预测器 -> 残差分量 -> 趋势和残差相加 -> 最终预测

    我们了解到,数据将通过 MT5 进行拉取。我们也已经讨论了 RevIn 的工作原理。

    PatchTST 的工作原理如下:假设你拉取了 80,000 条 EURUSD 数据的 H1 时间框架数据。这大约是 13 年的数据量。使用 PatchTST,你会将数据分割成所谓的“补丁”。作为类比,可以把补丁想象成 Vision Transformers (ViTs) 如何处理图像,但这里是针对时间序列数据进行了适应。例如,如果补丁长度为 16,则每个补丁将包含 16 个连续的价格值。这就像一次只看时间序列的一小部分,这有助于模型在考虑全局模式之前专注于局部模式。

    接下来,补丁中包含位置编码以保留序列顺序,这有助于模型记住每个补丁在序列中的位置。

    Transformer 将归一化和编码后的补丁通过一堆编码器层传递。每个编码器层包含一个多头注意力层和一个前馈层。多头注意力层允许模型关注输入序列的不同部分,而前馈层允许模型学习数据的复杂非线性变换。

    最后,我们有趋势分量和残差分量。趋势分量和残差分量都应用了相同的补丁分割、归一化、位置编码和 Transformer 层。然后,我们将趋势分量和残差分量的输出相加,以产生最终预测。


    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文件中找到。这就是我们将用于模型训练的文件。 


    在Python中使用PatchTST的要求

    在本节中,我将给出在Python中使用PatchTST的要求。这些要求可以概括如下:

    创建一个虚拟环境: 

    python -m venv myenv

    激活虚拟环境(Windows)

    .\myenv\Scripts\activate
    

    安装本文附带的zip文件中的requirements.txt所列出的依赖项: 

    pip install -r requirements.txt

    具体来说,运行此项目的要求是: 

    MetaTrader5
    pandas
    numpy
    torch
    plotly
    datetime


    模型训练代码开发的逐步指南

    对于以下代码,您可以使用我包含在zip文件中的Jupyter笔记本(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,获取给定交易品种、时间框架和K线数量的数据,然后返回一个包含开盘价、最高价、最低价和收盘价列的数据框(DataFrame)。

      # 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(即使用GPU),否则使用CPU。这对于在训练过程中利用GPU加速至关重要,尤其是当GPU可用时。

      # 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模型,并将其移动到所选设备(GPU或CPU)上。

      # 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)和损失函数(均方误差)。

      # 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根先前的K线,因为这是我们的模型进行预测所需的数量。 
      # 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. 后处理和可视化: 我们对预测结果进行处理,创建一个DataFrame,并使用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中运行预测代码后,我得到了以下图表。预测是在2024年7月8日周日晚上/周一早上开盘时(中欧夏令时+3,即凌晨12:30左右)创建的。此时正是周日晚上/周一早上的开盘时间。我们可以在图表中看到一个缺口,因为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智能交易系统(Expert Advisor)的格式开发的。以下是完整的脚本: 

    //+------------------------------------------------------------------+
    //|                                            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根K线数据作为输入,将这些数据送入ONNX模型,并获取对未来24根K线的预测。我们有4个输入特征:开盘价、最高价、最低价和收盘价。 

    此外,请注意OnTick()函数中的以下代码: 

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

    由于ONNX模型对计算机的处理能力要求较高,这段代码将确保每根K线只生成一次新的预测。在我们的案例中,因为我们使用的是小时线,所以预测将每小时更新一次。 

    最后,在这段代码中,我们将通过MQL5的绘图功能在屏幕上绘制未来的K线: 

    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(1小时)时间框架上之后,你应该会在你的图表上看到未来增加了一些额外的K线。在本例中,它看起来是这样的: 


    请注意,如果您在图表右侧没有看到新绘制的K线,您可能需要点击“将图表末端从右侧边界移开”的按钮。

    结论

    在本文中,我们采取了循序渐进的方法,来训练2023年推出的PatchTST模型。我们大致了解了PatchTST算法的工作原理。基础代码存在一些与ONNX转换相关的问题。具体来说,“Unfold”操作不受支持,因此我们解决了这个问题,使代码更加兼容ONNX。为了让文章内容对交易者更加友好,我们重点关注了模型的基础知识、数据的获取、模型的训练以及获取未来24小时的预测结果。然后,我们在MQL5中实现了这个预测功能,这样我们就可以将完全训练好的模型与我们喜爱的指标和专家顾问(EA)结合使用了。我很高兴将我的所有代码与MQL社区的成员们分享,代码已附在zip文件中。如有任何问题或意见,请随时告知我。 


    本文由MetaQuotes Ltd译自英文
    原文地址: https://www.mql5.com/en/articles/15198

    附加的文件 |
    PatchTST.zip (4876.35 KB)
    最近评论 | 前往讨论 (15)
    Thomas Sawyer
    Thomas Sawyer | 20 1月 2025 在 04:46
    我经常发现这个模型的预测结果与实际情况不太一致。我没有对这个模型的代码做任何修改。您能给我一些指导吗?谢谢。
    Shashank Rai
    Shashank Rai | 20 1月 2025 在 09:56
    Thomas Sawyer #:
    我经常发现这个模型的预测结果与实际情况不太一致。我没有对这个模型的代码做任何修改。您能给我一些指导吗?谢谢。

    感谢您分享使用该模型的经验。您提出的关于预测一致性的观点很有道理。当把 PatchTST 模型整合到考虑多种市场因素的综合交易方法中时,它的效果最佳。以下是我建议如何更有效地使用该模型预测的方法:

    1. 时间窗口优化:
    • 重点关注高峰时段(美国中部时间上午 6:00 至上午 10:00)的交易。
    • 主要在这些时段使用模型预测,因为这些时段的市场走势更容易预测
    • 特别注意前期日线和周线高点/低点附近的偏差。这些是主要的供需区。
    1. 模型整合策略:
    • 将预测作为更广泛分析的一部分,而不是独立的信号
    • 在预测的价格范围内寻找公平价值缺口 (FVG)。下面是我在 MQL5 中用于 FVG 的指标代码。
    • 将预测与旗形、楔形和水平整理等技术形态相结合。
    • 结合每日、每周和每月的价格位置考虑预测结果
    1. 风险管理:
    • 实施更广泛的止损(例如,黄金 10 点或 100 点止损、欧元兑美元 50 点、美元兑日元 65 点、英镑兑美元 60 点、澳元兑美元/新西兰元 30 点、美元兑加元 40 点、原油 0.80 点、US500 25 点、NQ 75 点、US30 200 点)
    • 对部分仓位使用适度的止盈(即第一部分仓位的风险回报为 1:1(初始仓位的 70%),留出一个空仓(初始仓位的 30)
    • 根据市场情况调整仓位规模--有大量汇合的好交易,2 - 3 倍的基本仓位规模。
    • 避免在影响较大的新闻事件期间进行交易
    1. 建立背景:
    • 分析多个时间段的市场结构:利用 5 分钟和 15 分钟--1 小时内可能有一个最佳的蜡烛/条形图/收盘价,模型会预测您的交易方向。
    • 考虑当前市场状态(趋势/徘徊/巩固/软弱/反转)--利用这些信息预先计划最佳交易时间。如果你的预期没有实现,就寻找模型给你提供的下一个机会。
    • 在预测的价格范围内寻找形态确认
    • 关注方向性倾向和主要支撑/阻力位
    1. 进场细化:
    • 等待结构性确认后再进场交易。最了解的结构:双顶/底、楔形、牛/熊旗、MTR 顶/底、高潮,尤其是在关键支撑和阻力区域/FVG 附近。
    • 如果预计会出现趋势通道,请勿入场。趋势通道是你最大的敌人。即使在较高的时间框架(如 4 小时或 1 天)上发现趋势通道,也不要逆向操作!
    • 在预测的范围内寻找盘整形态--利用每一次反转。这种模式在反转中大放异彩。
    • 考虑按比例进场,而不是全额进场。25% 的初始规模 - 当价格对你有利时扩大规模,直至满仓规模。不要在不利于你的头寸的情况下进场,也就是说,如果头寸不利于你。

    其他一些个人看法:

    • 使用这种模式,你必须关注和预测某些事情:关键领域的反转是最有利可图的。
      • 因此,如果该模型预测某个柱状图或时间框架的颜色会发生变化。这就是开始关注的时候了。在进场前寻找 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; // 显示中点线
    input color UpFVGColor = clrGreen;  // 上升 FVG 颜色
    input color DownFVGColor = clrRed;  // 羽绒被颜色
    
    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;  // 处理所有可用数据
        }
        else
        {
            start = prev_calculated - 1;  // 只处理新的条形图
        }
    
        for (int i = start; i >= 2; i--)  // 确保至少有 3 个小节
        {
            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; // 确保有足够的条形图
    
        if (close[index - 1] > open[index - 1] && high[index - 2] < low[index])
        {
            // 上升收烛条件和缺口存在
            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])
        {
            // 下跌收烛条件和缺口存在
            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 1月 2025 在 11:29
    Shashank Rai #:

    感谢您的关注!是的,对参数的这些更改原则上是可行的,但在切换到 M1 数据时有几个重要的注意事项:

    1.数据量:使用 10080 分钟(1 周)的 M1 数据进行训练意味着要处理比 H1 多得多的数据点。这将

    • 大大增加训练时间
    • 需要更多内存
    • 可能需要 GPU 加速来提高训练效率

    2.模型架构调整:在模型训练的第 8 步和预测代码的第 4 步,您可能需要调整其他参数,以适应更大的输入序列:

    3.预测质量:虽然您可以获得更精细的预测,但要注意 M1 数据通常包含更多噪声。您可能需要尝试不同的序列长度和预测窗口,以找到最佳平衡点。

    感谢您的见解。我的电脑性能不错,有 256GB 和 64 个物理内核。不过还需要更好的 GPU。

    一旦我更新了 GPU,我就会尝试更新配置设置。

    Thomas Sawyer
    Thomas Sawyer | 20 1月 2025 在 13:55
    Shashank Rai #:

    感谢您分享使用该模型的经验。您提出的关于预测一致性的观点很有道理。如果将 PatchTST 模型与考虑多种市场因素的综合交易方法结合起来,它就能发挥最大作用。以下是我建议如何更有效地使用该模型预测的方法:

    1. 时间窗口优化:
    • 重点关注高峰时段(美国中部时间上午 6:00 至上午 10:00)的交易。
    • 主要在这些时段使用模型预测,因为这些时段的市场走势更容易预测
    • 特别注意之前的日高点和周高点/低点附近的偏差。这些是主要的供需区。
    1. 模型整合策略:
    • 将预测作为更广泛分析的一部分,而不是独立的信号
    • 在预测的价格范围内寻找公平价值缺口 (FVG)。下面是我在 MQL5 中用于 FVG 的指标代码。
    • 将预测与旗形、楔形和水平整理等技术形态相结合。
    • 结合每日、每周和每月的价格位置考虑预测结果
    1. 风险管理:
    • 实施更广泛的止损(例如,黄金 10 点或 100 点止损、欧元兑美元 50 点、美元兑日元 65 点、英镑兑美元 60 点、澳元兑美元/新西兰元 30 点、美元兑加元 40 点、原油 0.80 点、US500 25 点、NQ 75 点、US30 200 点)
    • 对部分仓位使用适度的止盈(即第一部分仓位的风险回报为 1:1(初始仓位的 70%),留下一个跑腿仓位(初始仓位的 30)
    • 根据市场情况调整仓位规模--有大量汇合的好交易,2 - 3 倍的基本仓位规模。
    • 避免在影响较大的新闻事件期间进行交易
    1. 建立背景:
    • 分析多个时间段的市场结构:利用 5 分钟和 15 分钟--1 小时内可能有一个最佳的蜡烛/条形图/收盘价,模型会预测您的交易方向。
    • 考虑当前市场状态(趋势/徘徊/巩固/软弱/反转)--利用这些信息预先计划最佳交易时间。如果你的预期没有实现,就寻找模型给你提供的下一个机会。
    • 在预测的价格范围内寻找形态确认
    • 关注方向性倾向和主要支撑/阻力位
    1. 进场细化:
    • 等待结构确认后再进场交易。最了解的结构:双顶/底、楔形、牛/熊旗、MTR 顶/底、高潮,尤其是在关键支撑和阻力区域/FVG 附近。
    • 如果预计会出现趋势通道,请勿入场。趋势通道是你最大的敌人。即使在较高的时间框架(如 4 小时或 1 天)上发现趋势通道,也不要逆向操作!
    • 在预测的范围内寻找盘整形态--利用每一次反转。这种模式在反转中大放异彩。
    • 考虑按比例进场,而不是全额进场。25% 的初始规模 - 当价格对你有利时扩大规模,直至满仓规模。不要在不利于你的头寸的情况下进场,也就是说,如果头寸不利于你。

    其他一些个人看法:

    • 使用这种模式,你必须关注和预测某些事情:关键领域的反转是最有利可图的。
      • 因此,如果该模型预测某个柱状图或时间框架的颜色会发生变化。这就是开始关注的时候了。在进场前寻找 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 1月 2025 在 16:48
    Thomas Sawyer #:
    非常感谢你的耐心解答和无私分享。我从未见过如此详细和专业的回答。我会反复阅读您的文章。这些知识对我来说特别宝贵。祝您一切顺利。

    谢谢您。您的好意我心领了如果您需要更多帮助,请联系我们!

    开发回放系统(第 52 部分):事情变得复杂(四) 开发回放系统(第 52 部分):事情变得复杂(四)
    在本文中,我们将修改鼠标指针,以实现与控制指标的交互,确保可靠、稳定地运行。
    构建K线趋势约束模型(第五部分):通知系统(第三部分) 构建K线趋势约束模型(第五部分):通知系统(第三部分)
    本系列文章的这一部分专门介绍如何将WhatsApp与MetaTrader 5集成以实现通知功能。我们包含一张流程图以简化理解,并将讨论在集成过程中安全措施的重要性。指标的主要目的是通过自动化的简化分析过程,并且它们应包含通知方法,以便在满足特定条件时向用户发出警报。欲了解更多信息,请阅读本文。
    神经网络变得简单(第 86 部分):U-形变换器 神经网络变得简单(第 86 部分):U-形变换器
    我们继续研究时间序列预测算法。在本文中,我们将讨论另一种方法:U-形变换器。
    跨邻域搜索(ANS) 跨邻域搜索(ANS)
    本文揭示了跨邻域搜索(ANS)算法的潜力,作为重要的一步,旨在开发灵活且智能的优化方法,使其能够在搜索空间中考虑问题的具体特性和环境的动态变化。