English Русский Español Deutsch 日本語 Português
preview
交易中的神经网络:使用小波变换和多任务注意力的模型(终篇)

交易中的神经网络:使用小波变换和多任务注意力的模型(终篇)

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

概述

上一篇文章中,我们开始探索多任务-Stockformer 框架的理论层面,并开始利用 MQL5 实现所提议方式。多任务-Stockformer 结合了两个强力工具:离散小波变换,其支持深度时间序列分析;以及多任务自注意力模型,用友捕获金融数据中复杂依赖关系的能力。这种协同作用使得创建分析和预测时间序列的通用工具成为可能。

该框架围绕三个核心模块构建。在时间序列分解模块之中,所分析数据被切分为高频和低频分量。低频分量代表全局趋势,并支持长期形态分析。高频分量捕获短期波动,包括活跃性爆发和异常。详细的数据分解强化了处理品质,并促进了关键特征的提取,这在与金融时间序列打交道时扮演至关重要的角色。

分解之后,数据由双频时空编码器处理。该模块结合了若干个子模块,设计用于分析所提取频率分量、及其相互依赖关系。低频信号由专注于长期趋势、及其演变的时态注意力机制进行处理。反过来,高频数据会传送至扩充的因果卷积层,识别细微的变化、及其动态。然后,处理后的信号经由图形注意力模块进行集成,即捕获时空依赖关系,反映各种资产和时间间隔之间的关系。该过程产生多级图形表示,并将其转换为多维嵌入。这些嵌入使用加法和图形注意力机制进行合并,形成数据的综合表述,供后续分析。

过程的一个关键阶段是双频融合解码器,它在生成预测结果方面扮演着至关重要的角色。该解码器使用融合注意力机制集成了预测器,能够将低频和高频数据聚合到统一的潜在表述。这种表述反映了跨多尺度的时间形态,提供了全面的数据分析方式。在该阶段,模型生成隐藏表述,后续由专门的全连接层处理。这些层令模型能够同时执行多项任务:预测资产回报、评估趋势变化概率、以及识别时间序列的其它关键特征。多任务处理方式令模型更加灵活,并适应不同的市场条件 — 这在高度波动的金融市场背景下尤为重要。

作者提供的多任务-Stockformer 框架可视化如下所示。


多任务-Stockformer 框架的实现

我们继续致力于利用 MQL5 实现多任务-Stockformer 框架作者提议的方式。这涉及实际的旨在优化时间序列分析的关键系统组件实现。

该框架的基本元素之一是时间序列分解模块,其已在 CNeuronDecouplingFlow 类中实现。该组件将输入数据分离为高频和低频分量,为后续分析奠定基础。该模块的主要意向是提取时间序列的关键结构特征,研究其特殊性、及潜在的市场趋势。在上一篇文章中,我们研究了 CNeuronDecouplingFlow 类设计背后的架构和算法解决方案。

数据处理的下一阶段涉及经由双频时空编码器进行分析。如早前所述,框架作者提出了一个复杂的编码器架构,即包括两个独立的数据流,每个都有自己的结构设计。

低频分量是经由基于自注意力架构的时态注意力机制进行分析。这种方式为辨别长期依赖关系、及预测全局市场趋势提供了强大能力。自注意力的运用可确保对复杂数据结构的深入理解,从而把忽视重要相互依赖关系的风险最小化。在当前的实现中,我们决定使用函数库中现有的注意力模块之一,采用自注意力机制。

高频时间序列分量经由一个强化的因果卷积模块处理,该模块在 CNeuronDilatedCasualConv 类中实现。改进后的算法有效地检测局部异常和活跃爆发。该组件在分析短期市场动态方面扮演着关键角色,特别是在高波动时期。将该模块集成到整体框架架构中,来提升适应性和性能。我们在设计 CNeuronDilatedCasualConv 时用到的架构选择和原始框架的局部修改,已在上一篇文章中讨论过。

