交易中的神经网络:针对金融市场的多模态、扩增工具型智代(终篇)
概述
在上一篇文章中,我们开始探索 FinAgent 框架 — 这是一款设计用于金融市场数据分析和决策支持的高级工具。其开发专注于创建一个高效的机制,来构建交易策略,并把风险降至最小,从而应对复杂且快速变化的市场环境。FinAgent 的架构由五个互联模块组成,每个模块执行专门功能,从而确保系统的整体可适应性。
市场分析模块负责从多种来源提取和处理数据,包括价格图表、市场新闻、和报告。在该模块内,系统辨别可用于预测价格动态的稳定形态。
反射模块在模型的适应和学习过程中扮演着关键角色。低层反射模块分析当前市场信号之间的相互依赖性,提升短期预测的准确性。对比之下,高层反射模块则与长期趋势打交道 — 协同历史数据和以往交易决策结果 — 基于积累经验调整策略。
记忆模块为大体量市场数据提供长期存储。利用现代向量相似性技术,它把噪声降至最小,并强化信息检索的准确性,这对于开发长期策略和揭示复杂关系性尤为重要。
系统的核心是决策制定模块,其整合所有来自其它组件的结果。基于当前和历史数据,它生成最优交易建议。甚至,经由整合专家知识与传统指标,该模块具备生成平衡且依据充分建议的能力。
下面提供了 FinAgent 框架的原始可视化。

