交易中的神经网络:多智代自适应模型(MASA)·进阶篇
市场观测神经元的初始化链路
CNeuronMarketObserver 的 Init 方法把行情观测封装成一套多层 OpenCL 计算图,入参里 window 是回溯窗口、forecast 是预测步长、heads 与 layers 控制自注意力头数与堆叠深度,batch 决定训练批大小。 父类 CNeuronBaseOCL 先用 window * forecast 的总维度做基底初始化,失败直接返回 false;随后 cLayers 数组清空并绑定 OpenCL 上下文,这是后续所有子层挂载的容器。 链路依次挂了 5 类子层:转置层(CNeuronTransposeOCL)重排输入、分段线性层(CNeuronPLROCL)做非线性映射、变量自注意力层(CNeuronRMAT,用 window_key 限定键长)、残差卷积(CResidualConv)做预测映射、最后再转置回原始形态。任一层 new 或 Init 失败都 delete 并返回 false,只有全部通过才 SetOutput 暴露输出。 在 MT5 里调这套时,建议先单独打印 lay_count 确认五层索引为 0~4;若某层返回 false,多半是 units_count 与 window 的乘积超出显存分配。外汇与贵金属行情波动剧烈,此类模型仅作概率参考,实盘前务必用历史数据回测验证。
class="type">uint heads, class="type">uint layers, class="type">uint forecast, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override class="kw">const { class="kw">return defNeuronMarketObserver; } }; class="type">bool CNeuronMarketObserver::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 layers, class="type">uint forecast, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { class=class="str">"cmt">//--- Init parent object if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * forecast, optimization_type, batch)) class="kw">return false; class=class="str">"cmt">//--- Clear layers&class="macro">#x27; array cLayers.Clear(); cLayers.SetOpenCL(OpenCL); class=class="str">"cmt">//--- Tranpose input data class="type">int lay_count = class="num">0; CNeuronTransposeOCL *transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(class="num">0, lay_count, OpenCL, units_count, window, optimization, iBatch) || !cLayers.Add(transp)) { class="kw">delete transp; class="kw">return false; } class=class="str">"cmt">//--- Piecewise linear representation lay_count++; CNeuronPLROCL *plr = new CNeuronPLROCL(); if(!plr || !plr.Init(class="num">0, lay_count, OpenCL, units_count, window, false, optimization, iBatch) || !cLayers.Add(plr)) { class="kw">delete plr; class="kw">return false; } class=class="str">"cmt">//--- Self-Attention for Variables lay_count++; CNeuronRMAT *att = new CNeuronRMAT(); if(!att || !att.Init(class="num">0, lay_count, OpenCL, units_count, window_key, window, heads, layers, optimization, iBatch) || !cLayers.Add(att)) { class="kw">delete att; class="kw">return false; } class=class="str">"cmt">//--- Forecast mapping lay_count++; CResidualConv *conv = new CResidualConv(); if(!conv || !conv.Init(class="num">0, lay_count, OpenCL, units_count, forecast, window, optimization, iBatch) || !cLayers.Add(conv)) { class="kw">delete conv; class="kw">return false; } class=class="str">"cmt">//--- Back transpose forecast lay_count++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(class="num">0, lay_count, OpenCL, window, forecast, optimization, iBatch) || !cLayers.Add(transp)) { class="kw">delete transp; class="kw">return false; } if(!SetOutput(transp.getOutput(), true) ||
◍ 强化学习智能体的类结构与初始化链路
在 MT5 的神经网络模块里,CNeuronRLAgent 继承自 CNeuronRMAT,对外只暴露默认构造、析构以及一个重写的 Type 方法,返回 defNeuronRLAgent 常量用于运行时类型识别。 它的 Init 方法是整个 agent 的装配中心:先调用父类 CNeuronBaseOCL::Init 完成基础张量分配,任意一步失败直接返回 false,避免半初始化对象进入训练循环。 随后清空 cLayers 数组并绑定 OpenCL 上下文,接着按 layers 参数循环 new 出 CNeuronPSformer 实例——每一层都传入 window、units_count、segments、rho 等状态观测超参,任一层 Add 失败就 delete 并回退。 循环结束后追加一个 CNeuronConvSAMOCL 卷积层(核尺寸 window×window,步长 1),显式设激活函数为 GELU,再接一个 CNeuronBaseSAMOCL 扁平层做动作维度对齐。 实盘接这套结构做贵金属策略时,layers 设 3、window 设 64 是常见的起步组合,但外汇与贵金属杠杆高、滑点突变频繁,回测通过不代表实盘概率占优,建议先在 MT5 策略测试器用 Tick 级数据跑一遍梯度检查。
if(!SetGradient(transp.getGradient(), true)) class="kw">return false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronRLAgent : class="kw">public CNeuronRMAT { class="kw">public: CNeuronRLAgent(class="type">void) {}; ~CNeuronRLAgent(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, class="type">uint window, class="type">uint units_count, class="type">uint segments, class="type">float rho, class="type">uint layers, class="type">uint n_actions, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">const class="kw">override { class="kw">return defNeuronRLAgent; } }; class="type">bool CNeuronRLAgent::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl, class="type">uint window, class="type">uint units_count, class="type">uint segments, class="type">float rho, class="type">uint layers, class="type">uint n_actions, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { class=class="str">"cmt">//--- Init parent object if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, n_actions, optimization_type, batch)) class="kw">return false; class=class="str">"cmt">//--- Clear layers&class="macro">#x27; array cLayers.Clear(); cLayers.SetOpenCL(OpenCL); class=class="str">"cmt">//--- State observation class="type">int lay_count = class="num">0; for(class="type">uint i = class="num">0; i < layers; i++) { CNeuronPSformer *psf = new CNeuronPSformer(); if(!psf || !psf.Init(class="num">0, lay_count, OpenCL, window, units_count, segments, rho, optimization,iBatch)|| !cLayers.Add(psf)) { class="kw">delete psf; class="kw">return false; } lay_count++; } CNeuronConvSAMOCL *conv = new CNeuronConvSAMOCL(); if(!conv || !conv.Init(n_actions, lay_count, OpenCL, window, window, class="num">1, units_count, class="num">1, optimization, iBatch) || !cLayers.Add(conv)) { class="kw">delete conv; class="kw">return false; } conv.SetActivationFunction(GELU); lay_count++; CNeuronBaseSAMOCL *flat = new CNeuronBaseSAMOCL(); if(!flat || !flat.Init(class="num">0, lay_count, OpenCL, n_actions, optimization, iBatch) || !cLayers.Add(flat)) { class="kw">delete flat;
「控制代理类的双输入前向与初始化」
CNeuronControlAgent 继承自 CNeuronRMAT,但它把单输入版本的 feedForward 和 calcInputGradients、updateInputWeights 全部 override 成直接 return false,只保留带 SecondInput 的重载。这意味着这个类在设计上强制走双输入通道,单输入调用会被静默拒绝,MT5 里若误用父类单参接口不会报错只会返回失败。 初始化函数 Init 先调 CNeuronBaseOCL::Init,传入的窗口维度被乘上 units_count(window * units_count),再清空 cLayers 数组并绑定 OpenCL 上下文。lay_count 从 0 起步,后续由外部循环按 layers 参数逐层推入,外汇与贵金属行情下用 GPU 跑这类代理网络需注意显存占用随 window 与 units 乘积放大,杠杆品种高风险。 代码里还定义了 Type() 返回 defNeuronControlAgent,用作运行时类型辨识。若你要自己扩展控制代理,复制这段类壳后只需补完带 SecondInput 的三个虚函数体,单输入版本保持返回 false 即可避免歧义调用。
class="kw">return false; } SetActivationFunction(SIGMOID); if(!SetOutput(flat.getOutput(), true) || !SetGradient(flat.getGradient(), true)) class="kw">return false; class=class="str">"cmt">//--- class="kw">return true; } class CNeuronControlAgent : class="kw">public CNeuronRMAT { class="kw">protected: class="kw">virtual class="type">bool feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return 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 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 false; } class="kw">virtual class="type">bool updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override; class="kw">public: CNeuronControlAgent(class="type">void) {}; ~CNeuronControlAgent(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, class="type">uint window, class="type">uint window_key, class="type">uint units_count, class="type">uint heads, class="type">uint window_kv, class="type">uint units_kv, class="type">uint layers, ENUM_OPTIMIZATION optimization_type, class="type">uint batch); class=class="str">"cmt">//--- class="kw">virtual class="type">int Type(class="type">void) class="kw">override class="kw">const { class="kw">return defNeuronControlAgent; } }; class="type">bool CNeuronControlAgent::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 window_kv, class="type">uint units_kv, class="type">uint layers, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) { class=class="str">"cmt">//--- Init parent object if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch)) class="kw">return false; class=class="str">"cmt">//--- Clear layers&class="macro">#x27; array cLayers.Clear(); cLayers.SetOpenCL(OpenCL); class="type">int lay_count = class="num">0;
堆叠自注意力与交叉注意力的层装配
在 MT5 用 OpenCL 搭控制智能体时,先以 CNeuronBaseOCL 做展平层,再接 CNeuronTransposeOCL 做维度置换,二者都通过 Init 挂到 cLayers 容器,lay_count 每层 +1 用于后续索引定位。 若 units_count 大于 1,循环里优先建 CNeuronRelativeSelfAttention 捕捉单序列内部依赖;随后无条件接 CNeuronRelativeCrossAttention 融合外部键值序列(窗口 window_kv、单元 units_kv),最后补 CResidualConv 做残差卷积前馈。每一层失败都 delete 指针并返回 false,避免显存泄漏。 循环跑完 layers 次后,末端接 CNeuronConvSAMOCL 做空间压缩,Init 第 5 参数填 1 表示输出通道收敛;随后 SetActivationFunction(SIGMOID) 把输出压到 0~1 区间,适合表示动作概率。 feedForward 入口先校验 SecondInput 非空,再取 cLayers[0] 作为外部分支首层,任一为空直接返回 false——实盘跑前应在策略测试器用 EURUSD 5 分钟、2023 全年数据验证该网络前向不报空指针,外汇与贵金属杠杆交易高风险,回测通过不代表实盘盈利。
CNeuronBaseOCL *flat = new CNeuronBaseOCL(); if(!flat || !flat.Init(class="num">0, lay_count, OpenCL, window_kv * units_kv, optimization, iBatch) || !cLayers.Add(flat)) { class="kw">delete flat; class="kw">return false; } lay_count++; CNeuronTransposeOCL *transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(class="num">0, lay_count, OpenCL, units_kv, window_kv, optimization, iBatch) || !cLayers.Add(transp)) { class="kw">delete transp; class="kw">return false; } lay_count++; class=class="str">"cmt">//--- Attention Action To Observation for(class="type">uint i = class="num">0; i < layers; i++) { if(units_count > class="num">1) { CNeuronRelativeSelfAttention *self = new CNeuronRelativeSelfAttention(); if(!self || !self.Init(class="num">0, lay_count, OpenCL, window, window_key, units_count, heads, optimization, iBatch) || !cLayers.Add(self)) { class="kw">delete self; class="kw">return false; } lay_count++; } CNeuronRelativeCrossAttention *cross = new CNeuronRelativeCrossAttention(); if(!cross || !cross.Init(class="num">0, lay_count, OpenCL, window, window_key, units_count, heads, units_kv, window_kv, optimization, iBatch) || !cLayers.Add(cross)) { class="kw">delete cross; class="kw">return false; } lay_count++; CResidualConv *ffn = new CResidualConv(); if(!ffn || !ffn.Init(class="num">0, lay_count, OpenCL, window, window, units_count, optimization, iBatch) || !cLayers.Add(ffn)) { class="kw">delete ffn; class="kw">return false; } lay_count++; } CNeuronConvSAMOCL *conv = new CNeuronConvSAMOCL(); if(!conv || !conv.Init(class="num">0, lay_count, OpenCL, window, window, window, units_count, class="num">1, optimization, iBatch) || !cLayers.Add(conv)) { class="kw">delete conv; class="kw">return false; } SetActivationFunction(SIGMOID); class=class="str">"cmt">//--- if(!SetOutput(conv.getOutput(), true) || !SetGradient(conv.getGradient(), true)) class="kw">return false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronControlAgent::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) { if(!SecondInput) class="kw">return false; CNeuronBaseOCL *second = cLayers[class="num">0]; if(!second) class="kw">return false;
◍ 控制代理的前向推演与梯度回传骨架
这段 CNeuronControlAgent 的两个方法暴露了多层神经网络在 MT5 端的运转顺序:先正向把各层输出串起来,再反向把梯度从末层算回输入层。外汇与贵金属行情噪声大,用这类结构做信号模型时回测过拟合概率偏高,属高风险尝试。 前向部分从第二层开始接管,先给 second 层喂入 SecondInput 并做 SetOutput,失败直接 return false;随后循环 i=2 到 cLayers.Total(),每一层 main 都依赖前一层 first 与 second 的输出做 FeedForward,first 在每次迭代后被替换为当前 main。 反向的 calcInputGradients 更讲究:先把 SecondGradient 塞给第 0 层并设激活函数,再从倒数第二层往回走到索引 2。若下一层是 defNeuronRelativeCrossAttention 类型,就走 calcHiddenGradients 加 SumAndNormilize 的跨注意力路径,否则走普通隐藏层梯度;最后第 0 层拿 second 的对象补一次梯度。 在 MT5 里把这段直接贴进自定义神经层类,改 cLayers 的层数跑一次前反向,能验证你自己的注意力层是否真的参与了梯度汇总。
if(!second.SetOutput(SecondInput, true)) class="kw">return false; second = cLayers[class="num">1]; if(!second || !second.FeedForward(cLayers[class="num">0])) class="kw">return false; CNeuronBaseOCL *first = NeuronOCL; CNeuronBaseOCL *main = NULL; for(class="type">int i = class="num">2; i < cLayers.Total(); i++) { main = cLayers[i]; if(!main || !main.FeedForward(first, second.getOutput())) class="kw">return false; first = main; } class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNeuronControlAgent::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -class="num">1) { if(!NeuronOCL || !SecondGradient) class="kw">return false; CNeuronBaseOCL *main = cLayers[class="num">0]; if(!main) class="kw">return false; if(!main.SetGradient(SecondGradient, true)) class="kw">return false; main.SetActivationFunction(SecondActivation); class=class="str">"cmt">//--- CNeuronBaseOCL *second = cLayers[class="num">1]; if(!second) class="kw">return false; second.SetActivationFunction(SecondActivation); CBufferFloat *second_out = second.getOutput(); CBufferFloat *second_gr = second.getGradient(); CBufferFloat *temp = second.getPrevOutput(); if(!second_gr.Fill(class="num">0)) class="kw">return false; for(class="type">int i = cLayers.Total() - class="num">2; i >= class="num">2; i--) { main = cLayers[i]; if(!main) class="kw">return false; if(cLayers[i + class="num">1].Type() == defNeuronRelativeCrossAttention) { if(!main.calcHiddenGradients(cLayers[i + class="num">1], second_out, temp, SecondActivation) || !SumAndNormilize(temp, second_gr, second_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; } else { if(!main.calcHiddenGradients(cLayers[i + class="num">1])) class="kw">return false; } } if(!NeuronOCL.calcHiddenGradients(main.AsObject(), second_out, temp, SecondActivation)) class="kw">return false; if(main.Type() == defNeuronRelativeCrossAttention) { if(!SumAndNormilize(temp, second_gr, second_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1)) class="kw">return false; } main = cLayers[class="num">0]; if(!main.calcHiddenGradients(second.AsObject())) class="kw">return false; class=class="str">"cmt">//--- class="kw">return true; }
「一点提醒」
前面几节把 MASA 框架拆成了收益优化、风险最小化、趋势观察三个独立智代,并各自给出了 MQL5 模块:Research.mq5 与 ResearchRealORL.mq5 负责采样,Study.mq5 训练,Test.mq5 回测,Trajectory.mqh / NeuroNet.mqh / NeuroNet.cl 提供状态结构与网络底层。下一步是把它们接进同一套系统跑真实历史,这一步没做之前,任何单模块结论都只是组件级验证。 外汇与贵金属市场高杠杆、高波动,多智代框架即便回测漂亮,实盘也可能因滑点、断连、过拟合而失效。开 MT5 把 MQL5.zip(约 2.2 MB)里的 Test.mq5 先单独跑一遍,确认你本地环境能复现文中所说采样与训练流程,再谈集成。 下一篇若真把系统拼起来,重点看的是各智代在实数据下的耦合损耗,而不是单个 RL 收益曲线。这点搞清楚,才不会被‘框架创新’的叙事带偏。