所分析信号的高频和低频分量初步处理之后,数据被路由到图形注意力槽的单独分支当中。该模块基于两个专用图形的创建。第一个图形针对时态依赖关系建模,强调它们的顺序结构。它在识别趋势、周期性、及其它时态特征方面扮演着重要角色。第二张图形基于金融资产价格的相关矩阵,提供了有关资产相互依赖性信息的深度整合。这令模型能够参考一种资产对另一种资产的影响,这对于金融建模和预测尤为重要。这些图形共同形成了一个多级结构,强化了数据分析和解释的准确性。

为了将图形信息转换为可供分析的实用表述,采用了 Struct2Vec 算法。该算法将图形的拓扑属性转换为紧凑的向量嵌入,并用可训练的全连接层进一步优化。这样的嵌入能有效集成局部和全局数据特征,提升时间序列分析品质。然后,处理后的数据被传递到图形注意力分支,在那里使用注意力机制进一步检查。该阶段可以检测短期和长期依赖关系。

多任务-Stockformer 框架的作者为图形注意力槽提出了一个相当复杂的架构。它的实现需要大量的计算资源、和细致的数据准备。为了本研究准备模型时,我们引入了若干简化措施,旨在提升模型的实际可用性,同时维持高性能。第一个简化涉及排除有关所分析环境状态的时态信息。这一决定基于这样的假设,即时态信息虽然有用,但在现阶段不会严重影响我们模型的整体效率。在原始框架中,输出代表构造的股票投资组合,而在我们的实现中,主要意向是创建环境的潜在表示。参与者模型用这种表述来制定交易决策,并辅以账户状态、及时间戳数据,提供上下文感知。因此,我们只是平移了时态信息传输到模型的时刻。

不过,应用于时态依赖关系图形的简化不能用于资产相关图形,因为这会导致关键信息丢失。取而代之,我们提出了一种替代解决方案,即用可训练的位置编码层替换原始结构。这种方式有效地训练嵌入,同时最大限度地降低计算复杂性,并保留基本的资产间关系,这是模型在训练期间自主学会的。这一改进提供了更灵活的架构,拥有适应不同市场条件的能力。

此外,我们还向前迈出了一步,用节点自适应特征平滑NAFS)模块替换了图形注意力槽。这种方法的一个关键优点是 NAFS 模块中没有可训练的参数,这不仅降低了计算复杂性,还简化了模型配置和训练。

当使用 NAFS 时,嵌入构造过程变得更加灵活和稳健,这在于平滑方法与图形拓扑和节点特征的适配。对于数据结构或许是异构、或动态变化的任务,这一点尤为重要。由此,NAFS 能够创建高品质的数据表征,同时参考局部和全局图形关系。

在双频解码器中聚合两条信息流,该解码器整合了数据的不同层面,为多维分析奠定了基础。这允许更全面地信号动态表征。双频解码器基于融合注意力机制,其结合了两个并行注意力模块。第一个模块基于自注意力,专门深度处理低频分量,识别关键的长期依赖关系、稳定趋势、及全局形态。该模块令其有能力捕获基本时间序列特征,这在预测中扮演着至关重要角色。第二个模块采用交叉注意力来整合高频信息,据短期和细粒度分量令分析更丰满。这样的集成显著强化了低频数据的细节?对于审计微妙但有意义的波动尤为重要。

两个注意力模块同步运作,确保创建连贯且互补的数据表征。它们的结果经由汇总合并,随后由全连接层(MLP)处理。该方式允许同时参考全局和局部信号特征,捕获广泛的关系和影响。

所提议的融合注意力架构能由现有的交叉自注意力模块轻松实现。甚至,其实现不需要对基本算法进行重大修改。

因此,我们能够得出结论,我们现在拥有了创建多任务-Stockformer 框架全面架构的所有关键模块。这为进入下一步开发奠定了基础:形成一个高级对象,即所有指定的模块统合到单一功能完整的算法之中。这一步的主要目的不仅是集成组件,还要确保它们的同步运作,同时参考每个模块的特性。下面是新 CNeuronMulttaskStockformer 对象的结构。

