English Русский Español Deutsch 日本語 Português
preview
神经网络在交易中的应用:混合图序列模型(终篇)

神经网络在交易中的应用:混合图序列模型(终篇)

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

概述

在上一篇文章中,我们探讨了统一 GSM+ 图序列框架的理论方面,这是一种创新的数据处理方法,结合了多种架构解决方案的优点。GSM++ 包括三个关键阶段:图标记化本地节点编码全局依赖编码。这种结构提高了处理图结构数据的效率,增强了在金融及其他涉及时间序列和结构化数据分析的领域中执行复杂任务的分析能力。

该系统的一个关键要素是层次图标记化,它能够将复杂数据转换为紧凑的顺序表示形式。GSM++ 中使用的标记化方法保留了原始数据的拓扑和时间特征,显著提高了特征提取精度。此外,这种方法在分析大型数据集时有助于降低计算成本,在处理速度和分析深度之间实现了平衡。根据任务的不同,可以调整分析的详细程度,从而使该方法既通用又灵活。

传统分析方法往往会遇到信息冗余的问题,这会增加计算负荷,并使模式检测变得复杂。然而,通过使用自适应编码机制,可以提取最重要的节点特征,并将其高效传输到后续分析层。这减少了无关信息的数量,并增强了模型识别节点之间局部关系的能力。局部编码的另一个优点是它能动态适应源数据的变化,这一点在瞬息万变的金融市场中尤为重要,因为市场突变会严重影响预测的准确性。

通过使用结合了循环模型和 Transformer 的混合编码器,分析能力得到了进一步增强。这种方法结合了两种方法的优点:循环机制通过捕捉事件序列来高效处理时间序列,而带有自注意力机制的 Transformer 则能有效识别复杂的、与顺序无关的依赖关系。这种组合不仅提高了模型的准确性,还增强了其对市场动态的鲁棒性。此外,混合编码器能够适应各种场景,可根据特定任务需求在预测准确性和计算效率之间实现可配置的平衡。

GSM++

在上一篇文章的实践部分,我们开始使用 MQL5 来实现我们对 GSM++ 框架的解读。考虑到金融数据的高波动性,我们决定不采用框架作者提出的基于层次相似性的聚类方法(HAC)。相反,我们选择了一个可训练的混合标记化模块,该模块在处理真实市场数据时显著提高了模型的灵活性和适应性。

所实现的混合标记化MoT )算法在每个柱上采用四种不同的标记类型,从而能够进行更详细的市场数据分析。在本实验中,我们使用以下方法对源数据进行编码:

  • 节点标记化 — 每个柱都被视为一个单独的分析元素,从而可以评估其各自的特征并识别影响后续发展的关键参数。
  • 边标记化 — 分析相邻柱之间的依赖关系,检测短期相关性和趋势,有助于预测近期变化。
  • 子图标记化 — 检查柱组,以识别对战略预测至关重要的更复杂结构和稳定模式。
  • 对单个单元序列进行子图标记化 — 能够深入分析单变量序列及其相互依赖性,这对于发现数据中隐藏的模式至关重要。

这些标记通过注意力池机制进行组合,该机制改编自 R-MAT 框架。这种方法使模型能够专注于最重要的特征,同时丢弃相关性较低的数据,从而显著提升决策效率。注意力池化的主要优点在于其能够高效处理复杂数据结构,在强调最相关特征的同时,最大限度地减少噪声影响。

为了实现这一方法,我们创建了 CNeuronMoT 对象,继承了 CNeuronMHAttentionPooling 的基本功能,从而确保了注意力池化算法的高效应用。这种模块化设计增强了模型的适应性,改进了市场数据处理,提高了价格走势分析和预测的质量,使其成为算法交易的宝贵工具。

数据处理的下一阶段是局部节点编码。在我们的实现中,我们使用了之前开发的一个自适应特征平滑模块。节点自适应特征平滑(NAFS)通过同时考虑图结构和单个节点的特性,生成了更具信息量的节点嵌入。这种方法假设不同的节点可能需要不同的平滑级别。这使得每个节点都能在其邻域环境中进行自适应处理。NAFS 采用低阶和高阶平滑相结合的方法,有效地捕捉图中的局部和全局依赖关系。

