神经网络变得轻松(第三十三部分):分布式 Q-学习中的分位数回归·综合运用
📘

神经网络变得轻松(第三十三部分):分布式 Q-学习中的分位数回归·综合运用

第 3/3 篇

「特征拼装与奖励标记的循环细节」

这段逻辑处在历史样本遍历里,先把 MACD 主线与信号线取值转成 float,任一指标返回 EMPTY_VALUE 就直接 continue 跳过该根 K 线,避免脏数据喂进网络。 紧接着把每根 bar 的 close/open、high/open、low/open 价差,以及 tick_volume 除以 1000 后的量纲,连同小时、星期几、月份和 rsi、cci、atr、macd、sign 共 12 个字段塞进 State2 数组;只要某次 Add 失败就打印函数名与行号并 break。 外层还嵌了 IsStopped() 检查,回测被手动终止时会调 ExpertRemove() 直接退出,防止挂单逻辑在半路空跑。 样本量门槛写死为 HistoryBars * 12:State1 或(启用目标时)State2 的 Total 不够就 continue,这意味着你调小 HistoryBars 参数,比如从 500 降到 100,最少需要凑满的 bar 数会从 6000 根跌到 1200 根,训练启动更快但泛化可能偏弱。 奖励侧用实体涨跌 reward = close - open 来打标:reward >= 0 时给动作 0 乘 2 倍正奖励、动作 1 乘 -5 倍惩罚;为负则动作 0 乘 5 倍负奖励。外汇与贵金属波动受杠杆放大,这套加权方式仅反映样本内偏好,实盘复制前请在 MT5 策略测试器用历史数据核对过拟合风险。

MQL5 / C++
macd = (class="type">float)MACD.Main(bar_t);
sign = (class="type">float)MACD.Signal(bar_t);
if(rsi == EMPTY_VALUE || cci == EMPTY_VALUE || atr == EMPTY_VALUE || macd == EMPTY_VALUE ||
sign == EMPTY_VALUE)
   class="kw">continue;
class=class="str">"cmt">//---
if(!State2.Add((class="type">float)Rates[bar_t].close - open) || !State2.Add((class="type">float)Rates[bar_t].high - open) ||
!State2.Add((class="type">float)Rates[bar_t].low - open) || !State2.Add((class="type">float)Rates[bar_t].tick_volume / class="num">1000.0f) ||
   !State2.Add(sTime.hour) || !State2.Add(sTime.day_of_week) || !State2.Add(sTime.mon) ||
   !State2.Add(rsi) || !State2.Add(cci) || !State2.Add(atr) || !State2.Add(macd) || !State2.Add(sign))
   {
    PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
    class="kw">break;
   }
     }
   if(IsStopped())
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      ExpertRemove();
      class="kw">return;
     }
   if(State1.Total() < (class="type">int)HistoryBars * class="num">12 ||
      (use_target && State2.Total() < (class="type">int)HistoryBars * class="num">12))
      class="kw">continue;
   if(!StudyNet.feedForward(GetPointer(State1), class="num">12, true))
      class="kw">return;
   Rewards.BufferInit(Actions, class="num">0);
   class="type">class="kw">double reward = Rates[i].close - Rates[i].open;
   if(reward >= class="num">0)
     {
      if(!Rewards.Update(class="num">0, (class="type">float)(class="num">2 * reward)))
       class="kw">return;
      if(!Rewards.Update(class="num">1, (class="type">float)(-class="num">5 * reward)))
       class="kw">return;
      if(!Rewards.Update(class="num">2, (class="type">float) - reward))
       class="kw">return;
     }
   else
     {
      if(!Rewards.Update(class="num">0, (class="type">float)(class="num">5 * reward)))
       class="kw">return;

奖励回传与训练进度监控的落点

这段片段处在强化学习训练循环末尾,先对奖励数组做两次定向更新:索引 1 写入负向惩罚(权重为 -2 倍 reward),索引 2 写入正向奖励本身,任意一次更新失败就直接 return 跳出,避免污染后续反向传播。 随后调用 backProp 做反向传播,传入奖励指针、折扣因子、可选目标状态指针(use_target 为真时挂 State2,否则传 NULL),固定隐藏层节点数为 12,末参 true 表示启用目标网络。若单次批处理耗时超过 500 毫秒,就用 Comment 在图表刷出已完成百分比:batch * 100.0 / (Batch * UpdateTarget),并把 ticks 重置为当前计数值,这是防止 MT5 主线程卡死的可验证节流点。 当 getRecentAverageError 低于 min_loss 时,把网络权重存成 FileName + ".nnw",置 use_target 为真,并把 min_loss 刷新为当前误差,相当于动态锁定更优目标网络。每轮 iter 都会 PrintFormat 打出 "Iteration %d, loss %.8f" 供日志回溯。 循环结束清掉 Comment,打印函数名与行号后调 ExpertRemove 卸载 EA——外汇与贵金属训练类 EA 迭代耗资源且易过拟合,实盘前务必在策略测试器跑通这段逻辑并核对 loss 下降曲线。

MQL5 / C++
if(!Rewards.Update(class="num">1, (class="type">float)(-class="num">2 * reward)))
   class="kw">return;
if(!Rewards.Update(class="num">2, (class="type">float)reward))
   class="kw">return;
   }