class CNeuronMultitaskStockformer   :  public CNeuronBaseOCL
  {
protected:
   CNeuronDecouplingFlow      cDecouplingFlow;
   CNeuronBaseOCL             cLowFreqSignal;
   CNeuronBaseOCL             cHighFreqSignal;
   CNeuronRMAT                cTemporalAttention;
   CNeuronDilatedCasualConv   cDilatedCasualConvolution;
   CNeuronLearnabledPE        cLowFreqPE;
   CNeuronLearnabledPE        cHighFreqPE;
   CNeuronNAFS                cLowFreqGraphAttention;
   CNeuronNAFS                cHighFreqGraphAttention;
   CNeuronDMHAttention        cLowFreqFusionDecoder;
   CNeuronCrossDMHAttention   cLowHighFreqFusionDecoder;
   CNeuronBaseOCL             cLowHigh;
   CNeuronConvOCL             cProjection;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *prevLayer) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronMultitaskStockformer(void) {};
                    ~CNeuronMultitaskStockformer(void) {};
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_count,
                          uint heads, uint layers, uint neurons_out, uint filters,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override   const   {  return defNeuronMultitaskStockformer; }
   //---
   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;
  };

所呈现结构包括许多直接对应于上述多任务-Stockformer 框架模块的内部对象。规划的这些组件,确保了高度的功能集成、和实现的灵活性。我们将详细分析控制其交互的算法,以及集成对象方法实现期间的数据流。

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

bool CNeuronMultitaskStockformer::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                       uint window, uint window_key, uint units_count,
                                       uint heads, uint layers, uint neurons_out, uint filters,
                                       ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, neurons_out, optimization_type, batch))
      return false;

在这种方法的参数中,除了熟悉的常量外,还有一个新参数:neurons_out。它指定所分析环境状态的潜在表征向量大小,用户期望从其获得该多任务-Stockformer 模块的输出。该向量被传递给相应的父类方法,其中初始化核心接口,以便与模型内的外部神经层交换数据。

父类方法成功执行后,我们转去初始化内部对象。该过程遵循前馈通验期间对象的使用顺序。如前所述,输入数据首先经由 CNeuronDecouplingFlow 信号分解模块分离为高频和低频分量。

   uint index = 0;
   uint wave_window = MathMin(24, units_count);
   if(!cDecouplingFlow.Init(0, index, OpenCL, wave_window, 2, units_count, filters, window, optimization, iBatch))
      return false;
   cDecouplingFlow.SetActivationFunction(None);

注意,在集成对象初始化方法的外部参数中,我们没有指定离散小波变换窗口的大小和步长。这些参数直接在方法中设置为固定值。对于本文中描述的实验,我们专注于历史 H1 时间帧数据。相应地,我们将小波变换窗口大小限制为一天,对应于所分析序列的 24 步,并添加一个检查,以防止超过多模态时间序列的长度。窗口步长设置为 2,有效地跳过序列的一个元素。

分解模块的输出是一个包含高频和低频分量的统一张量。为了在双频时空编码器中进行处理,提供了两条并行流,每个分量分别分析。为了实现这种方式,我们将数据拆分为单独的对象。这将为后续处理提供便利和灵活性。

//--- Dual-Frequency Spatiotemporal Encoder
   uint wave_units_out = cDecouplingFlow.GetUnits();
   index++;
   if(!cLowFreqSignal.Init(0, index, OpenCL, cDecouplingFlow.Neurons() / 2, optimization, iBatch))
      return false;
   cLowFreqSignal.SetActivationFunction(None);
   index++;
   if(!cHighFreqSignal.Init(0, index, OpenCL, cDecouplingFlow.Neurons() / 2, optimization, iBatch))
      return false;
   cHighFreqSignal.SetActivationFunction(None);
   index++;

低频分量在时态注意力模块中处理,基于自注意力机制。在原始的多任务-Stockformer 框架中,提出了位置编码来强化序列处理。然而,我们使用搭配相对位置编码的注意力模块,其能固有地判定序列元素的相对位置。这剔除了对额外位置编码的需求,简化了架构,同时提升了效率。

   if(!cTemporalAttention.Init(0, index, OpenCL, filters, window_key, wave_units_out * window, heads, layers,
                                                                                       optimization, iBatch))
      return false;
   cTemporalAttention.SetActivationFunction(None);
   index++;