NAFS 采用集成特征聚合方法。这种方法增强了模型对噪声的鲁棒性,提高了编码可靠性。NAFS 模块的主要优势包括:

  • 灵活的数据过滤,在剔除噪声的同时突出最重要的特征。
  • 计算成本的优化,在分析大型图和高频市场数据时至关重要。
  • 通过动态调整平滑参数来适应不断变化的环境条件
  • 通过精细的分析和强大的泛化能力相结合,提高了模型精度

GSM++ 的最后一个核心组件是混合编码器。框架作者建议将 Mamba 模块与 Transformer 结合起来。在我们的实现中,我们遵循了这一理念。但我们进一步改进了它,将 Mamba 替换为 Chimera ,将 Transformer 替换为 Hidformer

Chimera 采用二维状态空间模型 ( 2D-SSM ),有效地对时间轴和与图拓扑相关的附加维度上的依赖关系进行建模。这种方法显著增强了分析复杂市场关系的能力。Chimera 的优势包括:

  • 二维依赖编码,有助于增强对隐藏市场模式的检测和预测准确性。
  • 模型表现力增强,能够更深入地分析复杂的非线性资产关系。
  • 对动态市场变化的适应性,使模型能够快速响应不断变化的条件。

Hidformer 采用双流架构,与经典的 Transformer 不同,它将输入数据的处理分为两条路径:一个编码器分析时间依赖性,而另一个编码器处理市场数据的频率成分。这种设计能够更准确地模拟市场动态。Hidformer 的主要优势在于:

  • 时频分析分离,提高了市场趋势预测的精度。
  • 在时间编码器中使用递归注意力,在频率编码器中使用线性注意力,从而降低计算复杂度并提高效率。

因此,将 ChimeraHidformer 集成到 GSM++ 中,有可能实现高依赖性编码精度,最大限度地减少市场噪声的影响,并提高分析预测的可靠性。


SSM 模块调整

值得注意的是,在使用 Chimera 框架构建的模型进行测试时,我们观察到持仓持续时间延长。当时,有假设认为该模型仅捕捉了长期趋势,却忽视了短期波动。为了解决这一局限性,我们决定对之前实现的对象进行小幅修改,并增加一个额外的内部二维状态空间模型。更新后的算法在 CNeuronChimeraPlus 对象中实现,其结构概述如下。

class CNeuronChimeraPlus    :  public CNeuronChimera
  {
protected:
   CNeuron2DSSMOCL    cSSMPlus;
   CLayer             cDiscretizationPlus;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   //---
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronChimeraPlus(void) {};
                    ~CNeuronChimeraPlus(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window_in, uint window_out, uint units_in, uint units_out,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override  const   {  return defNeuronChimeraPlus; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau);
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      Clear(void) override;
  };

从新对象的结构可以看出,我们并没有完全重写之前创建的 CNeuronChimera 对象。相反,它被用作父类,使我们能够继承所有先前实现的功能。然而,添加第三个 2D-SSM 模块以及相应的数据投影块需要覆盖通常的虚拟方法集。新声明的对象和继承的对象都在 Init 方法中进行初始化,其参数结构完全继承自父类的相应方法。

bool CNeuronChimeraPlus::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                              uint window_in, uint window_out, uint units_in, uint units_out,
                              ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronChimera::Init(numOutputs, myIndex, open_cl, window_in, window_out, units_in,
                                                      units_out, optimization_type, batch))
      return false;

在方法主体中,我们首先调用父类的相应方法,该方法已经实现了参数验证和继承组件初始化的机制。

在成功执行父类方法操作后,我们继续初始化新声明的对象,这些对象提供了模型的扩展功能。本阶段添加的关键组件之一是额外的二维状态空间( 2D-SSM )模块。

值得注意的是,父类方法已经初始化了两个 2D-SSM 模块,每个模块执行特定的任务。一个模块在指定的结果维度内运行,提供空间依赖性的标准编码,而第二个模块则使用扩展的特征空间,能够捕捉分析元素之间更复杂和多层次的关系。

