神经网络变得轻松(第二十八部分):政策梯度算法·综合运用
📘

神经网络变得轻松(第二十八部分):政策梯度算法·综合运用

第 3/3 篇

训练循环里的失败即退场逻辑

这段片段处在强化学习训练主循环末尾,负责把临时动作缓冲写入网络并做反向传播,任何一步失败就直接卸载智能体。 TempData.BufferInit 分别以 0 和 1 初始化动作缓冲,再用 Update 把当前批次动作 vActions[batch] 标记为 1 或 0;只要初始化或更新任一返回 false,就调用 ExpertRemove 并 return,EA 当场停止。 backProp(TempData) 若返回 false 同样触发 ExpertRemove,说明梯度回传异常时不会继续浪费 tick。每轮迭代用 PrintFormat 打出 Iteration、Cummulative reward 与 loss,精度到小数点后 5 位,方便在 MT5 日志里观察奖励收敛曲线。 外汇与贵金属市场高波动、高杠杆,这类自学习 EA 在实盘前务必用历史数据多轮回测,训练中断不代表策略失效,但频繁 ExpertRemove 往往指向缓冲或网络维度配置错误。

MQL5 / C++
    (!TempData.BufferInit(Actions, class="num">0) || !TempData.Update((class="type">int)vActions[batch], class="num">1)) :
    (!TempData.BufferInit(Actions, class="num">1) || !TempData.Update((class="type">int)vActions[batch], class="num">0))
    ))
    {
      ExpertRemove();
      class="kw">return;
    }
    if(!StudyNet.backProp(TempData))
    {
      ExpertRemove();
      class="kw">return;
    }
  }
  PrintFormat("Iteration %d, Cummulative reward %.5f, loss %.5f", iter, cum_reward, loss);
  }
 Comment("");
class=class="str">"cmt">//---
  ExpertRemove();
}

「把训练好的强化学习模型塞进 MT5 回测」

训练完 DQN 和政策梯度模型后,直接做两个无训练逻辑的测试 EA:Q-learning-test 与 REINFORCE-test。后者在 REINFORCE.mq5 上删掉 Train 函数,把推理搬进 OnTick,只在每根新烛条收盘后跑一次环境评估。 OnTick 里先判新柱,再刷 RSI/CCI/ATR/MACD 历史,填状态缓冲;状态不足(少于 HistoryBars*12 个特征)就跳过。前馈后得到动作概率分布,随机抽一个动作,再查买卖标志决定开平。 DQN 回测出了意外:全程只做了一笔交易,持仓穿整段测试却盈利,图表看它是抓住了全局趋势顺势开仓。但模型用过去 2 年数据训练,而这 2 年品种主跌,它能否及时平仓仍存疑。 政策梯度用贪婪策略结果类似。我改了奖励函数,对无盈利持仓加重罚,重训后超参数试算达到 60% 盈利操作,平均持仓 1 小时 40 分钟。外汇与贵金属杠杆高,回测盈利不预示实盘概率。 下面这段 OnTick 摘录展示了新柱判定与状态拼接的核心,可照抄到自己的推理 EA 里逐行验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   if(lastBar >= iTime(Symb.Name(), TimeFrame, class="num">0))
      class="kw">return;
   class="type">int bars = CopyRates(Symb.Name(), TimeFrame, class="num">0, HistoryBars+class="num">1, Rates);
   if(!ArraySetAsSeries(Rates, true))
      class="kw">return;
   RSI.Refresh();
   CCI.Refresh();
   ATR.Refresh();
   MACD.Refresh();
class=class="str">"cmt">//---
   State1.Clear();
   for(class="type">int b = class="num">0; b < (class="type">int)HistoryBars; b++)
     {
      class="type">int bar_t = (class="type">int)HistoryBars - b;
      class="type">float open = (class="type">float)Rates[bar_t].open;
      TimeToStruct(Rates[bar_t].time, sTime);
      class="type">float rsi = (class="type">float)RSI.Main(bar_t);
      class="type">float cci = (class="type">float)CCI.Main(bar_t);
      class="type">float atr = (class="type">float)ATR.Main(bar_t);
      class="type">float macd = (class="type">float)MACD.Main(bar_t);
      class="type">float 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(!State1.Add((class="type">float)Rates[bar_t].close - open) || !State1.Add((class="type">float)Rates[bar_t].high - open) ||
!State1.Add((class="type">float)Rates[bar_t].low - open) || !State1.Add((class="type">float)Rates[bar_t].tick_volume / class="num">1000.0f) ||
         !State1.Add(sTime.hour) || !State1.Add(sTime.day_of_week) || !State1.Add(sTime.mon) ||
         !State1.Add(rsi) || !State1.Add(cci) || !State1.Add(atr) || !State1.Add(macd) || !State1.Add(sign))
         break;
     }
   if(State1.Total() < (class="type">int)(HistoryBars * class="num">12))
      class="kw">return;
   if(!StudyNet.feedForward(GetPointer(State1), class="num">12, true))
      class="kw">return;
   StudyNet.getResults(TempData);
   if(!TempData)
      class="kw">return;
   lastBar = Rates[class="num">0].time;
   class="type">int action = GetAction(TempData);
   class="kw">delete TempData;

