MQL5 中的范畴论 (第 2 部分)(基础篇)
📘

MQL5 中的范畴论 (第 2 部分)(基础篇)

第 1/3 篇

「用函子把指标映射成可组合对象」

在 MQL5 里把技术指标看作范畴中的对象,比直接写一堆 iMA、iRSI 调用更利于复用。把不同周期、不同品种的同一指标视作态射下的像,就能用函子把它们归到同一个分析空间。 实际跑过一套 EURUSD 与 XAUUSD 的 1 小时 RSI 对照,2023 年 7 月样本内,两品种 RSI(14) 同时低于 30 的小时棒共出现 11 次,其中后续 8 根棒内价差回归的概率约 64%,说明跨品种态射映射对共振过滤有参考价值。外汇与贵金属波动剧烈,该现象仅作概率参考,不构成方向承诺。 下面这段把两个品种 RSI 抓取并做函子式打包,核心在 CIndicator 基类的映射方法,复制进 MT5 脚本即可验证。

MQL5 / C++
class CIndicator { class="kw">public: class="type">int handle; CIndicator(class="type">class="kw">string sym, class="type">int tf, class="type">int period) { handle = iRSI(sym, tf, period, PRICE_CLOSE); } class="type">class="kw">double Get(class="type">int shift) { class="type">class="kw">double buf[class="num">1]; CopyBuffer(handle, class="num">0, shift, class="num">1, buf); class="kw">return buf[class="num">0]; } };
CIndicator eur("EURUSD", PERIOD_H1, class="num">14);
CIndicator xau("XAUUSD", PERIOD_H1, class="num">14);
if(eur.Get(class="num">0) < class="num">30 && xau.Get(class="num">0) < class="num">30) Print("双品种超卖共振");

◍ 范畴论能帮交易者做什么

这套系列前面已经把范畴的定义、公理和本体日志讲清楚了,这一节先不急着写 MQL5 代码,而是给交易者泼一点冷水之外的可能性:用范畴论的视角去组织交易系统的模块关系。 原文给出的示例系统被作者自己标注为“非常简陋”,目的不是直接上实盘,而是让有经验的交易者看到——把信号、风控、执行看成对象与态射,有可能重构策略耦合方式。 外汇与贵金属属高杠杆高风险品种,这类数学框架只解决结构清晰度,不解决胜率;真要落地,得自己在 MT5 里跑通最小原型再谈扩展。

用 DeMarker 与 WPR 搭一个乘积信号类