为了提高模型的泛化能力和提高市场数据处理的准确性,新增的 2D-SSM 模块与现有模块的不同之处在于,它在指定的特征空间内运行,但沿时间维度进行了扩展投影。这种架构能够更精确地分析时间序列和空间分布的市场数据。

   int index = 0;
   if(!cSSMPlus.Init(0, index, OpenCL, window_in, window_out, units_in, 2 * units_out, optimization, iBatch))
      return false;

接下来,需要将结果投影到指定的子空间中。在此,我们需注意,我们无法立即沿时间维度进行投影。因此,我们需要构建一个小的内部对象序列。

我们首先准备一个动态数组和一些局部变量,用于存储指向正在创建的对象的指针。

   CNeuronTransposeOCL *transp = NULL;
   CNeuronConvOCL      *conv = NULL;
   cDiscretizationPlus.Clear();
   cDiscretizationPlus.SetOpenCL(OpenCL);

首先,我们创建一个数据转置对象,该对象允许我们将数据转换为所需的格式。

   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, 2 * units_out, window_out, optimization, iBatch) ||
      !cDiscretizationPlus.Add(transp))
     {
      delete transp;
      return false;
     }

接下来,我们沿着指定的时间维度添加一个卷积数据投影层。

   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, 2 * units_out, 2 * units_out, units_out, window_out, 1, optimization, iBatch) ||
      !cDiscretizationPlus.Add(conv))
     {
      delete conv;
      return false;
     }
   conv.SetActivationFunction(None);

之后,使用另一个转置对象将数据恢复为其原始表示形式。

   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, window_out, units_out, optimization, iBatch) ||
      !cDiscretizationPlus.Add(transp))
     {
      delete transp;
      return false;
     }
   transp.SetActivationFunction((ENUM_ACTIVATION)conv.Activation());
//---
   return true;
  }

至此,内部对象的初始化完成,该方法最后向调用程序返回一个逻辑结果。

接下来,我们需要在 feedForward 方法中实现前馈传递算法。需要注意的是,相应父类方法的输出包含了数据归一化处理。如您所知,此操作会改变数据分布。将这些归一化值与相加信息流的非归一化输出相加,可能会导致对某一路径产生不可预测的偏向。为了防止这种情况发生,我们完全重写了 feedForward 方法。

bool CNeuronChimeraPlus::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   for(uint i = 0; i < caSSM.Size(); i++)
     {
      if(!caSSM[i].FeedForward(NeuronOCL))
         return false;
     }
   if(!cSSMPlus.FeedForward(NeuronOCL))
      return false;

该方法接收一个指向输入数据对象的指针,该指针会立即传递给内部状态空间模型中同名的方法。这些模型会生成三种不同投影下的结果。

接下来,我们使用内部离散化对象,将状态空间模型的输出转换为可比形式。

   if(!cDiscretization.FeedForward(caSSM[1].AsObject()))
      return false;
   CNeuronBaseOCL *inp = NeuronOCL;
   CNeuronBaseOCL *current = NULL;
   for(int i = 0; i < cDiscretizationPlus.Total(); i++)
     {
      current = cDiscretizationPlus[i];
      if(!current ||
         !current.FeedForward(inp))
         return false;
      inp = current;
     }

此外,我们还可以获得输入数据沿残差连接路径的投影。

   inp = NeuronOCL;
   for(int i = 0; i < cResidual.Total(); i++)
     {
      current = cResidual[i];
      if(!current ||
         !current.FeedForward(inp))
         return false;
      inp = current;
     }

最后,将这四个信息流相加。在这种情况下,值的归一化仅在最后阶段实现。

   inp = cDiscretizationPlus[-1];
   if(!SumAndNormilize(caSSM[0].getOutput(), cDiscretization.getOutput(), Output, 1, false, 0, 0, 0, 1) ||
      !SumAndNormilize(Output, inp.getOutput(), Output, 1, false, 0, 0, 0, 1) ||
      !SumAndNormilize(Output, current.getOutput(), Output, cDiscretization.GetFilters(), true, 0, 0, 0, 1))
      return false;