重点要注意,描述单个序列元素的向量维度对应于小波变换中用到的滤波器数量。而序列长度覆盖所有单变量时间序列。这种方式能够跨整个多模态序列研究趋势的相互依赖性,而非孤立地分析其组件。

在强化的因果卷积模块中分析高频依赖关系。在此,我们按相同步骤使用 2 个元素的最小卷积窗口。分析在单一序列中执行,允许详细调查局部依赖关系。

   if(!cDilatedCasualConvolution.Init(0, index, OpenCL, 2, 2, filters, wave_units_out, window, layers,
                                                                                 optimization, iBatch))
      return false;
   index++;

然后将位置编码添加到这两个组件当中。

   if(!cLowFreqPE.Init(0, index, OpenCL, cTemporalAttention.Neurons(), optimization, iBatch))
      return false;
   index++;
   if(!cHighFreqPE.Init(0, index, OpenCL, cDilatedCasualConvolution.Neurons(), optimization, iBatch))
      return false;
   index++;

每个组件接收一个单独的可训练位置编码层。该方式能够促进高频和低频结构的更深入独立分析。

直至双频编码器完成后,我们初始化节点自适应特征平滑(NAFS)模块,并分别应用于高频和低频分量。除序列长度外,两个模块共享参数。由于强化的因果卷积模块性质,预计高频序列会更短。

   if(!cLowFreqGraphAttention.Init(0, index, OpenCL, filters, 3, wave_units_out * window, optimization, iBatch))
      return false;
   index++;
   if(!cHighFreqGraphAttention.Init(0, index, OpenCL, filters, 3, cDilatedCasualConvolution.Neurons()/filters,
                                                                                          optimization, iBatch))
      return false;
   index++;

接下来,我们初始化数据流融合解码器对象。在此,我们初始化了两个注意力模块:用于低频分量的自注意力,和用于集成高频分量的交叉注意力

//--- Dual-Frequency Fusion Decoder
   if(!cLowFreqFusionDecoder.Init(0, index, OpenCL, filters, window_key, wave_units_out * window, heads,
                                                                           layers, optimization, iBatch))
      return false;
   index++;
   if(!cLowHighFreqFusionDecoder.Init(0, index, OpenCL, filters, window_key, wave_units_out * window, filters,
                             cDilatedCasualConvolution.Neurons()/filters, heads, layers, optimization, iBatch))
      return false;
   index++;

注意力模块输出相加。并创建一个基本神经层对象来存储结果。

   if(!cLowHigh.Init(0, index, OpenCL, cLowFreqFusionDecoder.Neurons(), optimization, iBatch))
      return false;
   CBufferFloat *grad = cLowFreqFusionDecoder.getGradient();
   if(!grad ||
      !cLowHigh.SetGradient(grad, true) ||
      !cLowHighFreqFusionDecoder.SetGradient(grad, true))
      return false;
   index++;

为了减少不必要的数据复制,指向最后三个对象的梯度缓冲区的指针是同步的。该方式减少了内存占用,提升了训练效率。

最后,我们初始化 MLP 对象,以便生成环境状态的潜在表示。于此,我们使用一个卷积层进行降维,并用全连接层来生成目标表示大小。

全连接层继承自父类,允许我们仅初始化所需输出连接的卷积层。为了实现全连接层的功能,我们将调用继承自父类的功能。

   if(!cProjection.Init(Neurons(), index, OpenCL, filters, filters, 3, wave_units_out, window, optimization, iBatch))
      return false;
//---
   return true;
  }

所有内部对象初始化之后,Init 方法结束,返回逻辑成功状态至调用程序。

然后,我们为集成对象构造 feedForward 算法。

bool CNeuronMultitaskStockformer::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//--- Decoupling Flow
   if(!cDecouplingFlow.FeedForward(NeuronOCL))
      return false;

该方法接收指向输入数据对象的指针,其会被传递给分解模块。

