MQL5 中的范畴论 (第 13 部分):数据库制程的日历事件·进阶篇
🗓️

MQL5 中的范畴论 (第 13 部分):数据库制程的日历事件·进阶篇

(2/3)· 财经日历每天刷出十几国宏观指标,文本格式乱、数值不可比,怎么在 MQL5 里落地下一城

实战向进阶 第 2/3 篇
把日历事件直接塞进 EA 做信号,是多数人的第一反应。但德国央行讲话没有数值、中美指标量纲不同,硬比只会产出噪声。先解决“怎么读、怎么排”再谈交易,否则分类器本身就是错的。

「用矩形互倒类把事件日历压成货币对权重」

把范畴里的矩形互倒直接落成 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 自带部分数据库处理能力,你若嫌回测时占内存,可把填充好的集合导出到物理库,再从盘上读,能省一笔计算资源。 权重参数(出价货币 / 保证金货币)别写死。可用各国基准利率差或通胀相对水平去估,也可留给你自己的优化器跑。外汇和贵金属波动受宏观事件驱动明显,这类映射只是概率层面的加权,实盘仍属高风险。

MQL5 / C++
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 的前半,跑通后你能用同样套路把币种、数值也连起来,外汇和贵金属行情受事件冲击的链路就建模完了。 这类多域映射在实盘只是辅助过滤,外汇/贵金属杠杆高、跳空频繁,事件窗口进场仍可能反向扫损,验证时先用策略测试器跑历史新闻日。

MQL5 / C++
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 验证。

MQL5 / C++
      _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() 的总数,确认连通边数符合预期。

MQL5 / C++
      _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 历史段验证断言是否触发。外汇与贵金属品种波动跳跃大,这类域校验失败概率随品种而异,需自行回测确认。

MQL5 / C++
      _da.domain=_sc.da.domain;
      _da.codomain=_sc.da.codomain;
      _sc.da=_da; _sc.SquareAssert();
把事件清洗交给小布盯盘
这些多国日历事件的提取与初步归类,小布盯盘的 AIGC 已内置,打开对应品种页即可看到结构化后的预测/实际/前期值,你只需定权重与可比规则。

常见问题

很多事件如央行讲话没有数值,或实际值格式浮动很大,仅读数值会丢信息,必须同时处理文本描述与量纲差异。
没有唯一解,倾向按交易品种流动性与权重分配;可在 MQL5 里用偏序建模可选方案的排名,再结合相关性回测定权。
可以,小布盯盘内置了多国宏观指标的提取与结构化展示,并支持按品种页查看,省去你手写数据库制程的重复劳动。
因为秩序论里的偏序与线性序已足够描述事件可比性,先落地到数据库制程分类器比继续加抽象更有实战价值。
不可比事件若强行归一,可能引入虚假相关;概率上更稳妥的做法是分层处理,把无数值事件单独走文本分支。