在上一篇文章中,我们开始实现 FinAgent 框架作者所提议方式的 MQl5 版本。我们引入了低层和高层反射模块算法,它们作为 CNeuronLowLevelReflection 和 CNeuronHighLevelReflection 的两个对象实现。这些模块分析市场信号、交易决策历史、以及实际达成的财务结果,允许智代随着市场条件的变化调整其行为政策。它们还能灵活响应动态趋势转移,帮助识别数据内的关键形态。
在我们的实现中,一个显著特点是将记忆模块直接集成到反射对象当中。该方式不同于原始框架的架构,后者将所有信息流的记忆作为独立模块实现。通过将记忆嵌入反射组件本身,我们简化了构造数据流和框架不同元素间的交互。
继续这项工作,我们将试验若干个关键模块的实现,每个都在整体系统架构中扮演独特角色:
- 市场分析模块设计用于处理多源数据,包括财务报告、新闻推送、和股票报价。它将多模态数据整合为统一格式,提取稳定的形态,能够用来预测未来市场动态。
- 辅助工具基于先验知识,遍历历史形态、统计数据、和专家评估,为分析和决策制定提供支持。它们还为系统决策提供了逻辑可解释性。
- 决策支持系统,整合所有模块的结果,生成自适应、且最优的交易策略。该系统提供实时动作建议,令交易者和分析师能够迅速响应市场条件变化,并制定更明智的决策。
市场分析模块在系统中扮演核心角色,负责数据的预处理和统筹。这一步对于揭示传统数据分析方法难以检测的隐藏形态尤为重要。FinAgent 的作者利用大语言模型(LLMs)来提取数据的关键层面,并执行维度压缩。然而,在我们的实现中,我们选择不使用大语言模型,而是专注于分析时间序列的专用模型,这些模型提供了更高的精度和性能。在本系列文章中,我们已呈现了若干个分析和预测多变量时间序列的框架。这些都能应用于此。出于本文目的,我们选择了一个具有分段注意力的变换器模型,并在 CNeuronPSformer 类中实现。
话虽如此,这绝非意味着唯一方案。事实上,FinAgent 框架支持多模态输入数据。这不仅令我们能够尝试不同的时间序列表示和分析算法,还能把它们组合起来。该方式显著扩展了系统能力,对市场流程也有更详细的理解,并为开发高效且适应性强的交易策略做出贡献。
辅助工具模块将分析环境的有关先验知识整合进整体模型架构当中。该组件基于经典指标生成分析信号,譬如移动平均线、振荡器、和基于交易量的指标。它们在算法交易中的有效性,长期以来已得到证明。然而,该模块不局限于标准工具。
进而,基于技术指标读数遵照明确定义的规则生成信号,提升模型决策的可解释性,提升其可靠性和有效性。这是策略规划和风险管理的关键因素。
辅助工具模块
基于神经模型内经典指标输出,开发信号生成模块,比最初看起来要复杂得多。主要难点不在于解读信号,而在于评估投喂到模型输入中的量值。
在传统策略中,信号描述直接依赖于指标的实际读数。然而,这些数值往往属于完全无关、且不可比较的分布,这给模型构造带来了重大挑战。这个因素极大地降低了训练效率,在于算法必须适配正分析的异构数据。结果就是更长的处理时间,预测准确性降低,以及其它不利影响。出于该原因,我们之前决定在模型中仅用归一化数据。
归一化过程允许所分析特征全部被扩展到一个共同且可比的范围,从而显著提升模型训练品质。该方式能够将测量单位差异或时间变异性引起的失真风险最小化。归一化的一个重要优势是它能够更深度地分析数据,因为在这种形式下,输入对机器学习算法来说变得更加可预测和易于管理。
然而,应当注意的是,归一化会显著令经典策略中的信号生成复杂化。这些策略最初设计搭配原始数据工作,并假设解释指标时阈值固定。在归一化期间,数据会被变换,导致阈值水平出现未定义的偏移。甚至,归一化令基于经典指标中两条线交叉的信号无法生成,因为无法保证两条线会同步偏移。如是结果,产生的信号会失真,甚至或许完全不出现。这也促使我们有必要开发新的指标输出解读方式。
我相信,于此,我们找到了一个简单,但概念顺耳的解决方案。其本质依赖这样一个事实,在归一化期间,所有分析的特征都被转换为零均值和单位方差。如是结果,每个变量都变得可彼此比较,如此可被解释为一种振荡器。这提供了通用信号解读制程:大于 0 的数值被视为买入信号,低于 0 的数值被视为卖出信号。也可以引入阈值级别,创建“走廊”,从而过滤微弱或模糊信号。这令误报最小化,提高了分析准确性,并支持依据更充分的决策制定。
我们还考虑到某些特征出现逆转信号的可能性。这个问题能够经由可训练参数来解决,其适配历史数据。
应用该方式为构建有效适应变化条件,并生成更准确、更可靠的信号的模型奠定了基础。
为了实现这种信号生成方法,我们首先在 OpenCL 端打造 MoreLessEqual 内核。在这种情况下,采用了固定阈值的简单算法。
内核参数包括指向两个大小相等的数据缓冲区的指针。一个存储输入数据,另一个存储生成的信号,由三个数值之一表示:
- -1 — 卖出
- 0 — 无信号
- 1 — 买入
__kernel void MoreLessEqual(__global const float * input, __global float * output) { const size_t i = get_global_id(0); const float value = IsNaNOrInf(input[i], 0); float result = 0;
在内核主体中,我们辨别当前操作线程,并立即将相应的输入值读入局部变量。强制步骤是验证输入:任何无效数据会自动替换为 0,以防止后续处理过程中出现错误。
然后引入一个局部变量来存储中间结果。最初该变量被赋予一个表示信号缺失的数值。
接下来,我们检查所分析变量的绝对值。为了生成信号,该值必须超过指定的阈值。
if(fabs(value) > 1.2e-7) { if(value > 0) result = 1; else result = -1; } output[i] = result; }
高于阈值的正值表示买入信号,低于阈值的负值表示卖出信号。对应的标志存储在局部变量当中。在内核结束之前,这个标志会写入结果缓冲区。
上述算法是一种顺序前向通验过程,数据处理时不包含任何可训练参数。该方法依赖严格确定性计算,旨在计算成本最小化,并避免不必要的复杂性 — 这在处理大量信息时尤为重要。还值得注意的是,该数据流中未应用误差梯度传播,因为我们的目标是识别从指标读数推导出的稳定信号,而非将它们“拟合”到目标输出。这令该算法对于要求高速和高精度处理的系统,尤具吸引力。
一旦算法在 OpenCL 端实现,我们必须从主程序中组织管理和调用内核。为了实现该功能,我们创建一个新的对象 CNeuronMoreLessEqual,如下所示。
class CNeuronMoreLessEqual : public CNeuronBaseOCL { protected: virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override {return true; } public: CNeuronMoreLessEqual(void) {}; ~CNeuronMoreLessEqual(void) {}; };
这个新对象的结构非常简单。它甚至不包含初始化方法。父类几乎包办所有功能。我们仅覆盖前馈和反向传播方法。
在前馈通验中,数据缓冲区的指针传递给前述内核的参数,然后排队执行。
bool CNeuronMoreLessEqual::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!OpenCL || !NeuronOCL) return false; uint global_work_offset[1] = { 0 }; uint global_work_size[1] = { Neurons() }; ResetLastError(); const int kernel = def_k_MoreLessEqual; if(!OpenCL.SetArgumentBuffer(kernel, def_k_mle_inputs, NeuronOCL.getOutputIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } if(!OpenCL.SetArgumentBuffer(kernel, def_k_mle_outputs, getOutputIndex())) { printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__, GetLastError(), __LINE__); return false; } //--- if(!OpenCL.Execute(kernel, 1, global_work_offset, global_work_size)) { printf("Error of execution kernel %s: %d; line %d", OpenCL.GetKernelName(kernel), GetLastError(), __LINE__); return false; } //--- return true; }
初看,反向传播方法的功能看似不够明确,因之前提到缺乏可训练参数和梯度传播。然而,重点要注意,在神经网络架构中,这些方法是所有层的必备。否则,会调用对应的父类方法,这在我们的特殊架构中或有不正确行为。为避免此类问题,参数更新方法被覆盖为存根,即简单地返回 true。
至于梯度传播的省略,逻辑上这等同于传递零值。因此,在梯度分派方法中,我们简单地将源数据对象中的对应缓冲区重置为零,确保模型正常运行,并将运行时出错的风险最小化。
bool CNeuronMoreLessEqual::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL || !NeuronOCL.getGradient()) return false; return NeuronOCL.getGradient().Fill(0); }
我们就辅助工具模块的工作至此完毕。CNeuronMoreLessEqual 类及其所有方法的完整代码已在附件中提供。
在该阶段,我们已几乎涵盖了 FinAgent 框架的所有关键模块。剩下的部分是决策制定模块,它是整体架构的核心元素。该模块确保从多条数据流中综合信息 — 通常超过两条。我们决定将决策模块直接集成到复合框架对象中,而非将其作为一个独立实体实现。这一设计选择提升了所有系统组件的协作度。
构建 FinAgent 框架
现在,是时候将所有先前创建的模块整合成一个统一、综合性的结构 — FinAgent 框架,确保它们的集成和协同互动。不同功能类型的模块结合起来,达成一个共同目标:创建一个高效且灵活的系统,分析复杂市场数据,并参考金融市场动态和特性开发策略。该功能由一个新的对象 CNeuronFinAgent 实现。其结构如下所示。
class CNeuronFinAgent : public CNeuronRelativeCrossAttention { protected: CNeuronTransposeOCL cTransposeState; CNeuronLowLevelReflection cLowLevelReflection[2]; CNeuronHighLevelReflection cHighLevelReflection; CNeuronMoreLessEqual cTools; CNeuronPSformer cMarketIntelligence; CNeuronMemory cMarketMemory; CNeuronRelativeCrossAttention cCrossLowLevel; CNeuronRelativeCrossAttention cMarketToLowLevel; CNeuronRelativeCrossAttention cMarketToTools; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override; public: CNeuronFinAgent(void) {}; ~CNeuronFinAgent(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint window_key, uint units_count, uint heads, uint account_descr, uint nactions, uint segments, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronFinAgent; } //--- 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; };
在该结构中,我们见到一套熟悉的可覆盖方法、和若干内部对象,其中可以轻松识别我们已在 FinAgent 框架内实现的模块。在我们实证该类方法的算法实现时,将讨论定义它们交互信息流的构造。
所有内部对象都声明为静态,允许我们保持类构造和解构函数为空。所有声明和继承对象的初始化,都在 Init 方法中执行。该方法的参数包括若干常量,定义了所创建对象的架构。
bool CNeuronFinAgent::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint window_key, uint units_count, uint heads, uint account_descr, uint nactions, uint segments, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronRelativeCrossAttention::Init(numOutputs, myIndex, open_cl, 3, window_key, nactions / 3, heads, window, units_count, optimization_type, batch)) return false;
稍微往前看,我们的决策制定模块将由若干顺序交叉注意力层组成。最后一个则经由父对象实现,而这并非巧合,它基于 CNeuronRelativeCrossAttention 类。
在我们实现的 FinAgent 框架输出处,我们期望获得矩阵形式的智代动作张量表式,其中每行是描述独立动作的向量。买卖操作由该矩阵的不同行表示。每个操作由三个参数描述:交易量、和两个价位 — 止损和止盈。由此,我们的动作矩阵将包含三列。
因此,当调用父类初始化方法时,我们指定主流水线上的数据分析窗口为 3,并指定所分析序列中的元素数量为描述智代动作参数的向量大小三倍。这种配置令模型能够依据次级信息流的背景评估每个动作的有效性,经由系统传输处理过的关于周围环境的信息。因此,我们传送相应的参数。
父类初始化过程成功执行后,我们继续准备新声明的内部对象。我们从初始化市场分析模块的各个组件开始。在我们的实现中,该模块由两个对象组成:其一检测多变量时间序列数据中稳定形态的分段注意力变换器,以及一个记忆模块。
int index = 0; if(!cMarketIntelligence.Init(0, index, OpenCL, window, units_count, segments, 0.2f, optimization, iBatch)) return false; index++; if(!cMarketMemory.Init(0, index, OpenCL, window, window_key, units_count, heads, optimization, iBatch)) return false;
为了达成对环境的全面分析,我们采用了两个低层反射模块,它们并行作用于不同投影中表示的输入数据张量。为了获得所分析数据的第二个投影,我们用到一个置换对象。
index++; if(!cTransposeState.Init(0, index, OpenCL, units_count, window, optimization, iBatch)) return false;
接下来,我们初始化两个低层反射对象。它们分析的数据来自不同投影,由所分析张量的序列长度维度和窗口交换来示意。
index++; if(!cLowLevelReflection[0].Init(0, index, OpenCL, window, window_key, units_count, heads, optimization, iBatch)) return false; index++; if(!cLowLevelReflection[1].Init(0, index, OpenCL, units_count, window_key, window, heads, optimization, iBatch)) return false;
第一种情况中,我们分析一个多元时间序列,其中每个时间步由一个数据向量表示,并比较这些向量来揭示它们之间的相互依赖关系。在第二种情况下,我们分析独立的单变量序列,检测其动态中的依赖性和规律性。
然后我们初始化高层反射模块,在市场变化和财务结果背景下分析智代的近期行为。
index++; if(!cHighLevelReflection.Init(0, index, OpenCL, window, window_key, units_count, heads, account_descr, nactions, optimization, iBatch)) return false;
在该阶段,我们还准备了辅助工具模块对象。
index++; if(!cTools.Init(0, index, OpenCL, window * units_count, optimization, iBatch)) return false; cTools.SetActivationFunction(None);
所有初始化模块的结果在决策制定模块中聚合,如早前所述,其由多个顺序的交叉注意力模块组成。第一阶段整合来自两个低层反射模块的信息。
index++; if(!cCrossLowLevel.Init(0, index, OpenCL, window, window_key, units_count, heads, units_count, window, optimization, iBatch)) return false;
接下来,我们取来自低层反射模块中衍生的信息,来丰富市场分析模块的输出。
index++; if(!cMarketToLowLevel.Init(0, index, OpenCL, window, window_key, units_count, heads, window, units_count, optimization, iBatch)) return false;
然后,我们会加一个先验知识层。
index++; if(!cMarketToTools.Init(0, index, OpenCL, window, window_key, units_count, heads, window, units_count, optimization, iBatch)) return false; //--- return true; }
决策制定模块的最后一层已被提前初始化。它由父对象表示。
所有嵌套对象成功初始化后,我们将操作的逻辑结果返回至调用程序,并结束该方法。
我们工作的下一阶段是在 feedForward 方法中构造 FinAgent 框架实现的前向通验算法。在方法参数中,我们接收两个指向输入数据对象的指针。方法参数包括指向两个输入数据对象的指针:第一个运送当前环境状态的信息,第二个代表账户状态和当前财务结果。
bool CNeuronFinAgent::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) { if(!cMarketIntelligence.FeedForward(NeuronOCL)) return false; if(!cMarketMemory.FeedForward(cMarketIntelligence.AsObject())) return false;
有关所分析市场环境的信息会在市场分析模块中进行初步处理,其中利用分段注意力变换器识别形态,并在记忆模块中检测其稳定组合。
两个预测中发现的形态随后传递给低反射模块,进行市场动态的全面分析。
if(!cTransposeState.FeedForward(cMarketIntelligence.AsObject())) return false; if(!cLowLevelReflection[0].FeedForward(cMarketIntelligence.AsObject())) return false; if(!cLowLevelReflection[1].FeedForward(cTransposeState.AsObject())) return false;
注意,低层反射模块唯据当前市场环境中检测到的形态,不用来自市场分析模块记忆模块的数据。该方式专注于市场对所发现形态的即时反应,从而在不依赖历史数据的情况下,更精确地评估当前的变化和趋势。
同样的逻辑也适用于高层反射模块。
if(!cHighLevelReflection.FeedForward(cMarketIntelligence.AsObject(), SecondInput)) return false;
提醒一下,高层反射模块的输入包括当前市场环境数据(来自市场分析模块的输出)和财务结果向量。智代之前动作的张量,会从高层反射模块结果缓冲区中递归调用。
然而,辅助工具模块直接处理原产输入数据,因其基于所分析指标读数中包含的先验信息来寻找信号。
if(!cTools.FeedForward(NeuronOCL)) return false;
接下来,我们转到组织决策制定模块的流程。最初,我们通过整合单变量序列动态中识别的依赖关系来丰富低层反射分析的结果。这强化了分析准确性,加深了模型对系统交互的理解,提供了更全面的市场环境评估。
if(!cCrossLowLevel.FeedForward(cLowLevelReflection[0].AsObject(), cLowLevelReflection[1].getOutput())) return false;
在接下来的阶段,我们将低层反射获得的信息整合到市场分析模块中由记忆模块产生的稳定形态表示之中。这一步细化并强化了所发现关系,从而对市场当前动态和互动有了更精准和全面的认识。
if(!cMarketToLowLevel.FeedForward(cMarketMemory.AsObject(), cCrossLowLevel.getOutput())) return false;
重点要强调,低层反射模块分析当前市场状态,识别市场对各个形态的反应。然而,这些形态中有些的出现频率或许较低,故其对应的市场反应在统计学上并不显要。在这种情况下,信息存储在低层反射模块的记忆中,在于未来或许会出现类似的形态。这允许模型收集有关市场反应的额外数据。
无论如何,未经证实的信息不能用于决策制定。因此,在决策制定模块中,我们仅依赖稳定形态,自低层反射模块请求相应的反应数据,从而得到更准确、依据充分的评估。
随后,我们按协同的先验知识来强化市场分析结果。
if(!cMarketToTools.FeedForward(cMarketToLowLevel.AsObject(), cTools.getOutput())) return false;
注意,我们并未引入可训练参数来解释辅助工具模块生成的标志,尽管早前曾讨论过。取而代之,这一功能被委派给交叉注意力模块中的主键和数值形式参数。因此,这些标志的解读和处理直接集成到交叉注意力机制当中。这令显式的额外参数变得没有必要。
在前馈方法的结尾,我们结合已识别的稳定形态、及其市场反应的境况下,分析高层反射模块的结果。该操作使用父类的工具执行。
return CNeuronRelativeCrossAttention::feedForward(cHighLevelReflection.AsObject(), cMarketToTools.getOutput());
}
操作的逻辑结果随后返回给调用程序,完成前馈方法。
随之前向通验,我们继续组织反向传播过程。在本章节中,我们将详细实证误差梯度分派方法(calcInputGradients)的算法,同时将可训练参数优化方法(updateInputWeights)留待独立研究。
bool CNeuronFinAgent::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -1) { if(!NeuronOCL || !SecondGradient) return false;
方法参数同样包含指向相同输入数据对象的指针 — 这次,我们必须根据输入数据对模型最终输出的影响传递误差梯度。方法主体伊始,就验证这些指针,因为如果指针无效,后续操作也毫无意义。
正如您所知,误差梯度分派完全镜像了前馈通验的信息流,仅方向相反。前向通验调用父类对应的方法至此结束。因此,反向传播算法从调用父类的梯度分派方法开始。它在高层反射模块、和决策制定模块的前置交叉注意力模块之间分派模型的误差。
if(!CNeuronRelativeCrossAttention::calcInputGradients(cHighLevelReflection.AsObject(), cMarketToTools.getOutput(), cMarketToTools.getGradient(), (ENUM_ACTIVATION)cMarketToTools.Activation())) return false;
然后,我们将误差梯度依次传播到决策制定模块的所有交叉注意力模块,根据它们对模型输出的影响,将误差分派到框架内所有信息流。
if(!cMarketToLowLevel.calcHiddenGradients(cMarketToTools.AsObject(), cTools.getOutput(), cTools.getGradient(), (ENUM_ACTIVATION)cTools.Activation())) return false; //--- if(!cMarketMemory.calcHiddenGradients(cMarketToLowLevel.AsObject(), cCrossLowLevel.getOutput(), cCrossLowLevel.getGradient(), (ENUM_ACTIVATION)cCrossLowLevel.Activation())) return false;
接下来,我们将误差梯度传播到低层反射模块当中。
if(!cLowLevelReflection[0].calcHiddenGradients(cCrossLowLevel.AsObject(), cLowLevelReflection[1].getOutput(), cLowLevelReflection[1].getGradient(), (ENUM_ACTIVATION)cLowLevelReflection[1].Activation())) return false; if(!cTransposeState.calcHiddenGradients(cLowLevelReflection[1].AsObject())) return false;
此刻,误差梯度已分派到所有框架模块之中。我们现在必须从原始输入数据层面收集所有信息流的数据。回忆一下,所有反射模块和市场分析模块的记忆模块都依据分段注意力变换器生成的预处理数据运行,因此我们首先收集该变换器输出级别的误差梯度。
第一步是从记忆模块传送误差梯度。
if(!((CNeuronBaseOCL*)cMarketIntelligence.AsObject()).calcHiddenGradients(cMarketMemory.AsObject())) return false;
接下来,我们替换输入数据预处理对象的误差梯度缓冲区指针,这样就允许我们存储累计的梯度值。
CBufferFloat *temp = cMarketIntelligence.getGradient(); if(!cMarketIntelligence.SetGradient(cMarketIntelligence.getPrevOutput(), false) || !((CNeuronBaseOCL*)cMarketIntelligence.AsObject()).calcHiddenGradients(cHighLevelReflection.AsObject(), SecondInput, SecondGradient, SecondActivation) || !SumAndNormilize(temp, cMarketIntelligence.getGradient(), temp, 1, false, 0, 0, 0, 1)) return false;
然后调用高层反射模的梯度分派方法。之后我们必须将两条数据流的结果汇总。
应当注意的是,高层反射模块在两条数据流上运作。因此,在梯度传播期间,该模块同时处理来自主流和财务结果流的误差。这令模型能够参考分析两个关键方面的误差,确保系统调谐更精确。
低层反射模块按类似举措处理梯度传播。然而,不同于高层反射模块,这些操作基于单一输入数据源,简化了误差梯度分派过程。
if(!((CNeuronBaseOCL*)cMarketIntelligence.AsObject()).calcHiddenGradients(cLowLevelReflection[0].AsObject()) || !SumAndNormilize(temp, cMarketIntelligence.getGradient(), temp, 1, false, 0, 0, 0, 1)) return false; if(!((CNeuronBaseOCL*)cMarketIntelligence.AsObject()).calcHiddenGradients(cTransposeState.AsObject()) || !SumAndNormilize(temp, cMarketIntelligence.getGradient(), temp, 1, false, 0, 0, 0, 1) || !cMarketIntelligence.SetGradient(temp, false)) return false;
不要忘记,每次迭代后,新获得的梯度值必须加到之前累积的梯度当中。这确保能正确考虑到所有模型误差。所有信息流处理完毕后,重要的是将原始缓冲指针恢复到初始状态。
最后,我们将误差梯度回传到主信息流的输入层,并结束方法,将操作的逻辑结果返回给调用程序。
if(!NeuronOCL.calcHiddenGradients(cMarketIntelligence.AsObject())) return false; //--- return true; }
注意,辅助工具模块不涉及误差梯度分派算法。如早前所述,我们不打算经由这种信息流传播梯度。甚至,在该境况下清除数据源对象的梯度缓冲区会有害,因为同一对象也会经由主信息流接收梯度。
我们讨论 FinAgent 框架算法的 MQL5 实现至此完毕。所有展示的对象和方法的完整源代码均在附件中提供,供您参考和进一步实验。您还能在那里找到准备本文时的完整程序代码和可训练模型的架构。所有组件几乎都与之前关于构建层化记忆智代的文章保持不变。唯一的修改是模型架构,我们将单一神经层替换为上述集成的 FinAgent框架。
//--- layer 2 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronFinAgent; //--- Windows { int temp[] = {BarDescr, AccountDescr, 2 * NActions, Segments}; //Window, Account description, N Actions, Segments if(ArrayCopy(descr.windows, temp) < int(temp.Size())) return false; } descr.count = HistoryBars; descr.window_out = 32; descr.step = 4; // Heads descr.batch = 1e4; descr.activation = None; descr.optimization = ADAM; if(!actor.Add(descr)) { delete descr; return false; }
所有剩余层的架构予以保留,均未作更动。现在,我们转到工作最后阶段 — 评估所实现方式在真实历史数据上的有效性。
测试
在前两篇文章中,我们详细探讨了 FinAgent 框架。在该过程期间,我们针对作者提议方式实现了自己的诠释。我们调整了框架算法,来满足我们的具体需求。我们现在到达另一个重要阶段:评估已实现解决方案在真实历史数据上的有效性。
请注意,在开发期间,我们对 FinAgent 框架的核心算法进行了重大修改。这些修改影响了模型操作的关键层面。因此,在本次评估中,我们评估的是我们调整后的版本,而非原始框架。
该模型基于 EURUSD 货币对 2023 年的历史数据,采用 <i1>H1</i1> 时间帧训练。模型中用到的所有指标设置均保持默认值,允许我们专注于评估算法本身、及其在不需额外调优的情况下处理原产数据的能力。
在初始训练阶段,我们采用了之前研究中准备的数据集。我们应用了一种训练算法,能够为智代生成“近乎理想”的目标动作,令我们能够在不持续更新训练数据集的情况下训练模型。然而,这种方式行之有效,我们相信定期更新训练集会提升准确性,并拓宽不同账户状态的覆盖范围。
经过若干轮训练后,模型在训练和测试数据上都展现出稳定的盈利能力。最终测试基于 2024 年 1 月的历史数据进行,所有模型参数和指标设置均被保留。在尽可能接近真实市场环境的条件下,该方式客观评估模型表现。结果呈现如下。


测试期间,该模型完成了95笔交易,显著超过前几款模型同期表现。超过42% 的交易以盈利了结。由于平均盈利交易是平均亏损交易的 1.5 倍,模型总体上是盈利的。盈利因子记录为1.09。
有趣的是,大部分利润是在本月上半月兑现的,当时价格波动处于相对狭窄范围内。当看跌趋势展开后,余额曲线窄幅移动,且观察到一定回撤。

以我观点,观察到的行为很可能归因于市场分析模块和辅助工具模块内算法的结果。该区域仍开放,等待进一步调查。
结束语
我们探讨了 FinAgent 框架,这是一款全面分析市场、并评估历史数据的先进解决方案。通过整合文本和视觉信息,该框架显著拓展了制定明智交易决策的可能性。凭借其五个关键架构组件,FinAgent 展现出高度的准确性和高度适应性,这对于频繁变化的金融市场交易至关重要。
值得注意的是,该框架并不局限于单一类型的分析。它提供了多种能够高效处理文本和图形数据的工具。这种多样性令模型能够参考多个市场因素,从而更深度地理解市场动态。这些特性令 FinAgent 成为有前景的工具,能够开发适应市场变化、并考虑细微市场波动的交易策略。
在我们实际工作部分,我们遵照框架方式解释实现了 MQL5 版本。我们通过整合这些方式训练模型,并在真实历史数据上进行测试。成果证明了该模型能够产生盈利。然而,发现盈利能力取决于市场条件。此外,还需要进一步实验,以增强模型对动态变化市场环境的适应能力。
参考
文章中所用程序
| # | 名称 | 类型 | 说明 |
|---|---|---|---|
| 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/16867
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
交易中的神经网络:配备概念强化的多智代系统(FinCon)
交易中的神经网络:配备概念强化的多智代系统(终篇)
混沌博弈优化(CGO)