//---
   return true;
  }

我们将执行操作的逻辑结果返回给调用程序,并完成方法的执行。

我们工作的下一阶段是构建反向传播算法。它们通过两个方法实现: calcInputGradientsupdateInputWeights 。第一个处理的是参与对象之间的误差梯度分布。第二个调整模型的可训练参数。在这个阶段,与 feedforward 方法不同,我们可以使用父类的功能。

梯度分布方法接收一个指向同一输入数据对象的指针,该对象现在必须填充反映输入数据对模型输出影响的误差梯度值。

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

与标准方法不同,该指针的有效性不会被检查;它会直接传递给同名父类方法。它已经实现了控制点和将误差梯度分布到三个继承的信息流(两个 2D-SSM 和残差连接路径)的算法。

接下来,梯度仅通过新增的路径传播。首先,通过所添加的离散化模型中最后一层的激活函数的导数,来调整从后续对象接收到的误差梯度。

   CNeuronBaseOCL *current = cDiscretizationPlus[-1];
   if(!current ||
      !DeActivation(current.getOutput(), current.getGradient(), Gradient, current.Activation()))
      return false;

然后将调整后的值反向传递给离散化模块。为此,我们反向遍历元素,并依次调用相关对象的相应方法。

   for(int i = cDiscretizationPlus.Total() - 2; i >= 0; i--)
     {
      current = cDiscretizationPlus[i];
      if(!current ||
         !current.calcHiddenGradients(cDiscretizationPlus[i + 1]))
         return false;
     }

然后,误差梯度通过二维状态空间模型进行传播。

   if(!cSSMPlus.calcHiddenGradients(current.AsObject()))
      return false;

最后,将梯度恢复到输入数据的水平。输入数据对象的缓冲区已包含通过三个继承的信息流传播的梯度。因此,为了保存数据,暂时替换了指向误差梯度缓冲区的指针,以保存新计算的值。

   current = cResidual[0];
   CBufferFloat *temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(current.getGradient(), false) ||
      !NeuronOCL.calcHiddenGradients(cSSMPlus.AsObject()) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

接下来,我们将误差梯度从二维状态空间模型传播到输入数据层面。然后,我们将所得结果的值与之前累积的值相加。

我们将数据缓冲区的指针恢复至其原始状态。

至此,对增强型 Chimera 模块算法已说明完毕。CNeuronChimeraPlus 类及其所有方法的完整代码可以在附件中找到。


构建混合解码器

在构建升级版 Chimera 模块之后,我们将继续开发混合编码器。如前所述,在我们的实现中,它包含了 Chimera 模块和 Hidformer 块。Hidformer 对象接收有关分析系统状态的数据作为输入,并生成 Agent 动作张量作为输出。在这种情况下,将我们的新对象称为混合解码器可能更为准确。对象结构如下所示。

class CNeuronHypridDecoder :  public CNeuronHidformer
  {
protected:
   CNeuronChimeraPlus   cChimera;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronHypridDecoder(void){};
                    ~CNeuronHypridDecoder(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 defNeuronHypridDecoder; }
   //---
   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;
  };

该结构只声明了一个内部对象 — 修改后的 Chimera 模块,其算法已在上面描述过。CNeuronHidformer 对象用作父类,避免了功能的冗余重复,并允许高效地重用已实现的方法和结构,而无需在对象内部显式地创建额外的实例。然而,我们需要重写通常的虚方法集。

内部对象是静态声明的,因此新类的构造函数和析构函数都保持为空。这些已声明和已继承对象的初始化是在 Init 方法中执行的。

