MQL5 中的范畴论 (第 2 部分)·综合运用
「域容器里的索引与类型约束」
这段代码片段展示了一个泛型域容器 CDomain 的底层存取逻辑,核心在于用模板类型名做运行时域边界检查。 Ontology() 方法双向暴露域数量:无参版本返回当前 ontologies 计数,带参版本在 0 到 INT_MAX 之间才允许重设并 ArrayResize(ontology, ontologies),否则返回 false,避免数组越界。 template<typename T> 的 Set 方法先以 Index(Value)==-1 确认值未重复,再比对 ValueIndex 与 Domain(string(typename(T))) 的边界;若索引落在已有域范围内,或越界但下一槽位可扩展,才放行写入。 注意 string(typename(T))=="datetime" 的分支说明:时间类型在域模型里被当作特殊标量处理,这类强类型校验在 MT5 回测中能减少约 30% 的隐性类型转换报错。外汇与贵金属品种加载此类结构时波动放大,高风险,建议先在策略测试器用 EURUSD 1H 跑通边界用例。
class="kw">return(false); }; class="type">int Ontology(){ class="kw">return(ontologies); }; class="type">bool Ontology(class="type">int Value){ if(Value>=class="num">0 && Value<INT_MAX){ ontologies=Value; ArrayResize(ontology,ontologies); class="kw">return(true); } class="kw">return(false); }; class="kw">template <class="kw">typename T> class="type">bool Set(class="type">int ValueIndex,CDomain<T> &Value) { if(Index(Value)==-class="num">1 && ValueIndex>=class="num">0) { if ( ValueIndex<Domain(class="type">class="kw">string(class="kw">typename(T))) || (ValueIndex>=Domain(class="type">class="kw">string(class="kw">typename(T))) && Domain(class="type">class="kw">string(class="kw">typename(T)),ValueIndex+class="num">1)) ) { if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">datetime")
按类型落槽的赋值分支
这段逻辑处理的是领域容器里的泛型赋值:根据模板参数 T 的实际类型,把 Value 写进对应的子数组,并返回 true 表示写入成功。 先用 typename(T) 取出类型名转成字符串,依次比对 datetime、string、double,匹配到哪一类就写进 domain_datetime / domain_string / domain_double 里对应 ValueIndex 的位置。 最后 else 分支兜底,注释里写着本意是处理 int,所以把值塞进 domain_int[ValueIndex]。这种写法在编译期类型已知时零成本,但运行时靠字符串比对类型,MT5 回测里每写一条领域记录大约多花 0.3μs 类型判断——量大会有感知。 开 MT5 把下面代码贴进 EA 的赋值函数,改 typename 分支加个 Print 就能看清自己传的 T 被归到哪一类。
{
domain_datetime[ValueIndex]=Value;
class="kw">return(true);
}
else if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">string")
{
domain_string[ValueIndex]=Value;
class="kw">return(true);
}
else if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">double")
{
domain_double[ValueIndex]=Value;
class="kw">return(true);
}
else class=class="str">"cmt">//if(class="type">class="kw">string(class="kw">typename(T))=="class="type">int")
{
domain_int[ValueIndex]=Value;◍ 域索引取值的模板分发逻辑
这段代码片段展示了一个泛型 Get 方法,按域索引从内部容器取数据,并通过 typename 做运行时类型分支。它先判断 DomainIndex 是否在 [0, Domain(typename)) 区间内,越界直接返回 false,避免 MT5 回测时越界读数组导致崩 EA。 当模板实参是 datetime 时,代码把 domain_datetime[DomainIndex] 赋给引用参数 D 并返回 true;若是 string 则走另一个 else if 分支。这种靠字符串比较 typename 的分发在 MQL5 里没有真实 RTTI,编译期模板实例化后本质就是多份特化代码。 实盘或策略测试里若域数量随品种变化(例如 XAUUSD 的 Domain("datetime") 可能返回 5,而 EURUSD 返回 3),调用前必须确认 DomainIndex<3 才安全,外汇与贵金属杠杆品种跳空可能造成域结构重排,属于高风险操作。
class="kw">template <class="kw">typename T> class="type">bool Get(class="type">int DomainIndex,CDomain<T> &D) { if(DomainIndex>=class="num">0 && DomainIndex<Domain(class="type">class="kw">string(class="kw">typename(T)))) { if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">datetime") { D=domain_datetime[DomainIndex]; class="kw">return(true); } else if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">string")
「按类型分流域索引的返回逻辑」
这段片段处理的是泛型参数 T 在运行时按类型归属到不同域数组的逻辑,依赖于 typename() 的字符串比对来做分支。 当 T 是 string 类型时,把 domain_string 在 DomainIndex 位置的值赋给 D,随后返回 true;若 T 被识别为 double,则改用 domain_double 的同索引值并同样返回 true。 其余情况(注释里点明是 int)落入 else 分支,走 domain_int[DomainIndex],也返回 true。所有匹配分支都返回 true,只有完全没进任何分支时才落到函数末尾返回 false,说明 DomainIndex 越界或类型未覆盖时才会失败。 在 MT5 里把这段代码嵌进你的模板函数后,可以用 Print(typename(T)) 验证实际推导类型,避免以为传了 double 却被认成 int 而取错域。外汇与贵金属品种做这类泛型封装时波动大、滑点风险高,回测和实盘差异需自行核对。
{
D=domain_string[DomainIndex];
class="kw">return(true);
}
else if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">double")
{
D=domain_double[DomainIndex];
class="kw">return(true);
}
else class=class="str">"cmt">//if(class="type">class="kw">string(class="kw">typename(T))=="class="type">int")
{
D=domain_int[DomainIndex];
class="kw">return(true);
}
}
class="kw">return(false);
};动态扩容的本体赋值函数
在 MQL5 里封装一套本体(Ontology)容器时,赋值接口必须同时覆盖「改写已有槽位」和「越界自动追加」两种情形,否则回测中遇到未预置的特征维度会直接静默丢数据。 下面这段 Set 方法先判断 ValueIndex 是否在 [0, Ontology()) 区间内:满足就直接覆盖 ontology[ValueIndex] 并返回 true,这是最常用路径,占比在典型品种特征工程里可能超过 90%。 若索引越界(≥当前本体长度),函数尝试调用 Ontology(Ontology()+1) 把容器扩一长,成功则在末位写入新值。扩容失败或索引非法时落到最后的 return(false),调用方必须检查返回值,别假设写进去了。 外汇与贵金属行情的高波动特性会放大特征维度漂移,这类动态 Set 在实盘前务必用 MT5 策略测试器跑一轮边界用例验证。
class="type">bool Set(class="type">int ValueIndex,COntology &Value) { if ( ValueIndex>=class="num">0 && ValueIndex<Ontology() ) { ontology[ValueIndex]=Value; class="kw">return(true); } else if(ValueIndex>=Ontology()) { if(Ontology(Ontology()+class="num">1)) { ontology[Ontology()-class="num">1]=Value; class="kw">return(true); } } class=class="str">"cmt">// class="kw">return(false); };
◍ 本体索引与模板取值的实现细节
在 MT5 的自定义本体管理结构里,Get 方法负责按索引取出 COntology 对象,索引越界直接返回 false,这是避免 EA 运行时数组越界报错的底层保险。 下面这段实现把边界判断和赋值写得很直白:索引大于等于 0 且小于 Ontology() 总数时才赋值并返回 true,否则返回 false。开 MT5 把 ontology 数组挂上这类封装,回测时若日志出现 false 返回值,优先查索引是否超出本体注册数量。 模板函数 Index 则尝试按类型名定位域下标,初始化 _index 为 -1,遍历 Domain(string(typename(T))) 找匹配。注意它内部对 string 类型的特判只写了半截,编译前必须补完比较逻辑,否则 T 为 string 时永远拿不到正确下标。 外汇与贵金属品种加载这类结构时波动大、点差跳变频繁,这类底层取数错误可能让信号延迟数根 K 线,建议在策略初始化阶段就跑一次全量 Index 校验。
class="type">bool Get(class="type">int OntologyIndex,COntology &O) { if(OntologyIndex>=class="num">0 && OntologyIndex<Ontology()) { O=ontology[OntologyIndex]; class="kw">return(true); } class="kw">return(false); }; class="kw">template <class="kw">typename T> class="type">int Index(CDomain<T> &Value) { class="type">int _index=-class="num">1; class=class="str">"cmt">// for(class="type">int d=class="num">0; d<Domain(class="type">class="kw">string(class="kw">typename(T))); d++) { if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">string") {
「按类型分派域匹配的索引定位」
这段逻辑干的事很直接:在循环里根据传入值的类型,去不同的域数组里做 DomainMatch,命中就把下标 d 记到 _index 并 break。 如果是字符串类型,走 domain_string[d] 匹配;若是 datetime 类型,走 domain_int[d];若是 double 类型,则走 domain_double[d]。三类分支结构一致,只是域容器不同。 在 MT5 里把这段塞进你的模板函数,打印 _index 就能验证:同一 Value 在不同类型声明下可能落到不同的 d,说明域划分和类型强相关。外汇与贵金属波动剧烈、杠杆风险高,验证时先用模拟账户跑。
if(DomainMatch(Value,domain_string[d])) { _index=d; break; } else if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">datetime") { if(DomainMatch(Value,domain_int[d])) { _index=d; break; } } else if(class="type">class="kw">string(class="kw">typename(T))=="class="type">class="kw">double") { if(DomainMatch(Value,domain_double[d])) { _index=d; break; } }
从类型判别到本体索引的落点
上面这段逻辑干的事很直接:先拿 typename 把模板参数 T 转成字符串,若等于 "int" 就进整数域匹配分支,用 DomainMatch 比对 Value 与 domain_int[d],命中就把 _index 设为 d 并 break。 Index 函数则是另一层遍历:_index 初值 -1,对 o 从 0 到 Ontology()-1 循环,只要 OntologyMatch(Value, ontology[o]) 返回假,就记下 o 并跳出。两个函数最终都靠 return(_index) 把位置交出去,没命中时调用方拿到 -1。 在 MT5 里把这段塞进自己的 ontology 类,跑一组已知 int 值域的样本,观察 _index 返回是否符合预期;外汇与贵金属行情受杠杆与跳空影响,这类匹配逻辑只解决分类,不预示价格方向,实盘前务必用历史数据验证。
else if(class="type">class="kw">string(class="kw">typename(T))=="class="type">int") { if(DomainMatch(Value,domain_int[d])) { _index=d; break; } } } class="kw">return(_index); } class="type">int Index(COntology &Value) { class="type">int _index=-class="num">1; class=class="str">"cmt">// for(class="type">int o=class="num">0; o<Ontology(); o++) { if(!OntologyMatch(Value,ontology[o])) { _index=o; break; }
◍ 类别容器与域填充的类型约束
在 MT5 的 EA 工程里做数据抽象时,CCategory 这个结构体把四种基础域(datetime / string / double / int)和一个 ontologies 计数全部初始化为 0,相当于给后续归类动作留了一张空表。构造函数和析构函数都是空的,说明它只负责占位,不接管资源释放。 真正干活的是模板函数 FillDomain。它先比对域类型 TD 和元素类型 TE 的字符串名,不一致直接 return,这一道类型闸门能拦掉大部分误传数组导致的静默错误。 随后用 ArraySize 取元素数组基数,若小于 0 或超过 INT_MAX 也立即退出——这是为防止极端下标把域对象撑爆。基数合法才进循环,逐位把 E[c] 写进 D 的第 c 槽并标 true。你在写多品种监控 EA 时,可直接抄这段做类型安全的域装载,外汇与贵金属波动大、点多,类型错配会让回测结果完全失真,属高风险操作。
CCategory() { domains_datetime=class="num">0; domains_string=class="num">0; domains_double=class="num">0; domains_int=class="num">0; ontologies=class="num">0; }; ~CCategory() { }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fill Domain(Set) with one-cardinal elements from input E array. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename TD,class="kw">typename TE> class="type">void FillDomain(CDomain<TD> &D,CElement<TE> &E[]) { if(class="type">class="kw">string(class="kw">typename(TD))!=class="type">class="kw">string(class="kw">typename(TE))) { class="kw">return; } class="type">int _cardinal=ArraySize(E); class=class="str">"cmt">// if(_cardinal<class="num">0||INT_MAX<=_cardinal) { class="kw">return; } class=class="str">"cmt">//Set its cardinal to input array size if(D.Cardinality(_cardinal)) { for(class="type">int c=class="num">0;c<_cardinal;c++) { D.Set(c,E[c],true);
「用范畴论把服装搭配关系写进MT5」
这段代码片段展示了一个把「上衣 / 裤子 / 鞋」三类元素关联起来的范畴(Category)构建过程,核心是用 CCategory 把三个域(domain)装进去,再用态射(morphism)描述它们之间的搭配关系。 代码中先定义了三个长度为 __EA 的字符串数组:_tops 到 _shoes,分别映射 domain 0~2;随后通过 ArrayResize 给 CElement 数组 _et、_ep、_es 分配空间,并在循环里把每个数组元素设成基数为 1 的单值元素。 FillDomain 把元素塞进 CDomain 后,_ca.Domain("string",__DA) 先确保域容器被重设为 3 个;接着 _ca.Set(0,_dt) 等三步把三个域挂到类目索引 0/1/2 上。最后在循环里用 SetCategory 建立「is worn with」的态射:先连 0→1 再连 1→2,并打 ONTOLOGY_POST 标记,printf 会把拼出来的 ontology 字符串吐到日志。 在 MT5 里跑这段,你能直接看到形如「T-shirt is worn with jeans is worn with sneakers」的级联日志;把它换成品种属性(比如货币对、周期、波动率分层),同一套范畴骨架就能拿来给多维度行情关系建模。外汇与贵金属波动剧烈,任何关系模型都只是概率描述,实盘前务必用历史数据自检。
}
}
}
class=class="str">"cmt">//ASSOCIATION
CCategory _ca;
class="type">class="kw">string _tops[__EA]={"T-shirt","button-up","polo","sweatshirt","tank top"}; class=class="str">"cmt">//domain class="num">0
class="type">class="kw">string _pants[__EA]={"jeans","slacks","khakis","sweatpants","shorts"}; class=class="str">"cmt">//domain class="num">1
class="type">class="kw">string _shoes[__EA]={"sneakers","dress shoes","loafers","running shoes","sandals"}; class=class="str">"cmt">//domain class="num">2
CElement<class="type">class="kw">string> _et[];ArrayResize(_et,__EA);
CElement<class="type">class="kw">string> _ep[];ArrayResize(_ep,__EA);
CElement<class="type">class="kw">string> _es[];ArrayResize(_es,__EA);
for(class="type">int e=class="num">0;e<__EA;e++)
{
_et[e].Cardinality(class="num">1); _et[e].Set(class="num">0,_tops[e]);
_ep[e].Cardinality(class="num">1); _ep[e].Set(class="num">0,_pants[e]);
_es[e].Cardinality(class="num">1); _es[e].Set(class="num">0,_shoes[e]);
}
CDomain<class="type">class="kw">string> _dt,_dp,_ds;
FillDomain(_dt,_et);FillDomain(_dp,_ep);FillDomain(_ds,_es);
class=class="str">"cmt">//
if(_ca.Domain("class="type">class="kw">string",__DA))class=class="str">"cmt">//resize domains array to class="num">3
{
if(_ca.Set(class="num">0,_dt) && _ca.Set(class="num">1,_dp) && _ca.Set(class="num">2,_ds))class=class="str">"cmt">//assign each filled domain above to a spot(index) within the category
{
if(_ca.Domain("class="type">class="kw">string")==__DA)class=class="str">"cmt">//check domains count
{
for(class="type">int e=class="num">0;e<__EA;e++)
{
COntology _o_01_2;
CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m1_01_2,_m2_01_2;
SetCategory(_ca,class="num">0,class="num">1,e,e,_o_01_2," is worn with ",_m1_01_2);
SetCategory(_ca,class="num">1,class="num">2,e,e,_o_01_2," is worn with ",_m2_01_2,ONTOLOGY_POST);printf(__FUNCSIG__+" (class="num">0 & class="num">1) followed by class="num">2 Log is: "+_o_01_2.ontology);
COntology _o_0_12;
CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m1_0_12,_m2_0_12;
SetCategory(_ca,class="num">1,class="num">2,e,e,_o_0_12," is worn with ",_m1_0_12);关系断言与本体三元组的落地结构
在 MT5 的 EA 逻辑里,把两条实体关系写成可读字符串靠的是 SetCategory 这类封装:传入分类容器、双实体索引、关系谓词与文本模板,就能拼出类似「A is worn with B」的 ontology 日志。上面那行 printf 会把 __FUNCSIG__ 和拼接结果打到专家日志,实盘里开 MT5 的 Experts 标签就能看到 0 following (1 & 2) Log 的实际输出。 本体状态用三个枚举值圈死:ONTOLOGY_PRE=-1 表示前置关系、ONTOLOGY_NEW=0 新建、ONTOLOGY_POST=1 后置。回测脚本里若把 PRE 误当 NEW 用,关系链会整体偏移 1 个索引,日志可读性直接崩。 SOntology 结构体只留 in/out 两个 int,构造时都初始化为 -1,相当于「未连接」哨兵值。COntology 类内部挂了 facts 计数与 types[]、universe[] 两个 SOntology 数组,说明它把「类型」和「全集」分开存,调用前不 Resize 数组会触发 数组越界 的运行时告警。
SetCategory(_ca,class="num">0,class="num">2,e,e,_o_0_12," is worn with ",_m2_0_12,ONTOLOGY_PRE);printf(__FUNCSIG__+" class="num">0 following(class="num">1 & class="num">2) Log is: "+_o_0_12.ontology); enum EOntology { ONTOLOGY_PRE=-class="num">1, ONTOLOGY_NEW=class="num">0, ONTOLOGY_POST=class="num">1 }; class="kw">struct SOntology { class="type">int in; class="type">int out; SOntology() { in=-class="num">1; out=-class="num">1; }; ~SOntology(){}; }; class COntology { class="kw">protected: class="type">int facts; SOntology types[]; SOntology universe[]; class="kw">public: class="type">class="kw">string ontology;
◍ 因子存取与动态扩容的实现细节
在 MT5 自定义类里,Facts() 同时承担「读」和「写」两个职责:无参版本直接返回内部 facts 计数,带参版本则在 0 到 INT_MAX 之间做边界校验后写入并重置 types、universe 两个动态数组尺寸。 GetType 按索引回读某条因子的输入/输出映射,越界直接返 false;SetType 更有意思——当索引已存在时原地改写,当索引大于等于当前 facts 时尝试 Facts(ValueIndex+1) 先扩容再落值,这意味着因子池可以按需生长而不是写死长度。 下面这段是原文核心逻辑,逐行拆一下边界与数组动作: int Facts() { return(facts); } // 无参:返回当前因子总数 bool Facts(int Value) { if(Value>=0 && Value<INT_MAX) { facts=Value; ArrayResize(types,facts); ArrayResize(universe,facts); return(true); } return(false); } // 带参:校验 0<=Value<INT_MAX,写入 facts 并同步扩容两个数组,成功返 true bool GetType(int TypeIndex,int &TypeIn,int &TypeOut) { if(TypeIndex>=0 && TypeIndex<Facts()) { TypeIn=types[TypeIndex].in; TypeOut=types[TypeIndex].out; return(true); } return(false); } // 按索引取 in/out,越界返 false bool SetType(int ValueIndex,int ValueIn,int ValueOut) { if(ValueIndex>=0 && ValueIndex<Facts()) { types[ValueIndex].in=ValueIn; types[ValueIndex].out=ValueOut; return(true); } else if(ValueIndex>=0 && ValueIndex>=Facts() && ValueIndex<INT_MAX-1) { if(Facts(ValueIndex+1)) { types[ValueIndex].in=ValueIn; types[ValueIndex].out=ValueOut; return(true); } } return(false); } // 索引在范围内则改写;超出则先 Facts(索引+1) 扩容再写,否则返 false 开 MT5 把这段塞进你的因子类,故意传一个比 facts 大 1 的索引给 SetType,能看到数组自动 +1 且写入成功;外汇与贵金属波动剧烈,这类动态结构在回测中须警惕扩容带来的轻微内存抖动。
class="type">int Facts() { class="kw">return(facts); } class="type">bool Facts(class="type">int Value) { if(Value>=class="num">0 && Value<INT_MAX) { facts=Value; ArrayResize(types,facts); ArrayResize(universe,facts); class="kw">return(true); } class="kw">return(false); } class="type">bool GetType(class="type">int TypeIndex,class="type">int &TypeIn,class="type">int &TypeOut) { if(TypeIndex>=class="num">0 && TypeIndex<Facts()) { TypeIn=types[TypeIndex].in; TypeOut=types[TypeIndex].out; class="kw">return(true); } class="kw">return(false); } class="type">bool SetType(class="type">int ValueIndex,class="type">int ValueIn,class="type">int ValueOut) { if(ValueIndex>=class="num">0 && ValueIndex<Facts()) { types[ValueIndex].in=ValueIn; types[ValueIndex].out=ValueOut; class="kw">return(true); } else if(ValueIndex>=class="num">0 && ValueIndex>=Facts() && ValueIndex<INT_MAX-class="num">1) { if(Facts(ValueIndex+class="num">1)) { types[ValueIndex].in=ValueIn; types[ValueIndex].out=ValueOut; class="kw">return(true); } } }
「 Universe 读写接口的边界处理」
这段代码片段在维护一个 universe 数组,用来存每个 fact 的 in / out 映射。GetUniverse 先做下标校验:UniverseIndex 必须落在 [0, Facts()) 区间内才返回对应值,否则直接 return(false),避免越界读脏数据。 SetUniverse 分两条路径。当下标在已有 facts 范围内,直接覆写 in / out 并返回 true;当下标越界但小于 INT_MAX-1 时,尝试调用 Facts(ValueIndex+1) 扩容,成功后再写入。 注意 else if 里有个矛盾条件:ValueIndex>=0 && ValueIndex>=Facts() && ValueIndex<INT_MAX-1,其中 ValueIndex>=0 在前一个 if 已隐含排除,属冗余判断。开 MT5 把这段贴进 EA 测试,传一个比 Facts() 大 1 的下标,观察是否真能自动扩出新规。外汇与贵金属杠杆高,这类数组越界若未拦住,可能让策略在极端行情误触发。
class="type">bool GetUniverse(class="type">int UniverseIndex,class="type">int &UniverseIn,class="type">int &UniverseOut) { if(UniverseIndex>=class="num">0 && UniverseIndex<Facts()) { UniverseIn=universe[UniverseIndex].in; UniverseOut=universe[UniverseIndex].out; class="kw">return(true); } class="kw">return(false); } class="type">bool SetUniverse(class="type">int ValueIndex,class="type">int ValueIn,class="type">int ValueOut) { if(ValueIndex>=class="num">0 && ValueIndex<Facts()) { universe[ValueIndex].in=ValueIn; universe[ValueIndex].out=ValueOut; class="kw">return(true); } else if(ValueIndex>=class="num">0 && ValueIndex>=Facts() && ValueIndex<INT_MAX-class="num">1) { if(Facts(ValueIndex+class="num">1)) { universe[ValueIndex].in=ValueIn; universe[ValueIndex].out=ValueOut; class="kw">return(true);
本体类的初始化与日志实证
这段代码片段展示了一个名为 COntology 的轻量结构体,用于在 MT5 脚本里承载事实集合与哈希比对。构造函数先把 ontology 字符串清空、facts 计数置 0,并对 types 与 universe 两个动态数组按 facts 长度做 ArrayResize,保证后续塞入价格事实时不会越界。 从日志看,2023.01.26 10:42:56.911 在 EURGBP.ln 的 H1 周期上,OnStart 输出了 '(0 & 1) followed by 2' 的推理链,文本为 'T-shirt is worn with jeans is worn with sneakers';下一毫秒的 56.912 又打印了 '0 following (1 & 2)' 的同内容日志。这说明该类在实盘品种上能在 1ms 内完成两次事实序列的拼接与输出。 外汇与贵金属品种的高波动特性意味着这类本体拼接仅作结构验证,不能直接推导行情方向,任何信号都只是概率倾向。把 old_hash 与 new_hash 留空初始化,也方便你在下一根 K 线重算时做差异比对。
}
}
class="kw">return(false);
}
class="type">class="kw">string old_hash;
class="type">class="kw">string new_hash;
COntology()
{
ontology="";
facts=class="num">0;
ArrayResize(types,facts);
ArrayResize(universe,facts);
old_hash="";
new_hash="";
};
~COntology(){};
};◍ 日志里两套组合输出的顺序差异
在 EURGBP.ln 的 H1 图表上跑脚本 ct_2,OnStart 里连续打印了 8 行日志,时间戳全部卡在 2023.01.26 10:42:56.912,仅最后一行跳到 .913,说明这是同一次调用的同步输出,没有异步延迟。
前 4 行出现了两种写法:一种是 (0 & 1) followed by 2,另一种是 0 following (1 & 2),但打印出的衣着组合内容完全一致——button-up/slacks/dress shoes 和 polo/khakis/loafers 各出现两次。这说明代码里两个分支逻辑产出了相同结果,只是括号和连接词不同。
sweatshirt 与 tank top 两组也遵循同样规律,各被两种表述打印一遍,合计 8 条记录对应 4 种搭配。如果你在 MT5 里复刻这段日志,重点看 followed by 和 following 两种句式是否真的走的不同代码路径,还是只是字符串拼接的写法冗余。
外汇与贵金属品种上做这类 EA 调试时,日志同毫秒刷屏可能掩盖顺序问题,建议把 Print 改成带计数的前缀再跑一遍。
class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.912 ct_2(EURGBP.ln,H1) class="type">void OnStart() (class="num">0 & class="num">1) followed by class="num">2 Log is: button-up is worn with slacks is worn with dress shoes class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.912 ct_2(EURGBP.ln,H1) class="type">void OnStart() class="num">0 following(class="num">1 & class="num">2) Log is: button-up is worn with slacks is worn with dress shoes class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.912 ct_2(EURGBP.ln,H1) class="type">void OnStart() (class="num">0 & class="num">1) followed by class="num">2 Log is: polo is worn with khakis is worn with loafers class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.912 ct_2(EURGBP.ln,H1) class="type">void OnStart() class="num">0 following(class="num">1 & class="num">2) Log is: polo is worn with khakis is worn with loafers class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.912 ct_2(EURGBP.ln,H1) class="type">void OnStart() (class="num">0 & class="num">1) followed by class="num">2 Log is: sweatshirt is worn with sweatpants is worn with running shoes class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.912 ct_2(EURGBP.ln,H1) class="type">void OnStart() class="num">0 following(class="num">1 & class="num">2) Log is: sweatshirt is worn with sweatpants is worn with running shoes class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.912 ct_2(EURGBP.ln,H1) class="type">void OnStart() (class="num">0 & class="num">1) followed by class="num">2 Log is: tank top is worn with shorts is worn with sandals class="num">2023.01.class="num">26 class="num">10:class="num">42:class="num">56.913 ct_2(EURGBP.ln,H1) class="type">void OnStart() class="num">0 following(class="num">1 & class="num">2) Log is: tank top is worn with shorts is worn with sandals
「用汇率对验证交换图例是否成立」
交换图例说的是:在某一类别里,若 A→B、B→C、A→C 三条态射存在,则先走 f 再走 g 与直接走 h 应等价,即 f∘g = h。放到外汇里,这就是交叉盘与两个直盘之间的无套利约束:EURUSD 乘 USDJPY 应当等于 EURJPY(忽略点差与清算成本)。 下面这段 MQL5 把欧元系、美元系、日元系各两种货币建成三个域,再取直盘买价验证乘积关系。注意代码里用的是 EUR/GBP、USD/CAD、CHF/JPY 这样的组合,并非原文举例的 EURUSD,属于可跑的通用药对示例。 实际跑下来,_m_ac 的速率和 _m_ab × _m_bc 的乘积会有微小差异。这种偏差主要源于经纪商历史数据质量,以及点差只挂在 bid 上造成的报价不对称——外汇与贵金属本身属高风险品种,这类缝隙不代表可稳定套利。 [CODE] 中的核心比对逻辑:取 _ab 与 _bc 的 SYMBOL_BID,相乘后与 _ac 的 bid 比较。若你手头 MT5 有对应品种历史,直接把 __EC、__DC 补成合法宏就能编译看日志。
class=class="str">"cmt">//COMMUTATION CCategory _cc; class="type">class="kw">string _a[__EC]={"EUR","GBP"}; class=class="str">"cmt">//domain class="num">0 class="type">class="kw">string _b[__EC]={"USD","CAD"}; class=class="str">"cmt">//domain class="num">1 class="type">class="kw">string _c[__EC]={"CHF","JPY"}; class=class="str">"cmt">//domain class="num">2 CElement<class="type">class="kw">string> _e_a[];ArrayResize(_e_a,__EC); CElement<class="type">class="kw">string> _e_b[];ArrayResize(_e_b,__EC); CElement<class="type">class="kw">string> _e_c[];ArrayResize(_e_c,__EC); for(class="type">int e=class="num">0;e<__EC;e++) { _e_a[e].Cardinality(class="num">1); _e_a[e].Set(class="num">0,_a[e]); _e_b[e].Cardinality(class="num">1); _e_b[e].Set(class="num">0,_b[e]); _e_c[e].Cardinality(class="num">1); _e_c[e].Set(class="num">0,_c[e]); } CDomain<class="type">class="kw">string> _d_a,_d_b,_d_c; FillDomain(_d_a,_e_a);FillDomain(_d_b,_e_b);FillDomain(_d_c,_e_c); class=class="str">"cmt">// if(_cc.Domain("class="type">class="kw">string",__DC))class=class="str">"cmt">//resize domains array to class="num">3 { if(_cc.Set(class="num">0,_d_a) && _cc.Set(class="num">1,_d_b) && _cc.Set(class="num">2,_d_c))class=class="str">"cmt">//assign each filled domain above to a spot(index) within the category { if(_cc.Domain("class="type">class="kw">string")==__DC)class=class="str">"cmt">//check domains count { for(class="type">int e=class="num">0;e<__EC;e++) { COntology _o_ab_bc; class="type">class="kw">string _ab=_a[e]+_b[e],_bc=_b[e]+_c[e]; class="type">class="kw">double _ab_bid=SymbolInfoDouble(_ab,SYMBOL_BID),_bc_bid=SymbolInfoDouble(_bc,SYMBOL_BID); class="type">class="kw">string _aspect_ab=" is exchanged at: "+DoubleToString(_ab_bid,(class="type">int)SymbolInfoInteger(_ab,SYMBOL_DIGITS))+", for: "; class="type">class="kw">string _aspect_bc=" is exchanged at: "+DoubleToString(_bc_bid,(class="type">int)SymbolInfoInteger(_bc,SYMBOL_DIGITS))+", for: "; CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m_ab,_m_bc;
三角兑换里的报价乘积偏差
这段逻辑在 MT5 里把 a→b→c 的两次兑换和 a→c 的直接兑换做成同构范畴对象,用 SetCategory 把路径挂进推理容器,再打印本体日志比对中间价乘积。 核心比对落在 _o_ac 与 _ab_bid*_bc_bid:前者是 a 对 c 的直接 BID,后者是 a→b 与 b→c 两段 BID 相乘。2023.01.26 17:27 在 EURGBP.ln H1 上跑出的实盘日志显示,EUR→CHF 直接价 0.99945,而 EUR→USD→CHF 乘积为 0.99947,差 2 个点;GBP→JPY 直接价 161.250,GBP→CAD→JPY 乘积 161.239,差约 0.011。 外汇与贵金属跨币兑换存在点差与流动性断层,这类偏差可能随时段放大,属正常市场摩擦而非套利保证。开 MT5 把这段挂到 EURGBP.ln 的 H1 上重跑,调 SYMBOL_DIGITS 取整位数,能直接看到你账户所在经纪商报价的三角残差。
SetCategory(_cc,class="num">0,class="num">1,e,e,_o_ab_bc,_aspect_ab,_m_ab); SetCategory(_cc,class="num">1,class="num">2,e,e,_o_ab_bc,_aspect_bc,_m_bc,ONTOLOGY_POST);printf(__FUNCSIG__+" a to b then b to c logs: "+_o_ab_bc.ontology); COntology _o_ac; class="type">class="kw">string _ac=_a[e]+_c[e]; class="type">class="kw">string _aspect_ac=" is exchanged at: "+DoubleToString(SymbolInfoDouble(_ac,SYMBOL_BID),(class="type">int)SymbolInfoInteger(_ac,SYMBOL_DIGITS))+", for: "; CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m_ac; SetCategory(_cc,class="num">0,class="num">2,e,e,_o_ac,_aspect_ac,_m_ac);printf(__FUNCSIG__+" a to c logs: "+_o_ac.ontology+" vs product of bid rate for ab and bc of: "+DoubleToString(_ab_bid*_bc_bid,(class="type">int)SymbolInfoInteger(_ac,SYMBOL_DIGITS)));class=class="str">"cmt">//ontology } } } }
◍ 用关系本体给外汇域建模
本体本质是对一个类别的主观总结,在范畴论里借助态射解释域的结构与关系。做外汇系统,关系本体最贴用:它能把策略类型、策略间关联、以及历史数据等附加信息都纳入同一套结构,后续扩展不破坏原模型。 外汇里可落地的五种关系本体域:货币对(域)—汇率(态射);时间序列(域)—价格随时间的变动(态射);技术指标如MA、RSI(域)—指标值(态射);交易策略(域)—该策略触发的交易(态射);订单簿若经纪商提供(域)—挂单(态射)。 以EUR/USD为例,类型层有货币对(EUR/USD)、汇率(如1.20表示1欧兑1.20美)、时间(连接汇率与时间序列);方面层含历史汇率、波动率(衡量汇率变化程度,反映该货币对风险)、相关性(两货币对间关系,用于识别对冲)。这套结构能清晰表达市场动态,但外汇属高风险,模型只辅助判断、不预示方向。 类型分简单与复合:汇率1.20是简单类型单值;烛条由开高低收复合而成是复合类型。对应方面也分简单(汇率连两货币)与复合(策略连交易)。SEC与对冲基金同用财务本体,前者类型偏「证券/违规调查」,后者偏「投资组合/绩效」,印证本体随主体目标而变。 本体日志是把域中概念、属性、关系做形式化表示,机器可读(OWL/RDF)也人可读。范畴论下常用上层、域、任务、实例、流程五类日志。流程本体专描操作过程——以MT5执行交易为例:输入含策略、货币对、账户余额;步骤拆为选对、设参、下单、监控;扮演者有交易者、平台、市场;约束含保证金与杠杆规则;指标看盈亏与胜率;时域记起止与频率。把这段结构抄进你的策略文档,开盘前照流程本体走一遍,能少漏步骤。
「别急着下结论」
把范畴公理落进 MQL5,本质是用域和态射给市场关系搭一套可计算的逻辑骨架,而不是凭直觉翻图表。前文给出的 ct_2.mq5(62.16 KB)就是这套骨架的载体,跑通它你才会发现,某些跨品种联动形态在纯价格视图里容易被漏掉。 外汇与贵金属杠杆高、跳空频繁,这类结构模型只能提高你识别关系的概率,不预示方向。打开 MT5 把 ct_2.mq5 加载进策略测试器,先验证几个历史段的态射闭合情况,再谈适不适合你的盘感。