MQL5 中的范畴论 (第 2 部分)·进阶篇
🧩

MQL5 中的范畴论 (第 2 部分)·进阶篇

(2/3)· 接上篇基础概念,本文把恒等、关联公理和本体日志落到 MQL5 可跑的示例里

新手友好 第 2/3 篇
把金融时间序列硬塞进类而不验公理,态射链往往悄悄断裂。先看清恒等与关联约束,后续指标组合才不会自相矛盾。

同构判定的嵌套遍历与偶数奇数集验证

在 MT5 里做集合同构判定,核心是先调用 GetIsomorphisms 把候选映射全量展开,再用多层 for 循环逐一比对元素匹配关系。下面这段代码用 _factorial 控制外层排列数,_cardinal 控制集合基数,嵌套到第三层才执行 ElementMatch 与 Codomain 判断,逻辑密度极高。 外层循环 f 从 0 跑到 _factorial-1,中层 c 与内层 cc 都按 _cardinal 边界遍历;只有当 _output[f] 取出的元素 _ec 与基准集 B 中 _ecc 经 ElementMatch 命中,且 Codomain(cc) 返回真时才 break,说明该映射支路已闭合。这种写法在基数超过 6 时循环次数会突破 720,回测前先估一下 MT5 终端算力。 代码末段给了可直接抄的验证样例:定义 CHomomorphism<int,int> 数组存恒等映射,用 IsIsomorphic(_evens,_odds,_h_i) 测偶数集与奇数集是否同构。若成立,printf 会抛出可达的同态数量 IntegerToString(ArraySize(_h_i)),并在子循环里用 PrintMorphism 逐条打印映射结构,方便你粘到专家日志里核对。 外汇与贵金属价格序列做这类结构化比对时波动噪声大,同构结论仅代表数学上的可能对应关系,实盘高风险,别直接当信号源。

MQL5 / C++
GetIsomorphisms(B, class="num">0, _cardinal-class="num">1, _cardinal, _index, _output);

for(class="type">uint f=class="num">0;f<_factorial;f++)
{
  for(class="type">int c=class="num">0;c<_cardinal;c++)
  {
    CElement<TC> _ec;
    if(_output[f].Get(c,_ec))
    {
      for(class="type">int cc=class="num">0;cc<_cardinal;cc++)
      {
        CElement<TC> _ecc;
        if(B.Get(cc,_ecc))
        {
          if(ElementMatch(_ec,_ecc))
          {
            if(Output[f].morphism[c].Codomain(cc))
            {
              break;
            }
          }
        }
      }
    }

    if(Output[f].morphism[c].Domain(c))
    {
    }
  }
}

class="kw">return(true);
class=class="str">"cmt">//IDENTITY
CHomomorphism<class="type">int,class="type">int> _h_i[];
class=class="str">"cmt">//is evens isomorphic to odds?
if(IsIsomorphic(_evens,_odds,_h_i))
{
  printf(__FUNCSIG__+" evens can be isomorphic to odds by up to: "+IntegerToString(ArraySize(_h_i))+" homomorphisms. These could be... ");
  for(class="type">int s=class="num">0; s<ArraySize(_h_i); s++)
  {
    printf(__FUNCSIG__);

    class="type">class="kw">string _print="";
    for(class="type">int ss=class="num">0; ss<ArraySize(_h_i[s].morphism); ss++)
    {
      _print+=PrintMorphism(_h_i[s].morphism[ss],class="num">0);
    }

    printf(_print+" at: "+IntegerToString(s));
  }
}

◍ EURGBP 偶数态到奇数态的同构映射日志

在 EURGBP.ln 的 H1 图上跑一个 OnStart 脚本,终端会在 2023.01.26 10:42:56.909 起的一毫秒内连续打印多组映射关系。日志开头提示:偶数事件最多可通过 6 个同态映射到奇数事件,这是一种数学视角的状态对应,不是价格预测。 具体看打印内容,第一组 at:0 给出 (0)->(1)、(2)->(3)、(4)->(5) 的线性配对;第二组 at:1 变成 (0)->(1)、(2)->(5)、(4)->(3),跨跳了一步;第三组 at:2 是 (0)->(3)、(2)->(1)、(4)->(5)。 第四组 at:3 回到 (0)->(3)、(2)->(5)、(4)->(1),和 at:2 比只动了中间那一支。外汇与贵金属杠杆高,这类映射仅作结构观察,实盘下单仍按自己的风控来,映射本身不指向任何方向概率。 把下面这段日志直接贴进 MT5 的 Experts 或脚本输出窗,对照 H1 的 K 线编号就能复现同样的 at:0~3 四帧。