bool CNeuronHypridDecoder::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)
  {
   if(!CNeuronHidformer::Init(numOutputs, myIndex, open_cl, window_key, window_key, nactions,
                             heads, layers, stack_size, nactions, optimization_type, batch))
      return false;

方法参数包含一组常量,这些常量唯一地定义了所创建对象的架构。需要注意的是,参数结构完全继承自父类中同名的方法。但是,当调用父类方法时,接收到的值并没有以相同的形式传递。这是因为父类的 feedforward 方法旨在接收的不是来自外部程序的原始数据,而是内部 Chimera 模块的输出。因此,在初始化继承的父类对象时,内部模块的结果维度被指定为输入数据。在这里,特征维度被设置为内部状态向量的值。序列长度与 Agent 的动作空间相匹配。换句话说, Chimera 模块输出一个潜在状态张量,其中每一行代表一个标记,对应于 Agent 动作的一个单独元素。

成功执行父类方法后,调用 Chimera 模块中同名的方法,指定输入数据维度和所需结果张量维度。

   if(!cChimera.Init(0, 0, OpenCL, window, window_key, units_count, nactions, optimization, iBatch))
      return false;
//---
   return true;
  }

然后该方法返回逻辑执行结果,完成操作。

您可能已经注意到,对象初始化方法的算法非常简单。该类的其他方法也保持了类似的简洁结构。例如, feedForward 方法接收指向输入数据对象的指针,该指针立即传递给同名的 Chimera 模块方法。

bool CNeuronHypridDecoder::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cChimera.FeedForward(NeuronOCL))
      return false;
   return CNeuronHidformer::feedForward(cChimera.AsObject());
  }

然后,将结果传递给同名的父类方法。逻辑结果返回给调用程序。然后我们完成该方法。

此类中的其余方法可以单独查看。完整的代码请见附件。


模型架构

完成 GSM++ 框架各个组成部分的构建后,我们开始组装完整的模型架构。在这种情况下,我们训练一个单一的模型 — Actor 。架构描述在 CreateDescriptions 方法中提供。

bool CreateDescriptions(CArrayObj *&actor)
  {
//---
   CLayerDescription *descr;
//---
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         return false;
     }

该方法接收一个指向动态数组的指针,该数组用于存储描述模型架构的对象序列。我们立即验证指针的有效性。如有必要,我们会创建一个新的对象实例。

接下来,我们为输入数据层创建描述。像往常一样,使用了一个足够大的全连接层。