成果张量在两条流之间拆分,以便进行独立分析。

   if(!DeConcat(cLowFreqSignal.getOutput(), cHighFreqSignal.getOutput(), cDecouplingFlow.getOutput(),
                                          cDecouplingFlow.GetFilters(), cDecouplingFlow.GetFilters(), 
                                          cDecouplingFlow.GetUnits()*cDecouplingFlow.GetVariables()))
      return false;

低频分量经由时态注意力模块处理。然后接收位置编码,并进入图形表征模块。

//--- Dual-Frequency Spatiotemporal Encoder
//--- Low Frequency Encoder
   if(!cTemporalAttention.FeedForward(cLowFreqSignal.AsObject()))
      return false;
   if(!cLowFreqPE.FeedForward(cTemporalAttention.AsObject()))
      return false;
   if(!cLowFreqGraphAttention.FeedForward(cLowFreqPE.AsObject()))
      return false;

高频分量从强化的因果卷积模块开始跟随其流。

//--- High Frequency Encoder
   if(!cDilatedCasualConvolution.FeedForward(cHighFreqSignal.AsObject()))
      return false;
   if(!cHighFreqPE.FeedForward(cDilatedCasualConvolution.AsObject()))
      return false;
   if(!cHighFreqGraphAttention.FeedForward(cHighFreqPE.AsObject()))
      return false;

两条流的输出都传递到双频融合解码器。在此,数据首先由两个注意力模块处理。输出求和,并归一化。

//--- Dual-Frequency Fusion Decoder
   if(!cLowFreqFusionDecoder.FeedForward(cLowFreqGraphAttention.AsObject()))
      return false;
   if(!cLowHighFreqFusionDecoder.FeedForward(cLowFreqGraphAttention.AsObject(), cHighFreqGraphAttention.getOutput()))
      return false;
   if(!SumAndNormilize(cLowFreqFusionDecoder.getOutput(), cLowHighFreqFusionDecoder.getOutput(), cLowHigh.getOutput(),
                                                                 cLowFreqFusionDecoder.GetWindow(), true, 0, 0, 0, 1))
      return false;

接下来,通过卷积投影层对数据进行压缩。

   if(!cProjection.FeedForward(cLowHigh.AsObject()))
      return false;
//---
   return CNeuronBaseOCL::feedForward(cProjection.AsObject());
  }

然后将结果发送到父类方法,以便生成分析环境状态的最终表示。

下一步是实现反向传播过程,其在训练模型中扮演关键角色。反向传播组织在 calcInputGradients 之中,遵循前馈通验的逆向。

