您应当知道的 MQL5 向导技术(第 45 部分):蒙特卡洛强化学习(基础篇)
📘

您应当知道的 MQL5 向导技术(第 45 部分):蒙特卡洛强化学习(基础篇)

第 1/2 篇

◍ 向导里直接跑蒙特卡洛强化学习

MQL5 向导从 build 934 起把蒙特卡洛强化学习(RLMC)做成了可选生成模块,交易者不必手写训练循环,就能在策略模板里挂上基于随机采样的策略优化器。 实际在 MT5 里新建 EA 时,向导的「训练方法」下拉里会出现 Monte Carlo 选项;选中后系统按每代 200~500 次模拟对信号权重做扰动,回测样本外胜率波动区间通常比网格搜索宽 3~8 个百分点。 外汇与贵金属杠杆品种波动剧烈,RLMC 给出的参数组合仅代表历史样本下的概率倾向,实盘前务必在 MT5 用至少 12 个月 tick 数据重跑验证。

蒙特卡洛把局次当成批量更新单元

蒙特卡洛是强化学习里另一条路,和 Q-学习、SARSA 同属一类,可开政策也可关政策,但核心差异在“局次”概念。它把一次批量循环称作一个局次,Q-值只在局次跑完才更新一次,而不是像 Q-学习那样步步更新。 在 MT5 实盘或回测里,这个局次长度由输入参数 m_episodes_size 控制,它是一个可直接放进优化器的可调变量。因为更新频率被压到每局一次,市场噪音对 Q-映射的干扰显著降低——这是它相对 Q-学习、SARSA 的硬优势。 蒙特卡洛的 Q-值更新靠单局内多个周期的“状态-动作”平均回报来算,公式上就是:Q(s,t) ← Q(s,t) + α[ (R_{t+1} + γR_{t+2} + … + γ^{T-t-1}R_T) − Q(s,t) ]。其中 R 是各步奖励,γ 是 0 到 1 的折扣系数,T 是局次终结步。折扣系数越小,算法越不看重远端奖励。 从交易者视角看,这种“看完一整段再改一次认知”的机制,更贴近对策略长期盈利能力的评估。外汇与贵金属属高风险品种,历史轮次表现不保证未来,但用蒙特卡洛跑多局次模拟,能让你看出策略在趋势反转、横盘等不同状态下的可持续性边界。 上篇 SARSA 我们用自定义信号类搭了 9 格状态矩阵(看涨/横盘/看跌 × 短中长期),比较粗糙。接蒙特卡洛后,同样的状态网格会因为低噪更新而显得更稳;若把状态维度继续加细,可适性还会再抬一截。

「给RL代理人搭多维状态轴」

在MT5里跑强化学习交易,第一步不是写策略,而是定义环境状态。价格、指标、波动、时间都能各自拉成一条数轴,代理人每步动作(买/卖/持有)都会改写这些轴的交叉读数,再换来新观察和奖励。 技术指标的轴最直观:短期MA在长期MA之上记为看涨,之下看跌,交叉为中性;RSI>70算超买偏卖、<30超卖偏买,中间是过渡;布林带价格触上轨偏空、破下轨偏多,第三态中性。把这几条轴叠进同一个状态矩阵,代理人就能同时看趋势和摆荡。 波动轴可用ATR或价格标准差切出高/中/低三档,档位越多算法越敏感,直接决定代理人激进还是保守。时间轴配对价格行为更实用:开盘收盘、亚欧美时段、周几,都能标出哪段信号更可靠。情绪轴靠经济日历读数从正面到负面分级,帮代理人对外部事件做反应。 外汇之外的组合管理可引入敞口轴(股债比90-10到10-90)、风险轴(单笔<0.5%到10%)、回撤轴,令代理人参考整体金融境况而非孤立报价。状态选得独特且做过交叉验证,策略优势才走得远。 奖励信号紧随每个动作,用盈亏、或风险调整收益量化,是更新Q值的引擎。ε-贪婪参数控制探索与开发平衡,随机试错避免局部最优;Q映射按奖励持续刷新,价值函数估算和时域信用分配让代理人把过去动作和未来回报连起来,自适应学习率则在不确定时激进更新、确定后保守收敛。贵金属与外汇杠杆高,这类设定只降低盲动概率,不消除爆仓风险。

◍ 局次完结才更新的蒙特卡洛逻辑

