您应当知道的 MQL5 向导技术(第 37 部分):配以线性和 Matérn 内核的高斯过程回归·进阶篇
📘

您应当知道的 MQL5 向导技术(第 37 部分):配以线性和 Matérn 内核的高斯过程回归·进阶篇

第 2/2 篇

◍ 用 ν 调平滑度:Matérn 内核怎么选

Matérn 内核靠一个平滑度参数 ν 控制协方差形态,这是它比固定形态核灵活的地方。ν 取 ½ 时退化为锯齿状指数核,ν 趋向无穷则变成径向基函数核;实战里常落在 ½、3/2、5/2 这三档,数值越大曲线越平滑。 从盘面视角看,ν=½ 对应的是假定价格过程不连续、跳空频出的环境,对外汇或贵金属这种高波动品种更敏锐,但输出结果偏毛刺。ν=3/2 是中等平滑,能兼顾轻度波动与轻度趋势,常被用来捕捉时间序列里的转折或波动节点;ν=5/2 及以上更贴趋势市,尤其是汇率走出连贯方向时。 要注意,ν 越大内核可微次数越多,伴随的是 MT5 回测或实时计算里更吃资源。小 ν 适配含噪、跳空数据,大 ν 适配平缓变化数据,这个取舍直接决定你高斯过程模型是对噪声过度反应还是滞后。 下面这段 MQL5 实现把 ν 锁在 3/2,公式已简化为 (1+√3·d/l)·exp(-√3·d/l),其中 d 是两点距离、l 是长度参数 m_next: //+------------------------------------------------------------------+ // Matern Kernel Function //+------------------------------------------------------------------+ matrix CSignalGauss::Matern_Kernel(vector &Rows,vector &Cols) { matrix _matern; _matern.Init(Rows.Size(), Cols.Size()); for(int i = 0; i < int(Rows.Size()); i++) { for(int ii = 0; ii < int(Cols.Size()); ii++) { _matern[i][ii] = (1.0 + (sqrt(3.0) * fabs(Rows[i] - Cols[ii]) / m_next)) * exp(-1.0 * sqrt(3.0) * fabs(Rows[i] - Cols[ii]) / m_next); } } return(_matern); } 逐行拆解:函数接收两个 vector(行、列样本),初始化等尺寸矩阵;双层循环遍历每对样本,算绝对距离 fabs(Rows[i]-Cols[ii]) 再除以长度参数 m_next 得到缩放距离;按 3/2 简化式填入矩阵元;返回协方差矩阵供后续高斯过程调用。 开 MT5 把 m_next 从默认改到品种典型振幅量级(如 EURUSD 用 0.001~0.005 试),能直观看到协方差衰减快慢变化。外汇贵金属属高风险,核函数仅描述统计依赖,不预示方向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Matern Kernel Function
class=class="str">"cmt">//+------------------------------------------------------------------+
matrix CSignalGauss::Matern_Kernel(vector &Rows,vector &Cols)
{  matrix _matern;
   _matern.Init(Rows.Size(), Cols.Size());
   for(class="type">int i = class="num">0; i < class="type">int(Rows.Size()); i++)
   {  for(class="type">int ii = class="num">0; ii < class="type">int(Cols.Size()); ii++)
      {  _matern[i][ii] = (class="num">1.0 + (sqrt(class="num">3.0) * fabs(Rows[i] - Cols[ii]) / m_next)) * exp(-class="num">1.0 * sqrt(class="num">3.0) * fabs(Rows[i] - Cols[ii]) / m_next);
      }
   }
   class="kw">return(_matern);
}

把两个核函数塞进同一个信号类

