MQL5 中的范畴论 (第 10 部分):幺半群组(基础篇)
用幺半群重写 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 日线验证分布,再决定是否拆回均值。
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 策略测试器自行验证。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| ELEMENT CLASS |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="kw">template</span> <<span class="keyword">class="kw">typename</span> T> <span class="keyword">class</span> CElement : <span class="keyword">class="kw">public</span> CObject { <span class="keyword">class="kw">protected</span>: <span class="keyword">class="type">int</span> cardinal; T element[]; <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">bool</span> Cardinality(<span class="keyword">class="type">int</span> Value) { ... } <span class="keyword">class="type">int</span> Cardinality() { <span class="keyword">class="kw">return</span>(cardinal); } ... CElement(<span class="keyword">class="type">void</span>) { Cardinality(<span class="number">class="num">0</span>); }; ~CElement(<span class="keyword">class="type">void</span>) {}; }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| DOMAIN CLASS |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="kw">template</span> <<span class="keyword">class="kw">typename</span> T> <span class="keyword">class</span> CDomain : <span class="keyword">class="kw">public</span> CObject { <span class="keyword">class="kw">protected</span>: