交易中的神经网络:搭配预测编码的混合交易框架(终篇)(基础篇)
「用预测编码给神经网络交易系统打底」
很多交易者以为把神经网络塞进 EA 就能自动赚钱,但 MT5 实盘里裸跑 NN 多数会过拟合到崩。Dmitriy Gizlyk 在 2025-10-20 发布的框架里,先把预测编码(Predictive Coding)用作特征前置层,再接轻量网络做方向判定,思路是把价格序列的‘预测残差’当信号源而不是原始收盘价。 预测编码的核心是用上一帧的内部模型去猜当前帧,猜错的部分(残差)才往后送。这样喂给 NN 的输入维度被压低,噪声被前置过滤,回测里训练集外的泛化失败率比直接喂 OHLC 低了一截。外汇与贵金属杠杆高,残差信号也仅代表概率倾向,不是进场保证。 想验证这套,开 MT5 新建 EA,把下面这段残差计算先跑通,再看后续网络怎么接。
StockFormer 的三分支注意力骨架
StockFormer 不是一个单一预测器,而是把三条改过的变换器分支塞进同一套注意力机制里协同工作。第一条分支专门挖不同资产之间隐藏的联动关系,第二、第三条分别盯短期与长期走势,让模型同时审计当下和未来的市场倾向。 这种集成靠一系列注意力机制完成,强化了从多头模块里学形态的能力,对噪声大、跳变快的金融序列尤其关键。在上一篇文章的实践章节,我们已经落地了多样化多头注意力(DMH-Attn)模块,它替代标准注意力后,检测时间序列中异质形态和交叉依赖的效率有明显提升。 本文接着拆模型各部件的架构,以及它们如何在统一状态空间里相互作用,并走到决策制定智能体交易策略的训练环节。外汇与贵金属市场高杠杆、高波动,这类框架只提供概率倾向,实盘前务必在 MT5 用历史数据复算。
◍ StockFormer 的预测编码三模型怎么搭
StockFormer 框架用三种基于编码器-解码器变换器的模型做预测编码:一个专门搜多模态时间序列里的依赖关系,另外两个分别预测不同横向范围的走势。三者都跑修改版 DMH-Attn 模块,但在 MT5 实现里编码器和解码器是分开建模的。 依赖项搜索模型的骨架写在 CreateRelationDescriptions 里。编码器第一层全连接直接吃下 HistoryBars*BarDescr 大小的原始张量,因为要接收整段历史深度;多模态数据分布不一,紧接着用批量归一化预处理,训练时随机掩掉最多 50% 输入,由 Dropout 层承担,再叠可学习位置编码,末端是三层多样化多头注意力。解码器架构大半雷同,只是把多头注意力换成交叉注意力,输出接逆向归一化层好和原始输入比对。 两个预测模型共用 CreatePredictionDescriptions 里的同一套结构,编码器复用依赖项搜索版(去掉 Dropout,因为训练不掩码)。解码器只收最后一根柱线的特征向量,过全连接和批归一化;单品种分析时位置编码意义不大直接省掉,多品种才建议加。三层多样化多头交叉注意力拿编码器输出当第二信息源,末尾全连接投影层不带激活。 这里有两个坑得记牢:一是预测目标不是价格延续值,而是指标变化系数,输出维度和解码器输入对齐,所以逆向归一化变得多余;二是单品种时全连接层够用,并行多品种得换卷积层各自出系数。 训练阶段在 Train 方法里完成。先从经验回放缓冲区按盈利能力加权采样轨迹,偏向可盈利样本;循环里取轨迹和初始状态,校验历史与计划横向范围数据齐全后,把历史灌进缓冲区跑前向。每个预测模型自带独立编码器,计算成本高但能抓各自任务的依赖。解码器虽只吃最后一根柱线,但训练时直接传整段历史矩阵、取前两行匹配输入层即可,免得另建副本。 目标值构造上,依赖项搜索模型拿序列本身做目标;预测模型算变化系数——回放数据是未归一化的,按未来矩阵每参数最大绝对值归一,系数通常落在 {-2.0, 2.0}。短期模型取下一根柱线系数差,长期模型加总并乘折扣因子。反向传播先短后长更新参数,日志打印进度,附件 Study1.mq5 有完整源码。外汇与贵金属市场高风险,这类模型输出仅作概率参考,实盘前务必在 MT5 用历史数据回测验证。
<span class="keyword">class="type">bool</span> CreateRelationDescriptions(CArrayObj *&encoder, CArrayObj *&decoder) { <span class="comment">class=class="str">"cmt">//---</span> CLayerDescription *descr; <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">if</span>(!encoder) { encoder = <span class="keyword">new</span> CArrayObj(); <span class="keyword">if</span>(!encoder) <span class="keyword">class="kw">return</span> <span class="keyword">class="kw">false</span>; } <span class="keyword">if</span>(!decoder) { decoder = <span class="keyword">new</span> CArrayObj(); <span class="keyword">if</span>(!decoder) <span class="keyword">class="kw">return</span> <span class="keyword">class="kw">false</span>; } <span class="comment">class=class="str">"cmt">//--- Encoder</span> encoder.Clear(); <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">if</span>(!(descr = <span class="keyword">new</span> CLayerDescription())) <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>; descr.type = defNeuronBaseOCL; <span class="keyword">class="type">int</span> prev_count = descr.count = (HistoryBars * BarDescr); descr.activation = None; descr.optimization = ADAM; <span class="keyword">if</span>(!encoder.Add(descr)) { <span class="keyword">class="kw">delete</span> descr; <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>; } <span class="comment">class=class="str">"cmt">//--- layer class="num">1</span> <span class="keyword">if</span>(!(descr = <span class="keyword">new</span> CLayerDescription())) <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>; descr.type = defNeuronBatchNormOCL; descr.count = prev_count; descr.batch = <span class="number">class="num">1</span>e4; descr.activation = None; descr.optimization = ADAM; <span class="keyword">if</span>(!encoder.Add(descr)) { <span class="keyword">class="kw">delete</span> descr; <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>; } <span class="comment">class=class="str">"cmt">//--- layer class="num">2</span> <span class="keyword">if</span>(!(descr = <span class="keyword">new</span> CLayerDescription()))
「编码器与解码器的层栈拼装」
这段逻辑在 MT5 的神经网络封装里把编码器、解码器一层层堆起来,每层都用 CLayerDescription 描述后再 Add 进容器。注意 dropout 层 probability 写死 0.5f,意味着训练时随机屏蔽一半神经元,过拟合概率可能下降,但样本量不够时收敛会偏慢。 第四层用了 defNeuronDMHAttention,window_out=32、step=4(头数)、layers=3,batch 统一设 1e4。多头注意力在汇率序列上可能捕捉到跨周期依赖,但 1e4 的批大小在普通 VPS 上容易爆显存,实盘前先调小验证。 交叉注意力层(defNeuronCrossDMHAttention)用 ArrayCopy 把 windows 设成 {BarDescr, BarDescr},units 按 prev_count/windows[0] 与 HistoryBars 切分;若 ArrayCopy 返回值小于源数组长度直接 return false,这种防御写法能避免空层混进解码器。 最后一层 defNeuronRevInDenormOCL 的 count 由 prev_count = descr.units[0]*descr.windows[0] 推算,layers=1。跑通后 decoder.Clear() 留作复用入口,开 MT5 把这段贴进 Expert 的 OnInit 前面就能看层栈是否按预期构建。
class="kw">return class="kw">false; descr.type = defNeuronDropoutOCL; descr.count = prev_count; descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; descr.probability = class="num">0.5f; if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">3 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronLearnabledPE; descr.count = prev_count; descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">4 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronDMHAttention; descr.window = BarDescr; descr.window_out = class="num">32; descr.count = HistoryBars; descr.step = class="num">4; class=class="str">"cmt">//Heads descr.layers = class="num">3; class=class="str">"cmt">//Layers descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">4 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronCrossDMHAttention; class=class="str">"cmt">//--- Windows { class="type">int temp[] = {BarDescr, BarDescr}; if(ArrayCopy(descr.windows, temp) < (class="type">int)temp.Size()) class="kw">return class="kw">false; } descr.window_out = class="num">32; class=class="str">"cmt">//--- Units { class="type">int temp[] = {prev_count/descr.windows[class="num">0], HistoryBars}; if(ArrayCopy(descr.units, temp) < (class="type">int)temp.Size()) class="kw">return class="kw">false; } descr.step = class="num">4; class=class="str">"cmt">//Heads descr.layers = class="num">3; class=class="str">"cmt">//Layers descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">5 prev_count = descr.units[class="num">0] * descr.windows[class="num">0]; if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronRevInDenormOCL; descr.count = prev_count; descr.layers = class="num">1; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- class="kw">return true; } class=class="str">"cmt">//--- Decoder decoder.Clear();
解码器堆叠与轨迹采样训练落点
这段把解码器的层结构用代码直接铺开:输入层用 defNeuronBaseOCL,节点数等于 BarDescr,优化器统一挂 ADAM;第一层接 BatchNorm,batch 设到 1e4,这种大 batch 在 MT5 的 OpenCL 后端可能加快收敛但吃显存。 第二层是 CrossDMHAttention,windows 数组填 {BarDescr, BarDescr},window_out=32,units 设 {1, HistoryBars},step=4 即 4 个注意力头,layers=3 叠三层。Heads 和 Layers 写死在注释里,调参时直接改这两个常量就能换结构。 Train 函数里先按 0.9 阈值取轨迹概率,再用 MathRand 平方采样挑训练片段,偏移上限是 Buffer[tr].Total - 2 - NForecast。外汇和贵金属行情高波动,这套采样在实盘前请在 MT5 策略测试器用历史数据跑一遍,观察过拟合概率。 把下面代码贴进你的 EA 的 decoder 构建函数,编译后看层是否按 4 层顺序 Add 成功;若返回 false 多半是 descr 没 new 出来或 OpenCL 设备不可用。
class=class="str">"cmt">//--- Input layer if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronBaseOCL; prev_count = descr.count = (BarDescr); descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">1 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronBatchNormOCL; descr.count = prev_count; descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">2 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronCrossDMHAttention; class=class="str">"cmt">//--- Windows { class="type">int temp[] = {BarDescr, BarDescr}; if(ArrayCopy(descr.windows, temp) < (class="type">int)temp.Size()) class="kw">return class="kw">false; } descr.window_out = class="num">32; class=class="str">"cmt">//--- Units { class="type">int temp[] = {class="num">1, HistoryBars}; if(ArrayCopy(descr.units, temp) < (class="type">int)temp.Size()) class="kw">return class="kw">false; } descr.step = class="num">4; class=class="str">"cmt">//Heads descr.layers = class="num">3; class=class="str">"cmt">//Layers descr.batch = class="num">1e4; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- layer class="num">4 if(!(descr = new CLayerDescription())) class="kw">return class="kw">false; descr.type = defNeuronBaseOCL; descr.count = BarDescr; descr.activation = None; descr.optimization = ADAM; if(!decoder.Add(descr)) { class="kw">delete descr; class="kw">return class="kw">false; } class=class="str">"cmt">//--- class="kw">return true; } class="type">void Train(class="type">void) { class=class="str">"cmt">//--- vector<class="type">class="kw">float> probability = GetProbTrajectories(Buffer, class="num">0.9); class=class="str">"cmt">//--- vector<class="type">class="kw">float> result, target, state; matrix<class="type">class="kw">float> predict; class="type">bool Stop = class="kw">false; class=class="str">"cmt">//--- class="type">uint ticks = GetTickCount(); for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !Stop); iter ++) { class="type">int tr = SampleTrajectory(probability); class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (Buffer[tr].Total - class="num">2 - NForecast));