自定义信号类 CSignalGauss 里,我把线性核与 Matern 核做成可切换的两种实现,靠成员变量 m_kernel 在运行时挑一个用。GetOutput 被重写,按选中的核去算 _k_s 与 _k_ss 两个矩阵,供后续预测下一档价格变化使用。 切换逻辑很直接:m_kernel == KERNEL_LINEAR 就调 Linear_Kernel 填矩阵,否则走 Matern_Kernel。核矩阵维度由 _next_time 和 _past_time 的长度决定,_k_s 是 NxM、_k_ss 是 NxN,这一步和早先单核版本的步骤一致。 多空条件沿用「预测变化正负」的旧思路,再把差值归一化到 0–100 的整数区间——这是所有自定义信号类实例给智能系统喂分的统一格式。LongCondition 里若末尾输出大于首项,就按 (末项-首项)/(最大值-最小值)*100 四舍五入出分;ShortCondition 反过来判首项大于末项。 下面这段是类里最该在 MT5 里跑一遍的部分,注意 _o.Size()-1 取的是预测序列最后一个元素,归一化除零风险在 _o.Max()-_o.Min() 接近 0 时会出现,实盘前先打印 _o 分布。外汇与贵金属波动剧烈,该信号仅作概率参考,不代表方向必现。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalGauss::GetOutput(class="type">class="kw">double BasisMean, vector &Output)
{  
  ...
  matrix _k_s;
  matrix _k_ss;
  _k_s.Init(_next_time.Size(), _past_time.Size());
  _k_ss.Init(_next_time.Size(), _next_time.Size());
  if(m_kernel == KERNEL_LINEAR)
  {  _k_s = Linear_Kernel(_next_time, _past_time);
     _k_ss = Linear_Kernel(_next_time, _next_time);
  }
  else if(m_kernel == KERNEL_MATERN)
  {  _k_s = Matern_Kernel(_next_time, _past_time);
     _k_ss = Matern_Kernel(_next_time, _next_time);
  }
  ...
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that price will grow.                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalGauss::LongCondition(class="type">void)
{  class="type">int result = class="num">0;
   vector _o;
   GetOutput(class="num">0.0, _o);
   if(_o[_o.Size()-class="num">1] > _o[class="num">0])
   {  result = class="type">int(round(class="num">100.0 * ((_o[_o.Size()-class="num">1] - _o[class="num">0])/(_o.Max() - _o.Min()))));
   }
class=class="str">"cmt">//printf(__FUNCSIG__ + " output is: %.5f, change is: %.5f, and result is: %i", _mlp_output, m_symbol.Bid()-_mlp_output, result);class="kw">return(class="num">0);
   class="kw">return(result);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| "Voting" that price will fall.                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalGauss::ShortCondition(class="type">void)
{  class="type">int result = class="num">0;
   vector _o;
   GetOutput(class="num">0.0, _o);
   if(_o[_o.Size()-class="num">1] < _o[class="num">0])
   {  result = class="type">int(round(class="num">100.0 * ((_o[class="num">0] - _o[_o.Size()-class="num">1])/(_o.Max() - _o.Min()))));
   }
class=class="str">"cmt">//printf(__FUNCSIG__ + " output is: %.5f, change is: %.5f, and result is: %i", _mlp_output, m_symbol.Bid()-_mlp_output, result);class="kw">return(class="num">0);
   class="kw">return(result);
}

「回测报告里的内核表现与资金管理改造」

我们在 GBPJPY 的 2023 年日线周期上,分别跑通了线性内核与 Matérn 内核的优化。结果只说明智能系统在样本内可用,不指向未来表现;Matérn 内核在日线尺度上给出了更平滑的回归轨迹,但两者都只是验证「能跑」而非「能赚」。 高斯过程回归若想同时喂给信号类和资金管理类,最好抽一个通用锚点类让两边引用,否则两套自定义类里重复写相似函数,维护成本直接翻倍。信号类吃的是收盘价变化,而资金管理类这边换成 ATR 变化做内核输入,训练输出变成「下一根 ATR 变化量」。 原版仓位优化类会在连亏时按亏损串长度等比砍手数。我们改的逻辑是:只有「账户刚亏」且「预测的 ATR 上升」才降手数,预测步长由参数 m_next 控制。外汇与贵金属杠杆高,这类动态缩仓只能降低连续回撤杀伤,不保证扭亏。 下面这段是改造后的手数优化核心,注意它先拉历史成交、统计连亏,再调 GetOutput 拿 ATR 预测,决定是否缩仓。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Optimizing lot size for open.&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">class="kw">double</span> CMoneyGAUSS::Optimize(<span class="keyword">class="type">int</span> Type, <span class="keyword">class="type">class="kw">double</span> lots)
{&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> lot = lots;
<span class="comment">class=class="str">"cmt">//--- calculate number of losses orders without a break</span>
&nbsp;&nbsp; <span class="keyword">if</span>(m_decrease_factor &gt; <span class="number">class="num">0</span>)
&nbsp;&nbsp; {&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- select history for access</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">HistorySelect</span>(<span class="number">class="num">0</span>, <span class="functions">TimeCurrent</span>());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; orders = <span class="functions">HistoryDealsTotal</span>(); <span class="comment">class=class="str">"cmt">// total history deals</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; losses = <span class="number">class="num">0</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// number of consequent losing orders</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size = <span class="number">class="num">0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> series;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;series.Init(<span class="functions">fmin</span>(m_series_size, orders), <span class="number">class="num">2</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;series.Fill(<span class="number">class="num">0.0</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CDealInfo deal;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = orders - <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;deal.Ticket(<span class="functions">HistoryDealGetTicket</span>(i));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(deal.Ticket() == <span class="number">class="num">0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"CMoneySizeOptimized::Optimize: HistoryDealGetTicket failed, no trade history"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- check symbol</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(deal.<span class="functions">Symbol</span>() != m_symbol.Name())
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">continue</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- check profit</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> profit = deal.Profit();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; series[size][<span class="number">class="num">0</span>] = profit;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size++;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(size &gt;= m_series_size)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(profit &lt; <span class="number">class="num">0.0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;losses++;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> _cond = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--</span>
<span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">vector</span> _o;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GetOutput(<span class="number">class="num">0.0</span>, _o);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//decrease lots on rising ATR</span>

◍ 手数衰减与平台限制的对齐

当上一根 K 线的开盘价高于当前 K 线开盘价(即近期处于空头排列倾向)且账户出现亏损时,代码会把基础手数按亏损比例缩减:lot = NormalizeDouble(lot - lot * losses / m_decrease_factor, 2),保留两位小数,这是一种随回撤自动降仓的硬逻辑。 缩减完还不是终点,必须过一遍交易品种的真实合约限制。先取 LotsStep() 得到最小步进,把 lot 对齐到合法步长整数倍;再分别用 LotsMin() 和 LotsMax() 夹住下限与上限,低于最小手数直接拉回 minvol,高于最大手数压回 maxvol。 这段逻辑没有预测功能,只在执行层保证「降仓后报单不会被 MT5 拒掉」。外汇与贵金属杠杆高,自动降仓只能控制单笔风险暴露,不表示回撤会停止,实盘前请在策略测试器用历史数据跑一遍确认手数曲线。

MQL5 / C++
   if(_o[_o.Size()-class="num">1] > _o[class="num">0])
      lot = NormalizeDouble(lot - lot * losses / m_decrease_factor, class="num">2);
   }
class=class="str">"cmt">//--- normalize and check limits
   class="type">class="kw">double stepvol = m_symbol.LotsStep();
   lot = stepvol * NormalizeDouble(lot / stepvol, class="num">0);
class=class="str">"cmt">//---
   class="type">class="kw">double minvol = m_symbol.LotsMin();
   if(lot < minvol)
      lot = minvol;
class=class="str">"cmt">//---
   class="type">class="kw">double maxvol = m_symbol.LotsMax();
   if(lot > maxvol)
      lot = maxvol;
class=class="str">"cmt">//---
   class="kw">return(lot);
}

别急着下结论

线性核与 Matérn 核在金融时间序列建模里几乎是两条相反的路线。前者只吃得下规整、样本少的起始数据,后者靠参数 v 的可调性,能在噪声、断点甚至极平滑序列间切换角色,样本量从几十根 K 线涨到上千根时优势更明显。 附带工程文件里 MoneyWZ_37.mqh 曾在 197 行触发索引越界(series[size][0] = profit),作者已于 2024-09-18 发补丁,但 MT5 实盘前仍建议自己编译跑一遍策略测试器确认无报错。 高斯过程回归不是银弹,外汇与贵金属杠杆高、跳空频繁,核函数换得再花哨也只提高拟合概率,不消除爆仓风险。拿历史样本调通了,也先丢进tick级回测看滑点吃掉多少边缘收益,再谈上信号。

常见问题

ν 越小曲线越粗糙、越大越接近平方指数核;贵金属先试 ν=1.5 看折中平滑度,再用历史样本比对残差再定。
可以,把两核输出按权重相加进同一 CSignal 派生类即可;权重用入参暴露,避免硬编码导致回测无法对比。
小布可加载品种页的 AIGC 诊断,自动比对各内核回测衰减并推送异常,你只管看提醒做决策。
在仓位计算后做 NormalizeLots 对齐平台下限,截断而非四舍五入,防止单子因手数非法被拒。
高夏普可能来自样本内过拟合与平滑假象;外汇贵金属高风险,需用样本外与多周期交叉验证再谈概率倾向。