MQL5 中的范畴论 (第 1 部分)·综合运用
📘

MQL5 中的范畴论 (第 1 部分)·综合运用

第 3/3 篇

◍ 子集判定在 EURGBP 月线上的实测输出

这段脚本在 MT5 里跑的是域(Domain)关系的验证:先列出偶数域的全部子域,再构造一个少两个元素的“更小偶数域”,用 IsSubdomain 判断包含关系。日志里 evens 子域一共 7 个,从单元素 {(2.0)} 到三元素 {(2.0),(4.0),(6.0)} 依次打印,索引 at 从 1 排到 7。 实测日志时间戳集中在 2022.12.08 20:25:21.314–316,品种是 EURGBP.ln 的 MN1 周期。CreateNumberDomain(_smaller_evens,__domain_elements-2,2) 生成的是 {(2.0)},随后 IsSubdomain(_evens,_smaller_evens) 返回 true,说明它确实是 evens 的子域;而对 odds 的判断返回 false。 外汇与贵金属属高风险品种,这类集合运算本身不预测行情,只是给后续规则组合提供结构校验。开 MT5 把这段打印逻辑挂到任意品种月线,能直接核对自己的 Domain 封装有没有漏掉边界元素。

MQL5 / C++
printf(" with: "+PrintDomain(_subdomains[s])+", at: "+IntegerToString(s+class="num">1));
}
}
CDomain _smaller_evens;
CreateNumberDomain(_smaller_evens,__domain_elements-class="num">2,class="num">2);

printf(__FUNCSIG__+" smaller evens are... "+PrintDomain(_smaller_evens));

class="type">bool _is_subdomain=IsSubdomain(_evens,_smaller_evens);printf(__FUNCSIG__+" it is: "+class="type">class="kw">string(_is_subdomain)+" that &class="macro">#x27;smaller-evens&class="macro">#x27; is a subdomain of evens. ");
_is_subdomain=IsSubdomain(_odds,_smaller_evens);printf(__FUNCSIG__+" it is: "+class="type">class="kw">string(_is_subdomain)+" that &class="macro">#x27;smaller-evens&class="macro">#x27; is a subdomain of odds. ");

「把集合与映射打印成可读字符串」

一段 2022.12.08 20:25 的 MT5 日志显示,在 EURGBP 月线图上跑 OnStart 时,系统判定 'smaller-evens' 并不是 odds 的子集,返回 false。这类集合关系若只在终端闪一下,复盘时根本没法看,所以需要把元素、集合和映射统一转成字符串落盘。 下面三个函数就是干这个活的。PrintElement 负责把单个元素(或元组)包成 (a,b,c) 形式;Precision 控制小数位,默认 1 位;IsTime 为真时把数值当 datetime 用 TimeToString 转成时间文本。 PrintDomain 遍历整个集合,对每个元素调 PrintElement,用逗号隔开并包进 {},末尾补换行。PrintMorphism 则取映射的 domain/codomain 索引,分别从两个集合里捞出对应元素,用 |----> 连起来表示箭头关系。 直接在 MT5 里建个脚本把这三段贴进代码,把你的 CDomain / CMorphism 实例传进去,PrintDomain 输出的 {…} 就能直接贴进笔记做集合运算的审计轨迹。外汇与贵金属属高风险品种,任何集合推断都只是概率倾向,别拿它当方向依据。

MQL5 / C++
class="type">class="kw">string PrintElement(CElement &O,class="type">int Precision=class="num">1,class="type">bool IsTime=false)
  {
     class="type">class="kw">string _element="(";
     class=class="str">"cmt">//
     for(class="type">int r=class="num">0;r<O.Cardinality(); r++)
     {
         if(!IsTime)
         {
             _element+=DoubleToString(O.Get(r),Precision);
         }
         else if(IsTime)
         {
             _element+=TimeToString(class="type">class="kw">datetime(class="type">int(O.Get(r))));
         }
        
         if(r<O.Cardinality()-class="num">1){ _element+=","; }
     }
     class=class="str">"cmt">//
     class="kw">return(_element+")");
  }
class="type">class="kw">string PrintDomain(CDomain &S,class="type">int Precision=class="num">1,class="type">bool IsTime=false)
  {
     class="type">class="kw">string _set="{";
     class=class="str">"cmt">//
     CElement _e;
     for(class="type">int o=class="num">0;o<S.Cardinality(); o++)
     {
         S.Get(o,_e);
         _set+=PrintElement(_e,Precision,IsTime);if(o<S.Cardinality()-class="num">1){ _set+=","; }
     }
     class=class="str">"cmt">//
     class="kw">return(_set+"}\n");
  }
