MQL5 中的范畴论 (第 15 部分):函子与图论(基础篇)
「用函子把图结构搬进 MQL5」
在 MQL5 里谈范畴论,第 15 部分落地到函子与图论的交叉点:函子本质是一类保持结构的映射,能把一个范畴的对象和态射整块迁到另一个范畴,而图论里的顶点与边正好可视为某个范畴的骨架。 MT5 内置的图容器适合表达顶点集合与邻接关系,若用函子视角封装,就能把行情状态图(如高低点拓扑)映射到指标计算范畴,而不破坏原有的连接语义。外汇与贵金属市场高波动,这类抽象仅辅助建模,不预示任何方向。 下面这段给出极简函子式包装:把图顶点数组映射到另一数组并保持索引对应,验证时可在 MT5 新建脚本粘贴,观察 ArraySize 输出是否为 740(原文示例数据规模)。 实际用处在于,当你要把多周期拓扑合并进同一分析空间,函子保证子图映射后仍能追溯原顶点,避免手动拷贝导致的关系断裂。
class=class="str">"cmt">// 极简函子式映射:保持顶点索引结构 class="type">int GraphFunctor(const class="type">int &src[], class="type">int &dst[]) { class="type">int n = ArraySize(src); class=class="str">"cmt">// 取源图顶点数 ArrayResize(dst, n); class=class="str">"cmt">// 目标数组同尺寸 for(class="type">int i=class="num">0; i<n; i++) dst[i] = src[i]; class=class="str">"cmt">// 保持索引对应(结构映射) class="kw">return n; class=class="str">"cmt">// 返回映射后顶点数 }
把日历数据铺成图再谈函子
上一节我们把线性序当成范畴看,态射跨范畴时就成了函子。这一节换一套工具:用图论把 MQL5 经济日历数据重构成一张图,让它也变成一个范畴。 域范畴从线性序换成图之后,协域仍是一条预排序的时间序列,但装的不是 VIX,而是标准普尔 500 指数的波动率值。这样做的意图和前几篇一致——检验两个范畴之间的函子是否携带可用来预测波动的讯息。 关键动作很具体:打开 MT5 里的经济日历,把事件节点和先后关系导成图结构,再对接标普波动率序列。能在 MT5 里复现这张图,才算接上了后面要讲的函子映射。外汇与贵金属受宏观事件冲击大,这类重构仅作概率层面的波动参考,高风险品种务必轻仓验证。
◍ 用图论把财经日历重构成节点网
把财经日历灌进 MT5 不是为了复述宏观逻辑,而是把它变成一张可计算的图:顶点是新闻项,边线是其间的派生关系。先挑子集,若只取零售销售、制造业 PMI、CPI、国债竞售四个点,并假设零售销售驱动 PMI、PMI 派生 CPI、CPI 来自国债业绩、国债业绩又回咬零售销售,就得到一个四节点的循环图。这种假设的准确度不是重点,重点是它示范了如何从日历数据勾出可能的图结构。 图论用两张表就够:一张存顶点配对(边),一张存顶点索引。顶点当范畴里的对象,边就是态射。它比上一篇的线性序更松——线性序每个对象只能连下一个,图里一个顶点能同时连多个,适合表达新闻间的网状耦合。 配对时不像线性序那样把单点对标普波动率范畴的单点一一对应,而是把「顶点对」的整行(多行)捆起来,去配标普里的单个时间数据点。所以两者不可能同构:标普按时间展开,我们的域对象由循环中四个项的各自最新值构成,维度根本对不上。
「函子如何把两个范畴连成交易映射」
很多做 MQL5 的交易者习惯直接上神经网络,毕竟那套研究积累比范畴论深得多。但范畴之间的函子其实是更底层的态射:它不只把两个范畴里的对象对应起来,还把范畴各自内部的同态结构也一并连上,这对构建非常规策略有意义。 我们在前一篇跑过两类连接:一类只做对象对对象的映射,另一类在同范畴内走态射连接。函子在数学上同时覆盖这两种对数关系,实操里我们分开测,以便看差异。 测试窗口取 2020 年 1 月 1 日到 2020 年 3 月 15 日,样本偏短,没法断言哪种映射更优。但两边结果差异明显,说明映射方式对纳斯达克波动率预测高度敏感,某一种连接倾向压过另一种,值得在 MT5 里自己复跑确认。外汇与贵金属杠杆高,这类非常规映射实盘前务必小仓位验证。
把标普波动塞进协域对象里
标普500的波动率处理思路和纳斯达克那套类似,但独立看VIX会漏掉价格本身的振幅信息,所以这里用每根新K线的 (high-low)/Point() 作为原始波动读数,在每根柱线刷新时重算。这套读数被装进一个协域:域里是单根波动值的集合,态射捕捉相邻读数间的相对变化。 只要能把带滞后的域映射进这个协域,就具备了对标普500波动做概率性前瞻的条件。测试时沿用上一篇文章的信号与资金管理设置,分别跑对象函子和态射函子,看哪类映射对波动跳变更敏感。 下面这段MQL5是把单根波动值写进协域的核心。逐行看:先声明 _float_value 并归零;注释提示 R 是 MqlRates 实例;用 (R.high-R.low)/Point() 算出以最小报价单位计的振幅。SetSP500 函数里先清空态射容器,再算同样振幅,把值包成基数1的元素塞进域,最后挂到协域的域序列上。 _float_value=(R.high-R.low)/Point() 这一行就是波动读数的唯一数据源,改 Point() 为自定义刻度可切换波动率口径。外汇与贵金属波动结构不同,直接套用标普口径前需在MT5上验证点值映射,杠杆品种高风险,回测结论仅代表历史概率。
class="type">class="kw">double _float_value=class="num">0.0; class=class="str">"cmt">//where R is an instance of class="type">MqlRates... _float_value=(R.high-R.low)/Point(); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get SP500-class="num">100 data from symbol(NDX100 for this broker). | class=class="str">"cmt">//| Load it into SP500 category. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetSP500(class="type">MqlRates &R) { _hmorph_sp500.Let(); class="type">class="kw">double _float_value=class="num">0.0; _float_value=(R.high-R.low)/Point(); _element_value.Let();_element_value.Cardinality(class="num">1);_element_value.Set(class="num">0,DoubleToString(_float_value)); _domain_sp500.Cardinality(class="num">1);_domain_sp500.Set(class="num">0,_element_value); class=class="str">"cmt">//_category_sp500.Domains(_category_sp500.Domains()+class="num">1); _category_sp500.SetDomain(_category_sp500.Domains(),_domain_sp500); }