English Русский Español Português
preview
交易中的神经网络:层次化双塔变换器(终篇)

交易中的神经网络:层次化双塔变换器(终篇)

MetaTrader 5交易系统 |
49 0
Dmitriy Gizlyk
Dmitriy Gizlyk

概述

前一篇文章中,我们回顾了专为分析和预测复杂多变量时间序列而开发的 Hidformer 框架的理论层面。该模型在处理动态和易变性数据方面展现出高效能,这得益于其独特的架构。

Hidformer 的一个关键元素是采用先进的注意力机制,令其不仅识别数据中的显性依赖关系,还能揭示深层潜在的关系。为达成这一点,模型采用了双塔编码器,每个塔依据原产数据执行独立分析。其一专门分析时态结构,识别趋势和形态,其二则在频域中验证数据。该方式提供了市场动态的综合见解,令模型能够参考价格序列的短期和长期变化。

该模型的一个创新之处是采用递归注意力机制来分析时态依赖性,从而能够顺序积累关于所研究金融产品复杂动态形态的信息。与分析输入数据频谱的线性注意力机制相结合,该方式优化了计算成本,并确保训练稳定性。这令 Hidformer 框架能够有效适应输入数据的多维性和非线性,在高度波动的市场条件下提供更可靠的预测。

该模型的解码器建立在多层感知器之上,能够单步内预测整个价格序列,把分步预测中的典型误差积累最小化。这显著提升了长期预测的品质,令该模型在实际金融分析应用中尤具价值。

下面提供了 Hidformer 框架的原始可视化。

上一篇文章的实践章节,我们完成了准备工作,并分别实现了递归和线性注意力算法版本。今天,我们继续按 Hidformer 框架作者提议的方式进行开发。


时间序列分析

Hidformer 框架的作者提出了一种双塔编码器架构,我们予以采纳作为基础。在我们的实现中,每个编码器塔都表示为独立对象,允许模型灵活适配不同任务。然而,不同于原始框架,我们按照模型靶标的具体问题,引入了若干修改。起初,该框架设计用来预测所分析时间序列的延续,但我们迈进一步。

借鉴获自实现 MacroHFTFinCon 框架时的经验,我们把编码器塔的用途改作独立智代,生成未来交易操作的可能场景。这显著拓宽了系统功能的能力。

与原始的 Hidformer 架构一样,我们的智代会按多变量时间序列、及其频率特征的形式分析市场数据。递归注意力机制允许模型捕捉多元时间序列中的依赖关系,而频谱分析则利用线性注意力模块执行。该方式能够更深度地理解数据中的结构化形态,并允许模型实时适配变化中的市场条件 — 这在高频和算法交易中尤为重要。

此外,每名智代都配备了一个模块,可对既往指定的决策重现分析,令其能够在演化中的市场形势背景下进行评估。本模块提供分析既往决策、识别最有效策略、以及调整模型以适配市场条件的能力。

时间序列分析智代作为 CNeuronHidformerTSAgent 对象实现。其结构如下所示。