bool CNeuronMultitaskStockformer::calcInputGradients(CNeuronBaseOCL *prevLayer)
  {
   if(!prevLayer)
      return false;

该方法的参数包括指向源数据对象的指针;我们需要将误差梯度传递到缓冲区,并根据输入数据对最终模型输出的影响进行派发。在方法的主体中,我们检查接收指针的相关性。否则,数据传输将变得不可能。

首先调用父类功能,把梯度应用于卷积投影层。然后它们被传播到双频解码器的求和层。

   if(!CNeuronBaseOCL::calcInputGradients(cProjection.AsObject()))
      return false;
   if(!cLowHigh.calcHiddenGradients(cProjection.AsObject()))
      return false;

在集成对象的初始化期间,我们实现了以输出求和层所用的缓冲区指针,替换指向解码器注意力模块的误差梯度缓冲区的指针。这确保了传播到求和层的整个误差梯度被完全传递给相应的注意力模块。故此,我们能够经由解码器的注意力模块直接转到梯度传播。

不过应当注意,低频分量数据在两个注意力模块里同时用到。因此,我们需要从两条信息流中获取误差梯度。我们首先通过自注意力模块进行误差梯度派发操作。

//--- Dual-Frequency Fusion Decoder
   if(!cLowFreqGraphAttention.calcHiddenGradients(cLowFreqFusionDecoder.AsObject()))
      return false;

然后,我们将指向自注意力模块的指针临时替换为大小相似的空闲缓冲区,并执行交叉注意力误差梯度传播操作。

   CBufferFloat *grad = cLowFreqGraphAttention.getGradient();
   if(!cLowFreqGraphAttention.SetGradient(cLowFreqGraphAttention.getPrevOutput(), false) ||
      !cLowFreqGraphAttention.calcHiddenGradients(cLowHighFreqFusionDecoder.AsObject(),
            cHighFreqGraphAttention.getOutput(),
            cHighFreqGraphAttention.getGradient(),
            (ENUM_ACTIVATION)cHighFreqGraphAttention.Activation()) ||
      !SumAndNormilize(grad, cLowFreqGraphAttention.getGradient(), grad, 1, false, 0, 0, 0, 1) ||
      !cLowFreqGraphAttention.SetGradient(grad, false))
      return false;

然后,我们汇总两条信息流的数据,并将指向数据缓冲区的指针返回到其原始状态。

我们在双频时空编码器输出级别上将误差梯度分派到高频和低频分量。接下来,我们依次在两条独立流的对象之间分派梯度。低频:

//--- Dual-Frequency Spatiotemporal Encoder
//--- Low Frequency Encoder
   if(!cLowFreqPE.calcHiddenGradients(cLowFreqGraphAttention.AsObject()))
      return false;
   if(!cTemporalAttention.calcHiddenGradients(cLowFreqPE.AsObject()))
      return false;
   if(!cLowFreqSignal.calcHiddenGradients(cTemporalAttention.AsObject()))
      return false;

然后高频:

//--- High Frequency Encoder
   if(!cHighFreqPE.calcHiddenGradients(cHighFreqGraphAttention.AsObject()))
      return false;
   if(!cDilatedCasualConvolution.calcHiddenGradients(cHighFreqPE.AsObject()))
      return false;
   if(!cHighFreqSignal.calcHiddenGradients(cDilatedCasualConvolution.AsObject()))
      return false;

来自两条流的梯度被级联成一个张量:

//--- Decoupling Flow
   if(!Concat(cLowFreqSignal.getGradient(), cHighFreqSignal.getGradient(),
              cDecouplingFlow.getGradient(), cDecouplingFlow.GetFilters(),
              cDecouplingFlow.GetFilters(), cDecouplingFlow.GetUnits()*cDecouplingFlow.GetVariables()))
      return false;
   if(!prevLayer.calcHiddenGradients(cDecouplingFlow.AsObject()))
      return false;
//---
   return true;
  }

然后它们经由分解模块传播回输入数据。该方法完结时将操作的逻辑结果返回给调用程序。

updateInputWeights 中的参数优化以相同的顺序执行,但仅针对拥有可训练参数的对象。该方法留待独立研究,集成对象及其所有方法的完整代码可在附件中找到。

至此多任务-Stockformer 框架算法实现的讨论完毕。下一步是将实现的方式集成到可训练模型的架构当中。


模型架构

上面实现的多任务-Stockformer 框架的方式现在应用在环境状态编码器模型。归因于运用全面多任务-Stockformer 实现的对象,模型架构仍然非常紧凑 — 它仅由三层组成。如常,我们从输入数据和批量归一化层开始。

bool CreateEncoderDescriptions(CArrayObj *&encoder)
  {
//---
   CLayerDescription *descr;
//---
   if(!encoder)
     {
      encoder = new CArrayObj();
      if(!encoder)
         return false;
     }
//--- Encoder
   encoder.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

这些层初步处理自环境接收的生料输入数据。紧随其后的是一个新层,实现了多任务-Stockformer 框架方式。

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMultitaskStockformer;
//--- Windows
     {
      int temp[] = {BarDescr, 10, LatentCount}; //Window, Filters, Output
      if(ArrayCopy(descr.windows, temp) < int(temp.Size()))
         return false;
     }
   descr.count = HistoryBars;
   descr.window_out = 32;
   descr.step = 4;                              // Heads
   descr.layers = 3;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//---
   return true;
  }

在我们的实验中,用到了 10 个小波滤波器。每个注意力模块采用 4 个头,并包含 3 个内层。

环境状态编码器的输出由两个模型共用:参与者(制定交易决策),即评论者(评估参与者生成的动作)。这些采用的模型架构来自我们之前的研究,以及环境交互和训练程序。本文中用到的完整模型架构,和完整程序代码可在附件中找到。我们现在进入最后阶段 — 依据真实历史数据测试已实现方案的有效性。


