MQL5 中的范畴论 (第 10 部分):幺半群组(基础篇)
📘

MQL5 中的范畴论 (第 10 部分):幺半群组(基础篇)

第 1/3 篇

用幺半群重写 MT5 指标聚合

在 MQL5 里把一组数值运算抽象成幺半群组(monoid),核心就两条:存在一个单位元,且运算满足结合律。对交易者而言,最直接的落点是把多周期 RSI 或不同品种的同名指标做「可叠加聚合」,而不必关心先算哪一段。 下面这段演示了如何定义一个带单位元的求和幺半群,并在 EA 初始化时把三个不同时间帧的缓冲区值归并。注意 Identity() 返回 0.0 而非 NULL,这是浮点序列做结合律折叠的安全起点。 [CODE] //+------------------------------------------------------------------+

//Monoid summation structure

//+------------------------------------------------------------------+ struct SumMonoid { double value; void Add(double x) { value += x; } static double Identity() { return 0.0; } }; //+------------------------------------------------------------------+

//EA initialization

//+------------------------------------------------------------------+ int OnInit() { SumMonoid m; m.value = SumMonoid::Identity(); m.Add(iRSI(_Symbol,_Period,14,PRICE_CLOSE,0)); m.Add(iRSI(_Symbol,PERIOD_H1,14,PRICE_CLOSE,0)); m.Add(iRSI(_Symbol,PERIOD_D1,14,PRICE_CLOSE,0)); Print("Aggregated RSI sum: ", m.value); return(INIT_SUCCEEDED); } [/CODE] 逐行拆解:struct SumMonoid 封装一个 double 字段与两个方法;Add 做普通加法,满足结合律;Identity 静态返回 0.0 充当单位元。OnInit 中先以单位元初始化,再依次叠入当前帧、H1、D1 的 RSI(14) 即时值,最后打印聚合和。 外汇与贵金属市场杠杆高、跳空频繁,这类聚合仅描述结构而非信号方向;RSI 多帧相加后数值可能超 100,实盘使用前请在 MT5 策略测试器用 2023 年 XAUUSD 日线验证分布,再决定是否拆回均值。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Monoid summation structure                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct SumMonoid
  { class="type">class="kw">double value;
    class="type">void Add(class="type">class="kw">double x) { value += x; }
    class="kw">static class="type">class="kw">double Identity() { class="kw">return class="num">0.0; }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA initialization                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  { SumMonoid m; m.value = SumMonoid::Identity();
    m.Add(iRSI(_Symbol,_Period,class="num">14,PRICE_CLOSE,class="num">0));
    m.Add(iRSI(_Symbol,PERIOD_H1,class="num">14,PRICE_CLOSE,class="num">0));
    m.Add(iRSI(_Symbol,PERIOD_D1,class="num">14,PRICE_CLOSE,class="num">0));
    Print("Aggregated RSI sum: ", m.value);
    class="kw">return(INIT_SUCCEEDED);
  }

「从幺半群动作切到幺半群-组视角」

前一篇我们用幺半群动作把幺半群集合做了扩展,这次换一个参数下手:衡点元素。借由重定义幺半群里的衡点,我们把观察对象从幺半群动作挪到幺半群-组,相当于在交易决策点上换了一套代数镜头。 到目前为止这个系列已经铺过域、态射、范畴条例、单态回拉和表态外推。没有哪一篇是奔着完整交易系统去的,和之前用 MQL5 向导拼 EA 信号类、尾随类的做法不同,本篇只拆已经写好的幺半群、幺半群-动作、幺半群-组类里的单个函数。 你可以直接开 MT5 把这几类源码调出来,在关键决策点单步跟函数,看代数结构怎么影响下单判断。外汇和贵金属杠杆高,这类抽象框架只提供概率倾向,不替代风控。

◍ 幺半群到幺半群组的逆元约束

幺半群由三要素构成:一个集合、集合内的衡点元素、以及输出始终封闭在集合内的二元操作。若任意成员与衡点配对,输出仍是该成员本身。 幺半群组与通常说的“组”在结构上并无本质差别,区别只在于前缀限定了它严格属于某一类集合。按组的定义,幺半群组是在幺半群基础上附加了逆元属性:集合中每个元素都存在另一个元素,两者经二元操作配对后输出恒为衡点元素 e。 从交易视角看,前文把幺半群当作决策选项池,并用“幺半群-动作”按特征权重扩展纵深,实测系统表现反而差于受限默认幺半群的平均水准。对于幺半群组,更合理的做法是退回含动作的受限幺半群,再转换到组结构,而非继续加深原集合。 本文未给出交易系统的具体实现,读者可在 MT5 自行搭建受限幺半群并验证逆元配对后输出衡点的性质,外汇与贵金属市场高风险,此类代数结构仅作决策框架参考,不预示任何收益。

用类继承搭出幺半群组骨架

在 MT5 里落地幺半群组,先由向导建脚本 ct_10,它引用头文件 ct_10.mqh,该文件是前两篇元素与域类设计的修订版。核心思路不是另起炉灶定义新集合,而是让幺半群类公开继承域类——代码上直观表达「幺半群只是一个带二元运算和衡点元素的域」。 衡点元素不单独实例化,只保存它在域元素数组里的索引,避免重复占用内存。幺半群类可由脚本指针自动初始化,而「幺半群组」在语义上和幺半群无类结构差异,仅多一个 HasInversion 检查函数,用来验证每个元素相对衡点是否存在逆元。 此前文章的元素在做二元运算前需转成可等价比较的加权格式,本文把权重定为双精度并移入「幺半群-组」类做参数化存储。实例 _vg 给定大小 5,但因整数集配对 2+1=3 未列在集中,它实际指向无界整数集 Z,组律只可能在无限集上严格满足。 跑一遍打印检查,能看到每个元素是否都有逆元输出;外汇与贵金属市场波动剧烈、杠杆风险高,这类抽象结构仅作策略建模底座,实盘前务必在 MT5 策略测试器自行验证。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| ELEMENT CLASS&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="kw">template</span> &lt;<span class="keyword">class="kw">typename</span> T&gt;
<span class="keyword">class</span> CElement&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;: <span class="keyword">class="kw">public</span> CObject
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span>&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; cardinal;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;T&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; element[];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">bool</span>&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;Cardinality(<span class="keyword">class="type">int</span> Value) { ... }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span>&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; Cardinality() { <span class="keyword">class="kw">return</span>(cardinal); }
&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CElement(<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;&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; Cardinality(<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;&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;~CElement(<span class="keyword">class="type">void</span>) {};
&nbsp;&nbsp; };
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| DOMAIN CLASS&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="kw">template</span> &lt;<span class="keyword">class="kw">typename</span> T&gt;
<span class="keyword">class</span> CDomain&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : <span class="keyword">class="kw">public</span> CObject
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">protected</span>:

常见问题

把每次聚合看成二元运算,只要满足结合律和有个单位元,就能用折叠(fold)替代嵌套判断,代码更短也更好改。
组视角强制要求逆元存在,能帮你检查某个指标变换是否可逆,避免把单向滤波误当对称信号用。
可以,小布能读你的指标逻辑并标注出缺失的单位元或违背结合律的运算,省去手工推演。
坑在滑动窗口类计算:窗口丢弃旧值若无显式逆运算抵消,就构不成群,只能算幺半群。
留二元组合方法和取单位元方法,派生类只补具体运算与逆元,骨架就能直接复用。