class CNeuronHidformerTSAgent    :  public CResidualConv
  {
protected:
   CNeuronBaseOCL                caRole[2];
   CNeuronRelativeCrossAttention cStateToRole;
   CNeuronS3                     cShuffle;
   CNeuronRecursiveAttention     cRecursiveState;
   CResidualConv                 cResidualState;
   CNeuronRecursiveAttention     cRecursiveAction;
   CNeuronMultiScaleRelativeCrossAttention   cActionToState;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronHidformerTSAgent(void) {};
                    ~CNeuronHidformerTSAgent(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_count,
                          uint heads, uint stack_size, uint action_space,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override   const   {  return defNeuronHidformerTSAgent; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      Clear(void) override;
  };

作为父类,我们使用带反馈的卷积模块,其作为内部注意力模块之一的 FeedForward 模块。

值得注意的是,所呈现的结构包含了宽广的多元化组件,每个都在算法新类的组织中执行其独有功能。这些元素确保了多方面的方式,令模型能够适应复杂形态的各种信息处理和分析场景。在类方法构造期间,我们将更详尽地实证每一个组件。

所有对象都声明为静态,允许我们将类构造和析构函数留 “空”。所有继承和声明对象的初始化均在 Init 方法里实现。该方法接收若干常量参数,清晰定义所创建对象的架构。

bool CNeuronHidformerTSAgent::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                   uint window, uint window_key, uint units_count,
                                   uint heads, uint stack_size, uint action_space,
                                   ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CResidualConv::Init(numOutputs, myIndex, open_cl, 3, 3, (action_space + 2) / 3, optimization_type, batch))
      return false;

初始化从调用父类对应的方法开始,其已包含继承对象所需的控制点、及初始化流程。应当在脑海里留意,父对象的接口必须生成与智代预期行为一致的输出。在这种情况下,智代期待输出一个交易操作张量,其中每个操作由三个关键参数表示:交易量、止损和止盈价位。买卖操作由该矩阵的独立行表示。因此,当调用父类初始化方法时,我们把输入数据和输出结果的窗口大小均设为 3,并将序列长度设置为智代动作向量的三分之一。

父类操作执行成功之后,我们初始化新引入的内部对象。首先,我们初始化负责形成智代角色张量的结构。我们适配出自 FinCon 框架的这个概念,并针对当前任务调整它。这一概念的主要优点在于将分析输入数据的职责划分给若干并行智代,令它们能够专注于所分析序列的特定层面。

//--- Role
   int index = 0;
   if(!caRole[0].Init(10 * window_key, index, OpenCL, 1, optimization, iBatch))
      return false;
   caRole[0].getOutput().Fill(1);
   index++;
   if(!caRole[1].Init(0, index, OpenCL, 10 * window_key, optimization, iBatch))
      return false;

接下来,我们初始化相对交叉注意力模块,其根据智代分配的角色高亮输入数据的关键属性。

//--- State to Role
   index++;
   if(!cStateToRole.Init(0, index, OpenCL, window, window_key, units_count, heads, window_key, 10, 
                                                                            optimization, iBatch))
      return false;

原产数据的初步处理之后,我们回到原始的 Hidformer 架构,其在投喂数据至编码器之包含一个分段步骤。重点要注意,在每个塔内的分端都是独立执行的,这有助于避免不同数据流之间的不必要关联,并提升模型对异构输入序列的适应性。

在我们修订版本中,我们用一个专门的 S3 模块取代经典的分段机制,扩展了智代的功能。该模块不仅执行分段,还实现了可学习分段乱序机制。这样的方式令其能更好地识别序列不同部分之间的潜在关系。如是结果,智代能够形成更健壮、普适的表示。

//--- State
   index++;
   if(!cShuffle.Init(0, index, OpenCL, window, window * units_count, optimization, iBatch))
      return false;

在前几个步骤里准备的数据投喂至编码器,其由递归注意力模块、及带反馈的卷积模块组成。

   index++;
   if(!cRecursiveState.Init(0, index, OpenCL, window, window_key, units_count, heads, stack_size, optimization, iBatch))
      return false;
   index++;
   if(!cResidualState.Init(0, index, OpenCL, window, window, units_count, optimization, iBatch))
      return false;

这种编码器允许我们在最新价格的背景下分析输入序列,识别可能的支撑和阻力级别、或稳定形态形成的区域。

在下一阶段,我们再次偏离原 版的 Hidformer,增加了分析智代先前操作的模块。首先,我们以历史序列为背景,递归分析最新动作。

//--- Action
   index++;
   if(!cRecursiveAction.Init(0, index, OpenCL, 3, window_key, (action_space + 2) / 3, heads, stack_size,
                                                                                   optimization, iBatch))
      return false;

然后,我们使用多尺度交叉注意力模块,在动态市场条件背景下分析智代的政策。

   index++;
   if(!cActionToState.Init(0, index, OpenCL, 3, window_key, (action_space + 2) / 3, heads, window,
                                                                units_count, optimization, iBatch))
      return false;
//---
   return true;
  }

