神经网络变得简单(第 97 部分):搭配 MSFformer 训练模型·综合运用
「训练循环里的误差缩放与超时打印」
这段逻辑处在强化学习训练主循环尾部,核心是对 Critic 输出做不对称缩放:非负值乘 1.01,负值乘 0.99,相当于给正反馈轻微放大、负反馈轻微衰减,倾向让网络对盈利状态更敏感。 Actor 与 Critic 的反向传播任一步失败就打印函数名与行号、置 Stop 并 break,避免在 MT5 上无声卡死。这种 __FUNCTION__ 配合 __LINE__ 的报错方式,开终端看 Experts 日志能直接定位到行。 每累计tick超过 500 毫秒,用 Comment 在图表刷一次进度:iter 占总 Iterations 的百分比,以及 Actor/Critic 的近期平均误差,精度到 8 位小数。若你跑黄金 5 分钟图训练,把 500 调小到 200 可能更跟手,但会多吃 CPU。 循环结束清 Comment,PrintFormat 打出最终 Actor 与 Critic 误差后调 ExpertRemove 自卸,防止 EA 反复 OnInit。外汇与贵金属波动剧烈,这类自训练 EA 实盘前务必在策略测试器跑够样本,误差收敛只是概率信号,不等于未来收益。
PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; class="kw">break; } if(Buffer[tr].States[class="num">0].rewards[class="num">0] > class="num">0) if(!Actor.backProp(GetPointer(bActions), GetPointer(Encoder), LatentLayer)) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; class="kw">break; } Critic.getResults(Result); for(class="type">int c = class="num">0; c < Result.Total(); c++) { class="type">float value = Result.At(c); if(value >= class="num">0) Result.Update(c, value * class="num">1.01f); else Result.Update(c, value * class="num">0.99f); } if(!Critic.backProp(Result, (CNet *)GetPointer(Encoder), LatentLayer) || !Actor.backPropGradient((CNet *)GetPointer(Encoder), LatentLayer, -class="num">1, true)) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; class="kw">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">//--- }
◍ 用策略测试器喂出第一份训练集
真正能用的训练数据,得让智能体跟环境实打实碰过才有意义。在 MT5 里跑策略测试器,按历史数据回测并把每步状态、动作、奖励落盘到训练文件,是最直接的采集路径。 手工标理想交易能凑出正样本,但集子一大,人力成本和出错率都随规模线性往上走,人为偏差会直接污染标签。我平时用 Real-ORL 框架拉初始数据,交互程序在系列前篇和附件里都已给全,本小节不重复贴。 初始集只让模型对环境有个粗轮廓。金融市场的多面性决定任何离线集都覆盖不全,学到的指标—盈利依赖可能是错漏的,因为缺反例。所以训练中途必须持续扩集。 这阶段目标是优化参与者策略。我们要采到贴近当前策略轨迹的数据,看清动作偏一点时奖励往哪走,才能朝回报最大化推。随机策略就多跑几遍通验让随机头铺开动作空间;判定式策略就往动作里加扰动,围出云状动作集。两种做法都适合用测试器慢速优化模式补数据。 外汇和贵金属杠杆高、跳空频繁,这种采集方式只解决数据来源,不承诺样本外泛化,集子质量仍要在 MT5 里反复验证。
用 EURUSD 历史数据训练编码器与参与者
把前面梳理过的算法落到过程上:模型用 EURUSD 真实历史、H1 周期、2023 全年数据训练。先训环境状态编码器,它只吃历史价格走势和训练期分析的指标,同一历史窗内通验数据一致,所以初始集上无需优调,跑到预测误差不再降且波动收窄就停。 单纯看误差不够,我抽了训练集子集比预测和真实走势:模型抓住了主趋势,曲线平滑微抖,容易让人怀疑它只是背了整体趋势。换一组状态再比,预测波动更明显但仍贴着实际值,假设没坐实。 编码器就绪后进第二阶段,迭代训参与者政策。首轮在初始集上让模型认环境,Real-ORL 收集的可盈利运行打了底。这阶段盯评论者误差——评论者依赖关系建对了,参与者才调得对方向。评论者误差稳住后,去策略测试器跑 Research.mq5 收新数据,建议慢速优化。 新数据进来重训时,两个模型误差会先小涨再探新低,循环扩集重训即可。参与者带随机头,测试时同一周期要多跑几次通验,偏差可忽略才算训完。 我在 2024 年 1 月(非训练集)测了模型,区间邻近训练窗、数据可比。测试完成 37 笔、17 笔盈利,胜率近 46%,多空盈利份额差 0.32%;盈利因子 1.14,最大与平均盈利均高于亏损。但利润集中在前半月,后半月横盘、末周回撤。外汇与贵金属属高风险,样本外表现可能随市场结构变化而衰减,MT5 慢速优化跑一遍最能验证这套流程。
「训练尾段的回撤信号」
MSFformer 这套多尺度特征提取的训练与测试跑下来,整体样本外表现倾向合格,说明用多尺度聚合做时间序列预测在 FX/贵金属行情里是有可行性的。 但有个细节不能忽略:测试期最后一周出现了余额回撤。这个回撤幅度虽未击穿前期低点,却提示模型在临近样本边界时可能过拟合,或市场状态发生了切换。 碰到这种尾段回撤,较务实的做法是补一段额外训练,而不是直接上实盘。MT5 里把那段回撤周单独切片,重新喂回多尺度提取流程,看损失曲线是否收敛再决定。外汇与贵金属杠杆高,样本外回撤随时可能放大,任何训练结论都只是概率倾向。
◍ 别急着下结论
上面列的那套程序不是拿来直接跑就赚钱的。Research.mq5 到 Test.mq5 五个 EA 只负责把行情切成数据集、训模型、测误差,真正落地前还得靠 Trajectory.mqh 定义状态、NeuroNet.mqh 搭网络、NeuroNet.cl 在 GPU 上算。 整套压缩包 MQL5.zip 体积 1378.56 KB,包含了俄文原版迁移过来的全部源码与类库,但作者明确写了不对策略准确性负责,外汇和贵金属杠杆品种用这套任何输出都只是概率倾向,回测飘红不代表实盘能活。 所以拿到 ZIP 先别急着重构,开 MT5 把 Research.mq5 单独挂一个 symbol 跑一周,看它落地的样本维度跟你自己品种匹不匹配,再谈后面的训练管道。