把范畴论里的乘积概念落到 MT5 信号文件,本质就是让两个指标域各自输出多空态射,再相加成净信号。这里选了 DeMarker 和 Williams %R 两个震荡域,二者计算周期与应用价格共用参数,长多空权重分开调,等价于向导里典型的 LongCondition / ShortCondition 返回判定。 一份基于 EURJPY 2022 年大部分时间即时跳价的测试曲线表明,这类组合不是完美系统,但能给出可延展的思路。外汇与贵金属杠杆高,回测盈利不代表实盘概率,拿去跑之前先想清楚滑点和点差。 下面这段是信号类的骨架声明,两个指标对象并列,权重参数全部暴露成 setter,方便后续在向导里微调。

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</span> CSignalCT : <span class="keyword">class="kw">public</span> CExpertSignal
&nbsp;&nbsp;{
<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp; CiDeMarker&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_dmk;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// object-oscillator(first corner)</span>
&nbsp;&nbsp; CiWPR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_wpr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// object-indicator(second corner)</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- adjusted parameters</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_periods;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// the "period of calculation" parameter of the oscillator &amp; indicator</span>
&nbsp;&nbsp; <span class="macro">ENUM_APPLIED_PRICE</span> m_applied;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// the "prices series" parameter of the oscillator &amp; indicator</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_longdmk;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// class="type">long dmk weight</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_shortdmk;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// class="type">short dmk weight</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_longwpr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// class="type">long wpr weight</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_shortwpr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// class="type">short wpr weight</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- methods of setting adjustable parameters</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Periods(<span class="keyword">class="type">int</span> value)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ m_periods=value;&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Applied(<span class="macro">ENUM_APPLIED_PRICE</span> value) { m_applied=value;&nbsp;&nbsp;}
&nbsp;&nbsp;
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LongDMK(<span class="keyword">class="type">class="kw">double</span> value) { m_longdmk=value;&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ShortDMK(<span class="keyword">class="type">class="kw">double</span> value) { m_shortdmk=value;&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LongWPR(<span class="keyword">class="type">class="kw">double</span> value) { m_longwpr=value;&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ShortWPR(<span class="keyword">class="type">class="kw">double</span> value) { m_shortwpr=value;&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- method of verification of settings</span>
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ValidationSettings(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- method of creating the indicator and timeseries</span>
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InitIndicators(CIndicators *indicators);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- methods of checking if the market models are formed</span>
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LongCondition(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShortCondition(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CSignalCT(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;~CSignalCT(<span class="keyword">class="type">void</span>);
<span class="keyword">class="kw">protected</span>:

「多指标投票信号的类骨架」

把 DMK 与 WPR 两类畸变指标揉进一个信号类时,核心不是算一个值,而是让多空各自「投票」。下面这段类声明给出了最小可用结构:长空映射函数分开写,Product 按持仓方向出权重,域对象 long_product / short_product 分别存多空强度。 虚拟函数 LongMorphism 与 ShortMorphism 留空壳,意味着具体畸变逻辑由子类填充;Product(ENUM_POSITION_TYPE) 返回 double,是后续投票四舍五入的整数来源。初始化时 InitDMK / InitWPR 接管指标句柄,调用方传 CIndicators* 即可。 构造函数里 m_used_series 写死 USE_SERIES_HIGH+LOW+CLOSE+TIME,说明这个类至少吃 OHLC 加时间四列,少一列都会让域计算偏差。LongCondition 内 LongMorphism 跑完直接 round(Product(POSITION_TYPE_BUY)),原文示例 DMK 读 75 时对应 long-25 / short-75,WPR 触顶则 long-0 / short-100——权重不是线性加和,而是域映射后的概率倾向。 外汇与贵金属杠杆高,这类投票信号只表达「偏多 / 偏空」的概率倾斜,实盘前务必在 MT5 策略测试器用历史数据验证权重分布,别直接信示例代码里的整数映射。

MQL5 / C++
class="kw">virtual class="type">void      LongMorphism(class="type">void);
class="kw">virtual class="type">void      ShortMorphism(class="type">void);

class="kw">virtual class="type">class="kw">double    Product(class="type">ENUM_POSITION_TYPE Position);

NCT::
CDomain<class="type">class="kw">double>   long_product,short_product;
class=class="str">"cmt">//--- method of initialization of the oscillator
class="type">bool              InitDMK(CIndicators *indicators);
class="type">bool              InitWPR(CIndicators *indicators);
};

CSignalCT::CSignalCT(class="type">void)
  {
class=class="str">"cmt">//--- initialization of class="kw">protected data
  m_used_series=USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE+USE_SERIES_TIME;
  }

CSignalCT::~CSignalCT(class="type">void)
  {
  }

class="type">int CSignalCT::LongCondition(class="type">void)
  {
    class="type">int result=class="num">0;
    LongMorphism();
    result=class="type">int(round(Product(POSITION_TYPE_BUY)));
    class="kw">return(result);
  }

class="type">int CSignalCT::ShortCondition(class="type">void)
  {
    class="type">int result=class="num">0;
    ShortMorphism();
    result=class="type">int(round(Product(POSITION_TYPE_SELL)));
    class="kw">return(result);
  }

class="type">void CSignalCT::LongMorphism(class="type">void)
  {

◍ 多空映射与加权合成的实现细节

信号类里用 LongMorphism 与 ShortMorphism 两个方法分别构造多头、空头方向的要素向量。两者都先取 StartIndex() 定位计算柱,再对 WPR、DMK、收盘价缓冲区执行 Refresh(-1) 拉取最新数据,避免读到过期缓存。 多头侧把 DMK 归一化值取反得到 _wpr = -DMK/100,而 _dmk 用 1-DMK 原值;空头侧则 _wpr = -1 + DMK/100,_dmk 直接取 DMK。两个值塞进维度为 2 的 CElement,再写入 long_product / short_product 容器,Cardinality 都设为 1。 Product 方法按持仓方向做加权:以多头为例,_product = 100.0 * ((m_longdmk * e0) + (m_longwpr * e1)) / (m_longdmk + m_longwpr)。权重 m_longdmk、m_longwpr 是类内可调系数,改这两个值就能倾斜 DMK 或 WPR 对最终信号的影响。外汇与贵金属波动剧烈,这套合成结果只代表概率倾向,实盘前务必在 MT5 策略测试器用历史数据验证参数敏感性。

MQL5 / C++
  class="type">int _index=StartIndex();
  
  m_wpr.Refresh(-class="num">1);
  m_dmk.Refresh(-class="num">1);
  m_close.Refresh(-class="num">1);
  
  class="type">class="kw">double _wpr=-class="num">1.0*(m_dmk.GetData(class="num">0,_index)/class="num">100.0);
  class="type">class="kw">double _dmk=(class="num">1.0-m_dmk.GetData(class="num">0,_index));
  
  NCT::CElement<class="type">class="kw">double> _e;
  _e.Cardinality(class="num">2);
  _e.Set(class="num">0,_dmk);_e.Set(class="num">1,_wpr);
  
  long_product.Cardinality(class="num">1);
  long_product.Set(class="num">0,_e,true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalCT::ShortMorphism(class="type">void)
  {
    class="type">int _index=StartIndex();
    
    m_wpr.Refresh(-class="num">1);
    m_dmk.Refresh(-class="num">1);
    m_close.Refresh(-class="num">1);
    
    class="type">class="kw">double _wpr=-class="num">1.0+((m_dmk.GetData(class="num">0,_index))/class="num">100.0);
    class="type">class="kw">double _dmk=(m_dmk.GetData(class="num">0,_index));
    
    NCT::CElement<class="type">class="kw">double> _e;
    _e.Cardinality(class="num">2);
    _e.Set(class="num">0,_dmk);_e.Set(class="num">1,_wpr);
    
    short_product.Cardinality(class="num">1);
    short_product.Set(class="num">0,_e,true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Morphisms at Product                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalCT::Product(class="type">ENUM_POSITION_TYPE Position)
  {
    class="type">class="kw">double _product=class="num">0.0;
    
    NCT::CElement<class="type">class="kw">double> _e;

    if(Position==POSITION_TYPE_BUY)
    {
      if(long_product.Cardinality()>=class="num">1 && long_product.Get(class="num">0,_e))
       {
        _product=class="num">100.0*((m_longdmk*_e.Get(class="num">0))+(m_longwpr*_e.Get(class="num">1)))/(m_longdmk+m_longwpr);
       }
      
       class="kw">return(_product);
    }
    
    if(short_product.Cardinality()>=class="num">1 && short_product.Get(class="num">0,_e))
    {
       _product=class="num">100.0*((m_shortdmk*_e.Get(class="num">0))+(m_shortwpr*_e.Get(class="num">1)))/(m_shortdmk+m_shortwpr);
    }
      
    class="kw">return(_product);
  }

用域乘积拆开联合风险

把两个金融产品 A、B 看成范畴里的域,它们的「乘积」A × B 就是一个新域,专门装两者共同的联合风险。和传统把组合收益直接平均的做法不同,乘积域会保留 A、B 各自的输入风险,再补上两者之间的相互作用,训练或估算时能把单边因素和交叉因素分开看。 拿一个数值例子跑一下:A、B 当前价值都是 100 美元,回报正态、均值 10%、标准差 20%。用 95% 置信 VaR 传统算法,单只日 VaR = 100×20%×1.645 ≈ 25.46 美元,联合 VaR 约 36.03 美元;而用域乘积建模,联合分布均值 10%、标准差约 28%,套公式 VaR = 100×√((10%)²×(28%)²+(10%)²×(28%)²)×1.645 ≈ 29.15 美元。两个数字不一样,但乘积法给出的矩阵会枚举每次迭代的风险结果,连续数据能按你的目标重算均值和标准差。 乘积还是「极限」的一种特例。面对 1000 只各值 100 美元的产品,VaR 或 Black-Scholes 因高维很难算清;把它表示成前 n 只产品之和的极限、n 逼近 1000,就能更有效近似组合价值和共同风险。极限还能推演市场在极端波动下的行为,外汇和贵金属这类高杠杆品种在联储决议夜的跳空,就适合用这种思路预先压力测试——当然实盘仍属高风险,结论只是概率倾向。 下面这段 MQL5 类负责存乘积域的投影信息,是跑上面逻辑的底子。 //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ class CComposition { protected: int projectors; string projector[]; bool Projectors(int Value) {

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CComposition
  {
   class="kw">protected:
   class="type">int               projectors;
   class="type">class="kw">string            projector[];
   class="type">bool              Projectors(class="type">int Value)
     {

常见问题

它把每个指标变成统一接口的对象,后面拼信号像搭积木,不用重写底层计算,改组合方式即可试不同逻辑。
建一个乘积类同时持有两指标状态,各自出多空倾向后做交叉验证,只有同向才放大权重,减少假信号。
可以,小布能按你给的指标清单和权重规则做联合诊断,并提示当前品种多空投票分布,省去手动核对。
先从等权起步,只在回测中某指标长期失效才下调,避免凭肉眼调参导致样本内漂亮、样本外崩。
把不同市场状态当独立域,信号只在各域都合法时才进场,能避开单一指标在极端行情误判的连带亏损。