MQL5 中的范畴论 (第 8 部分):幺半群(Monoids)·进阶篇
📘

MQL5 中的范畴论 (第 8 部分):幺半群(Monoids)·进阶篇

第 2/3 篇

「先把数据喂干净再谈结构」

在把行情数据丢进幺半群归类之前,预处理不是可选项,而是品控的第一道闸。核心矛盾在于:幺半群运算要求每个输出都是集合里的确定成员,而不是一堆小数点后六七位的浮点噪声——那些数四舍五入之后语义就飘了。 具体做法有两条路:要么对原始值做规范化(常规化),把它压进幺半群可接受的定义域;要么直接转换成适合代数运算的离散格式。两条路都绕不开一个坑:数值缺失。MT5 里跳空、非交易时段、品种停牌都会制造空洞,不填不补,后面整条系统的自洽性就会塌。 实操上,开 MT5 调 CopyClose 拉一段 XAUUSD 的 H1 收盘,你会看到约 0.3%–1.2% 的柱在流动性差的时段出现价格粘连或缺失(视经纪商而定)。先写个清洗函数把这些点归一,再进下一步代数结构,否则回测结果大概率虚高。外汇与贵金属杠杆高、跳空频繁,预处理不当会放大实盘风险。

◍ 分类前先保证数据同质性

做聚类或状态分类前,喂给模型的每一类数据得先有内在一致性。以指标组合为例,若想把 RSI 振荡器和布林带放进同一个幺半群集合里比权重,两者量纲不同,直接混用会破坏可比性。 实操上至少要对其中一个做常规化(归一或标准化),让两类序列落在可比较的区间。经验上,当每个类里的样本特征足够相似时,幺半群划分的边界才清晰,后续分类误判概率会明显走低。 外汇与贵金属波动带有高杠杆风险,同质化处理只解决数据可比问题,不预示任何方向性结果,参数仍建议在 MT5 用历史数据复验。

情绪分类的基数陷阱

用幺半群给新闻情绪打标签时,类别的基数(不同取值个数)直接决定模型能不能用。基数太高,解释性崩;太低,又容易把市场异动全压成一个标签。 拿外汇事件驱动策略举例:目标变量 Sentiment 取三值——高于预期、符预期、低于预期。样例里美联储制造业生产月率标“低于”,GDT 价格指数标“符合”,企业库存月率标“高于”,NAHB 住房市场指数标“低于”。这一列变量的基数就是 3。 场景 1 是基数没变但样本倾斜:若 80% 样本是“高于”,10%“符合”、10%“低于”,分类器会偏向多数类,次要类的精度与召回率大概率塌掉,整体可解释性跟着废。外汇和贵金属受事件冲击大,这类偏颇会放大实盘误判风险。 场景 2 换用浮点权重描述幺半群元素,基数不再绑定为固定几类,每个集合元素可带连续权重。这意味着你不再受“三档情绪”的硬限制,但基数失控时,过拟合概率会显著上升。

「用同态把财务数据压成比率,幺半群才扛得住大样本」

评估幺半群分类的可伸缩性,核心看它在大型数据集上的计算复杂度。若直接拿原始财务字段堆量,内存与算力消耗会随公司数线性膨胀,这时候该做的不是加机器,而是先做特征工程。 具体做法是用幺半群同态把输入特征映射到新的特征空间。例如原始营收幺半群里列着上千家独立公司的营收绝对值,定义一个同态把它映射到「营收增长率」幺半群后,大量公司会共享同一个增长率数值——独立键的数量可能从 1200 个降到 80 个以内,数据需求直接砍掉一个数量级。 同理,收益幺半群可经同态映射为每股收益(EPS)幺半群。EPS 作为每股盈利能力指标,比绝对收益更适合跨公司比较。这类比率派生都能让模型在协域层面收敛,避免域层面的爆炸。 别急着上 Apache Hadoop 或 Spark 做分布式并行。集群方案能把处理时间压下来,但下游成本显著:网络调度、节点一致性和运维开销都不小。更关键的是,这些问题本可在幺半群设计层用同态归约巧妙消化,不一定非得推到计算框架层。

◍ 用 unseen 数据压住过拟合

验证基于幺半群的分类是否真的泛化,只能靠没参与训练的数据。拿贷款信誉预测举例:训练集若只覆盖某区域、某时间段的收入与信用评分,测试集必须换区域、换时段再跑一遍;两边准确率、召回率都稳,才算普适性过关。 三个坑最致命:过拟合是运算函数把训练样本记太死,新数据直接掉点;数据偏颇来自训练集本身不代表总体;特征选择偏颇则是挑的维度漏掉了上下文关键信息。三者都会让模型在实盘外显原形。 评估指标别乱套,准确性、精确度、召回率、F1 分数按你的业务目标选,但必须对齐问题本身。交叉验证、正则化、提前停止这三样至少上一道,才能把过拟合和欠拟合概率压下去。 幺半群给的结论再强,也得能反推决策路径。分类结果不可释义,就没法在 MT5 外接脚本里做人工否决,实盘外汇与贵金属的高风险环境下尤其危险。

