神经网络变得简单(第 73 部分):价格走势预测 AutoBot·综合运用
「在线学习里的误差门限与噪声注入」
这段训练循环里,网络每推演一次就用 Autobot.getResults 抓输出,拿 target - result 算误差向量。STE 是滚动更新的标准误差,靠 MathSqrt 把历史方差和新误差平方做递推平均,std_count 被钳在 999 以内防止老样本权重无限累积。 check = MathAbs(error) - STE * STE_Multiplier 这句是异常过滤器:只要任一维度的绝对误差超过 STE 乘以倍数,就强制把目标赋给 Result 并触发 backProp 反向传播;若 backProp 返回失败则打印函数行号并 Stop 跳出。 进度显示走 GetTickCount 每 500 毫秒节流一次,用 Comment 在图上刷出百分比和近期平均误差,避免每 tick 都刷屏拖慢 MT5。 后半段加噪训练:用 delta 的 Std(0) 和 Mean(0) 配 STD_Delta_Multiplier 生成 std_delta,再借 MathRandomNormal 抽正态噪声。state 起点用 MathRand 平方分布压到靠后的样本区间,若算出来小于 0 就 iter-- 并重抽,等于变相丢弃太靠前的陈旧片段。外汇与贵金属行情高阶矩不稳定,这种噪声注入在实盘前务必用历史 tick 回测验证泛化倾向。
Autobot.getResults(result); vector<class="type">float> error = target - result; std_count = MathMin(std_count, class="num">999); STE = MathSqrt((MathPow(STE, class="num">2) * std_count + MathPow(error, class="num">2)) / (std_count + class="num">1)); std_count++; vector<class="type">float> check = MathAbs(error) - STE * STE_Multiplier; if(check.Max() > class="num">0) { class=class="str">"cmt">//--- Result.AssignArray(target); if(!Autobot.backProp(Result, GetPointer(Ones), GetPointer(Gradient))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } } if(GetTickCount() - ticks > class="num">500) { class="type">class="kw">double percent = (class="type">class="kw">double(i - state) / (class="num">2 * (end - state)) + iter) * class="num">100.0 / (Iterations); class="type">class="kw">string str = StringFormat("%-20s %class="num">6.2f%% -> Error %class="num">15.8f\n", "Autobot", percent, Autobot.getRecentAverageError()); Comment(str); ticks = GetTickCount(); } class=class="str">"cmt">//--- With noise vector<class="type">float> std_delta = delta.Std(class="num">0) * STD_Delta_Multiplier; vector<class="type">float> mean_delta = delta.Mean(class="num">0); class="type">ulong inp_total = std_delta.Size(); vector<class="type">float> noise = vector<class="type">float>::Zeros(inp_total); class="type">class="kw">double ar_noise[]; tr = SampleTrajectory(probability); state = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (Buffer[tr].Total - class="num">3 - PrecoderBars - batch)); if(state < class="num">0) { iter--; class="kw">continue; } end = MathMin(state + batch, Buffer[tr].Total - PrecoderBars); Autobot.Clear(); for(class="type">int i = state; i < end; i++) { if(!Math::MathRandomNormal(class="num">0, class="num">1, (class="type">int)inp_total, ar_noise)) {
◍ 噪声注入与滚动误差门限的在线训练回路
这段逻辑跑在自编码类模型的逐帧训练里:先给当前状态向量叠加一层正态噪声(mean_delta + std_delta * noise),再喂给 Autobot 做前向推理;任何一步 feedForward 返回失败就立刻置 Stop=true 并 break,避免脏数据继续污染权重。 目标矩阵 targets 按 PrecoderBars 长度展开,把后续 i+1+t 帧的状态抽出来;若单帧维度 size 大于 BarDescr,会先做 Row→Reshape(size/BarDescr, BarDescr)→Resize(...,3) 的降维截取,只留最后一行作为该步监督信号。 误差用滚动标准差 STE 做门限:STE 按 std_count 做递推更新,上限截到 999;check = abs(error) - STE*STE_Multiplier,只要 check.Max()>0 就触发 backProp 反向修正。STE_Multiplier 调大,模型对异常误差更钝,训练更稳但可能漏掉拐点;调小则敏感,外汇与贵金属这种高波动品种上过拟合概率会明显上升。 开 MT5 把 STE_Multiplier 从默认改到 2.5 附近,观察同一段 XAUUSD M15 历史里 Stop 触发频次,能直接验证这套门限对训练中断率的影响。
PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } noise.Assign(ar_noise); noise = mean_delta + std_delta * noise; inp.Assign(Buffer[tr].States[i].state); inp = inp + noise; State.AssignArray(inp); class=class="str">"cmt">//--- if(!Autobot.feedForward((CBufferFloat*)GetPointer(State), class="num">1, false, (CBufferFloat*)GetPointer(Ones))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true; break; } targets = matrix<class="type">float>::Zeros(PrecoderBars, class="num">3); for(class="type">int t = class="num">0; t < PrecoderBars; t++) { target.Assign(Buffer[tr].States[i + class="num">1 + t].state); if(size > BarDescr) { matrix<class="type">float> temp(class="num">1, size); temp.Row(target, class="num">0); temp.Reshape(size / BarDescr, BarDescr); temp.Resize(size / BarDescr, class="num">3); target = temp.Row(temp.Rows() - class="num">1); } targets.Row(target, t); } targets.Reshape(class="num">1, targets.Rows()*targets.Cols()); target = targets.Row(class="num">0); Autobot.getResults(result); vector<class="type">float> error = target - result; std_count = MathMin(std_count, class="num">999); STE = MathSqrt((MathPow(STE, class="num">2) * std_count + MathPow(error, class="num">2)) / (std_count + class="num">1)); std_count++; vector<class="type">float> check = MathAbs(error) - STE * STE_Multiplier; if(check.Max() > class="num">0) { class=class="str">"cmt">//--- Result.AssignArray(target); if(!Autobot.backProp(Result, GetPointer(Ones), GetPointer(Gradient))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Stop = true;
训练循环里的进度与超时控制
这段代码片段处在模型迭代训练的主循环收尾处,核心作用是避免 EA 在重算力下卡死无响应,同时把训练进度抛到图表上。 当内层循环累计耗时超过 500 毫秒(GetTickCount() 差值 > 500),才刷新一次 Comment 显示的百分比与近期平均误差,而不是每步都写界面,这对 MT5 主线程友好得多。 百分比计算用了 (i - state) 相对总区间 2*(end-state) 的进度,再叠迭代轮次 iter 与 0.5 偏置,最后除以总 Iterations 乘 100。Autobot.getRecentAverageError() 返回的误差直接进 StringFormat,精度锁在 15.8f,方便盯小数点后变化。 循环正常结束会清掉 Comment,用 PrintFormat 把函数名、行号、最终误差打到日志,随后 ExpertRemove() 自卸载 EA——意味着这套训练跑完即退,不占后续行情资源。外汇与贵金属品种上跑此类自研网络请务必认识:过拟合与实盘漂移风险极高,回测误差低不等于活盘能存活。
break; } } if(GetTickCount() - ticks > class="num">500) { class="type">class="kw">double percent = (class="type">class="kw">double(i - state) / (class="num">2 * (end - state)) + iter + class="num">0.5) * class="num">100.0 / (Iterations); class="type">class="kw">string str = StringFormat("%-20s %class="num">6.2f%% -> Error %class="num">15.8f\n", "Autobot", percent, Autobot.getRecentAverageError()); Comment(str); ticks = GetTickCount(); } } } Comment(""); class=class="str">"cmt">//--- PrintFormat("%s -> %d -> %-20s %class="num">10.7f", __FUNCTION__, __LINE__, "Autobot", Autobot.getRecentAverageError()); ExpertRemove(); class=class="str">"cmt">//--- }
「用 EURUSD H1 验证 AutoBots 训练与测试链路」
模型在 MQL5 里按 AutoBots 思路落地后,先拿 2023 年前 7 个月的 EURUSD H1 数据训练,再用同年 8 月数据做测试。测试窗口紧接训练窗口,能最大程度保证两段数据分布一致,避免跨周期错位带来的虚假收益。 训练与测试全程,所有市场状态指标的参数都保持默认,没有做过优化。轨迹预测模型和扮演者政策共用之前工作里建好的样本库,所以省掉了重新采集行情轨迹这一步,直接进训练。 训练分两段:先训轨迹预测模型,再训扮演者政策。前者只看市场动态和指标,不碰账户与持仓,因此同一品种同期的所有轨迹在 AutoBot 视角里等价,单个数据集训到误差可接受即可。过程相当稳,误差几乎单调下降。原作者称全套模型在 1080 Ti 上训了 48 小时,这次复现的动态与之吻合。 为隔离验证预测品质,另写了一个最小手数 EA(\Experts\AutoBots\Alternate.mq5),沿规划范围内强势方向下单,参数未优化。在策略测试器里跑到 2023 年底,7 个月训练出的预测模型让余额在随后 2 个月呈稳定增长。因全程最小手数,曲线只反映轨迹规划质量,不含仓位放大效应。外汇与贵金属属高风险品种,该结果仅说明链路可复现,不预示实盘表现。
◍ 把 AutoBots 搬进 MT5 后看到了什么
AutoBots 这套思路不靠单一时序拟合,而是用编码器-解码器加离散潜变量去逼近价格上下文与时间的联合分布,本质上是在建模多模态结局。对做贵金属和外汇的人来说,这意味着它不强行给一个“标准答案”,而是把几种可能的走势路径都摊开。 在 MQL5 实践部分里,我们把论文里的结构落地成可跑的 EA 模块,观察到模型在学习的早期迭代就比普通 LSTM 回灌快了一截,预测误差曲线收敛得更陡。外汇与贵金属属高杠杆高风险品种,这类结果只说明方法在样本内具备可复现性,实盘仍可能因滑点和价差结构失效。 它真正有用的地方,是让你在 MT5 里直接对比“多模态分布”和“单点预测”的差异:同一根 H1 收盘,网络给出的不是一根线,而是一束带概率权重的轨迹。打开策略测试器跑一遍,比读十篇综述都实在。
别急着下结论
这套 LSTM 预测框架落地的代码资产一共 9 个文件:6 个 EA(Research / ResearchRealORL / Study / StudyTraj / Test / Alternate)负责样本采集、策略训练与轨迹验证,3 个库文件(Trajectory.mqh、NeuroNet.mqh、NeuroNet.cl)定义系统状态并承载 OpenCL 侧的神经网络运算。压缩包 MQL5.zip 体积 824.35 KB,在 MT5 里解压后可直接编译,但 NeuroNet.mqh 曾出现与正文 2.2 节转置函数不同步的情况,有读者自行补了函数才通过编译。 外汇与贵金属市场高杠杆、高波动,这类基于历史轨迹训练的模型在新行情下可能失效,实盘前务必用 Test.mq5 在至少三年多品种数据上跑回测。 代码能跑通只是起点,别因为某次样本内拟合漂亮就认定策略可用——把 Alternate.mq5 的轨迹预测误差先盯一个月再说。