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

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

(2/3)·从集合与映射的数学骨架出发,把策略、工具与行情条件编织成可组合的严谨结构

进阶 第 2/3 篇

不少交易者写 EA 时把指标、信号和订单逻辑揉成一团,改一个参数就牵一发动全身。问题往往不在代码量,而在缺少一套描述「对象之间如何映射」的语言。范畴论提供的域与态射,正好补上这层抽象。

用态射比值给品种间关系建模

这段结构把两个域(domain / codomain)之间的映射抽象成 CMorphism 对象,核心动作是取比值:当 DE.Get(c) 不为 0.0 时,把 morphism 在 c 位置的值设为 CE.Get(c) 除以 DE.Get(c)。 也就是说,它算的是 codomain 对 domain 的归一化相对强度,分母为零直接跳过,避免 MT5 回测时抛出除零错误。 CHomomorphism 类里用 morphism[] 动态数组装这些映射,cardinal 记录势,Cardinality() 方法带 Add 参数(默认 true)去登记域间索引对。你在 EA 里若想比较 EURUSD 与 XAUUSD 的波动传导,可照这个骨架把 Get() 换成 iClose 或 iATR 取值。 外汇与贵金属杠杆高、跳空频繁,这类比值只是结构参考,实际信号概率随品种流动性变化。

MQL5 / C++
if(DE.Get(c)!=class="num">0.0){ morphism.Set(c,CE.Get(c)/DE.Get(c)); }
class="kw">return(false);

CMorphism(class="type">void){ domain=-class="num">1; codomain=-class="num">1; };
~CMorphism(class="type">void){};

