MQL5 中的范畴论 (第 13 部分):数据库制程的日历事件·进阶篇
(2/3)· 财经日历每天刷出十几国宏观指标,文本格式乱、数值不可比,怎么在 MQL5 里落地下一城
「用矩形互倒类把事件日历压成货币对权重」
把范畴里的矩形互倒直接落成 MQL5 类,核心是在原有 CSquareCommute 上补一条通用属性同态 da。这样事件集、货币集、事件类型集和货币对价值集四角之间的映射就能在内存里跑起来,不必先建外部库。 下面这段是改造后的类骨架,四个角之间的同态 ab/ac/db/dc 描述已知边,da 是通用属性要求的“回拉”映射: //+------------------------------------------------------------------+
| // | Square Commute Class to illustrate Universal Property |
|---|
//+------------------------------------------------------------------+ template <typename TA,typename TB,typename TC,typename TD> class CCommuteSquare { public: CHomomorphism<TA,TB> ab; CHomomorphism<TA,TC> ac; CHomomorphism<TD,TB> db; CHomomorphism<TD,TC> dc; CHomomorphism<TD,TA> da; //universal property virtual void SquareAssert() { ab.domain=ac.domain; ab.codomain=db.codomain; dc.domain=db.domain; dc.codomain=ac.codomain; da.domain=db.domain; da.codomain=ac.domain; } SquareAssert 里只做一件事:断言四角的域和协域对齐。ab.domain=ac.domain 说明事件集同时是 ab 和 ac 的出发集;da.domain=db.domain 则把货币对价值集拉回事件集,这是通用属性成立的前提。 数据存在内存而非硬盘,和 SQL 的外存方案不同。MT5 的 MQL5 IDE 自带部分数据库处理能力,你若嫌回测时占内存,可把填充好的集合导出到物理库,再从盘上读,能省一笔计算资源。 权重参数(出价货币 / 保证金货币)别写死。可用各国基准利率差或通胀相对水平去估,也可留给你自己的优化器跑。外汇和贵金属波动受宏观事件驱动明显,这类映射只是概率层面的加权,实盘仍属高风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Square Commute Class to illustrate Universal Property | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template <class="kw">typename TA,class="kw">typename TB,class="kw">typename TC,class="kw">typename TD> class CCommuteSquare { class="kw">public: CHomomorphism<TA,TB> ab; CHomomorphism<TA,TC> ac; CHomomorphism<TD,TB> db; CHomomorphism<TD,TC> dc; CHomomorphism<TD,TA> da; class=class="str">"cmt">//universal class="kw">property class="kw">virtual class="type">void SquareAssert() { ab.domain=ac.domain; ab.codomain=db.codomain; dc.domain=db.domain; dc.codomain=ac.codomain; da.domain=db.domain; da.codomain=ac.domain; }
用范畴结构搭财经事件库骨架
在 MT5 里做宏观事件驱动策略,先得把事件、类型、币种、数值拆成独立的域(Domain)与元素(Element)。上面这段把事件集基数设为 7,类型集和币种集分别用外部变量 _types、_currencies 初始化,数值集基数定为 4,相当于一张四维关系表,方便后面做同态映射。 CDatabase 类把这些表收拢到一起:events / event_types / countries / currncies 四个成员直接对应四张结构表,构造和析构留空壳,你往里塞数据即可。注意原文把 currencies 拼成了 currncies,复制时若编译报错先查拼写。 同态(Homomorphism)部分才是重点:_ab 遍历事件到类型的映射域,逐个取元素字符串,调 EventType(_s) 转码后写进 _m.Morph(),再把 morphisms 数量 +1。这段只贴了 ab 和 ac 的前半,跑通后你能用同样套路把币种、数值也连起来,外汇和贵金属行情受事件冲击的链路就建模完了。 这类多域映射在实盘只是辅助过滤,外汇/贵金属杠杆高、跳空频繁,事件窗口进场仍可能反向扫损,验证时先用策略测试器跑历史新闻日。
CCommuteSquare(){} ~CCommuteSquare(){} }; class=class="str">"cmt">//sample constructor for event set CElement<class="type">class="kw">string> _e_event;_e_event.Cardinality(class="num">7); class=class="str">"cmt">// class=class="str">"cmt">//sample constructor for type set CDomain<class="type">class="kw">string> _d_type;_d_type.Cardinality(_types); class=class="str">"cmt">//data population CElement<class="type">class="kw">string> _e_type;_e_type.Cardinality(class="num">1); class=class="str">"cmt">//sample constructor for currency set CDomain<class="type">class="kw">string> _d_currency;_d_currency.Cardinality(_currencies); class=class="str">"cmt">//data population CElement<class="type">class="kw">string> _e_currency;_e_currency.Cardinality(class="num">1); class=class="str">"cmt">//sample constructor for values set CDomain<class="type">class="kw">string> _d_values;_d_values.Cardinality(_values); class=class="str">"cmt">//data population CElement<class="type">class="kw">string> _e_values;_e_values.Cardinality(class="num">4); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CDatabase { class="kw">public: STableEvents events; STableEventTypes event_types; STableCountries countries; STableCurrencies currncies; CDatabase(class="type">void); ~CDatabase(class="type">void); }; class=class="str">"cmt">//ab homomorphisms CHomomorphism<class="type">class="kw">string,class="type">class="kw">string> _ab; CElement<class="type">class="kw">string> _e; for(class="type">int s=class="num">0;s<_sc.ab.domain.Cardinality();s++) { _e.Let(); if(_sc.ab.domain.Get(s,_e)) { class="type">class="kw">string _s=""; if(_e.Get(class="num">0,_s)) { CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m; _m.Morph(_sc.ab.domain,_sc.ab.codomain,s,EventType(_s)); _ab.Morphisms(_ab.Morphisms()+class="num">1); _ab.Set(_ab.Morphisms()-class="num">1,_m); } } } class=class="str">"cmt">//ac homomorphisms CHomomorphism<class="type">class="kw">string,class="type">class="kw">string> _ac; for(class="type">int s=class="num">0;s<_sc.ac.domain.Cardinality();s++) {
◍ 从事件域构造三类态射映射
这段逻辑在做一件事:把原始事件集合按不同业务维度(账户、数据库、保证金)拆成三张 homomorphism 表,方便后续做币种与类型的代数归并。
先看账户态射 _ac 的构建:遍历 _values 个事件,取索引 1 的字符串调用 EventCurrency 转成整数代号;若返回值不是 -1,就通过 Morph 把 domain 元素 s 映射到 codomain 的币种代号,并将态射计数 +1 后写回数组。
数据库态射 _db 走的是索引 3 的字段,用 TypeToInt 把类型字符串变成整数,不做币种校验直接入库——这意味着 _db 承载的是事件类型而非币种映射。
保证金态射 _dc 只取索引 0 且经过 EventCurrency 转换成功的记录,注释明确写了『morphisms for margin currency only』,所以这张表是纯保证金币种的子集映射。
在 MT5 里跑这套,可以把 _values 打印出来确认事件规模;若某次 _ac.Morphisms() 明显小于 _values,大概率是有事件索引 1 的币种解析失败,需要回头查 EventCurrency 的覆盖表。外汇与贵金属品种映射错乱会带来爆仓级风险,参数改动前务必在策略测试器用历史 tick 验证。
_e.Let(); if(_sc.ac.domain.Get(s,_e)) { class="type">class="kw">string _s=""; if(_e.Get(class="num">1,_s)) { CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m; class="type">int _c=EventCurrency(_s); if(_c!=-class="num">1) { _m.Morph(_sc.ac.domain,_sc.ac.codomain,s,_c); _ac.Morphisms(_ac.Morphisms()+class="num">1); _ac.Set(_ac.Morphisms()-class="num">1,_m); } } } class=class="str">"cmt">//db homomorphisms CHomomorphism<class="type">class="kw">string,class="type">class="kw">string> _db; for(class="type">int s=class="num">0;s<_values;s++) { _e.Let(); if(_sc.db.domain.Get(s,_e)) { class="type">class="kw">string _s=""; if(_e.Get(class="num">3,_s)) { class="type">int _t=TypeToInt(_s); CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m; _m.Morph(_sc.db.domain,_sc.db.codomain,s,_t); _db.Morphisms(_db.Morphisms()+class="num">1); _db.Set(_db.Morphisms()-class="num">1,_m); } } } class=class="str">"cmt">//dc homomorphisms CHomomorphism<class="type">class="kw">string,class="type">class="kw">string> _dc; for(class="type">int s=class="num">0;s<_values;s++) { _e.Let(); if(_sc.dc.domain.Get(s,_e)) { class="type">class="kw">string _s=""; if(_e.Get(class="num">0,_s))class=class="str">"cmt">//morphisms for margin currency only { class="type">int _c=EventCurrency(_s); CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m; _m.Morph(_sc.dc.domain,_sc.dc.codomain,s,_c);
「用同币种约束补全态射映射」
在账户配置对象里,保证金币种是跨表校验的关键字段。上面这段逻辑先遍历 domain 的每一个元素,取出索引 0 和 3 的字符串,分别代表某资产的源币种与目标币种。 内层再扫一遍 codomain,抓出索引 1 和 6 的币种串做比对;只有源、目标币种完全相等时,才在 _da 里新增一条从 s 到 ss 的态射。这种写法把「同币种」作为唯一连通条件,避免把不同结算货币的工具误连成一条变换路径。 每次写入后调用 _sc.SquareAssert(),相当于在运行期强制方阵闭合。若你改了索引位(比如保证金字段挪到 4 和 7),MT5 编译不会报错,但 Assert 会在回测初始化时直接抛异常,逼你当场核对表结构。 外汇与贵金属杠杆品种币种错配会引发保证金重算偏差,实盘前务必用脚本打印 _da.Morphisms() 的总数,确认连通边数符合预期。
_dc.Morphisms(_dc.Morphisms()+class="num">1); _dc.Set(_dc.Morphisms()-class="num">1,_m); } } } class=class="str">"cmt">//da homomorphisms CHomomorphism<class="type">class="kw">string,class="type">class="kw">string> _da; for(class="type">int s=class="num">0;s<_values;s++) { _e.Let(); if(_sc.da.domain.Get(s,_e)) { class="type">class="kw">string _s_c="",_s_t=""; if(_e.Get(class="num">0,_s_c) && _e.Get(class="num">3,_s_t))class=class="str">"cmt">// for margin currency { for(class="type">int ss=class="num">0;ss<_sc.ac.domain.Cardinality();ss++) { CElement<class="type">class="kw">string> _ee; if(_sc.da.codomain.Get(ss,_ee)) { class="type">class="kw">string _ss_c="",_ss_t=""; if(_ee.Get(class="num">1,_ss_c) && _ee.Get(class="num">6,_ss_t))class=class="str">"cmt">// for margin currency { if(_ss_c==_s_c && _ss_t==_s_t) { CMorphism<class="type">class="kw">string,class="type">class="kw">string> _m; class=class="str">"cmt">// _m.Morph(_sc.da.domain,_sc.da.codomain,s,ss); _da.Morphisms(_da.Morphisms()+class="num">1); _da.Set(_da.Morphisms()-class="num">1,_m); _sc.da=_da; _sc.SquareAssert(); break; } } } } } } }
把映射域写回结构再校验
这段代码出现在局部映射域调整后,负责把临时域对象 _da 的 domain 与 codomain 回灌进原结构 _sc.da,确保后续断言基于最新区间。 _sc.da=_da 执行指针替换后,立刻调用 _sc.SquareAssert() 做一次方形约束自检;若域边界不满足平方关系,运行期会直接报错,避免在 MT5 策略测试器里带着错误域跑完整个样本。 实操上,改完映射域不写回就跳过的写法在复杂指标里很常见,建议复制这段代码到自己的 CSeries 类收尾处,编译后开一个 EURUSD 的 M1 历史段验证断言是否触发。外汇与贵金属品种波动跳跃大,这类域校验失败概率随品种而异,需自行回测确认。
_da.domain=_sc.da.domain;
_da.codomain=_sc.da.codomain;
_sc.da=_da; _sc.SquareAssert();