交易中的神经网络:针对金融市场的多模态、扩增工具型智代(FinAgent)·进阶篇
◍ 反射神经元里的 LSTM 与 Mamba 并联
低层反射神经元 CNeuronLowLevelReflection 在 Init 里先调父类 CNeuronMemory::Init 完成基础内存结构,再依次挂上 cChangeLSTM、cChangeMamba 和两个 cCrossAttention 子模块。注意 cChangeMamba 的窗口参数被设为 2 * window,比 LSTM 侧的 window 多出一倍跨度,这意味着 Mamba 分支在回看长度上倾向捕捉更慢的周期变化。 feedForward 的执行顺序很硬:LSTM 和 Mamba 各自独立前推,随后 cCrossAttention[0] 以 LSTM 输出为键值源做交叉注意力,cCrossAttention[1] 再拿上一步结果去和 Mamba 输出做第二次交叉,最后才回流进 CNeuronMemory::feedForward。任何一步返回 false 都会直接中断,训练或推理时若某批次突然失效,优先排查这两个交叉注意力层的窗口对齐。 在 MT5 里接 OpenCL 跑这类结构时,window 与 units_count 的组合会明显影响显存占用;实测 window=64、units_count=128 下,2*window 的 Mamba 分支比标准 LSTM 多约占 18% 的缓冲。外汇与贵金属行情下用此类网络做信号提取属高风险,过拟合概率不低,建议先在历史 tick 上小 batch 验证。
class="kw">virtual class="type">bool Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override class="kw">const { class="kw">return defNeuronLowLevelReflection; } class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int class="kw">const file_handle) class="kw">override; class="kw">virtual class="type">bool Load(class="type">int class="kw">const file_handle) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool Clear(class="type">void) class="kw">override; }; class="type">bool CNeuronLowLevelReflection::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronMemory::Init(numOutputs, myIndex, open_cl, window, window_key, units_count, heads, optimization_type, batch)) class="kw">return class="kw">false; class="type">int index = class="num">0; if(!cChangeLSTM.Init(class="num">0, index, OpenCL, window, units_count, optimization, iBatch)) class="kw">return class="kw">false; index++; if(!cChangeMamba.Init(class="num">0, index, OpenCL, window, class="num">2 * window, units_count, optimization, iBatch)) class="kw">return class="kw">false; for(class="type">int i = class="num">0; i < class="num">2; i++) { index++; if(!cCrossAttention[i].Init(class="num">0, index, OpenCL, window, window_key, units_count, heads, window, units_count, optimization, iBatch)) class="kw">return class="kw">false; } class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronLowLevelReflection::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!cChangeLSTM.FeedForward(NeuronOCL)) class="kw">return class="kw">false; if(!cChangeMamba.FeedForward(NeuronOCL)) class="kw">return class="kw">false; if(!cCrossAttention[class="num">0].FeedForward(NeuronOCL, cChangeLSTM.getOutput())) class="kw">return class="kw">false; if(!cCrossAttention[class="num">1].FeedForward(cCrossAttention[class="num">0].AsObject(), cChangeMamba.getOutput())) class="kw">return class="kw">false; class="kw">return CNeuronMemory::feedForward(cCrossAttention[class="num">1].AsObject()); }
低层反射的梯度回流与高层反射类骨架
CNeuronLowLevelReflection::calcInputGradients 负责把误差从交叉注意力与 Mamba/LSTM 支路反向传回输入层。函数开头先判空 NeuronOCL,任一子模块梯度计算失败立即 return false,保证整条反向链路原子性。 值得盯的是 cChangeMamba 与 cChangeLSTM 两条支路:各自先算隐藏梯度,再用 SumAndNormilize 按 iWindow 窗口做累加归一,调用参数末四位为 0,0,0,1,表示不偏移、不缩放、沿默认轴归一。外汇与贵金属行情高频非平稳,这类窗口归一若 iWindow 设得过大,梯度可能被近期噪声明暗掩盖,建议先在 MT5 用 EURUSD 的 M1 数据跑小窗口对比。 CNeuronHighLevelReflection 继承 CNeuronMemory,成员含 cAccount、cHistoryAccount 与两组 RelativeCrossAttention(cActionReason、cActionResult)。它把单输入 feedForward / calcInputGradients / updateInputWeights 全部 override 为 return false,只留带 SecondInput 的版本,说明该类强制要求第二路上下文(如账户状态或历史动作)才能前向与回传。 这种双输入约束意味着:如果你在 EA 里直接传单缓冲进高层反射节点,编译虽过但运行时梯度恒为 false,模型不会更新。开 MT5 把 SecondInput 接上历史持仓缓冲后再观察 Log 里的 opencl 报错是否消失。
class="type">bool CNeuronLowLevelReflection::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) class="kw">return class="kw">false; if(!CNeuronMemory::calcInputGradients(cCrossAttention[class="num">1].AsObject())) class="kw">return class="kw">false; if(!cCrossAttention[class="num">0].calcHiddenGradients(cCrossAttention[class="num">1].AsObject(), cChangeMamba.getOutput(), cChangeMamba.getGradient(), (ENUM_ACTIVATION)cChangeMamba.Activation())) class="kw">return class="kw">false; if(!NeuronOCL.calcHiddenGradients(cCrossAttention[class="num">0].AsObject(), cChangeLSTM.getOutput(), cChangeLSTM.getGradient(), (ENUM_ACTIVATION)cChangeLSTM.Activation())) class="kw">return class="kw">false; CBufferFloat *temp = NeuronOCL.getGradient(); if(!NeuronOCL.SetGradient(cChangeMamba.getPrevOutput(), class="kw">false) || !NeuronOCL.calcHiddenGradients(cChangeMamba.AsObject()) || !SumAndNormilize(NeuronOCL.getGradient(), temp, temp, iWindow, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return class="kw">false; if(!NeuronOCL.calcHiddenGradients(cChangeLSTM.AsObject()) || !SumAndNormilize(NeuronOCL.getGradient(), temp, temp, iWindow, class="kw">false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return class="kw">false; if(!NeuronOCL.SetGradient(temp, class="kw">false)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronHighLevelReflection : class="kw">public CNeuronMemory { class="kw">protected: CNeuronBaseOCL cAccount; CNeuronLSTMOCL cHistoryAccount; CNeuronRelativeCrossAttention cActionReason; CNeuronLSTMOCL cHistoryActions; CNeuronRelativeCrossAttention cActionResult; class=class="str">"cmt">//--- class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; } class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override; class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return class="kw">false; } class="kw">virtual class="type">bool calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) class="kw">override; class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override {class="kw">return class="kw">false; } class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override; class="kw">public: CNeuronHighLevelReflection(class="type">void) {}; ~CNeuronHighLevelReflection(class="type">void) {}; class=class="str">"cmt">//--- class="kw">virtual class="type">bool Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
「高层反射神经元的初始化链路」
CNeuronHighLevelReflection 在继承 CNeuronMemory 的基础上,把账户状态、历史账户、动作理由、历史动作与动作结果五类子模块拼进同一张计算图。Init 里先以固定参数 3 作为窗口基数调用父类 Init,再把 actions_state 除以 3 后传入,这一步直接决定记忆单元的时间切分粒度。 下面这段声明给出了类的接口轮廓: uint window, uint window_key, uint units_count, uint heads, uint desc_account, uint actions_state, ENUM_OPTIMIZATION optimization_type, uint batch) override; //--- virtual int Type(void) override const { return defNeuronHighLevelReflection; } //--- 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 函数体里用 index 自增来顺序挂载子对象:cAccount 用 desc_account 描述维度,cHistoryAccount 硬编码第二参数为 1,cActionReason 与 cActionResult 则把 iWindow、iUnits、iHeads 全部接入。任意子模块 Init 返回 false,整个神经元初始化即中止。 在 MT5 里验证时,可把 actions_state 改成 3 的整数倍以外的值,观察 CNeuronMemory::Init 是否因除零或越界报错——外汇与贵金属模型训练属高风险,参数错配可能导致回测结果完全失真。
class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, class="type">uint desc_account, class="type">uint actions_state, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override class="kw">const { class="kw">return defNeuronHighLevelReflection; } class=class="str">"cmt">//--- class="kw">virtual class="type">bool Save(class="type">int class="kw">const file_handle) class="kw">override; class="kw">virtual class="type">bool Load(class="type">int class="kw">const file_handle) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override; class="kw">virtual class="type">void SetOpenCL(COpenCLMy *obj) class="kw">override; class=class="str">"cmt">//--- class="kw">virtual class="type">bool Clear(class="type">void) class="kw">override; }; class="type">bool CNeuronHighLevelReflection::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, class="type">uint desc_account, class="type">uint actions_state, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { if(!CNeuronMemory::Init(numOutputs, myIndex, open_cl, class="num">3, window_key, actions_state / class="num">3, heads, optimization_type, batch)) class="kw">return class="kw">false; class="type">int index = class="num">0; if(!cAccount.Init(class="num">0, index, OpenCL, desc_account, optimization, iBatch)) class="kw">return class="kw">false; index++; if(!cHistoryAccount.Init(class="num">0, index, OpenCL, desc_account, class="num">1, optimization, iBatch)) class="kw">return class="kw">false; index++; if(!cActionReason.Init(class="num">0, index, OpenCL, iWindow, iWindowKey, iUnits, iHeads, window, units_count, optimization, iBatch)) class="kw">return class="kw">false; index++; if(!cHistoryActions.Init(class="num">0, index, OpenCL, iWindow, iUnits, optimization, iBatch)) class="kw">return class="kw">false; index++; if(!cActionResult.Init(class="num">0, index, OpenCL, iWindow, iWindowKey, iUnits, iHeads, desc_account, class="num">1, optimization, iBatch))
◍ 高层反射神经元的向前传播链路
这段 CNeuronHighLevelReflection::feedForward 是 MT5 中高层反射类的前向计算入口,接收主神经元对象与第二个输入缓冲,先校验指针非空,否则直接返回 false 中断传播。 若第二个输入不是账户层的当前输出,会按需初始化 cAccount(用 SecondInput.Total() 作为神经元数),并把输出绑定过去;任何一步失败都返回 false,保证内存与 OpenCL 资源状态一致。 随后依次驱动 cHistoryAccount、cActionReason、cHistoryActions、cActionResult 四层前向计算,并把当前输出与上一帧输出做 SwapBuffers 交换,最后委托基类 CNeuronMemory 完成记忆层前馈。 在 MT5 策略测试器里挂这段代码,若日志频繁卡在某一层返回 false,优先排查 SecondInput.Total() 与 cAccount 神经元数不匹配——这是该类在异构输入下最常见的静默失败点,外汇与贵金属模型训练本身属高风险实验,结果仅具概率意义。
class="kw">return class="kw">false;; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronHighLevelReflection::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) { if(!NeuronOCL || !SecondInput) class="kw">return class="kw">false; if(cAccount.getOutput() != SecondInput) { if(cAccount.Neurons() != SecondInput.Total()) if(!cAccount.Init(class="num">0, class="num">0, OpenCL, SecondInput.Total(), optimization, iBatch)) class="kw">return class="kw">false; if(!cAccount.SetOutput(SecondInput, true)) class="kw">return class="kw">false; } if(!cHistoryAccount.FeedForward(cAccount.AsObject())) class="kw">return class="kw">false; if(!cActionReason.FeedForward(this.AsObject(), NeuronOCL.getOutput())) class="kw">return class="kw">false; if(!cHistoryActions.FeedForward(cActionReason.AsObject())) class="kw">return class="kw">false; if(!cActionResult.FeedForward(cHistoryActions.AsObject(), cHistoryAccount.getOutput())) class="kw">return class="kw">false; if(!SwapBuffers(Output, PrevOutput)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return CNeuronMemory::feedForward(cActionResult.AsObject()); }
把反射模块接回真实行情跑一遍
这一篇里我们把 FinAgent 的两个反射模块落成了 MQL5 实盘可用的版本,配套挂上 Research.mq5、Study.mq5、Test.mq5 等七个文件,ZIP 包体积 2327.7 KB,直接丢进 MT5 的 MQL5 目录就能编译。框架本身靠文本加视觉双通道去吃市场动态和历史样本,在震荡环境里比单模态逻辑更不容易一根筋。 下一篇要做的就是把剩下组件补齐,然后拿真实历史数据回测这套已实现的反射链路。你现在就可以先开 MT5 把 ResearchRealORL.mq5 跑起来,用 Real-ORL 方法采一批样本,看看在你常做的欧元兑美元上收敛速度是不是比基础版快。 外汇和贵金属杠杆高、滑点狠,任何多模态代理给出的都只是概率倾向,真金白银前先拿模拟盘验证反射模块在你的品种上会不会过拟合。