MQL5 中的范畴论 (第 1 部分)·综合运用
◍ 子集判定在 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 封装有没有漏掉边界元素。
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 输出的 {…} 就能直接贴进笔记做集合运算的审计轨迹。外汇与贵金属属高风险品种,任何集合推断都只是概率倾向,别拿它当方向依据。
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 根的映射在重大数据行情中可能瞬时失真,验证时建议先在非农时段之外跑。
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 取像元素,两步均成功才继续填集。外汇与贵金属属高风险品种,这类结构映射只作形态概率参考,不能直接当方向指令。
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 的随机分布,外汇与贵金属行情下这类结构易因跳空失效,属高风险验证项。
{
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 跑分类之前,先认清一件事:外汇和贵金属是高风险品种,任何分类输出都只是倾向,不是必涨必跌的判词。下一篇我们从这里接着往下写,先别急着神话工具,能稳定复现回测结果比追新概念更实在。