FeedForward 模块的功能则经由父类的能力实现。

所有内部对象初始化成功之后,我们将操作的逻辑结果返回给调用程序,并结束该方法。

我们现在继续设计在 feedForward 方法内实现的前向通验算法。方法参数包括包含输入数据对象的指针。

bool CNeuronHidformerTSAgent::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(bTrain && !caRole[1].FeedForward(caRole[0].AsObject()))
      return false;

在方法内,我们先从生成描述智代当前角色的张量开始。然而,这一操作仅在模型训练期间执行。很容易见到,在模型推理期间,每次前馈通验迭代都会生成一个固定角色张量。这一步是冗余的。因此,我们首先检查当前操作模式,然后才调用负责生成角色张量的内部全连通层的前向通验。该方式剔除了不必要的操作,并降低了决策制定延迟。

然后我们继续处理接收到的输入数据。首先,我们提取与智代角色相关的元素。这是经由交叉注意力模块做到的。

//--- State to Role
   if(!cStateToRole.FeedForward(NeuronOCL, caRole[1].getOutput()))
      return false;

接下来,强化环境状态被分段并乱序。

//--- State
   if(!cShuffle.FeedForward(cStateToRole.AsObject()))
      return false;

随后由递归注意力模块处理该它,由先前价格走势动态的有关来丰富环境状态的表示。

   if(!cRecursiveState.FeedForward(cShuffle.AsObject()))
      return false;
   if(!cResidualState.FeedForward(cRecursiveState.AsObject()))
      return false;

在下一阶段,我们智代行为政策的深入分析。首先,以之前存储在递归注意力模块记忆中的动作为背景,分析最新的决策。 

//--- Action
   if(!cRecursiveAction.FeedForward(AsObject()))
      return false;

然后,我们利用多尺度交叉注意力模块,以演化中的市场环境为背景分析智代的政策。

   if(!cActionToState.FeedForward(cRecursiveAction.AsObject(), cResidualState.getOutput()))
      return false;

您能见到动作分析模块的架构借鉴了经典的变换器解码器。经典解码器顺序使用模块:自注意力交叉注意力前馈。在我们的情况下,遵照 Hidformer 框架,自注意力模块被递归注意力模块取代。遵循同样的逻辑,我们用多尺度注意力替代了多头交叉注意力。其余的组件是前馈模块。它是通过父类来实现的。然而,在用它之前,我们必须注意,这个类似解码器的结构输入是由我们方法之前的前馈通验结果组成。为了正确执行反向传播通验,我们需要存储这些信息。因此,我们暂时重定向继承的数据缓冲区指针,然后才调用父类的前馈方法。

   if(!SwapBuffers(Output, PrevOutput))
      return false;
//---
   return CResidualConv::feedForward(cActionToState.AsObject());
  }

这些操作的逻辑结果会返回至调用程序,并结束方法。

下一步是构造反向传播算法。我们对象中的反向传播通验由两种方法表示:calcInputGradientsupdateInputWeights。前者确保误差梯度在所有参与决策过程的对象之间正确分派,并与它们对最终输出的影响成正比。后者对模型的可训练参数进行优化,从而最小化总误差。updateInputWeights 方法通常很简单。典型情况下它调用包含可训练参数的内部对象的相应方法,传递在前馈通验期间保存的数据。然而,梯度分派方法与前馈信息流密切相关,需要更详细的解释。

calcInputGradients 方法的参数包括指向输入数据对象的指针。这与前馈通验期间所传递的是同一对象。不过这次,我们必须把输入数据对模型输出影响的误差梯度发送给它。显然,传送这些信息需要有效的指针。因此,在方法内部,我们立即检查指针的有效性。

bool CNeuronHidformerTSAgent::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

在这个小控制模块之后,我们继续构造梯度分派算法。