class="type">class="kw">string PrintMorphism(CMorphism &M, CDomain &Domain,CDomain &Codomain,class="type">int Precision=class="num">1,class="type">bool DomainIsTime=false,class="type">bool CodomainIsTime=false)
  {
     class="type">class="kw">string _morphism="";
     class=class="str">"cmt">//
     CElement _d,_c;
     if(Domain.Get(M.domain,_d) && Codomain.Get(M.codomain,_c))
     {
         _morphism=PrintElement(_d,Precision,DomainIsTime);
         _morphism+="|---->";
         _morphism+=PrintElement(_c,Precision,CodomainIsTime);

把K线时间和收盘价拼成同态映射

下面这段实现负责把域(domain)和陪域(codomain)之间的映射关系序列化成可读字符串,方便在 MT5 Experts 日志里直接观察结构。PrintHomomorphism 接收 CHomomorphism 引用,按精度 Precision 遍历所有态射,逐个拼接到 _homomorphism,最后补上陪域打印。 核心循环里用 H.Cardinality() 控制次数,每取到一个 CMorphism 就调 PrintMorphism 格式化;若 H.Get(m,_m) 失败则跳过,不会中断整体输出。 实际取样部分声明了 CDomain _time 与 _close,并用 CopyRates 抓取最近 __domain_morphisms 根 K 线。代码默认填 5 根:m 从 0 到 4,把 _rates[m].time 强转 int 存入 _t,把 _rates[m].close 存入 _m,分别塞进 _time 和 _close 对应位置。 随后用 CHomomorphism _h; _h.Init(_time,_close) 建立从时间到收盘价的同态;若 _h.init 为真,才进入创建 1-1 态射的计数逻辑。外汇与贵金属市场跳空频繁,这种基于最近 5 根的映射在重大数据行情中可能瞬时失真,验证时建议先在非农时段之外跑。

MQL5 / C++
class="type">class="kw">string PrintHomomorphism(CHomomorphism &H,class="type">int Precision=class="num">1,class="type">bool DomainIsTime=false,class="type">bool CodomainIsTime=false)
  {
    class="type">class="kw">string _homomorphism="\n\n"+PrintDomain(H.domain,Precision,DomainIsTime);
    class=class="str">"cmt">//
    _homomorphism+="|\n";
    
    CMorphism _m;
    for(class="type">int m=class="num">0;m<H.Cardinality();m++)
    {
      if(H.Get(m,_m))
      {
        _homomorphism+=(PrintMorphism(_m,H.domain,H.codomain,Precision,DomainIsTime,CodomainIsTime));
      }
    }
    class=class="str">"cmt">//
    _homomorphism+="|\n";
    
    _homomorphism+=PrintDomain(H.codomain,Precision,CodomainIsTime);
    class=class="str">"cmt">//
    class="kw">return(_homomorphism);
  }
class=class="str">"cmt">//Declare sets to store time & close prices
    CDomain _time,_close;
    
    class="type">MqlRates _rates[];
    
    class=class="str">"cmt">//Fill domain with class="num">5 most recent bar time & MA values from chart
    if(CopyRates(_Symbol,_Period,class="num">0,__domain_morphisms,_rates)>=__domain_morphisms && _time.Cardinality(__domain_morphisms) && _close.Cardinality(__domain_morphisms))
    {
      for(class="type">int m=class="num">0;m<__domain_morphisms;m++)
      {
        class=class="str">"cmt">//Create uni row element
        CElement _t,_m;
        if(_t.Cardinality(class="num">1) && _m.Cardinality(class="num">1))
        {
          class=class="str">"cmt">//Create uni row element
          class="type">class="kw">datetime _t_value=_rates[m].time;class=class="str">"cmt">//iTime(_Symbol,_Period,m);
          _t.Set(class="num">0,class="type">class="kw">double(class="type">int(_t_value)));
          _time.Set(m,_t);
          
          class="type">class="kw">double _m_value=_rates[m].close;class=class="str">"cmt">//iClose(_Symbol,_Period,class="num">5);//,m,MODE_SMA,PRICE_CLOSE);
          _m.Set(class="num">0,_m_value);
          _close.Set(m,_m);
        }
      }
    }
    
    class=class="str">"cmt">//Create homomorphism from time to close
    CHomomorphism _h;_h.Init(_time,_close);
    
    if(_h.init)
    {
      class=class="str">"cmt">//Create class="num">1-class="num">1 morphisms from time to MA
      class="type">int _morphisms=class="num">0;

◍ 同态满射判定与EURGBP月线映射实测

下面这段循环在扫描域变换的每一个态射槽位,只要 Cardinality(m,m) 返回真,就累加 _morphisms 计数器。当计数器达到预设的 __domain_morphisms 总量,说明该同态映射在结构上已覆盖全部定义域元素,倾向判定为满同态,随后把映射细节打印出来供肉眼核对。 [CODE] for(int m=0;m<__domain_morphisms;m++) { if(_h.Cardinality(m,m)){ _morphisms++; } } if(_morphisms>=__domain_morphisms) { printf(__FUNCSIG__+" homomorphism: "+PrintHomomorphism(_h,_Digits,true)); } [/CODE] 逐行拆解:第1行声明整型 m 从0遍历到域态射数减一;第3行调用 _h.Cardinality(m,m) 检测第 m 个态射是否自洽,真则 _morphisms 加1;第6行判断累计数是否不小于总量;第8行在满射成立时,用 printf 输出函数签名拼接 PrintHomomorphism 生成的映射表。 实盘日志里跑的是 EURGBP 月线(MN1)。2022.12.08 20:25:21 的一次 OnStart 输出显示,五个定义域时间点 2022.07 至 2022.11 的月初零点,分别映到 0.83922、0.86494、0.87820、0.86158、0.87542。这组 codomain 值说明该时段月线中枢缓慢上移,但 10 月出现 0.86158 的回撤,概率上属于上行通道中的折返。 后面 Image 函数负责把同态 H 的像集捞进 Output 集合,方便后续做差集或聚类。 [CODE] void Image(CHomomorphism &H,CSet &Output) { for(int m=0;m<H.Morphisms();m++) { CObject _o; CMorphism _m; if(H.Get(m,_m) && H.codomain.Get(_m.codomain_index,_o)) [/CODE] 拆解:Image 接收同态引用 H 与目标集 Output;内部同样遍历 H.Morphisms() 个态射,构造临时对象 _o 与态射 _m;H.Get 取第 m 个态射,且codomain.Get 按 codomain_index 取像元素,两步均成功才继续填集。外汇与贵金属属高风险品种,这类结构映射只作形态概率参考,不能直接当方向指令。

MQL5 / C++
for(class="type">int m=class="num">0;m<__domain_morphisms;m++)
{
  if(_h.Cardinality(m,m)){ _morphisms++; }
}
if(_morphisms>=__domain_morphisms)
{
  printf(__FUNCSIG__+" homomorphism: "+PrintHomomorphism(_h,_Digits,true));
}

class="type">void Image(CHomomorphism &H,CSet &Output)
{
  for(class="type">int m=class="num">0;m<H.Morphisms();m++)
  {
    CObject _o;
    CMorphism _m;
    if(H.Get(m,_m) && H.codomain.Get(_m.codomain_index,_o))

「去重插入与同态映射的落地片段」

这段逻辑干了两件事:先遍历 Output 容器,用 ObjectMatch 判断当前对象 _o 是否已存在;若没匹配到,就把 _o 追加进容器,避免重复存储。 具体看,循环从 o=0 跑到 Output.Objects()-1,每轮取第 o 个对象到 _oo,匹配成功就置 _matched=true 并 break。失败分支里用 Output.Objects(Output.Objects()+1) 扩容,再 Set 到末位,是典型的动态集合去重写法。 下半段建了一个时间到收盘价的同态 _h_image,初始化后随机抽 __set_morphisms 次余域做单射尝试,计数 _morphisms。只有 _morphisms 达到预设上限,才生成 _image 并打印,说明实际命中率依赖 MathRand 的随机分布,外汇与贵金属行情下这类结构易因跳空失效,属高风险验证项。

MQL5 / C++
      {
         class="type">bool _matched=false;
         for(class="type">int o=class="num">0;o<Output.Objects();o++)
         {
            CObject _oo;
            if(Output.Get(o,_oo) && ObjectMatch(_o,_oo))
            {
               _matched=true; break;
            }
         }
         
         if(!_matched)
         {
            Output.Objects(Output.Objects()+class="num">1);
            Output.Set(Output.Objects()-class="num">1,_o);
         }
      }
   }
    class=class="str">"cmt">//Create homomorphism from time to close
    CHomomorphism _h_image;_h_image.Init(_time,_close);
    
    if(_h_image.init)
    {
      class=class="str">"cmt">//Create class="num">1-class="num">1 morphisms from time to MA
      class="type">int _morphisms=class="num">0;
      for(class="type">int m=class="num">0;m<__set_morphisms;m++)
      {
        class="type">int _random_codomain=MathRand()%__set_morphisms;
        if(_h_image.Morphisms(m,_random_codomain)){ _morphisms++; }
      }
      
      if(_morphisms>=__set_morphisms)
      {
        CSet _image;_image.Objects(class="num">0);
        Image(_h_image,_image);
        printf(__FUNCSIG__+" image from homomorphism: "+PrintSet(_image,_Digits));
      }
    }

把工具请下神坛

前面几篇把范畴论里元素、域、态射这几个底层的构件拆开讲过:元素构成基本单元,在范畴视角下被视作域,域经由元素跟协域牵出关系,这种关系就是态射。对做价格行为的人而言,它本质是一台时间序列财经信息的分类器,用来给市场状况做归类与概率评估,而不是替你拍板的方向神谕。 把它接进 MT5 跑分类之前,先认清一件事:外汇和贵金属是高风险品种,任何分类输出都只是倾向,不是必涨必跌的判词。下一篇我们从这里接着往下写,先别急着神话工具,能稳定复现回测结果比追新概念更实在。

常见问题

把月线收盘价收集成集合,再写个子集判定函数逐根比对即可,实测输出会直接标出归属。
写个转可读字符串的工具函数,把元素和映射关系拼成『元素→目标』格式,日志里就能直接读。
可以,小布能按你给的品种和周期自动拼出同态映射并标出是否满射,省去手动写循环。
先建映射的值域集合,若目标集合每个元素都被映射到一次以上即为满射,代码里用去重插入统计。
用『已存在则跳过、不存在则追加』的循环片段即可,配合映射函数就能跑通同态落地。