神经网络变得轻松(第五十部分):软性扮演者-评价者(模型优化)·综合运用
(3/3)· 前两次实现跑不出盈利模型,问题卡在采样与学习分布脱节,本篇补齐最后一块拼图
◍ 账户特征与双评论家网络的奖励回传
这段逻辑把下一状态的账户信息压进特征向量,再喂给双评论家网络算 TD 目标。account[3] 是绝对数值直接入栈,account[4]~[6] 都除以 PrevBalance 做归一,避免不同本金规模把网络带偏。 时间周期特征用了正余弦编码:先拿 account[7](累计 tick 或时长类字段)除以 2023.01.01 到 2024.01.01 的秒数,再分别对月、周、日线周期 PeriodSeconds 求比值,用 2*M_PI*x 跑 sin/cos。这种周期嵌入能让模型感知'年中/周末'这类节律,外汇和贵金属受周期切换影响明显,属高风险品种,节律错位可能直接放大回撤。 奖励项由 TargetCritic1 和 TargetCritic2 各算一次,取两者最小值减旧奖励估值为保守 TD 误差,再叠加 log_prob.Sum()/PrevBalance 的策略熵项——LogProbMultiplier 控制探索强度。 每偶数次迭代(iter%2==0)才反向更新:Critic1 前向失败就 PrintFormat 打行号并 break,成功则冻 TrainMode 后 backProp,AlphasGradient 调温度系数,Actor 再借 Account 与 Gradient 回传策略梯度。开 MT5 把这段接进你的 EA,先盯 iter 偶数步的 AlphasGradient 是否触发,能验证双评论家结构有没有真在学。
Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">3]); Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">4] / PrevBalance); Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">5] / PrevBalance); Account.Add(Buffer[tr].States[i + class="num">1].account[class="num">6] / PrevBalance); class="type">class="kw">double x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)(D&class="macro">#x27;class="num">2024.01.class="num">01&class="macro">#x27; - D&class="macro">#x27;class="num">2023.01.class="num">01&class="macro">#x27;); Account.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0)); x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_MN1); Account.Add((class="type">class="kw">float)MathCos(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0)); x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1); Account.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0)); x = (class="type">class="kw">double)Buffer[tr].States[i + class="num">1].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1); Account.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0)); class=class="str">"cmt">//--- TargetCritic1.getResults(Result); class="type">class="kw">float reward = Result[class="num">0]; TargetCritic2.getResults(Result); reward = Buffer[tr].Revards[i] + DiscFactor * (MathMin(reward, Result[class="num">0]) - Buffer[tr].Revards[i + class="num">1] + LogProbMultiplier * log_prob.Sum() / (class="type">class="kw">float)PrevBalance); class=class="str">"cmt">//--- if((iter % class="num">2) == class="num">0) { if(!Critic1.feedForward(GetPointer(Actor), LatentLayer, GetPointer(Actor)) || !Critic2.feedForward(GetPointer(Actor), LatentLayer, GetPointer(Actions))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); class="kw">break; } Critic1.getResults(Result); Actor.GetLogProbs(log_prob); Result.Update(class="num">0, reward); Critic1.TrainMode(class="kw">false); if(!Critic1.backProp(Result, GetPointer(Actor)) || !Critic1.AlphasGradient(GetPointer(Actor)) || !Actor.backPropGradient(GetPointer(Account), GetPointer(Gradient), LatentLayer)) ||
「奖励回传与目标网络软更新」
这段片段处在强化学习训练回路的尾段:当 Actor 的反向传播失败时,直接打印函数名与行号并跳出,同时把 Critic1 切回训练模式,避免网络被冻结在推理态。 Critic1 恢复训练后,用 reward 减去 LogProbMultiplier 乘的对数概率均值(除以前一余额做归一)写入 Result 的第 0 位;若 Critic2 的反向传播也失败则同样打印并 break。随后通过 TargetCritic2.WeightsUpdate 以 Tau 系数做软更新,目标网权重缓慢跟踪在线网。 回测出口由 OnTester 控制:input double MinProfit = 10 设定最低盈利门槛,只有 TesterStatistics(STAT_PROFIT) 大于等于 10 且非零时,才用 FrameAdd 把 profit 写入优化帧。这意味着遗传优化里低于 10 净利的参数组合不会进入帧传递,过滤掉 marginal 结果。 外汇与贵金属市场高杠杆、滑点随机,这套 RL 训练逻辑在 MT5 策略测试器里跑出来的帧数据,只能作为概率参考而非稳定收益凭据。
!Actor.backPropGradient(GetPointer(Account), GetPointer(Gradient))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); Critic1.TrainMode(true); class="kw">break; } Critic1.TrainMode(true); Result.Update(class="num">0, reward - LogProbMultiplier * log_prob.Sum() / (class="type">class="kw">float)PrevBalance); if(!Critic2.backProp(Result, GetPointer(Actions), GetPointer(Gradient))) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); class="kw">break; } class=class="str">"cmt">//--- Update Target Nets TargetCritic2.WeightsUpdate(GetPointer(Critic2), Tau); } class="kw">input class="type">class="kw">double MinProfit = class="num">10; class="type">class="kw">double OnTester() { class=class="str">"cmt">//--- class="type">class="kw">double ret = class="num">0.0; class=class="str">"cmt">//--- class="type">class="kw">double profit = TesterStatistics(STAT_PROFIT); Frame[class="num">0] = Base; if(profit >= MinProfit && profit != class="num">0) FrameAdd(MQLInfoString(MQL_PROGRAM_NAME), class="num">1, profit, Frame); class=class="str">"cmt">//--- class="kw">return(ret); }
跳出训练集后的真实表现
模型在训练区间内迭代约 15 轮,覆盖样本库更新、重训与盈利门槛上调后,历史内样本能稳定产出利润。但真正该警惕的是样本外泛化——我把训练截止后的 2023 年 6 月数据喂进去,看它能不能在没见过的数据上活下来。 这一个月里模型只开了 4 笔多头,仅 1 笔盈利。表面看胜率 25% 挺糟,但余额曲线说了另一回事:3 笔亏单合计吐掉 300 美元,起始本金 10,000 美元;那 1 笔盈利单捞回超 2,000 美元。月末净值 +17.5%,盈利因子 6.77,恢复因子 1.32,余额回撤压在 1.65%。 外汇与贵金属这类高杠杆品种,单月仅 4 笔同向交易、样本外稀疏信号本就暗示过拟合风险。比起纠结「交易够不够多、多空全不全」,不如盯死余额的最终偏移——少而精的单向信号,也可能在概率倾斜下给出正期望,只是回撤路径未必平滑。
◍ 架构不是唯一解,试错才是常态
这套软性扮演者-评论者框架只是把盈利策略的训练往前推了一步,但别把它当终点。原文作者也承认,成果模型「优秀与否」是相对的,没法给绝对结论。 论坛里用户 JimReaper 提过另一套模型架构,作者没测过但承认可行。这说明 SAC 类方案不是唯一路径,换架构、调超参、加新数据,都可能让模型效率往上走。 外汇和贵金属这类高波动市场接 RL 模型,高风险始终在:过拟合和历史分布偏移会直接吃掉纸面收益。真要落地,建议自己在 MT5 里搭个小型 SAC 代理,先用 EURUSD 的 M1 历史跑一遍熵系数扫描。 探索不该停。新数据接入、不同 critic 结构,都是可验证的改进点,跑一次回测比争论架构孰优孰劣实在。
「随包附带的六个文件」
这套 LSTM 优化方案不是只给思路,而是把整套训练链路拆成了可直接跑的 MT5 程序。压缩包 MQL5.zip(1721.81 KB)里一共 6 个文件,分工很明确:Research.mq5 负责样本收集,Study.mq5 做代理者训练,Test.mq5 跑模型测试;后三个是支撑库,Trajectory.mqh 定义系统状态结构,NeuroNet.mqh 封装建网类,NeuroNet.cl 则是 OpenCL 侧的算子代码。 开 MT5 把这三个 EA 依次挂上同一品种,先 Research 攒轨迹、再 Study 训、最后 Test 验,是最低成本的复现路径。有读者在 90 个循环后反馈模型仍偏随机,作者原话是「Actor 随机性随学习深入而减小,但可能不会完全消失」——外汇与贵金属杠杆高,这类强化学习策略实盘前务必用历史数据多周期压测。 评论区还提到 Test.mq5 会保存无交易的 pass,并在奖励计算里施以惩罚,逼模型脱离「画直线、余额零波动」的退化区;而 Research.mq5 有检查机制不存空 pass。如果你本地跑出整库都是零收益轨迹,别急着删库,让 Test 先记着惩罚项继续喂,比手动挪模型更稳。