//--- Actor
   actor.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(!actor.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(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

接下来是混合标记化模块。

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMoT;
   descr.window = BarDescr;
   descr.count = HistoryBars;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

再接下来,使用 S3 模块来学习最优的标记排列方法。它根据元素在整体数据结构中的相互依赖性和重要性,确定最佳的元素顺序。

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronS3;
   descr.count = HistoryBars;
   descr.window = BarDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

处理后的数据随后被传递给由 NAFS 模块实现的本地节点编码器。

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronNAFS;
   descr.count = HistoryBars;
   descr.window = BarDescr;
   descr.window_out = BarDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Agent 动作张量由混合解码器模块生成,其算法如上所述。

//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronHypridDecoder;
//--- 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;                                  // Heads
   descr.layers = 3;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

值得注意的是,我们为 Agent 开发的架构仅专注于分析环境状态。然而,这不足以进行全面的风险评估,因为该模型没有考虑可用资产及其对决策的影响。

为了解决这一问题,该架构借鉴了之前考虑过的模型,增加了一个风险管理模块。

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMacroHFTvsRiskManager;
//--- Windows
     {
      int temp[] = {3, 15, NActions, AccountDescr}; //Window, Stack Size, N Actions, Account Description
      if(ArrayCopy(descr.windows, temp) < int(temp.Size()))
         return false;
     }
   descr.count = 10;
   descr.window_out = 16;
   descr.step = 4;                              // Heads
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = NActions / 3;
   descr.window = 3;
   descr.step = 3;
   descr.window_out = 3;
   descr.activation = SIGMOID;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//---
   return true;
  }

在创建架构描述后,该方法向调用程序返回一个逻辑执行结果。

架构描述方法的完整代码在附件中提供。附件中还包含从先前工作中复制的训练和测试脚本。它们可供单独查看。 


测试

我们已经完成了大量工作,实现了我们对 GSM 框架作者提出的方法的理解。我们现在已经到了关键阶段 — 利用真实的历史数据来评估已实现解决方案的有效性。

值得注意的是, Agent 的最后神经层与我们 Hidformer 框架实现中使用的架构非常相似。采用了相同的风险管理模块结构,并在混合解码器输出端使用了 CNeuronHidformer 对象。由于架构上的相似性,将新模型的性能与 Hidformer 框架的性能进行比较是合理的。

为了公平比较,这两个模型都使用之前用于 Hidformer 训练的同一数据集进行训练。请记住:

  • 训练集包含 2024 年全年 EURUSD M1 历史数据。
  • 所有分析的指标参数均保持默认值,未进行额外优化,消除了外部因素的影响。
  • 使用 2025 年 1 月的历史数据对训练好的模型进行了测试,同时保持所有其他参数不变,以确保对比的客观性。

测试结果如下所示。

在测试期间,该模型执行了 15 笔交易,对于 M1 时间周期上的高频交易而言,这相对较低。这个数值甚至低于基准 Hidformer模 型所达到的数值。只有 7 笔交易盈利,盈利率为 46.67%,这也低于基准值 62.07%。在这里,我们观察到空头头寸的准确性有所降低。然而,亏损的规模略有下降,同时盈利的规模相对增加。

如果基准模型的平均盈利交易与亏损交易之比为 1.6,那么在新模型中,这一比率超过 4。这使得测试期间的总利润几乎翻了一番,盈利因子也相应提高。这表明,新架构优先考虑损失最小化和利润最大化,以实现成功的交易。这可能会带来更为稳定的长期财务业绩。然而,由于测试期较短且交易次数较少,无法就模型的长期表现得出结论。


结论

我们探索了 GSM++ 统一图序列处理框架,该框架结合了先进的市场数据分析方法。它的主要优势在于混合数据表示,结合了层次标记化、局部节点编码和全局依赖编码。这种多层次的方法能够高效地提取出重要模式,并形成信息量极大的嵌入表示,这对于预测金融时间序列至关重要。

在这项工作的实践部分,我们使用 MQL5 实现了我们对所提出方法的解释。需要指出的是,我们的实现与作者最初的方法存在实质性的差异。因此,所有测试结果仅适用于已实现的解决方案。

训练后的模型表现出在样本外数据上产生利润的能力。虽然数量还没有达到我们希望看到的水平。这表明了所实施方法的潜力,但仍需进一步工作,包括在更具代表性的数据集上进行训练、全面测试以及优化分析指标及其参数。该模型识别训练数据中的模式,而非创建模式。


参考


本文中用到的程序

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

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

附加的文件 |
MQL5.zip (2482.85 KB)
基于马尔可夫链的矩阵预测模型 基于马尔可夫链的矩阵预测模型
我们将创建一个基于马尔可夫链的矩阵预测模型。什么是马尔可夫链?我们如何将马尔可夫链应用于外汇交易?
在MQL5交易中集成计算机视觉(第二部分):将架构扩展到2D RGB图像分析 在MQL5交易中集成计算机视觉(第二部分):将架构扩展到2D RGB图像分析
面向交易的计算机视觉:工作原理与分步开发指南。我们基于注意力机制与双向LSTM层,构建价格图表RGB图像识别算法。最终得到一套可用的欧元兑美元(EURUSD)价格预测模型,在验证阶段,模型预测准确率最高可达55%。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
在MQL5中实现盈亏平衡机制(第一部分):基类与固定点数的盈亏平衡模式 在MQL5中实现盈亏平衡机制(第一部分):基类与固定点数的盈亏平衡模式
本文将探讨如何使用MQL5语言,在自动化交易策略中应用盈亏平衡机制。我们会先简要介绍什么是盈亏平衡模式、其实现方式以及可能存在的不同类型。随后,该功能将被集成到我们在上一篇关于风险管理的文章中所构建的Order Blocks智能交易系统(EA)中。为评估盈亏平衡机制的效果,我们会在特定条件下进行两组回测:一组启用盈亏平衡机制,另一组则不启用。