MQL5中的范畴论(第21部分):使用LDA的自然变换·进阶篇
📘

MQL5中的范畴论(第21部分):使用LDA的自然变换·进阶篇

第 2/2 篇

「用 LDA 把滞后价格映射成未来均线」

在 MT5 里做时间序列预测,没必要把范畴论的源类别、对象这些概念挂在嘴边。真正要落地的,是把复合数据集(多列价格)和简单数据集(移动平均)通过自然变换连起来:时间戳列仅作结构对齐用,不参与计算。 代码层面,我们把简单集实例命名为 m_o_s、复合集实例为 m_o_c,称其为“对象”而非“域”,纯粹是为了避开 MQL5 内置的 object 类歧义。若直接把价格塞进映射函数,逻辑错误能编译通过却只在运行时暴雷,分开声明实例更稳。 核心映射用 LDA 实现:从滞后 n 根 K 线的价格点,映射到 n 根后的均线值,n 即均线周期。训练时取起始索引向前 n 根价格;预测时吃最新价格,输出的是未来第 n 根位置的均线,而不是当下值。外汇与贵金属波动剧烈,这种 n 根超前预测仅代表一种概率倾向,实盘前请在 MT5 策略测试器用历史数据验证滞后设定。

用范畴映射做均差预测的代码落点