MQL5 / C++
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.909 ct_2(EURGBP.ln,H1)    class="type">void OnStart() evens can be isomorphic to odds by up to: class="num">6 homomorphisms. These could be...
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.909 ct_2(EURGBP.ln,H1)    class="type">void OnStart()
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">0)|----->(class="num">1)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">2)|----->(class="num">3)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">4)|----->(class="num">5)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)        at: class="num">0
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    class="type">void OnStart()
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">0)|----->(class="num">1)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">2)|----->(class="num">5)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">4)|----->(class="num">3)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)        at: class="num">1
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    class="type">void OnStart()
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">0)|----->(class="num">3)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">2)|----->(class="num">1)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">4)|----->(class="num">5)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)        at: class="num">2
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    class="type">void OnStart()
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">0)|----->(class="num">3)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">2)|----->(class="num">5)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)    (class="num">4)|----->(class="num">1)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)        at: class="num">3

「奇偶分组在数组遍历里的实际打印」

下面这段是在 EURGBP.ln 的 H1 周期下跑脚本时,终端打印出的真实日志。时间戳集中在 2023.01.26 10:42:56.899–56.911 这 13 毫秒内,说明逻辑是同步循环里跑完的,没有异步延迟。 第一次 OnStart 进入后,映射关系打出 (0)->(5)、(2)->(3)、(4)->(1),随后 at: 4 表示当前锚定索引为 4。第二次重入时 (0)->(5) 不变,(2)->(1)、(4)->(3) 发生交换,at 变成 5,可见偶数位 {0,2,4} 与奇数位 {1,3,5} 被分成了两个集合在交叉重排。 日志末尾显式列出了 evens are... {(0),(2),(4)} 与 odds are... {(1),(3),(5)},这验证了分组边界就是按索引奇偶切的。外汇与贵金属品种波动剧烈,这类数组重排若用于订单分组或信号分桶,先在 MT5 策略测试器里用小样本核对打印,再上实盘。

MQL5 / C++
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)      class="type">void OnStart()
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)      (class="num">0)|----->(class="num">5)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)      (class="num">2)|----->(class="num">3)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)      (class="num">4)|----->(class="num">1)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)         at: class="num">4
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)      class="type">void OnStart()
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.910 ct_2(EURGBP.ln,H1)      (class="num">0)|----->(class="num">5)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.911 ct_2(EURGBP.ln,H1)      (class="num">2)|----->(class="num">1)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.911 ct_2(EURGBP.ln,H1)      (class="num">4)|----->(class="num">3)
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.911 ct_2(EURGBP.ln,H1)         at: class="num">5
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.899 ct_2(EURGBP.ln,H1)      class="type">void OnStart() evens are... {(class="num">0),(class="num">2),(class="num">4)}
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.899 ct_2(EURGBP.ln,H1)      class="type">void OnStart() odds are... {(class="num">1),(class="num">3),(class="num">5)}

态射组合免括号的落地写法

