MQL5 中的范畴论 (第 2 部分)(基础篇)
把交易信号塞进函子看
在 MQL5 里用范畴论建模,第一步是把行情数据当成对象,把指标变换当成态射。这样一套 K 线序列经过移动平均映射后,仍是同一个范畴里的对象,只是形态被重新描述。 具体落地时,可以写一个函子把原始蜡烛数组映射到标准化收益数组。下面这段代码在 MT5 里能直接编译,核心是把每根收盘相对前收的变动算出来。 外汇与贵金属波动剧烈,这类抽象映射只描述结构、不预测方向,实盘使用前请在策略测试器里跑至少 3 个月 tick 数据验证稳定性。
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 是同态类数组,装下了两域间所有可枚举的同构同态。 跑这段代码,偶数域与奇数域的日志打印结果不同,直接看终端输出就能判别。外汇与贵金属市场高风险,这类结构识别只解决「关系是否可逆」的数学判定,不预示任何价格方向。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| HOMO-MORPHISM CLASS |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="kw">template</span> <<span class="keyword">class="kw">typename</span> TD,<span class="keyword">class="kw">typename</span> TC> <span class="keyword">class</span> CHomomorphism : <span class="keyword">class="kw">public</span> CObject { <span class="keyword">class="kw">protected</span>: <span class="keyword">class="type">int</span> morphisms; <span class="keyword">class="kw">public</span>: CDomain<TD> domain; CDomain<TC> codomain; CMorphism<TD,TC> morphism[]; <span class="keyword">class="type">int</span> Morphisms() { <span class="keyword">class="kw">return</span>(morphisms); } <span class="keyword">class="type">bool</span> Morphisms(<span class="keyword">class="type">int</span> Value) { <span class="keyword">if</span>(Value>=<span class="number">class="num">0</span> && Value<<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>); } <span class="keyword">class="type">bool</span> Get(<span class="keyword">class="type">int</span> MorphismIndex,CMorphism<TD,TC> &Morphism) { <span class="keyword">if</span>(MorphismIndex>=<span class="number">class="num">0</span> && MorphismIndex<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>); } <span class="keyword">class="kw">template</span> <<span class="keyword">class="kw">typename</span> TDD,<span class="keyword">class="kw">typename</span> TDC> <span class="keyword">class="type">bool</span> Set(<span class="keyword">class="type">int</span> ValueIndex,CMorphism<TDD,TDC> &Value) {
类型不匹配直接拦截,映射才落库
在自定义范畴结构里,定义域与值域的类型校验是第一道闸。若模板参数 TD 与运行期实际类型 TDD 的字符串名不一致,或 TC 与 TDC 对不上,函数立刻 return(false),后续映射逻辑根本不会执行。 下面这段判断才是真正写值的地方:先确认 ValueIndex 在 [0, Morphisms()) 区间内,再把 domain、codomain 赋给 Value 对象;若 Index(Value) 返回 -1 说明该态射尚未存在,才允许 morphism[ValueIndex]=Value 并 return(true)。 开 MT5 把这段塞进你的范畴类,故意传一个 typename 不同的伪类型,能在日志里看到返回 false 的概率接近 100%,这说明类型墙是硬生效的,不是摆设。外汇与贵金属品种映射时务必确认合约类型一致,这类市场杠杆高、滑点突变频繁,类型错配可能让策略在实盘直接哑火。
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 各跑一次编译验证。
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 响应,这是高频过滤要避开的点。
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);