class CHomomorphism
  {
   class="kw">protected:
   class="type">int                cardinal;
   CMorphism          morphism[];
   class="kw">public:
   class="type">bool               init;
   CDomain            domain;
   CDomain            codomain;
   class="type">bool               Cardinality(class="type">int DomainIndex,class="type">int CodomainIndex,class="type">bool Add=true)
     {

◍ 映射前的两道闸门

在把价格状态做态射(morphism)之前,这段逻辑先卡了两道关口,任一不过就直接返回 false,不做后续计算。 第一道是初始化标志 init:若未初始化,_morphed 置 false 并立即 return。第二道是索引越界检查:DomainIndex 或 CodomainIndex 超出各自集合的基数(Cardinality())范围,同样放弃态射。 过了闸门才进 for 循环,按 cardinal 次数遍历 morphism 数组,比对 DomainIndex 与已有映射的 domain 字段。外汇与贵金属杠杆高、跳空频繁,这类越界防护能避免 EA 在异常行情下写出脏映射。

MQL5 / C++
class="type">bool _morphed=true;
if(!init)
{
  _morphed=false; class="kw">return(_morphed);
}
if(DomainIndex<class="num">0 || DomainIndex>=domain.Cardinality() ||
   CodomainIndex<class="num">0 || CodomainIndex>=codomain.Cardinality())
{
  _morphed=false; class="kw">return(_morphed);
}
for(class="type">int m=class="num">0;m<cardinal;m++)
{
  if(DomainIndex==morphism[m].domain)

「形态映射的收口与基数统计」

上面这段逻辑干一件事:当一次形变(morph)被确认成立后,把对应的定义域元素和陪域元素绑定进 morphism 数组,并让 cardinal 计数加一。 _morphed 为 false 时直接 break,说明本次扫描没匹配到合法映射,后续不写数组也不增计数。 若 _morphed 为 true,先 cardinal++,再 ArrayResize(morphism,cardinal) 把动态数组扩一格;随后用 domain.Get 和 codomain.Get 分别取出 DomainIndex、CodomainIndex 对应的 CElement,成功才调用 Morph() 写入 morphism[cardinal-1]。 Cardinality() 只是返回 cardinal 当前值——也就是已落盘的形态映射总数。在 MT5 里跑这套,你可以打印 Cardinality() 观察某段行情窗口内被识别出的形态数量,外汇与贵金属波动剧烈,该数值仅反映历史样本,实战需警惕假突破带来的过拟合风险。

MQL5 / C++
            {
                  _morphed=false; break;
            }
           
           if(_morphed)
           {
              cardinal++;
              ArrayResize(morphism,cardinal);
              CElement _de,_ce;
              if(domain.Get(DomainIndex,_de) && codomain.Get(CodomainIndex,_ce))
              {
                 morphism[cardinal-class="num">1].Morph(domain,codomain,_de,_ce,Add);
              }
           }
           
           class="kw">return(_morphed);
           };

           class="type">int                Cardinality()
              {
                 class="kw">return(cardinal);
              };

映射表的读写边界检查

在自定义态射集合(CMorphism 数组)里做取数和赋值,必须先卡住索引与域值域的边界,否则 MT5 回测时极易触发数组越界导致 EA 静默终止。 下面这段是 Get 方法的核心:仅当 Index 落在 [0, Cardinality()) 区间内才返回 true 并把对应态射交给引用参数,否则直接 false。 Set 方法更严格,除了 Index 合法,还要求传入的 Value.domain 落在 domain 集合基数内、Value.codomain 落在 codomain 集合基数内,三重条件全过才允许写入。 实盘或复盘前,建议把 Cardinality() 打印到日志确认实际长度——若返回 0,说明集合未初始化,此时任何 Get/Set 调用都会走 false 分支,策略逻辑可能整段失效。外汇与贵金属波动剧烈,这类底层容器错误不会弹窗提示,只会让信号不触发。

MQL5 / C++
class="type">bool Get(class="type">int Index,CMorphism &Morphism)
{
   if(Index>=class="num">0 && Index<Cardinality())
   {
      class="kw">return(true);
   }
   class="kw">return(false);
};

class="type">bool Set(class="type">int Index,CMorphism &Value)
{
   if
   (
      Index>=class="num">0 && Index<Cardinality() &&
      Value.domain>=class="num">0 && Value.domain<domain.Cardinality() &&
      Value.codomain>=class="num">0 && Value.codomain<codomain.Cardinality()
   )
   {
      if(!MorphismMatch(Index,Value,morphism,Cardinality()))

◍ 同态映射的赋值与初始化实现

上面这段 CHomomorphism 类的尾部代码,补完了映射写入与对象初始化的逻辑。morphism[Index]=Value 负责把源域中第 Index 个元素映射到目标域的 Value,写入成功后直接 return(true),外部调用方可以据此判断这一次映射是否落库。 若索引越界或映射关系未定义,函数走到末尾返回 false,这意味着当前同态关系在该点上不构成合法映射。外汇与贵金属市场的高波动特性下,这类离散映射若用于状态机,必须确认每个 tick 的 Index 都在 morphism 数组边界内,否则可能漏判信号。 Init 方法接收两个 CDomain 引用,把 domain 与 codomain 绑定到实例,并把 init 置为 true。无参构造函数则把 init 设 false、cardinal 清零,相当于一个尚未挂载域的空壳对象,调用前必须先 Init,否则 DomainMatch 里的基数比较会指向未初始化引用。 DomainMatch 函数先比 A 与 B 的 Cardinality(),不一致直接返回 false。只有基数相等的域才具备逐点比对的前提,这一步把后续同构判定的计算量从笛卡尔积量级压到了线性扫描。

MQL5 / C++
{
   morphism[Index]=Value;
   class="kw">return(true);
   }
   }
   class="kw">return(false);
};

class="type">void Init(CDomain &Domain,CDomain &Codomain)
   {
   domain=Domain;
   codomain=Codomain;
   init=true;
   }

CHomomorphism(class="type">void){ init=false; cardinal=class="num">0; };
~CHomomorphism(class="type">void){};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Domain Match function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool DomainMatch(CDomain &A,CDomain &B)
  {
  if(A.Cardinality()!=B.Cardinality())
   {
   class="kw">return(false);
   }
  

「用域匹配验证奇偶子集关系」

这段脚本把前面封装的域(CDomain)拿来跑实测:先声明_evens和_odds两个域,各取3个元素,偶数域步长2从0起,奇数域步长2从1起,相当于生成{0,2,4}与{1,3,5}两组离散集合。 IsSubdomain函数先通过GetSubdomains把_evens的全部子集捞出来(3元素集合理论上有8个子集,含空集),再逐个用DomainMatch比对传入的SubDomain,命中即返回true并break,避免无谓遍历。 OnStart里把_evens传进去后,打印出的_subdomain_count若等于8,就说明GetSubdomains的递归展开没漏项;此时若把_odds塞进IsSubdomain,返回必然是false——因为奇偶两域元素无交,不可能构成子集关系。外汇与贵金属行情里这类离散状态判定常用于形态分类,但市场高波动下误判概率会放大,参数__domain_elements和__domain_morphisms建议先在策略测试器里改小跑通逻辑再放大。

MQL5 / C++
class="type">bool _matched=true;

for(class="type">int o=class="num">0; o<A.Cardinality(); o++)
{
   CElement _a,_b;

   if(A.Get(o,_a) && B.Get(o,_b) && !ElementMatch(_a,_b))
   {
      _matched=false; break;
   }
}

class="kw">return(_matched);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Is Subdomain function                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool IsSubdomain(CDomain &Domain,CDomain &SubDomain)
  {
   class="type">bool _is_subdomain=false;

   class="type">int _subdomain_count=class="num">0; CDomain _subdomains[];
   GetSubdomains(Domain,_subdomain_count,_subdomains);

   for(class="type">int c=class="num">0;c<_subdomain_count;c++)
   {
      if(DomainMatch(SubDomain,_subdomains[c]))
      {
         _is_subdomain=true;
         break;
      }
   }

   class="kw">return(_is_subdomain);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| INPUTS                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">int __domain_elements=class="num">3;
input class="type">int __domain_morphisms=class="num">5;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class=class="str">"cmt">//Declare a sets of natural even & odd numbers
   CDomain _evens,_odds;
   CreateNumberDomain(_evens,__domain_elements,class="num">2);
   CreateNumberDomain(_odds,__domain_elements,class="num">2,class="num">1);

   printf(__FUNCSIG__+" evens are... "+PrintDomain(_evens));

   printf(__FUNCSIG__+" odds are... "+PrintDomain(_odds));

   class="type">int _subdomain_count=class="num">0; CDomain _subdomains[];
   GetSubdomains(_evens,_subdomain_count,_subdomains);
   printf(__FUNCSIG__+" evens subs are... "+IntegerToString(_subdomain_count));
   for(class="type">int s=class="num">0; s<_subdomain_count; s++)
   {
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到策略结构之间的依赖关系,你只需专注决策本身。

常见问题

在范畴论语境下,域是用来定义某一类元素的集合,MQL5 中可把行情状态、指标缓冲或订单池视作不同域,态射则是它们之间的映射规则。
本域元素必须全被映射,协域可存在未映射元素;若误把协域当满射处理,EA 可能在部分行情下取不到预期目标状态,概率上出现逻辑缺口。
目前小布盯盘内置的 AIGC 可呈现品种页上的策略依赖与信号流向,虽未输出标准范畴图,但足以辅助你识别重复映射与悬空态射。
注释依赖人读,范畴结构用态射组合保证一致性,能在编译前暴露域之间错误关联,降低维护成本并提升跨策略复用可能。
常对应策略未覆盖的行情情形,比如突变波动率或流动性真空,属于外汇贵金属高风险场景里需单独兜底的部分。