神经网络变得轻松(第四十四部分):动态学习技能·综合运用
强化学习回放里的状态推进与反向传播
这段逻辑跑在经验回放的遍历循环里,针对某个 transition 把当前状态喂给 Actor 网络做前向推理;若 feedForward 返回 false,直接打印函数名与行号、移除 Expert 并 break,说明网络推理异常时不再继续训练该批次。 推理成功后,用 GetAgentReward 拿到该步奖励写入 Result,再把下一帧状态读入 StateSkill,并通过 GetNewState 依据 Actor 动作与单级利润 prof_1l 生成新账户向量 account。 shift 按 skill*AccountDescr 偏移,随后把账户的相对变化量逐维追加进 StateSkill:包括余额变化率 (account[0]-PrevBalance)/PrevBalance、权益占比 account[1]/PrevBalance、权益回撤率 (account[1]-PrevEquity)/PrevEquity,以及持仓数、空闲保证金等 8 个归一化特征。这些维度直接决定下一状态表征是否包含足够的资金曲线信息。 最后调 Actor.backProp 做反向传播,DiscountFactor 控制奖励衰减;同样一旦失败就打印并退出训练循环。外汇与贵金属杠杆高,这类自学习模块在实盘前必须用 MT5 策略测试器跑至少 3 个月 Tick 数据验证数值稳定性。
if(!Actor.feedForward(GetPointer(State), class="num">1, false)) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); ExpertRemove(); break; } reward = GetAgentReward(skill, DiscriminatorResult, Buffer[tr].States[i].account); Result.AssignArray(reward); StateSkill.AssignArray(Buffer[tr].States[i + class="num">1].state); account = GetNewState(Buffer[tr].States[i].account, Actor.getAction(), prof_1l); shift = skill * AccountDescr; StateSkill.Add((account[class="num">0] - PrevBalance) / PrevBalance); StateSkill.Add(account[class="num">1] / PrevBalance); StateSkill.Add((account[class="num">1] - PrevEquity) / PrevEquity); StateSkill.Add(account[class="num">2] / PrevBalance); StateSkill.Add(account[class="num">4] / PrevBalance); StateSkill.Add(account[class="num">5]); StateSkill.Add(account[class="num">6]); StateSkill.Add(account[class="num">7] / PrevBalance); StateSkill.Add(account[class="num">8] / PrevBalance); if(!Actor.backProp(Result, DiscountFactor, GetPointer(StateSkill), class="num">1, false)) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); ExpertRemove(); break; } } break; case class="num">2: if(!Scheduler.feedForward(GetPointer(State), class="num">1, false)) {
◍ 回测循环里的状态回收与止损退出
在强化学习驱动的 MT5 回测框架里,每一根 K 线推进后都要先拉取调度器的回报并塞进状态容器:Scheduler.getResults 拿到本次收益向量,State.AddArray 把它接在原有特征后面。若中途 IsStopped() 命中,说明用户点了终止或测试被强制打断,这时打印函数名加行号、调 ExpertRemove() 再 break,EA 会干净退出而不是卡在死循环。 feedForward 这一步把状态指针喂给 Actor 网络做前向推理,参数 1 表示单步、false 代表不训练模式;返回 false 同样触发打印+移除+跳出。拿到 action 后,GetNewState 依据账户快照和动作算出下一刻的余额、权益等 9 维账户数组,其中 account[0] 为余额、account[1] 为权益、account[4]~[8] 为持仓与浮盈类指标。 回报折算用 SchedulerResult = SchedulerResult * (account[0] / PrevBalance - 1.0),即把绝对收益转成以_prevBalance 为基准的收益率再乘原回报。随后 State 连续 Add 七个比值特征:如 (account[0]-PrevBalance)/PrevBalance 是余额变化率,account[5]、account[6] 则是未缩放的原始字段。这些拼接好的 State 直接进 backProp 做反向传播,DiscountFactor 控制远期回报衰减。 开 MT5 把这段粘进你的回测 EA,故意在循环里改 PrevBalance 为 0 看是否触发除零崩溃,就能验证状态拼接顺序是否和你的特征维度对齐。外汇与贵金属回测天然高杠杆高风险,这类收益率折算在极端滑点下可能严重偏离实盘。
PrintFormat("%s -> %d", __FUNCTION__, __LINE__); ExpertRemove(); break; } Scheduler.getResults(SchedulerResult); State.AddArray(SchedulerResult); if(IsStopped()) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); ExpertRemove(); break; } if(!Actor.feedForward(GetPointer(State), class="num">1, false)) { PrintFormat("%s -> %d", __FUNCTION__, __LINE__); ExpertRemove(); break; } action = Actor.getAction(); account = GetNewState(Buffer[tr].States[i].account, action, prof_1l); SchedulerResult = SchedulerResult * (account[class="num">0] / PrevBalance - class="num">1.0); Result.AssignArray(SchedulerResult); State.AssignArray(Buffer[tr].States[i + class="num">1].state); State.Add((account[class="num">0] - PrevBalance) / PrevBalance); State.Add(account[class="num">1] / PrevBalance); State.Add((account[class="num">1] - PrevEquity) / PrevEquity); State.Add(account[class="num">2] / PrevBalance); State.Add(account[class="num">4] / PrevBalance); State.Add(account[class="num">5]); State.Add(account[class="num">6]); State.Add(account[class="num">7] / PrevBalance); State.Add(account[class="num">8] / PrevBalance); if(!Scheduler.backProp(Result, DiscountFactor, GetPointer(State), class="num">1, false))
「训练循环里的退出与进度播报」
上面这段是 GAN 式训练主循环末尾的控制逻辑,重点不在算法本身,而在怎么让 EA 不卡死、出错了能自尽。 遇到不可恢复分支时,先用 PrintFormat 把当前函数和行号打出来,便于定位;随后 ExpertRemove() 直接卸载 EA,break 跳出内层循环。这种写法比抛异常更干净,MT5 回测时不会卡在死循环里空耗。 default 分支专门接住未定义的 phase 值,PrintFormat 输出 'Wrong phase %d' 并显示具体数值,方便你查状态机是不是漏了 case。 进度播报靠 GetTickCount() 做节流:每过 500 毫秒才刷新一次 Comment。str 里用 StringFormat 把 Scheduler 和 Discriminator 的近期平均误差按 '%-15s %5.2f%% -> Error %15.8f' 格式拼好,iter*100.0/Iterations 给出整数百分比进度,误差精确到小数点后 8 位。外汇与贵金属模型训练波动大,这类误差数值仅反映局部收敛状态,过拟合概率不低,实盘前务必用历史数据交叉验证。
{
PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
ExpertRemove();
break;
}
break;
class="kw">default:
PrintFormat("Wrong phase %d", phase);
break;
}
}
}
if(GetTickCount() - ticks > class="num">500)
{
class="type">class="kw">string str = StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "Scheduler",
iter * class="num">100.0 / (class="type">class="kw">double)(Iterations), Scheduler.getRecentAverageError());
str += StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "Discriminator",
iter * class="num">100.0 / (class="type">class="kw">double)(Iterations), Discriminator.getRecentAverageError());
Comment(str);
ticks = GetTickCount();
}
}用 5 月外推样本压调度器模型
模型在 EURUSD H1 上用 2023 年前 4 个月历史训练,指标走默认参数,和前一篇训练设定一致,这样两种技能训练方法的结果才可直接比对。 测试放在 MT5 策略测试器里跑 2023 年 5 月,样本完全在训练区间之外,时间长度约为训练集的 25%,属于典型外推检验。 外推结果:盈利因子 1.75,恢复因子 0.85,盈利交易占比 52.64%;平均盈利 57.37 点,平均亏损 -1.90 点,盈亏比约 2.99:1。所有技能都被动用过,不存在某技能被闲置。 代理者动作按最大预测奖励做贪婪式选择,调度器因此输出完整概率分布、几乎消除了采样期的随机性——这也正是训练调度器的方式。 换用贪婪式技能选择后,盈利因子抬到 1.80,盈利占比 +0.91% 至 53.55%,平均盈利交易增至 3.08。外汇与贵金属属高风险品种,以上为历史样本统计,后续实盘表现可能偏离。
◍ 演示模型离实盘还有多远
DADS 这套无监督技能探索在 MT5 里跑通了,构建的模型在训练集之外也展现出盈利倾向,说明动态感知技能的确能学到可预测、易调度的行为模式。 但必须点明:外汇和贵金属是高杠杆高风险品种,文中所有 MQL5 程序只是演示算法工作方式,没有经过实盘风控与滑点校验,直接挂真仓可能迅速回撤。 想验证的人,开 MT5 把文中神经网络模块加载到 EURUSD 十五分钟图,先跑离线回测看技能聚类的稳定性,别急着接订单执行。
「随包附带的七个文件」
这套 LSTM 预测方案不是单文件 EA,而是拆成了 7 个组件一起跑。前三个是智能交易系统:Research.mq5 负责在策略测试器里收集样本,Study.mq5 在真实模式下做模型训练,Test.mq5 用来回测验证;后四个是类库,Trajectory.mqh 定义系统状态结构,FQF.mqh 管完全参数化模型的工作调度,NeuroNet.mqh 封装神经网络创建逻辑,NeuroNet.cl 则是 OpenCL 端的核函数代码库。 压缩包 MQL5.zip 体积约 1176.93 KB,直接在 MT5 里解压到 MQL5 目录就能看到对应文件。不少人在测试时碰到过 OnInit 返回非零代码 1 的报错,根因往往是没先跑 Research.mq5 采集样本就直接上 Study 或 Test——顺序错了 EA 找不到前置数据文件就会初始化失败。 外汇与贵金属市场杠杆高、滑点跳空频繁,这类神经网络模型在历史样本上表现好,不等于实盘能稳定复现,任何信号都只是概率倾向。开 MT5 前先确认显卡 OpenCL 可用,否则 NeuroNet.cl 那段并行训练会直接拖垮回测速度。