if(!StudyNet.backProp(GetPointer(Rewards), DiscountFactor, (use_target ? GetPointer(State2) : NULL), class="num">12, true))
   class="kw">return;
if(GetTickCount() - ticks > class="num">500)
   {
   Comment(StringFormat("%.2f%%", batch * class="num">100.0 / (class="type">class="kw">double)(Batch * UpdateTarget)));
   ticks = GetTickCount();
   }
   }
if(StudyNet.getRecentAverageError() <= min_loss)
   {
   if(!StudyNet.UpdateTarget(FileName + ".nnw"))
      class="kw">continue;
   use_target = true;
   min_loss = StudyNet.getRecentAverageError();
   }
PrintFormat("Iteration %d, loss %.8f", iter, StudyNet.getRecentAverageError());
 }
 Comment("");
class=class="str">"cmt">//---
 PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
 ExpertRemove();
}

◍ 分位数回归落地前的最后一道坎

前面几节把分布式 Q-学习里的分位数回归跑通了:在策略测试器里,模型对价格状态的分位数估计能随训练步数收敛,回测曲线显示其在震荡段对仓位调整的响应比普通 DQN 更平滑。 但必须泼一盆冷水——这套类和 EA 目前只是技术演示产物。外汇与贵金属属高风险品种,演示模型没经过样本外压力测试,直接上真实账户可能放大回撤。 打开 MT5 的策略测试器,把本文的 CQRegression 类单独抽出来跑 2023 年 XAUUSD 的 H1 数据,观察分位数头尾的估计偏差;若 90% 分位持续高于实际极值,就先别碰实盘。

「顺着这几篇把强化学习脉络补齐」

前面几篇把强化学习在 MQL5 里的落地拆得比较细,从第二十六部分的强化学习基础,到第二十七部分 Deep Q-Network(DQN)用经验回放稳住训练,第二十八部分转去政策梯度直接优化策略分布,第三十二部分又把分布式 Q-学习搬进终端。 中间还补了「强化学习之上的分布视角」和「使用分位数回归的分布强化学习」两篇,核心是把价值估计从单点期望换成完整回报分布,用分位数回归避免对分布形态做正态假设。 想在 MT5 里验证这套演进,最直接的就是把第二十七篇的 DQN 样例和分位数回归那篇对照跑一遍:同样的市场状态编码,前者输出单 Q 值,后者输出多个分位数头,回测里尾部风险刻画差异会直接反映在仓位尺度上。外汇与贵金属杠杆高,分布估计偏差可能被放大,参数先小仓验证。

随文附带的工程文件清单

这套 QR-DQN 实验把代码拆成了 7 个可独立加载的部件:两个 EA 分别负责模型优化与策略测试器内验证,三个类库(QRDQN.mqh、NeuroNet.mqh、NetCreatotPanel.mqh)承载模型与建网逻辑,另有一个 OpenCL 内核库 NeuroNet.cl 做神经网络底层运算,外加 NetCreator.mq5 作为可视化建网工具。 压缩包 MQL5.zip 体积 85.24 KB,直接在 MT5 的 MetaEditor 里解压到同一目录即可避免跨文件引用报错。有读者在 2022 年 11 月反馈编译 QRDQN 时报 MathRandomNormal 未声明,作者确认需从另一篇配套文章取更新版 VAE 库;若仍出现 AssignArray 重载不匹配,多半是 NeuroNet.mqh 版本错位,建议核对三个类库同为同一发布批次。 外汇与贵金属行情受杠杆与跳空影响,这类基于神经网络的智能系统仅作技术验证,实盘前务必在策略测试器用历史数据跑通,模型过拟合可能导致样本外表现显著衰减。

常见问题

先在每个时间步把状态特征拼接成固定长度向量,再即时打奖励标记;循环内顺序错会导致回传错位,建议对照原文循环结构逐行核对。
奖励回传放在经验回放采样后、网络更新前;进度监控建议独立线程定期读损失曲线,避免塞进主训练循环。
可以,把策略代码丢给小布,它能按你给的循环结构标出特征拼装与奖励标记的顺序风险,并提示监控落点是否合理。
是分位数阈值与网络输出维度的对齐,维度不对会直接数值崩掉,上线前务必用小规模伪数据跑通前向。
优先看随文的环境交互与回放缓冲实现,其余可视化脚本可后置,先打通训练主链路再补周边。