范畴论里说的关联公理,本质就是态射组合满足结合律:h∘(g∘f) 和 (h∘g)∘f 结果一致。用服装做例子,T恤→纽扣衬衫、纽扣衬衫→牛仔裤、牛仔裤→运动鞋这三段映射无论怎么加括号分组,最终配对结果相同,类别定义时就不必去算嵌套括号。 在 MT5 里验证这点,得先把上篇的类别类重构成支持 enumerated 类型、且新增 FillDomain 函数的版本。我们建一个类别实例 _ca,声明三个字符串数组分别装衬衫、裤子、鞋子,转成元素数组 _et/_ep/_es 后塞进三个域 _dt/_dp/_ds,再把域数设成 3。 跑脚本后本体日志会输出三组态射的组合结果,重点看 h∘(g∘f) 与 (h∘g)∘f 的日志行是否完全一致——若一致,说明你这套类别实现确实满足关联。外汇与贵金属行情高波动,这类数学结构只帮你理清对象间关系,不预示任何价格方向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CATEGORY CLASS                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CCategory
  {
   class="kw">protected:
   
    class="type">int               domains_datetime;
    class="type">int               domains_string;
    class="type">int               domains_double;
    class="type">int               domains_int;
   
    class="type">int               ontologies;
   
   CDomain<class="type">class="kw">datetime>  domain_datetime[];
   CDomain<class="type">class="kw">string>    domain_string[];
   CDomain<class="type">class="kw">double>    domain_double[];
   CDomain<class="type">int>       domain_int[];
   
   COntology          ontology[];
   
   class="kw">public:
   
   class="type">int                Domain(class="type">class="kw">string T)
                     {
                      if(T=="class="type">class="kw">datetime"){ class="kw">return(domains_datetime); }
                      else if(T=="class="type">class="kw">string"){ class="kw">return(domains_string); }
                      else if(T=="class="type">class="kw">double"){ class="kw">return(domains_double); }
                      else if(T=="class="type">int"){ class="kw">return(domains_int); }
                      class="kw">return(-class="num">1);
                     }

◍ 按类型分配域数组的内存

上一节把域容器拆成了 datetime、string、int 三类独立数组,这一节用 Domain() 函数把「类型名 + 元素个数」映射成实际内存分配。 调用 Domain("datetime", 256) 时,先卡一道 Value 范围:必须 ≥0 且 < INT_MAX(在 MT5 里 INT_MAX 为 2147483647),越界直接不分配。 类型匹配走字符串比较:T=="datetime" 就 ArrayResize(domain_datetime, Value),成功后再把 domains_datetime 记为当前长度并返回 true;T=="string" 同理操作 domain_string 与 domains_string。 别把正态当圣经:这段代码只认精确类型字符串,传 "Datetime" 或 "DATETIME" 都会静默失败,调试时建议在 if 分支外补一个 Print("unknown domain type")。 开 MT5 新建一个 include 把这段塞进去,用 Domain("string", 100) 测一下 ArrayResize 返回值,能直观看到 domains_string 被写成 100。

MQL5 / C++
class="type">bool Domain(class="type">class="kw">string T,class="type">int Value)
  {
   if(Value>=class="num">0 && Value<INT_MAX)
     {
      if(T=="class="type">class="kw">datetime")
      {
       if(ArrayResize(domain_datetime,Value)>=Value)
         {
          domains_datetime=Value;  
          class="kw">return(true);
         }
      }
      else if(T=="class="type">class="kw">string")
      {
       if(ArrayResize(domain_string,Value)>=Value)
         {
          domains_string=Value;  
          class="kw">return(true);

「按类型扩容域数组的收尾分支」

这段逻辑处在域容器初始化函数的末尾,负责在运行时根据实际传入的样本容量,对 double 与 int 两套域数组做动态扩容。 当模板参数 T 被判定为 "double" 时,先调用 ArrayResize(domain_double, Value) 把数组拉到 Value 长度;若返回值大于等于 Value,说明 resize 成功,随即把 domains_double 记为 Value 并返回 true。 若 T 不是 double(注释里点明其实就是 "int" 分支),则对 domain_int 走同样流程:ArrayResize 成功才把 domains_int 赋值并返回 true,否则静默落到函数尾部的失败处理。 在 MT5 里直接把 Value 设成 0 去跑这套,ArrayResize 必然返回 0,两个分支都会跳过赋值——这能帮你确认域长度统计只在有效扩容后才更新。

MQL5 / C++
   else if(T=="class="type">class="kw">double")
   {
      if(ArrayResize(domain_double,Value)>=Value)
      {
         domains_double=Value;  
         class="kw">return(true);
      }
   }
   else class=class="str">"cmt">//if(T=="class="type">int")
   {
      if(ArrayResize(domain_int,Value)>=Value)
      {
         domains_int=Value;  
         class="kw">return(true);
      }  
   }
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到范畴关系是否出现非交换异常,你专注决策。

常见问题

每个域到自身的态射不改变元素,相当于指标原样映射;若自映射扭曲了数值,就破了恒等约束,不能算合法范畴。
不同路径算出不同结果,策略可能在同一根 K 线给出互斥指令,概率上增加滑点和错单。
可以,小布在品种页用 AIGC 扫描域间态射,发现不满足关联或恒等时会提示,省去手工验算。
本体日志把域和态射做成可查询层级,承接上篇基本单位,方便在 MQL5 里做跨周期推理。
它演示了多域选择如何经态射合成行程,类似多周期信号合成交易路径,帮助直观建立范畴直觉。