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

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

第 1/3 篇

把交易信号塞进函子看

在 MQL5 里用范畴论建模,第一步是把行情数据当成对象,把指标变换当成态射。这样一套 K 线序列经过移动平均映射后,仍是同一个范畴里的对象,只是形态被重新描述。 具体落地时,可以写一个函子把原始蜡烛数组映射到标准化收益数组。下面这段代码在 MT5 里能直接编译,核心是把每根收盘相对前收的变动算出来。 外汇与贵金属波动剧烈,这类抽象映射只描述结构、不预测方向,实盘使用前请在策略测试器里跑至少 3 个月 tick 数据验证稳定性。

MQL5 / C++
class="type">class="kw">double Close[class="num">100];
CopyClose(_Symbol,_Period,class="num">0,class="num">100,Close);
class="type">class="kw">double Returns[class="num">99];
for(class="type">int i=class="num">1;i<class="num">100;i++)
  Returns[i-class="num">1]=Close[i]-Close[i-class="num">1];

「用日常例子看清范畴里的域与态射」

范畴论把信息拆成基本单位「元素」,元素聚成「域」,域与域之间靠「态射」建立关系。这套结构并不抽象,放到交易外的生活场景里反而一眼能看穿。 以交通方式为例:域可以是汽车、飞机、火车;汽车域里的元素有共享车、租赁车、私家车、出租车,飞机域里有私人机、商业航班、租赁机,列车域含电车、子弹头、蒸汽机。一次行程若用 lyft 去机场、搭商务航班、再转子弹头列车,每一步选择就是跨域的态射,完整行程被映射进同一个类别。 一顿饭也能这么看:开胃菜、汤、主菜、甜点、奶酪是五个域,每个域下列出具体菜品元素(如蜜饯胡萝卜、南瓜浓汤)。顾客点单的路径就是态射,把五道菜连成一顿饭的类别。每周娱乐同理——体育直播、流媒体、公园是域,NFL/Netflix/迪士尼是元素,观众的选择跨越域形成娱乐安排。 这些例子说明,范畴能记录的远不止金融序列。它的真正价值在「量化等价性」:两个看似无关的主题,深入后可能同构或镜像。决策信息不足时,手里有相关类别就能补上缺口。外汇与贵金属市场高波动、高杠杆,用这类结构梳理品种关联,也只是辅助,不替代风控。

◍ 同构判定与 MT5 里的可枚举验证