梯度分派的信息流与前馈通验的流动互逆。前馈通验以调用父类方法结束。相应地,梯度分派始于调用继承机制。在该阶段,我们调用父类的相关方法,将误差向下传递给负责多尺度交叉注意力的模块,涵盖智代的政策和市场动态。

   if(!CResidualConv::calcInputGradients(cActionToState.AsObject()))
      return false;

接下来,我们必须将所得梯度划分为两条信息流:分析智代政略,分析以多变量时间序列表示的环境状态。

   if(!cRecursiveAction.calcHiddenGradients(cActionToState.AsObject(),
         cResidualState.getOutput(),
         cResidualState.getGradient(),
         (ENUM_ACTIVATION)cResidualState.Activation()))
      return false;

首先,我们沿政策分析分支分派梯度。为此,我们必须将其传递给负责处理智代先前动作的递归注意力模块。注意,该模块的输入是我们对象之前的前馈通验结果。我们之前把它们保存在一个独立的数据缓冲区之中。为了正确梯度分派,我们必须暂时将这些值恢复到结果缓冲区,同时预留当前结果。因此,我们再次替换缓冲区指针。

甚至,在梯度分派期间,对应接口缓冲区中的值会被覆盖。这并不理想,因为这些数值仍是参数更新所需的。因此,我们也重定向了误差梯度缓冲区。

仅在确保所有必要数据保留完整之后,我们才经由递归注意力模块执行梯度分派操作。成功执行后,缓冲区指针会恢复到原始状态。

//--- Action
   CBufferFloat *temp = Gradient;
   if(!SwapBuffers(Output, PrevOutput) ||
      !SetGradient(cRecursiveAction.getPrevOutput(), false))
      return false;
   if(!calcHiddenGradients(cRecursiveAction.AsObject()))
      return false;
   if(!SwapBuffers(Output, PrevOutput))
      return false;
   Gradient = temp;

接着我们沿多元时间序列分析路径继续分派梯度。首先,我们将梯度传播到递归注意力模块级别,分析环境状态。

//--- State
   if(!cRecursiveState.calcHiddenGradients(cResidualState.AsObject()))
      return false;

接着,梯度被传递至分段和乱序模块。

   if(!cShuffle.calcHiddenGradients(cRecursiveState.AsObject()))
      return false;

深入跟进该分支,我们将梯度传输到交叉注意力模块,按智代角色背景分析原产数据。

   if(!cStateToRole.calcHiddenGradients(cShuffle.AsObject()))
      return false;

从该点,梯度再次分切为两条流:朝输入数据对象,以及朝智代角色形成分支。

   if(!NeuronOCL.calcHiddenGradients(cStateToRole.AsObject(),
                                     caRole[1].getOutput(),
                                     caRole[1].getGradient(),
                                     (ENUM_ACTIVATION)caRole[1].Activation()))
      return false;
//---
   return true;
  }

值得注意的是,沿角色形成分支不再出现进一步的梯度传播。该 MLP 的第一层是固定的,只有第二层神经包含可训练参数,我们已把误差信号传递其中。

最后,我们将执行的逻辑结果返回调用程序,并结束该方法。

我们讨论构建环境状态时间序列分析智代方法的算法至此完毕。您可在附件中找到该对象、及其所有方法的完整代码。


频域工作

下一步是构造一个智代,据所分析信号分析频域特性。应当注意的是,该智代的结构与之前创建的时间序列分析智代非常相似。同时,它具备将输入信号转换为频域的独特功能。为了隔离环境状态信号中的高频和低频分量,我们借鉴多任务-Stockformer 框架,实现了一种离散小波变换。

频域智代算法在 CNeuronHidformerFreqAgent 对象中实现。其结构如下所示。

