神经网络变得简单(第 59 部分):控制二分法(DoC)·综合运用
📘

神经网络变得简单(第 59 部分):控制二分法(DoC)·综合运用

第 3/3 篇

「把周期相位塞进强化学习状态向量」

这段代码在做一件事:把账户指标在时间轴上的相位信息,编码成神经网络的输入特征。用 cos/sin 套上 2π·x,本质是把线性时间折叠成圆周坐标,避免网络把「周线第 50 根」和「周线第 5 根」误判成量级差距。 x 先除以 PeriodSeconds(PERIOD_W1) 再取 sin,等于把 account[7] 归一到周线周期相位;紧接着用 PERIOD_D1 又算一次日线相位。两套三角函数叠加进 State,让策略对多周期共振有数学意义上的感知,而不是靠肉眼切图表。 训练循环里 RTG.feedForward 跑前向、backProp 跑回报梯度,Agent 那一侧用 CAGrad(Actions - result) 做策略梯度修正。任何一步返回 false 就置 StopFlag 并 break,说明这套在线学习对数值稳定性极敏感,外汇与贵金属的高波动样本下更容易触发中断,属正常防护。 循环中还埋了 GetTickCount() - ticks > 500 的节流:每跑满 500 毫秒才打印一次 Agent 的平均误差和迭代进度(iter*100.0/Iterations)。你可以直接把这段粘进 MT5 的 EA 训练例程,把 500 改成 100 看日志刷新率对回测耗时的真实影响。

MQL5 / C++
State.Add((class="type">float)MathCos(class="num">2.0 * M_PI * x));
x = (class="type">class="kw">double)Buffer[tr].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1);
State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
x = (class="type">class="kw">double)Buffer[tr].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1);
State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
class=class="str">"cmt">//--- Prev action
State.AddArray(Actions);
class=class="str">"cmt">//--- Return to go
if(!RTG.feedForward(GetPointer(State)))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   StopFlag = true;
   break;
  }
Result.AssignArray(Buffer[tr].States[state+class="num">1].rewards);
if(!RTG.backProp(Result))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   StopFlag = true;
   break;
  }
class=class="str">"cmt">//--- Policy Feed Forward
State.AddArray(Buffer[tr].States[state+class="num">1].rewards);
if(!Agent.feedForward(GetPointer(State), class="num">1, false, (CBufferFloat*)NULL))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   StopFlag = true;
   break;
  }
class=class="str">"cmt">//--- Policy study
Actions.Assign(Buffer[tr].States[state].action);
vector<class="type">float> result;
Agent.getResults(result);
Result.AssignArray(CAGrad(Actions - result) + result);
if(!Agent.backProp(Result, (CBufferFloat*)NULL))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   StopFlag = true;
   break;
  }
class=class="str">"cmt">//---
if(GetTickCount() - ticks > class="num">500)
  {
   class="type">class="kw">string str = StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "Agent", iter * class="num">100.0 / (class="type">class="kw">double)(Iterations), Agent.getRecentAverageError());

训练进度与误差回传的终端打印

这段片段出现在神经网络博弈训练的主循环末尾,负责把每一轮迭代的关键指标推到图表和日志。str 拼接了 RTG 网络的近期平均误差,用 StringFormat 按左对齐 15 字符、百分比保留两位小数格式输出,Comment(str) 直接挂在图表左上角,实盘跑 EURUSD 这类品种时你能肉眼看到误差随迭代收敛的曲线。 迭代计数 iter 乘 100.0 再除 Iterations 得到已完成进度百分比,RTG.getRecentAverageError() 返回的是 15.8f 精度浮点,说明误差量级可能在 1e-3 以下。GetTickCount() 在块尾重置计时,配合外层判断可以控制每多少毫秒刷新一次界面,避免 60 帧重绘拖慢 MT5 回测速度。 循环结束后 Comment("") 清空图表注释,PrintFormat 把 Agent 与 RTG 的最终平均误差打到 Experts 日志,格式含函数名和行号便于定位。最后 ExpertRemove() 卸载 EA——这意味着该脚本本质是一次离线训练器,不是常驻策略,外汇与贵金属市场高杠杆高风险,这类实验仅在历史数据上验证网络收敛性,不代表任何实盘盈利能力。

MQL5 / C++
    str += StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "RTG", iter * class="num">100.0 / (class="type">class="kw">double)(Iterations), RTG.getRecentAverageError());
    Comment(str);
    ticks = GetTickCount();
   }
  }
 }
 Comment("");
class=class="str">"cmt">//---
 PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "Agent", Agent.getRecentAverageError());
 PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "RTG", RTG.getRecentAverageError());
 ExpertRemove();
class=class="str">"cmt">//---
}

◍ EURUSD H1 上的训练与八月验证