同构不是单个映射的事,而是一组态射(同态集)共同满足可逆且保结构的性质。用衣服尺寸打比方:衬衫的「小/中/大」映射到裤子的「28/30/32」且能反向还原,才叫同构;若裤子侧有「26/28/30/32」四个码,小号能配 26 也能配 28,就没有一一对应,可逆性破裂。两个域的基数(元素数量)必须相同,否则必然出现多对一,同态无法逆转。 确认同构前必须把域里所有可能的态射列全。只看一条映射就下结论,可能误判两对象同构。典型做法是取一对互逆态射:一个从衬衫到裤子,一个从裤子回衬衫,复合后回到原域的恒等映射,才算数。 在 MT5 里我用模板重写了同态类,范畴类支持 datetime、string、double、int 四种类型,int 为默认(color 之类也归 int)。测试时调用 IsIsomorphic 函数,返回 true 表示两域同构、false 表示否;输出变量 _h_i 是同态类数组,装下了两域间所有可枚举的同构同态。 跑这段代码,偶数域与奇数域的日志打印结果不同,直接看终端输出就能判别。外汇与贵金属市场高风险,这类结构识别只解决「关系是否可逆」的数学判定,不预示任何价格方向。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| HOMO-MORPHISM 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;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="kw">template</span> &lt;<span class="keyword">class="kw">typename</span> TD,<span class="keyword">class="kw">typename</span> TC&gt;
<span class="keyword">class</span> CHomomorphism&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; morphisms;
&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;CDomain&lt;TD&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; domain;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CDomain&lt;TC&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; codomain;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CMorphism&lt;TD,TC&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;morphism[];
&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; Morphisms() { <span class="keyword">class="kw">return</span>(morphisms); }
&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;Morphisms(<span class="keyword">class="type">int</span> Value) { <span class="keyword">if</span>(Value&gt;=<span class="number">class="num">0</span> &amp;&amp; Value&lt;<span class="macro">INT_MAX</span>) { morphisms=Value; <span class="functions">ArrayResize</span>(morphism,morphisms); <span class="keyword">class="kw">return</span>(<span class="macro">true</span>); } <span class="keyword">class="kw">return</span>(<span class="macro">false</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;Get(<span class="keyword">class="type">int</span> MorphismIndex,CMorphism&lt;TD,TC&gt; &amp;Morphism) { <span class="keyword">if</span>(MorphismIndex&gt;=<span class="number">class="num">0</span> &amp;&amp; MorphismIndex&lt;Morphisms()) { Morphism=morphism[MorphismIndex]; Morphism.domain=domain; Morphism.codomain=codomain; <span class="keyword">class="kw">return</span>(<span class="macro">true</span>); } <span class="keyword">class="kw">return</span>(<span class="macro">false</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;<span class="keyword">class="kw">template</span> &lt;<span class="keyword">class="kw">typename</span> TDD,<span class="keyword">class="kw">typename</span> TDC&gt;
&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;Set(<span class="keyword">class="type">int</span> ValueIndex,CMorphism&lt;TDD,TDC&gt; &amp;Value)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

类型不匹配直接拦截,映射才落库

在自定义范畴结构里,定义域与值域的类型校验是第一道闸。若模板参数 TD 与运行期实际类型 TDD 的字符串名不一致,或 TC 与 TDC 对不上,函数立刻 return(false),后续映射逻辑根本不会执行。 下面这段判断才是真正写值的地方:先确认 ValueIndex 在 [0, Morphisms()) 区间内,再把 domain、codomain 赋给 Value 对象;若 Index(Value) 返回 -1 说明该态射尚未存在,才允许 morphism[ValueIndex]=Value 并 return(true)。 开 MT5 把这段塞进你的范畴类,故意传一个 typename 不同的伪类型,能在日志里看到返回 false 的概率接近 100%,这说明类型墙是硬生效的,不是摆设。外汇与贵金属品种映射时务必确认合约类型一致,这类市场杠杆高、滑点突变频繁,类型错配可能让策略在实盘直接哑火。

MQL5 / C++
if
            (
            (class="type">class="kw">string(class="kw">typename(TD))!=class="type">class="kw">string(class="kw">typename(TDD)))
            ||
            (class="type">class="kw">string(class="kw">typename(TC))!=class="type">class="kw">string(class="kw">typename(TDC)))
            ||
            )
            {
                class="kw">return(false);
            }
            class=class="str">"cmt">// 
            if(ValueIndex>=class="num">0 && ValueIndex<Morphisms())
            {
              Value.domain=domain;
              Value.codomain=codomain;

              if(Index(Value)==-class="num">1)
              {
                 morphism[ValueIndex]=Value;

                 class="kw">return(true);

「模板实例的索引匹配逻辑」

在泛型映射类里,索引函数先拿一个默认无效值 _index=-1,只有后续校验通过才会改写它。 函数开头用 if 判断传入的域类型与余类型是否和模板参数一致:把 typename(TD)typename(TDD) 等转成字符串做不等比较,任一不等就直接 return(_index) 保持 -1。

这段代码片段里类型守卫是残缺的——第三个 `` 后面为空,编译可能报错,实盘前需在 MT5 的 MetaEditor 里补完条件或删掉空或项。

外汇与贵金属品种叠加此类泛型封装时,类型错配会导致 EA 静默失效,属于高风险调试点,建议开 MT5 用 EURUSD 与 XAUUSD 各跑一次编译验证。

MQL5 / C++
class="kw">template <class="kw">typename TDD,class="kw">typename TDC>
class="type">int Index(CMorphism<TDD,TDC> &Value)
{
 class="type">int _index=-class="num">1;
 class=class="str">"cmt">//
 if
 (
 (class="type">class="kw">string(class="kw">typename(TD))!=class="type">class="kw">string(class="kw">typename(TDD)))
 ||
 (class="type">class="kw">string(class="kw">typename(TC))!=class="type">class="kw">string(class="kw">typename(TDC)))
 ||
 )
 {
 class="kw">return(_index);
 }
 class=class="str">"cmt">//

◍ 同构判定的阶乘级映射生成

在域对象基数相等的前提下,两个域之间可能的态射总数由基数阶乘决定。上面这段实现里,先以 A.Cardinality()!=B.Cardinality() 做快捷否决,不等就直接返回 false,避免无意义的排列计算。 随后用 MathFactorial(_cardinal) 拿到全排列数 _factorial,并据此 ArrayResize(Output,_factorial) 把同态数组扩到对应规模。每个下标 f 代表一种排列,内层再按 _cardinal 把 morphism[c] 的 domain 与 codomain 分别绑到 A 和 B。 外汇与贵金属行情里套这层结构做形态同构匹配时,基数一旦到 10,阶乘就是 3628800 量级,MT5 实盘跑前务必先在小样本上压测耗时。基数越大,枚举成本越容易拖垮 tick 响应,这是高频过滤要避开的点。

MQL5 / C++
for(class="type">int m=class="num">0; m<morphisms; m++)
  {
   if(MorphismMatch(Value,morphism[m]))
     {
      _index=m; break;
     }
  }

 class="kw">return(_index);
 }

 CHomomorphism(class="type">void){ Morphisms(class="num">0); };
 ~CHomomorphism(class="type">void){};
 };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Get Isomorphisms function                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">template <class="kw">typename TD,class="kw">typename TC>
class="type">bool IsIsomorphic(CDomain<TD> &A,CDomain<TC> &B,CHomomorphism<TD,TC> &Output[])
  {
   if(A.Cardinality()!=B.Cardinality())
     {
      class="kw">return(false);
     }
   
   class="type">int _cardinal=A.Cardinality();
   
   class="type">uint _factorial=MathFactorial(_cardinal);
   
   ArrayResize(Output,_factorial);
   
   for(class="type">uint f=class="num">0;f<_factorial;f++)
     {
      ArrayResize(Output[f].morphism,_cardinal);
      class=class="str">"cmt">//
      for(class="type">int c=class="num">0;c<_cardinal;c++)
        {
         Output[f].morphism[c].domain=A;
         Output[f].morphism[c].codomain=B;
        }
     }
   
   class="type">int _index=class="num">0;
   CDomain<TC> _output[];ArrayResize(_output,_factorial);

常见问题

把各周期信号先定义成源范畴的对象,再用函子映射到目标范畴的统一结构,避免人工比对漏看拐点。
域就像欧元兑美元的不同时间框架,态射是框架间的转换规则;看清谁映射到谁才不会乱套用指标。
小布可自动比对信号域与态射映射,标出不一致处,省去手算阶乘级映射组合的麻烦。
多是类型不匹配被拦截所致,先核对模板实例字段类型,再放映射落库,索引才能对准。
元素少时阶乘级可接受,超过6个对象建议剪枝或分层验证,否则枚举会卡住验证流程。