class CNeuronHidformerFreqAgent    :  public CResidualConv
  {
protected:
   CNeuronTransposeOCL           cTranspose;
   CNeuronLegendreWaveletsHL     cLegendre;
   CNeuronTransposeRCDOCL        cHLState;
   CNeuronLinerAttention         cAttentionState;
   CResidualConv                 cResidualState;
   CNeuronS3                     cShuffle;
   CNeuronRecursiveAttention     cRecursiveAction;
   CNeuronMultiScaleRelativeCrossAttention cActionToState;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronHidformerFreqAgent(void) {};
                    ~CNeuronHidformerFreqAgent(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint filters, uint units_count,
                          uint heads, uint stack_size, uint action_space,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override   const   {  return defNeuronHidformerFreqAgent; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      Clear(void) override;
  };

在所呈现的结构中,能容易地注意到内部对象名称的相似性,指出时间域和频域智代之间的相关结构。然而,也存在差异,我们将在构造该新类的方法时详讨。

所有内部对象都声明为静态,这允许我们将对象的构造和析构函数留空。所有新声明和继承的对象,都在 Init 方法中初始化。

bool CNeuronHidformerFreqAgent::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                     uint window, uint filters, uint units_count,
                                     uint heads, uint stack_size, uint action_space,
                                     ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CResidualConv::Init(numOutputs, myIndex, open_cl, 3, 3, (action_space + 2) / 3, optimization_type, batch))
      return false;

方法参数包含一组常量,无歧义的解释所创建对象的架构。在方法内,我们首先调用父类的相关方法,其已实现了继承对象和接口的必要控制点和初始化。应当注意的是,尽管数据域不同,智代仍期待输出相同的交易操作张量。因此,上述调用时序智代中父类初始化方法的方式于此等同。

接下来,我们初始化新声明的对象。注意神对不同领域,智代设计中的区别。频域智代没有角色生成模块。在我们的实现中,我们不打算使用大量频域智代。

此外,分段模块已被离散小波变换模块取代。从时域到频域的变换是在单位序列上进行的。为了方便处理这些序列,我们首先转置输入数据矩阵。

   int index = 0;
   if(!cTranspose.Init(0, index, OpenCL, units_count, window, optimization, iBatch))
      return false;

单变量时间序列被切分为相等的段落。每个段落都应用离散小波变换,允许我们提取时态依赖关系的重要结构化分量。最小段落尺寸限制为五个元素,在分析准确性和计算成本之间尽力平衡。

   index++;
   uint wind = (units_count>=20 ? (units_count + 3) / 4 : units_count);
   uint units = (units_count + wind - 1) / wind;
   if(!cLegendre.Init(0, index, OpenCL, wind, wind, units, filters, window, optimization, batch))
      return false;

应当注意的是,离散小波变换模块的输出是一个张量,包含信号的高频和低频分量。每个段落的高频分量紧随低频分量之后,数据能以三维张量表示 [,[低频高频],滤波器]。

为了进一步分析,重点是要把数据拆分为相应的组量。然而,由于两种信号类型都应用雷同的操作,故并行处理它们更为高效。因此,我们不会显式将信号拆分为多个独立对象;取而代之,我们转置张量,这就能更高效地利用计算资源,并加速数据处理。

   index++;
   if(!cHLState.Init(0, index, OpenCL, units * window, 2, filters, optimization, iBatch))
      return false;

正如 Hidformer 框架作者提供的,我们随后应用线性注意力算法。在我们的情况下,我们分开执行高频和低频分量的分析,从而识别最重要的形态,并根据其频域特征自适应调整信号处理策略。

   index++;
   if(!cAttentionState.Init(0, index, OpenCL, filters, filters, units* window, 2, optimization, iBatch))
      return false;

输出的成果经由带反馈的卷积模块,作为我们频域编码器的前馈模块。

   index++;
   if(!cResidualState.Init(0, index, OpenCL, filters, filters, 2 * units * window, optimization, iBatch))
      return false;

接下来,我们初始化智代-政策分析模块,拟同构造时间序列智代。但这里有一个警告。对于线性注意力模块,段落的顺序无关紧要,因为分析是一次性应用于整个序列。然而,在使用多尺度交叉注意力模块时,我们必须寻址段落的优先级,在于该模块专为时间序列设计,最新的元素更优先。

为了解决这个问题,我们使用了分段和乱序对象。在这种情况下,我们的数据已经被分段,关键焦点是可学习的段落乱序。这允许模型基于训练数据独立学习分段优先级。

   index++;
   if(!cShuffle.Init(0, index, OpenCL, filters, cResidualState.Neurons(), optimization, iBatch))
      return false;

我们不会进一步详细说明智代-政策分析模块所用对象的功能,在于描述时间序列智代所的方式仍被保留。

//--- Action
   index++;
   if(!cRecursiveAction.Init(0, index, OpenCL, 3, filters, (action_space + 2) / 3, heads, stack_size,
                                                                               optimization, iBatch))
      return false;
   index++;
   if(!cActionToState.Init(0, index, OpenCL, 3, filters, (action_space + 2) / 3, heads, filters, 
                                                           2 * units * window, optimization, iBatch))
      return false;
//---
   return true;
  }