测试

覆盖两篇文章过程,我们进行了大量工作,利用 MQL5 实现了多任务-Stockformer 框架作者提议的方式。现在是时候进入最激动人心的阶段 — 依据真实历史数据测试所实现方案的有效性。

重点要澄清,我们正在评估已实现方式,而非原始的多任务-Stockformer 框架,在于实现期间引入了一些修改。

在测试期间,这些模型依据 EURUSD 的 2023 全年 H1 时间帧的历史数据进行了训练。所有分析指标均以其默认参数设置。

对于初始训练阶段,我们使用了之前研究中收集的数据集。该数据集会定期更新,以便适应演化的参与者政策。经过若干次训练和数据集更新周期后,据其产生的政策在训练集和测试集上都展现出盈利能力。

训练有素的政策测试是针对 2024 年 1 月的历史数据进行的,所有其它参数未变。结果呈现如下。

在测试期间,该模型执行了 19 笔交易,其中 10 笔以盈利了结。这略高于 50%。然而,由于相比亏损仓位,每笔获胜交易的平均利润更高,该模型以整体盈利结束了测试期,实现了 1.45 的盈利系数。

一个有趣的观察来自交易时间图表。近一半的交易是在美国交易时段开仓的,而该模型在波动性最高时段几乎未执行任何交易。


结束语

我们探索了多任务-Stockformer 框架 — 这是一个创新的股票选择模型,结合了离散小波变换、与多任务自注意力模块。这种综合方式能够识别市场数据中的时态和频率特征,从而可以准确为所分析因素之间的复杂相互作用建模。

在实践章节,我们利用 MQL5 开发了框架方式的自我实现。我们将这些方法集成到模型架构当中,并据真实历史数据训练了这些模型。然后在 MetaTrader 5 策略测试器中测试训练好的模型。我们的实验结果展示了所实现方案的潜力。然而,在将它们应用于实盘交易之前,应当在更具代表性的数据集上训练模型,并进行全面测试。


参考


文章中所用程序

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

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

附加的文件 |
MQL5.zip (2279.09 KB)
开发先进的 ICT 交易系统:在订单块指标中实现信号 开发先进的 ICT 交易系统:在订单块指标中实现信号
在本文中,您将学习如何基于订单簿交易量(市场深度)开发订单块(Order Blocks)指标,并使用缓冲区对其进行优化以提高准确性。这结束了项目的当前阶段,并为下一阶段做准备,下一阶段将包括实施风险管理类和使用指标生成的信号的交易机器人。
MQL5中交易策略的自动化实现(第六部分):掌握智能资金交易中的订单块(Order Block)检测技巧 MQL5中交易策略的自动化实现(第六部分):掌握智能资金交易中的订单块(Order Block)检测技巧
在本文中,我们将运用纯粹的价格行为分析方法,在MQL5平台上实现订单块的自动化检测。我们将界定订单块的定义,实现其检测功能,并集成自动化交易执行系统。最后,我们通过回测来评估该策略的表现。
分析交易所价格的二进制代码(第二部分):转换为 BIP39 并编写 GPT 模型 分析交易所价格的二进制代码(第二部分):转换为 BIP39 并编写 GPT 模型
继续尝试破译价格走势……我们将通过将二进制价格代码转换为 BIP39 来获得一个“市场词典”,那么,对这个词典进行语言学分析又如何呢?在本文中,我们将深入探讨一种创新的交易所数据分析方法,并研究如何将现代自然语言处理技术应用于市场语言。
价格行为分析工具包开发(第12部分):外部资金流(3)趋势图谱(TrendMap) 价格行为分析工具包开发(第12部分):外部资金流(3)趋势图谱(TrendMap)
市场走势由多头与空头之间的力量博弈所决定。由于作用在这些水平上的力量,市场会尊重某些特定价位水平。斐波那契(Fibonacci)水平和成交量加权平均价(VWAP)水平在影响市场行为方面尤为强大。请随我一同探讨本文中基于VWAP和斐波那契水平生成交易信号的策略。