模型按 2023 年前 7 个月历史训练,品种锁定波动性最高的 EURUSD H1,所有分析指标参数沿用默认未动。训练采用迭代式:交替收集训练数据并训练模型,而非一次性堆大库长训——因为连续动作空间加环境高随机性,资源有限时很难覆盖全样本,模型易闯入未探索区,需交叉迭代补环境认知。 初始数据收集阶段每个智能体走纯随机政策,把探索拉满;后续重交互时改回预训练政策,研究范围随政策随机性收窄。迭代越多,随机区收敛越平滑,及时反馈能掰正训练方向,提升摸到全局最大预期回报的概率。离线间隔一长,动作随机性骤降,容易卡在局部极值出不来。 模型里的稀疏关注度模块训练更折腾:自关注结构复杂要久训,且像 Dropout 那样每次迭代不全用神经元,部分梯度到不了、随机辍训,得额外补迭代;好处是单次迭代更快、模型更灵活。 测试用 2023 年 8 月 EURUSD H1(紧接训练段),尽可能贴近实盘日常。结果整月约 43% 盈利仓位,最大盈利超最大亏损 2 倍,平均盈利比平均亏损高 1/3,盈利因子 1.01、恢复因子 0.03。余额前 10 天飙约 20%,中 10 天在成果区波动、振幅达余额 10%,末 10 天连亏。对比不接 DoC 的版本(前作决策转换器八月亏),DoC 让后二十天保住成果、没立刻连亏。外汇/贵金属高杠杆高风险,这仅是历史段回测,实盘可能失效。 自回归短窗口出好结果但难持久,DoC 改法把有益区间拉长,说明这套还有可调潜力。

「把 DoC 塞进决策转换器后的实测偏移」

我们把控制二分法(DoC)的原则接进了前一篇搭好的决策转换器里,核心改动是:训练时不追那些随智能体动作波动的依赖项,只围绕环境随机性建模、去逼出最大化结果的政策。 在测试样本上跑完,模型表现相对基线有明显正向偏移,虽然离可用还远,但偏移方向一致、幅度可复现。 外汇与贵金属行情属强随机环境,这类改造只提高概率优势,不消除爆仓风险,拿去 MT5 回测前先准备好滑点假设。

把决策转换器接进行情序列

MQL5 近年的系列文章把强化学习从「调参数」扭到「看序列」:2023 年发布的《神经网络变得简单(第 58 部分):决策转换器(DT)》直接把交易决策当成序列建模问题,而非每一步独立估算奖励。它延续了第 34 部分提出的完全参数化分位数函数思路——不是只输出一个期望收益,而是把回报分布拆成多个分位数,再让 Transformer 在状态-动作-回报三元组上做自回归。 实战上值得区分的是控制二分法:你控得了特征窗口长度、奖励分位权重、回看 bar 数;控不了点差跳空和流动性断裂。把能控的写进模型输入,不能控的留给风控层,是 DT 落地 MT5 的前置条件。 外汇与贵金属属高风险品种,DT 序列模型在样本外仍可能大幅回撤,任何分位预测都只是概率倾向,不是方向保证。开 MT5 把第 58 部分代码跑通前,先固定分位数为 0.1/0.5/0.9 三档,观察极端分位对平仓触发的灵敏度。

◍ 把工具请下神坛

这套 LSTM 预测方案在 MT5 里落地时,实际分成了六个文件:Research.mq5、Study.mq5、Test.mq5 三个 EA 分别管样本收集、智能体训练和模型测试,Trajectory.mqh 与 NeuroNet.mqh 定义系统状态和建网类库,NeuroNet.cl 则是 OpenCL 端的算子代码,整套压缩包约 1871 KB。 从社区反馈看,有交易者拿 48 个布林带做输入、对 28 个货币对各跑 20 次以上训练再筛出 9 个上实盘,但作者和跟帖都明确说这更像方法范例而非可直接实盘的神器,外汇与贵金属杠杆交易本身高风险,模型跑顺也只是概率倾向。 真要验证,直接下 ZIP 在 MT5 策略测试器里把 Test.mq5 挂上,先确认显卡 OpenCL 能调起 NeuroNet.cl,再决定要不要把它当辅助模块接进你已有的网格或人工管理逻辑里。

常见问题

把相位编码成连续周期函数(如 sin/cos 对小时取模),拼到状态向量末尾,避免用独热码导致维度爆炸。
盯住误差是否随 episode 稳定下降、梯度是否非 NaN;若打印值长期震荡不收敛,优先降学习率或查数据归一。
可以,小布内置 AIGC 序列诊断,打开对应品种页即可让它替你跑训练进度监控与验证偏移测算。
多为序列窗口与训练分布错位,或 DoC 控制量超出历史区间;先回看验证集同周期偏移再调窗口长度。
不必神化任何单一工具,DoC 已含方向约束,传统指标仅作交叉确认,冗余过滤反而拖慢决策。