当所有内部对象成功初始化之后,我们结束该方法,返回逻辑结果至调用程序。

为了降低文章篇幅,前馈和反向传播的通验方法留待独立研究。它们的算法遵循与描述时间序列智代的原理相同。两个智代及其方法的完整代码已在附件中提供。


顶层对象

在构造多变量时间序列和频域塔对象之后,构建完整 Hidformer 框架的下一步是将它们合并成一个结构,并添加解码器。Hidformer 的作者使用 MLP 作为解码器,预测所分析时间序列的预期延续。尽管我们对任务进行了修改,感知器仍可用来生成最终决策。然而,我们更进一步,借鉴来自 MacroHFT 框架中的超智代概念。受该思路启发,我们创建了具有以下结构的 CNeuronHidformer 对象。

class CNeuronHidformer  :  public CNeuronBaseOCL
  {
protected:
   CNeuronTransposeOCL        cTranspose;
   CNeuronHidformerTSAgent    caTSAgents[4];
   CNeuronHidformerFreqAgent  caFreqAgents[2];
   CNeuronMacroHFTHyperAgent  cHyperAgent;
   CNeuronBaseOCL             cConcatenated;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronHidformer(void) {};
                    ~CNeuronHidformer(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_count,
                          uint heads, uint layers, uint stack_size, uint nactions,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override   const   {  return defNeuronHidformer; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      Clear(void) override;
  }; 

在该架构中,可以清晰地见到类似于 MacroHFT 框架中 CNeuronMacroHFT 类的结构。本质上,新结构代表其修改版,维持核心设计原则,同时引入针对性的变化,以便提升数据处理效率。

一个关键区别在于环境分析智代的配置。在该版本中,用到六个专用智代:四个用来分析多变量时间序列,两个用来处理频域输入数据。为确保分析平衡,在处理输入数据的直接和转置表示之间,均匀分派至所有智代。该架构允许对输入数据的各个层面进行更详细的探索,揭示隐藏形态,并自适应调整处理策略。

总体而言,对智代结构的修改仅细微调整了对象方法的算法。主要逻辑维持不变,模型的所有关键操作原则得以保留。因此,我们将方法的构造算法留给读者独立研究。附件中提供了该对象、及其所有方法的完整代码。


模型架构

关于可训练模型的架构,简单说几句。正如您或许注意到的,我们的构造架构是 HidformerMacroHFT 框架的协同。可训练模型的架构、及其训练方法也不例外。我们复现了来自 MacroHFT 框架的模型架构,仅修改了一个单层。

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronHidformer;
//--- Windows
     {
      int temp[] = {BarDescr, 120, NActions}; //Window, Stack Size, N Actions
      if(ArrayCopy(descr.windows, temp) < int(temp.Size()))
         return false;
     }
   descr.count = HistoryBars;
   descr.window_out = 32;
   descr.step = 4;                              // Scales
   descr.layers =3;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

此外,操作架构保持不变,包括所用的风险管理智代。模型架构的完整描述,以及训练和测试程序的完整代码已在附件中提供,它们来自上一篇文章,且未作更改。


测试

我们按 Hidformer 作者提出的方法解读,实现其方式已完成大部工作。现在我们到达关键阶段:基于真实历史数据评估我们的方案有效性。在我们的实现中,我们大量借鉴了 MacroHFT 框架。因此,新模型与其比较是合乎逻辑的。故此,我们采用之前为训练 MacroHFT 实现而编译的训练数据集来训练新模型。

该训练数据集是从 EURUSD 货币对,2024 年整个年度, M1 时间帧的历史数据中收集而来。所有指标参数均按其默认值设置。

模型的训练和测试均采用同一智能系统。测试基于 2025 年 1 月的历史数据,维持所有其它参数。测试结果呈现如下。

结果展示,该模型在训练数据集之外的历史数据中达成盈利。总体来看,在该日历月度内,该模型完成了 29 笔交易。每个交易日都有略多一笔交易,这对于高频交易来说还不够。与此同时,超过 60% 的交易是盈利的,平均盈利交易比平均亏损交易高出 60%。


结束语

我们探讨了 Hidformer 框架,其设计用来分析和预测复杂的多变量时间序列。该模型得益于其独特的双塔编码器架构,展现出高效率。一个塔分析输入数据的时间结构,另一个塔则在频域操作。递归注意力机制揭示了复杂的价格变化形态,而线性注意力则降低了分析长序列的计算复杂度。

在工作的实践部分,我们按拟议方法的自我诠释,实现 MQL5 版本。我们在真实历史数据上训练了模型,并在样本外的数据上进行了测试。测试结果展示了该模型的潜力。然而,在投入实盘交易之前,必须在更具代表性的数据集上训练模型,随后进行全面测试。


参考


文章中所用程序

# 名称 类型 说明
1 Research.mq5 智能系统 收集样本的智能系统
2 ResearchRealORL.mq5
智能系统
利用 Real-ORL 方法收集样本的智能系统
3 Study.mq5 智能系统 模型训练智能系统
4 Test.mq5 智能系统 模型测试智能系统
模型测试智能系统 Trajectory.mqh 类库 系统状态和模型架构描述结构
6 NeuroNet.mqh 类库 创建神经网络的类库
7 NeuroNet.cl 函数库 OpenCL 程序代码

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17104

附加的文件 |
MQL5.zip (2406.07 KB)
MQL5 中的交易策略自动化(第十五部分):可视化价格行为的谐波形态模式 MQL5 中的交易策略自动化(第十五部分):可视化价格行为的谐波形态模式
本文探讨了在 MQL5 中实现谐波形态的自动化,详细介绍了如何在 MetaTrader 5 图表上对其进行检测和可视化。我们将实现一个EA,用于识别摆动点,验证基于斐波那契比率的形态,并通过清晰的图形标注执行交易。文章最后还提供了关于回测和优化程序的指导,以助力有效的交易。
MQL5 交易工具包(第 8 部分):如何在代码库中实现和使用历史管理 EX5 库 MQL5 交易工具包(第 8 部分):如何在代码库中实现和使用历史管理 EX5 库
在本系列的最后一篇文章中,我们将探讨如何轻松地将历史管理 EX5 库导入到 MQL5 源代码中,以处理 MetaTrader 5 账户中的交易历史记录。通过 MQL5 中简单的单行函数调用,可以高效管理和分析交易数据。此外,您还将学习如何创建不同的交易历史分析脚本,并开发基于价格的 EA 交易,作为实际用例示例。该示例 EA 利用价格数据和历史管理 EX5 库做出明智的交易决策、调整交易量,并根据先前已平仓的交易实施恢复策略。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
用于MetaTrader 5的WebSocket:借助Windows API实现异步客户端连接 用于MetaTrader 5的WebSocket:借助Windows API实现异步客户端连接
本文详细介绍了开发一款自定义动态链接库的过程,该库旨在为MetaTrader程序提供异步WebSocket客户端连接功能。