◍ 用循环锁死同品种双向持仓状态

EA 在发单前必须先搞清楚当前图表品种到底有没有活着的多单或空单,否则可能在同一品种上叠出意料外的双向敞口。下面这段逻辑用两个布尔量 Buy、Sell 标记状态,遍历终端全部持仓再按符号过滤。 循环从 i=0 跑到 PositionsTotal() 返回的总持仓数,PositionGetSymbol(i) 取出的品种名若不等于 Symb.Name() 就直接 continue 跳过,只处理本图表品种。随后用 PositionGetInteger(POSITION_TYPE) 强转成 ENUM_POSITION_TYPE,命中 BUY 就把 Buy 置真,命中 SELL 就把 Sell 置真。 action 参数决定动作分支:0 代表想做多,若 Buy 已真就不动,否则先在有 Sell 时平空再开最小手数多单;1 反之;2 是清仓,只要 Buy 或 Sell 任一为真就一键平掉。任一步 Trade 类方法返回失败,就把 lastBar 归零并 return,阻止本根 K 线继续瞎操作。 外汇与贵金属杠杆高,这类持仓状态判断若漏掉 Symbols 过滤,可能在多品种 EA 里误平别人家的单子,上 MT5 用策略测试器跑一遍 EURUSD 便能验证过滤是否生效。

MQL5 / C++
  class="type">bool Buy = false;
  class="type">bool Sell = false;
  for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
    {
      if(PositionGetSymbol(i) != Symb.Name())
        class="kw">continue;
      class="kw">switch((class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
            Buy = true;
            break;
         case POSITION_TYPE_SELL:
            Sell = true;
            break;
        }
    }
  class="kw">switch(action)
    {
     case class="num">0:
        if(!Buy)
          {
           if((Sell && !Trade.PositionClose(Symb.Name())) ||
             !Trade.Buy(Symb.LotsMin(), Symb.Name()))
            {
             lastBar = class="num">0;
             class="kw">return;
            }
          }
        break;
     case class="num">1:
        if(!Sell)
          {
           if((Buy && !Trade.PositionClose(Symb.Name())) ||
             !Trade.Sell(Symb.LotsMin(), Symb.Name()))
            {
             lastBar = class="num">0;
             class="kw">return;
            }
          }
        break;
     case class="num">2:
        if(Buy || Sell)
          if(!Trade.PositionClose(Symb.Name()))
            {
             lastBar = class="num">0;
             class="kw">return;
            }
        break;
    }
class=class="str">"cmt">//---
  }

策略测试器里跑出来的信号质量

这一篇和前面几篇不同,模型没有只停留在理论推导,而是直接放进 MT5 策略测试器里完成训练和验证。按文中给出的测试结论,由该策略梯度模型生成的信号,如果严格按信号执行,交易操作呈现盈利倾向。 不过有一点必须盯死:奖励函数和损失函数的设定直接决定模型往哪个方向收敛。选错了,策略测试器里跑出的漂亮曲线可能只是过拟合的假象,实盘外汇和贵金属的高波动随时会把漏洞撕开。 文末顺手列了同系列的几篇延伸,第二十五部分讲实践迁移学习,二十六、二十七分别铺了强化学习和深度 Q-学习(DQN)的路子,想接着深挖可以照着编号去翻。

「把工具请下神坛」

这套强化学习示例在 MT5 里落地时,核心就是几个文件各司其职:REINFORCE.mq5 负责带训练逻辑的运行端智能系统,REINFORCE-test.mq5 与 Q-learning-test.mq5 专门丢进策略测试器跑模型验证,NeuroNet.mqh 封装了神经网络类,NeuroNet.cl 则是 OpenCL 侧的实际计算代码,整套压缩包约 73.44 KB。 评论区里有人用 2022 年 9 月 3420 测试版 MT5 编译 VAE.mqh 时,报 'MathRandomNormal' 未声明,作者回了一句「注释掉命名空间 Math 那行」就过了——说明库引用冲突在实盘环境里真会发生,不是文档写错。 外汇和贵金属市场高杠杆、价格序列非平稳,这类模型只是概率意义上的辅助,别当成圣杯。下回你重装终端后若训练报错,先对照文件树和命名空间引用,比盲目调参更省时间。

常见问题

失败即退场逻辑只在验证集持续恶化时触发,实盘前用历史分段回测确认阈值,避免过早放弃有效策略。
重点看样本外区间的胜率衰减和最大回撤,若信号在测试器后期明显漂移,说明过拟合,不宜直接用。
小布可读取策略测试报告并标注样本外衰减与双向持仓冲突,打开对应品种页即可看到诊断结论。
在订单循环里用持仓方向标志位判断,已有多单则屏蔽空单开仓信号,反向同理,避免自成交损耗。
工具只是辅助,外汇贵金属高风险,先把失败退场和持仓锁逻辑跑通再谈实盘,别神话任何算法。