交易中的神经网络:搭配预测编码的混合交易框架(终篇)·综合运用
「把周期相位塞进神经网络缓冲」
这段逻辑干的事很直接:用周线和日线的时间戳换算成相位,喂给一个叫 bAccount 的浮点缓冲。x = time / PeriodSeconds(PERIOD_W1) 得到的是自纪元起的周数,再乘 2π 取正弦,等于把价格时间轴压成了一个 [-1,1] 的周期性特征;日线那行同理,只是分母换成 PERIOD_D1,分辨率更细。 如果 bAccount 启用了 OpenCL 加速,代码会先尝试 BufferWrite() 把数据推到显存。写失败就打印函数名加行号、置 Stop 并 break——这说明在 GPU 模式下,缓冲同步是硬性前置,没写进去后面全白跑。 随后进入隐状态生成段:RelateEncoder、Short/Long 的编解码器依次 feedForward,最后由 LongShort 和 PredictRelate 做跨尺度融合。任意一步返回 false 都会触发同样的打印+中断。这里能验证的一点是,整套网络在前向时全用 train=false 的推断态(第三个参数 false),说明这是在用已训权重抽特征,不是反向更新。 Critic 部分先把目标动作 clip 到 [0,1] 再赋给 bActions,开 OpenCL 时同样先 BufferWrite 保底。Critic.TrainMode(true) 之后才做带动作输入的前向。外汇与贵金属杠杆高,这类模型特征若直接接实盘信号,回测漂移和过拟合风险都偏大,只建议拿来跑 MT5 策略测试器看曲线形状。
x = time / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1); bAccount.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0)); x = time / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1); bAccount.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0)); if(!!bAccount.GetOpenCL()) { if(!bAccount.BufferWrite()) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } } class=class="str">"cmt">//--- Generate Latent state if(!RelateEncoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, class="kw">false, (CBufferFloat*)NULL) || !ShortEncoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, class="kw">false, (CBufferFloat*)NULL) || !ShortDecoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, class="kw">false, (CNet*)GetPointer(ShortEncoder)) || !LongEncoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, class="kw">false, (CBufferFloat*)NULL) || !LongDecoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, class="kw">false, (CNet*)GetPointer(LongEncoder)) || !LongShort.feedForward(GetPointer(LongDecoder), -class="num">1, GetPointer(ShortDecoder), -class="num">1) || !PredictRelate.feedForward(GetPointer(LongShort), -class="num">1, GetPointer(RelateEncoder), -class="num">1) ) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } class=class="str">"cmt">//--- Critic target.Assign(Buffer[tr].States[i].action); target.Clip(class="num">0, class="num">1); bActions.AssignArray(target); if(!!bActions.GetOpenCL()) if(!bActions.BufferWrite()) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } Critic.TrainMode(true); if(!Critic.feedForward(GetPointer(PredictRelate), -class="num">1, (CBufferFloat*)GetPointer(bActions))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true;
◍ Critic-Actor 联动回传的容错断点
这段训练循环里,Critic 先拿相邻两步的 rewards 做差分:result 取 i+1 步、target 取 i+2 步,再减去 target 乘折扣因子 DiscFactor,得到时序误差后写回 Result 数组。任何一步 backProp 或 feedForward 返回 false,立刻 PrintFormat 打出函数名与行号,并把 Stop 置 true 后 break,避免脏梯度继续污染网络。 Actor 策略侧只在 result.Sum() >= 0 时才反向传播,依次穿过 PredictRelate、LongShort、ShortDecoder、ShortEncoder、LongDecoder、LongEncoder 的梯度通道。只要其中任一 backPropGradient 失败,同样走 PrintFormat + Stop + break 的硬退出路径。 值得在 MT5 里验证的细节:Critic.getResults 之后对 Result 每个元素做判断,value >= 0 时原地乘 1.01f。这个 1.01 的放大系数不是衰减而是轻微激励,意味着正 Critic 输出会被刻意推高,负向则原样保留——调小到 1.001 或改为条件乘子,可能改变策略对盈利样本的追逐强度。外汇与贵金属杠杆品种下,这类强化学习代理的决策具有高不确定性,实盘前务必用历史 tick 回测。
break; } result.Assign(Buffer[tr].States[i + class="num">1].rewards); target.Assign(Buffer[tr].States[i + class="num">2].rewards); result = result - target * DiscFactor; Result.AssignArray(result); if(!Critic.backProp(Result, (CBufferFloat *)GetPointer(bActions), (CBufferFloat *)GetPointer(bGradient))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } class=class="str">"cmt">//--- Actor Policy if(!Actor.feedForward(GetPointer(PredictRelate), -class="num">1, (CBufferFloat*)GetPointer(bAccount))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } Critic.TrainMode(class="kw">false); if(!Critic.feedForward(GetPointer(PredictRelate), -class="num">1, (CNet*)GetPointer(Actor), -class="num">1)) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } if(result.Sum() >= class="num">0) if(!Actor.backProp(GetPointer(bActions), (CBufferFloat*)GetPointer(bAccount), GetPointer(bGradient)) || !PredictRelate.backPropGradient(GetPointer(RelateEncoder), -class="num">1, -class="num">1, class="kw">false) || !LongShort.backPropGradient(GetPointer(ShortDecoder), -class="num">1, -class="num">1, class="kw">false) || !ShortDecoder.backPropGradient((CNet *)GetPointer(ShortEncoder), -class="num">1, -class="num">1, class="kw">false) || !ShortEncoder.backPropGradient((CBufferFloat*)NULL) || !LongDecoder.backPropGradient((CNet *)GetPointer(LongEncoder), -class="num">1, -class="num">1, class="kw">false) || !LongEncoder.backPropGradient((CBufferFloat*)NULL) ) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } Critic.getResults(Result); for(class="type">int c = class="num">0; c < Result.Total(); c++) { class="type">class="kw">float value = Result.At(c); if(value >= class="num">0) Result.Update(c, value * class="num">1.01f);
反向传播失败即停与半秒级误差播报
这段训练循环里,只要 Critic、Actor 或各编解码器的 backProp/backPropGradient 任一返回 false,就立刻 PrintFormat 打出函数名与行号、把 Stop 置真并 break,等于强制中断本轮强化学习。这种写法能在 MT5 日志里精确定位是哪一层网络梯度回传崩了,而不是闷头跑完无效迭代。 进度与误差展示靠 GetTickCount 节流:只有距上次刷新超 500 毫秒才重算 percent(iter*100.0/Iterations)并用 Comment 输出 Actor、Critic 的 getRecentAverageError,精度到 15.8f。实盘或回测时你能直接看到误差是不是在收敛,不用等跑完。 循环结束清掉 Comment,随后 PrintFormat 把最终 Actor、Critic 平均误差打到日志,并调 ExpertRemove 把 EA 从图表卸掉。外汇与贵金属杠杆高、模型误差不收敛时继续跑只会放大风险,看到误差横盘就该手动停而非等它自卸。
else Result.Update(c, value * class="num">0.99f); } if(!Critic.backProp(Result, (CNet *)GetPointer(Actor), LatentLayer) || !Actor.backPropGradient((CBufferFloat*)GetPointer(bAccount), GetPointer(bGradient)) || !PredictRelate.backPropGradient(GetPointer(RelateEncoder), -class="num">1, -class="num">1, class="kw">false) || !LongShort.backPropGradient(GetPointer(ShortDecoder), -class="num">1, -class="num">1, class="kw">false) || !ShortDecoder.backPropGradient((CNet *)GetPointer(ShortEncoder), -class="num">1, -class="num">1, class="kw">false) || !ShortEncoder.backPropGradient((CBufferFloat*)NULL) || !LongDecoder.backPropGradient((CNet *)GetPointer(LongEncoder), -class="num">1, -class="num">1, class="kw">false) || !LongEncoder.backPropGradient((CBufferFloat*)NULL) ) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } class=class="str">"cmt">//--- if(GetTickCount() - ticks > class="num">500) { class="type">class="kw">double percent = class="type">class="kw">double(iter) * class="num">100.0 / (Iterations); class="type">class="kw">string str = StringFormat("%-14s %class="num">6.2f%% -> Error %class="num">15.8f\n", "Actor", percent, Actor.getRecentAverageError()); str += StringFormat("%-14s %class="num">6.2f%% -> Error %class="num">15.8f\n", "Critic", percent, Critic.getRecentAverageError()); Comment(str); ticks = GetTickCount(); } } Comment(""); class=class="str">"cmt">//--- PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "Actor", Actor.getRecentAverageError()); PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "Critic", Critic.getRecentAverageError()); ExpertRemove(); class=class="str">"cmt">//--- }
「用次年首月数据验模型成色」
模型训练分两段走:先吃 2023 全年 EURUSD 的 H1 历史数据,指标全用默认值,只喂环境状态、不掺智能体动作,跑到误差稳在窄区间就停;第二段才迭代优化行为策略,并周期性把当前策略产生的新数据补进训练集。 验证环节直接交给 MT5 策略测试器,拿 2024 年 1 月这段紧接训练区间的行情跑。外汇与贵金属属高风险品种,样本外表现仅代表该时段概率倾向,不等于可持续。 实测跑了 15 笔交易,10 笔获利,胜率 66% 出头。更关键的是平均盈利交易约为平均亏损的 4 倍,权益曲线整体向上倾斜——这种盈亏比结构比单纯胜率更值得在 MT5 里复跑确认。
◍ 记住这一条就够了
StockFormer 把预测编码和三分支结构揉进 MT5,短期趋势、长期变化与跨资产关系三类潜在表现经多头注意力级联成统一状态空间,实盘决策不再只盯单品种。我们在 MQL5 里落地了 Research / Study1 / Study2 / Test 等八个模块,用真实历史跑通了训练到测试闭环,结果指向该方法在样本内有效。 但样本内有效不等于能直接上外汇或贵金属实盘,这两类品种高杠杆、跳空频繁,模型必须在更长的历史集上重训并做穿透测试才可能站得住。把 ZIP 里八个文件拖进 MT5 先跑一遍 Research.mq5 收集样本,比任何口头结论都实在。