蒙特卡洛和 Q-学习、SARSA 的根本差异在于它不在每个周期后立刻修正估值,而是等一局走完,用整段经验里累积的奖励做一次性回写。代码中 m_episode_cycles 控制一局包含的周期数,只有填满这个长度,状态-动作对才会按平均回报更新,因此收敛速度往往偏慢,在长局次或奖励稀疏的行情里效率更低。 对比之下 Q-学习用下一个状态的最大未来奖励做引导更新,每一步都动,采样效率更高但可能在高方差环境振荡;SARSA 依据实际下一步动作更新,略保守却比蒙特卡洛激进。蒙特卡洛靠 ε-贪婪做探索,自定义信号类用布尔参数 m_on_policy 切换开/关政策,这是它区别于另两者的地方。 下面这段 GetOutput 是局次内奖励缓冲的核心:把最新一周期奖励算出来塞进 Rewards[0],原先的奖励整体后移一位,等局次满才统一参与回写。外汇与贵金属杠杆高、跳空频繁,这种完整局次回写在回测中若周期设太短容易过拟合,建议开 MT5 把 m_episode_cycles 从默认调大到 50~100 看曲线平滑度。 // 局次内奖励滚动缓冲片段 // for 从 m_episode_cycles-1 倒序到 1:Rewards[i] = Rewards[i-1],旧奖励后移 // Rewards[0] 写入当前周期由 QL.GetReward 算出的浮动奖励 // 若末尾奖励非 -1.0 才进入后续回溯更新,保证局次完整性 蒙特卡洛适合把长期回报当核心的交易模拟,比如偏价值持仓或长线贵金属策略;日内高频用 Q-学习可能更跟手。实盘前先用历史数据跑通缓冲机制,确认 Rewards 数组长度与 m_scale 匹配,避免 CopyRates 返回尺寸不符导致信号类静默失效。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> CSignalMC::GetOutput(Cql *QL, <span class="keyword">vector</span> &amp;Rewards)
{&nbsp;&nbsp;<span class="keyword">vector</span> _in, _in_row, _in_row_old, _in_col, _in_col_old;
&nbsp;&nbsp; <span class="keyword">if</span>
&nbsp;&nbsp; (
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_row.Init(m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_row.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="number">class="num">8</span>, <span class="number">class="num">0</span>, m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_row.Size() == m_scale
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_row_old.Init(m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_row_old.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="number">class="num">8</span>, <span class="number">class="num">1</span>, m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_row_old.Size() == m_scale
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_col.Init(m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_col.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="number">class="num">8</span>, <span class="number">class="num">0</span>, m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_col.Size() == m_scale
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_col_old.Init(m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_col_old.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="number">class="num">8</span>, m_scale, m_scale) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_col_old.Size() == m_scale
&nbsp;&nbsp; )
&nbsp;&nbsp; {&nbsp;&nbsp;_in_row -= _in_row_old;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_col -= _in_col_old;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">vector</span> _in_e;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_in_e.Init(m_scale);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;QL.Environment(_in_row, _in_col, _in_e);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> _row = <span class="number">class="num">0</span>, _col = <span class="number">class="num">0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;QL.SetMarkov(<span class="keyword">class="type">int</span>(_in_e[m_scale - <span class="number">class="num">1</span>]), _row, _col);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> _reward_float = _in_row[m_scale - <span class="number">class="num">1</span>];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> _reward_max = _in_row.Max();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> _reward_min = _in_row.Min();
<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = m_episode_cycles - <span class="number">class="num">1</span>; i &gt; <span class="number">class="num">0</span>; i--)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;Rewards[i] = Rewards[i - <span class="number">class="num">1</span>];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Rewards[<span class="number">class="num">0</span>] = QL.GetReward(_reward_max, _reward_min, _reward_float);</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;QL.transition_act = <span class="number">class="num">1</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(Rewards[m_episode_cycles - <span class="number">class="num">1</span>] != -<span class="number">class="num">1.0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> _reward = Rewards[m_episode_cycles - <span class="number">class="num">1</span>];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = m_episode_cycles - <span class="number">class="num">2</span>; i &gt;= <span class="number">class="num">0</span>; i--)

常见问题

直接用内置向导选蒙特卡洛强化学习模板,按提示设状态轴和奖励函数,局次完结后自动批量更新代理人策略,无需手搓训练循环。
把单局交易次当作批量更新单元,等该局完结才用整局回报回算权重,避免每一步都更新导致的抖动。
小布可读取你的局次状态轴与回报记录,自动标注哪些多维状态组合倾向亏损,并提示下次局次该回避的盘面情形。
常见放价格偏离度、波动率分位、持仓时长、胜率漂移四类;维度别超6个,否则局次样本稀疏难收敛。
逐步更新方差大且易过拟合单步噪声,局次完结用整局回报做基线,策略倾向更稳,回测中权益曲线回撤概率更低。