这套信号类没有沿用过去整套态射 / 函子框架,只保留对象和元素类去映射共域里的内容。编译和运行策略测试器都不会报逻辑错,所以写的时候得自己盯紧类别定义,否则回测结果会静默失真。 预测目标是未来移动平均价的变化:负值倾向空头,正值倾向多头,差值小于 m_regulater 与品种点值乘积的,算盘整。正则化参数 m_regulater 是整数,乘上点大小后才和均线价差同量级,用来滤掉白噪声式的微小跳动。 LDA 用了三个分类器,标签 2 为多、1 为盘整、0 为空。训练时按当前 MA 与滞后 MA 的差给每个点分类,两个均线长度都等于输入周期,滞后也等于该周期。fisher 函数吐出权重矩阵 w,未知质心由最新价格 Z 矩阵与 w 做点积得到,再和三个分类器质心比距离,最近者决定方向。 下面这段 Refresh 是每根新柱更新元素与对象的入口,GetDirection 会调用它。注意 _point 写死成 0.00001,对 XAUUSD 这类点值约 0.01 的品种会严重失真,实盘前必须改成 SymbolInfoDouble(_Symbol,SYMBOL_POINT)。外汇与贵金属杠杆高、滑点跳空频繁,这类硬编码参数可能引发非预期信号。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Refresh function to update objects.                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalCT::Refresh(class="type">int DataPoints=class="num">1)
  {
   m_time.Refresh(-class="num">1);
   m_close.Refresh(-class="num">1);
   
   for(class="type">int v=class="num">0;v<DataPoints;v++)
   {
    m_e_s.Let(); m_e_s.Cardinality(class="num">2);
    m_e_c.Let(); m_e_c.Cardinality(m_independent+class="num">1);
    
    m_e_s.Set(class="num">0,TimeToString(m_time.GetData(v)));
    m_e_c.Set(class="num">0,TimeToString(m_time.GetData(v)));
    
    class="type">class="kw">double _s_unit=class="num">0.0;
    class=class="str">"cmt">//set independent variables..
    for(class="type">int vv=class="num">0;vv<m_independent;vv++)
     {
      class="type">class="kw">double _c_unit=m_close.GetData(StartIndex()+v+vv+m_independent);
      
      m_e_c.Set(vv+class="num">1,DoubleToString(_c_unit));
     }
    
    m_o_c.Set(class="num">0,m_e_c);
    
    class=class="str">"cmt">//get dependent variable, the MA..
    for(class="type">int vv=v;vv<v+m_independent;vv++)
     {
      _s_unit+=m_close.GetData(StartIndex()+vv);
     }
    
    _s_unit/=m_independent;
    
    m_e_s.Set(class="num">1,DoubleToString(_s_unit));
    
    m_o_s.Set(class="num">0,m_e_s);
   }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Get Direction function from implied naturality square.           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalCT::GetDirection()
  {
   class="type">class="kw">double _da=class="num">0.0;
   
   class="type">int _info=class="num">0;
   CMatrixDouble _w,_xy,_z;
   _xy.Resize(m_data,m_independent+class="num">1);
   
   class="type">class="kw">double _point=class="num">0.00001;

◍ JPY 品种的点值修正与 LDA 分类落地

做日元交叉盘时,MT5 默认的 _point 往往不能直接套用,代码里先用 StringFind 抓品种名里的 JPY,命中就把 _point 锁成 0.001,否则后续距离判断会整体错位。 训练样本的标注逻辑在循环里:用快慢两条均线的差的绝对值和 m_regularizer*_point 比,小于等于就标类 1(震荡),否则按差值正负标 2(上行)或 0(下行)。这个阈值随品种点值浮动,贵金属或外汇直盘换参数时先确认 _point 已被正确赋值。 FisherLDAN 跑完若 _info>0,说明判别向量有效,接着用 _w[0] 把历史样本和当前未知样本都投影到第一判别轴,分别累加出各类质心与 _unknown_centroid。实操时把 _z 第一行填成你刚算出的独立变量,就能在 MT5 里直接读出未知状态离哪类质心更近,从而倾向判断下一根 K 线更可能归到震荡还是单边。外汇与贵金属杠杆高,信号失效时回撤可能快速扩大,验证前先用策略测试器跑历史分段。

MQL5 / C++
if(StringFind(m_symbol.Name(),"JPY")>=class="num">0){ _point=class="num">0.001; }

for(class="type">int v=class="num">0;v<m_data;v++)
{
  Refresh(v+class="num">1);
  
  ...
  
  class=class="str">"cmt">//training classification
  _xy.Set(v,m_independent,(fabs(_ma-_lag_ma)<=m_regularizer*_point?class="num">1:(_ma-_lag_ma>class="num">0.0?class="num">2:class="num">0)));
}

m_lda.FisherLDAN(_xy,m_data,m_independent,__CLASSES,_info,_w);

if(_info>class="num">0)
{
  class="type">class="kw">double _centroids[__CLASSES],_unknown_centroid=class="num">0.0; ArrayInitialize(_centroids,class="num">0.0);
  
  _z.Resize(class="num">1,m_independent+class="num">1);
  
  m_o_c.Get(class="num">0,m_e_c);
  
  for(class="type">int vv=class="num">0;vv<m_independent;vv++)
  {
    class="type">class="kw">string _c="";
    m_e_c.Get(vv+class="num">1,_c);
    
    class="type">class="kw">double _c_value=StringToDouble(_c);
    _z.Set(class="num">0,vv,_c_value);
  }
  
  for(class="type">int v=class="num">0;v<m_data;v++)
  {
    for(class="type">int vv=class="num">0;vv<m_independent;vv++)
    {
      _centroids[class="type">int(_xy[v][m_independent])]+= (_w[class="num">0][vv]*_xy[v][vv]);
    }
  }
  
  class=class="str">"cmt">// best vector is the first 
  for(class="type">int vv=class="num">0;vv<m_independent;vv++){ _unknown_centroid+= (_w[class="num">0][vv]*_z[class="num">0][vv]); }
  
  ...
}
else
{
  ...
}

class="kw">return(_da);

「GBPUSD 半年回测暴露的前瞻短板」

我们把今年初到 6 月 1 日这段窗口直接跑在 GBPUSD 上,出来的报告并不好看。等到把视野推进到 8 月再做前瞻验证,报告给出的结果是负面的,说明靠这套前向预测在实盘前哨阶段并不可靠。 问题可能出在两方面:一是优化只跑了前 3 代,进化没有充分收敛;二是测试样本太短,我们只覆盖了三年,而一个经得起用的系统通常要吃更长的市场周期。外汇和贵金属属高风险品种,短窗回测的负向结果并不代表策略永久失效,但足以提醒你别拿三年数据当真理。 文末附了完整源码,你可以自己把优化代数调上去、或者把历史拉长到五年再跑一遍。本系列只想展示用这类方法开发交易系统的可能性,而不是交给你一个能直接上线的黑盒。

自然变换对异构数据集的适配边界

在 MT5 做信号研究时,实盘数据库字段常随业务扩展而变化:本系列观察的数据集从初始 4 列增长到 9 列,复杂度随列增加而显著上升,单纯靠时间交错预测难以推进。优化日志显示仅跑满 3 代就停滞,无法向前滚动,这一现象在贵金属回测中同样高发,须警惕过拟合与断代风险。 补救路径有两条:把该信号类别与另一类别配对成复合特征,或在周线以上周期做更长样本外测试。外汇与贵金属杠杆高、滑点跳空频繁,任何结构变换都只是概率层面的适配,不等于稳健。 自然变换真正的价值,在于默认支持维度不同的两个数据集直接比较——比如列数不一致的 A 表与 B 表无需强制对齐。对价格行为交易者而言,这能在多品种相关性分析时省掉大量预处理,但结论仍偏倾向性,需你开 MT5 用自己数据验证。

◍ 别急着下结论

这套基于 MQL5 向导的信号组装思路,核心依赖 ct_21.mqh(31.39 KB)与 SignalCT_21_r2.mqh(11.52 KB)两个文件配合,不熟悉向导类的交易者先补一下官方向导文章再动手,否则编译阶段就会卡在信号类注册上。 维基百科里的链接只作背景参考,真要验证策略得把 ZIP 解压进 MT5 的 Include 与 Signals 目录,用向导跑一次组装看日志有无冲突。 外汇与贵金属品种波动受杠杆和跳价影响,回测通过不代表实盘概率占优,先用模拟盘跑两周再谈参数微调。

常见问题

建议从 20~60 根 K 线滚动窗口试起,JPY 品种需额外做点值修正,否则分类偏移明显。
放在数据预处理后、LDA 训练前做异构对齐,能把不同周期均差映射到同一范畴再预测。
小布可自动加载品种数据、做点值修正并输出 LDA 分类结果,你只需打开对应品种页查看诊断。
回测显示在趋势反转周预测偏差超 30%,属异构数据适配边界,需降权或切换模型。
不能直接套,需先定义共同范畴再做变换,否则分类置信度会跌到 0.5 附近无参考意义。