用幺半群配对把八维参数压成单一尾随停止

回溯期用编号 1 到 8 的幺半群域表达,一个周期单位固定为 4 根 H1 柱。每根新柱重选周期,权重按当前周期波动绝对值占前后两周期之和的比例算:ABS(A)/(ABS(A)+ABS(B)),分母做了防零处理,下限为一个点值。 时间帧集合挂了 H1、H2、H3、H4、H6、H8、H12、D1 共 8 档,权重逻辑同回溯期,只是比的是各档当前与前期收盘价的相对变化。应用价格给 4 种:中位数、典型价、开盘、收盘,权重改抓回溯段内的标准差,不再用涨跌比。 指标只放 RSI 和布林带。布林带先算 C/(ABS(C)+ABS(D)),C 是价与中轨差,D 是上下轨距;再加 1.0 乘 50.0 映射到 0–100,和 RSI 同量程。决策集合二选一:顺势或区段,用回溯段内逆尾趋势的折返点占全距比衡量,趋势权重取其补数。 Operate_8 把 8 元素两两配对出 4 选,Operate_4 再压到 2 选,Operate_2 定唯一胜出项。下面这段代码就是 Operate_8 的骨架,先抓回溯值,再按 OP_LEAST / OP_MOST 挑小或挑大。 回测里这套尾随停止接 RSI 信号与固定资金管理,对比相近但用均线尾随的系统,报告差异值得在 MT5 自建 EA 复跑验证;外汇与贵金属杠杆高,参数优不代表实盘稳,需自测。

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="type">void</span> CTrailingCT::Operate_8(CMonoid&lt;<span class="keyword">class="type">class="kw">double</span>&gt; &amp;M,EOperations &amp;O,<span class="keyword">class="type">class="kw">double</span> &amp;Values[],<span class="keyword">class="type">int</span> &amp;InputIndices[],<span class="keyword">class="type">int</span> &amp;OutputIndices[])
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>;i&lt;<span class="number">class="num">8</span>;i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_element.Let();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(m_lookback.Get(i,m_element))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(!m_element.Get(<span class="number">class="num">0</span>,Values[InputIndices[i]]))
&nbsp;&nbsp;&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 class="functions">printf</span>(<span class="keyword">__FUNCSIG__</span>+<span class="class="type">class="kw">string">" Failed to get class="type">class="kw">double for class="num">1 at: "</span>+<span class="functions">IntegerToString</span>(i+<span class="number">class="num">1</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; <span class="keyword">else</span>{ <span class="functions">printf</span>(<span class="keyword">__FUNCSIG__</span>+<span class="class="type">class="kw">string">" Failed to get element for class="num">1 at: "</span>+<span class="functions">IntegerToString</span>(i+<span class="number">class="num">1</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">//</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<span style="background-class="type">color:rgb(class="num">164, class="num">192, class="num">228);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(O==OP_LEAST)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>;i&lt;<span class="number">class="num">8</span>;i+=<span class="number">class="num">2</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(Values[InputIndices[i]]&lt;Values[InputIndices[i+<span class="number">class="num">1</span>]]){ OutputIndices[i/<span class="number">class="num">2</span>]=i; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> <span class="keyword">if</span>(Values[InputIndices[i]]&gt;Values[InputIndices[i+<span class="number">class="num">1</span>]]){ OutputIndices[i/<span class="number">class="num">2</span>]=i+<span class="number">class="num">1</span>; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> { OutputIndices[i/<span class="number">class="num">2</span>]=m_lookback.Identity(); }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span>
<span style="background-class="type">color:rgb(class="num">255, class="num">246, class="num">200);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> <span class="keyword">if</span>(O==OP_MOST)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>;i&lt;<span class="number">class="num">8</span>;i+=<span class="number">class="num">2</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span>

常见问题

先按品种、周期、波动状态切分样本,看各组分布是否明显偏移;不同质的数据混在一起分类会直接失真,建议分组单独建模。
维度高容易记噪声,用幺半群配对把八维压成单一尾随停止量,再用 unseen 数据验证,能压住过拟合倾向。
可以,小布能按品种页自动标出分类基数异常和样本同质性问题,你只需打开对应页看预警,不用自己写校验。
先用同态把绝对财务数据映射成可比比率,保证幺半群在大样本下封闭,否则大样本会放大单位不一致带来的偏差。
常见留 20%~30% 作 unseen 验证;样本少就做交叉验证,核心是模型在没见过的数据上表现不退化。