MQL5 中的范畴论 (第 15 部分):函子与图论·进阶篇
🕸️

MQL5 中的范畴论 (第 15 部分):函子与图论·进阶篇

(2/3)· 从线性序跳到图论,域范畴换成循环图后,标普波动率函子该怎么配

实战向进阶 第 2/3 篇
把财经日历当扁平列表读,会漏掉事件之间的环环相扣。用图论把新闻项连成带环的图形,再映射到波动率序列,函子才装得下多对一的复杂依赖。

◍ 把财经事件折成标普波动率的函子

做这套映射时,先把财经日历域里的对象定义成成对日历值,至少有一个顶点落在协域(标普波动率)的时间窗内。数据大概隔月发布一次,所以测试放在月度周期跑,和上一篇一样,函子给对象里每个数据点挂一个系数。 麻烦点在多对一:不同日历对象可能映射到同一个波动率值。线性映射下各自算系数会互相打架,所以这里改成把每个日历到标普的函子映射做加权,把所有值的点积求和,直接落到一个波动率值上。 MQL5 策略测试器在历史回测里读不到财经日历事件,这是硬伤。只能先写脚本,基于经纪商价格数据把日历图和标普波动率范畴搭起来,打印预测变化,自己翻历史去感受准不准。 跑出来的日志有两个现象:前5个月预测全是0,大概率是价格样本不够,和很多指标冷启动一样;另外会出现负值,但我们要的是严格正值来提示下一根K线的波动率,根源在用了标准库回归(恒等矩阵解多系数)没做低波动缩放。把负值用归一化函数压掉后再跑,对象到对象的相关性从0.3爬到0.46,说明归一化有点用,但方程常数极敏感,单输入下只能靠测试器优化它才出更有意义的结果。外汇和贵金属叠加这类宏观映射时波动会被放大,属高风险玩法,参数没调顺前别直接挂实盘。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp; Get Ocean data from file defined in class="kw">input.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | </span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp; Load it into Ocean category.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> SetEconomic(<span class="predefines">class="type">MqlRates</span> &amp;R)
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">datetime</span> _time=R.time;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> _elements=<span class="number">class="num">1</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_e_retail.Let();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SampleEvents(_time,_e_retail,IntToCountry(__country),__currency,TYPE_RETAIL_SALES);<span class="comment">class=class="str">"cmt">//printf(__FUNCSIG__+"...retail. ");</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_e_cpi.Let();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> _cpi_time=<span class="class="type">class="kw">string">""</span>;_e_retail.Get(<span class="number">class="num">0</span>,_cpi_time);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SampleEvents(<span class="functions">StringToTime</span>(_cpi_time),_e_cpi,IntToCountry(__country),__currency,TYPE_CPI);<span class="comment">class=class="str">"cmt">//printf(__FUNCSIG__+"...cpi. ");</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_e_auction.Let();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> _auction_time=<span class="class="type">class="kw">string">""</span>;_e_cpi.Get(<span class="number">class="num">0</span>,_auction_time);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SampleEvents(<span class="functions">StringToTime</span>(_auction_time),_e_auction,IntToCountry(__country),__currency,TYPE_AUCTION_10YR);<span class="comment">class=class="str">"cmt">//printf(__FUNCSIG__+"...auction. ");</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_e_pmi.Let();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> _pmi_time=<span class="class="type">class="kw">string">""</span>;_e_auction.Get(<span class="number">class="num">0</span>,_pmi_time);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SampleEvents(<span class="functions">StringToTime</span>(_pmi_time),_e_pmi,IntToCountry(__country),__currency,TYPE_PMI);<span class="comment">class=class="str">"cmt">//printf(__FUNCSIG__+"...pmi. ");</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _domain_economic.Cardinality(__ECON);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _domain_economic.Set(<span class="number">class="num">0</span>,_e_retail);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _domain_economic.Set(<span class="number">class="num">1</span>,_e_cpi);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _domain_economic.Set(<span class="number">class="num">2</span>,_e_auction);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _domain_economic.Set(<span class="number">class="num">3</span>,_e_pmi);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _category_economic.SetDomain(_category_economic.Domains(),_domain_economic);
&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Get Forecast value, forecast for next change in price bar range. |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">class="kw">double</span> GetObjectsForecast(<span class="predefines">class="type">MqlRates</span> &amp;R)
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> _output=<span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//econ init</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetEconomic(R);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//sp500 init</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetSP500(R);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> _domain;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">vector</span> _codomain,_inputs;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_domain.Init(__ECON+<span class="number">class="num">1</span>,__ECON);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

「经济变量与标的价格的矩阵装配」

这段逻辑干的事是把宏观经济序列和标的价格序列塞进同一个数值矩阵,供后面的形态映射函数 M() 消费。外层循环 r 从 0 到 __ECON(经济因子个数),每次取倒数第 r+1 个经济域,再内层循环 c 遍历 __ECON 个因子,把字符串字段转成 double 写进 _domain[r][c];也就是说矩阵行数是因子数加 1,列数是因子数。 标的一侧更简单:_codomain 只初始化 __ECON 长度,循环里取标的价格域的最新一根(Domains()-r-1)第 0 列字符串,转 double 后直接赋值。这里隐含一个约束——你喂进来的经济域和标的价格域,Domains() 数量必须 ≥ __ECON+1,否则 GetDomain 会越界返回空。 预测分支 GetMorphsForecast 里 _domain[r][c] 算的是新旧两期经济字符串的差值(StringToDouble(_s_new)-StringToDouble(_s_old)),相当于把绝对水平换成边际变化。外汇和贵金属受利差与预期差驱动,用差分矩阵可能比原始值更贴近价格反应,但这类映射在高波动期失效概率明显上升,实盘前务必在 MT5 用历史域跑一遍边界。 下面把第一段装配代码逐行拆开,方便你直接抄进 EA 改 __ECON 调试:

MQL5 / C++
for(class="type">int r=class="num">0;r<__ECON+class="num">1;r++)
  {
    CDomain<class="type">class="kw">string> _d;_d.Let();
    _category_economic.GetDomain(_category_economic.Domains()-r-class="num">1,_d);
    
    for(class="type">int c=class="num">0;c<__ECON;c++)
    {
      CElement<class="type">class="kw">string> _e; _d.Get(c,_e);
      
      class="type">class="kw">string _s; _e.Get(class="num">1,_s);
      
      _domain[r][c]=StringToDouble(_s);
    }
  }
  
  _codomain.Init(__ECON);
  
  for(class="type">int r=class="num">0;r<__ECON;r++)
  {
    CDomain<class="type">class="kw">string> _d;_d.Let();
    _category_sp500.GetDomain(_category_sp500.Domains()-r-class="num">1,_d);
    
    CElement<class="type">class="kw">string> _e; _d.Get(class="num">0,_e);
    
    class="type">class="kw">string _s; _e.Get(class="num">0,_s);
      
    _codomain[r]=StringToDouble(_s);
  }
  
  _inputs.Init(__ECON);_inputs.Fill(__constant_morph);
  
  M(_domain,_codomain,_inputs,_output,class="num">1);
  
  class="kw">return(_output);

从字符串差值到预测输出的断点

这段片段在做一件事:把相邻两期字符串形态的数值差写进映射值域,再喂给形态算子 M 出预测。注意 _codomain[r] 用的是 StringToDouble(_s_new)-StringToDouble(_s_old),也就是拿新旧的文本数值做减法,而不是直接读 double 列。 CElement<string> _e_old; _d_old.Get(0,_e_old); // 从旧数据容器取第 0 个字符串元素 string _s_old; _e_old.Get(0,_s_old); // 再从该元素里取第 0 字段存成字符串 _codomain[r]=StringToDouble(_s_new)-StringToDouble(_s_old); // 新旧字符串转 double 后相减填入值域 _inputs.Init(__ECON);_inputs.Fill(__constant_morph); // 初始化输入并用常量形态填充 M(_domain,_codomain,_inputs,_output,1); // 调用形态算子,步长 1,结果写 _output return(_output); // 返回预测输出 日志暴露了真实表现:2023.07.30 在 SPX500 月线跑 OnStart,2020.01 至 2020.05 的 forecast 全是 0.00000000,而 actual 从 54260 一路变到 29694;到 2020.06 突然给出 -716790.11 的离谱值,actual 才 21905。这种前段全零、后段暴走的现象,说明映射在样本外大概率失稳。 外汇与贵金属品种换这套逻辑跑,杠杆和跳空会放大差值计算的噪声,高风险且预测可能完全失效。开 MT5 把 _codomain 的填充改成直接 double 差分,对比前后日志里 2020.06 那个 -716790 是否消失,是最快的验证路径。

MQL5 / C++
CElement<class="type">class="kw">string> _e_old; _d_old.Get(class="num">0,_e_old);
class="type">class="kw">string _s_old; _e_old.Get(class="num">0,_s_old);
_codomain[r]=StringToDouble(_s_new)-StringToDouble(_s_old);
}
_inputs.Init(__ECON);_inputs.Fill(__constant_morph);
M(_domain,_codomain,_inputs,_output,class="num">1);
class="kw">return(_output);

◍ SPX500月线对象的预测偏差实录

在 SPX500 的 MN1 图表上跑 ct_15_w 脚本,日志直接暴露了对象预测(Objects forecast)与实际值的鸿沟。2022.10.01 预测 46751.60、实际 39338.00;到了 2023.01.01 预测 -4662833.98、实际仅 25359.00,量级完全失控。 整段 9 个月预测里,只有 2022.10 和 2023.02 给出正数,其余七个月全是负值,而实际值始终在 12502~39338 区间。这种符号频繁反转说明该对象的形态映射在月线尺度上极不稳定。 脚本收尾打印了 corr = 0.36053106、morph constant = 50000.00,相关性刚过 0.36,谈不上可靠。随后 Morphs forecast 在 2020.01.01 给出 0.00,实际却是 54260.00,进一步印证变形项在该参数下基本失效。 开 MT5 把 morph constant 从 50000 往下调到 10000 附近,重跑同一段,观察 corr 是否抬升、负预测占比是否下降,比直接信这套输出更实在。

「形变预测的实测输出与误差读数」

在 SPX500 月线(MN1)上跑 ct_15_w 的 OnStart,日志直接吐出 Morphs forecast 与 actual 的对照。2020.02 至 2020.05 四个月,预测值全是 0.00000000,而实际值从 95438 一路滑到 29694,说明模型在趋势启动段完全没给出有效形变估计。 到了 2020.06,预测突然跳成 -2482719.31391738,实际仅 21905,量级差出两个数量级,这类溢出式误报在后续月份反复出现,例如 2023.02 预测 -280104.88246660、实际 30681。 临近样本尾端,2023.03 预测 24129.34336223、实际 12502;2023.04 预测 4293.73674465、实际 18835。预测符号虽转正,但绝对误差仍超实际值的 50%,用作月线级别拐点参考时概率倾向偏低,需配合价格行为结构过滤。 开 MT5 把这段日志对应的 EA 挂到 SPX500(MN1) 回放,重点看 forecast 为零和溢出的月份分布,比盯着单一预测数字更有用。

交给小布盯盘看盘口
这些把日历事件重构为图形并追踪标普波动映射的诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到,你专注判断信号强弱。

常见问题

线性序对象只能单向连,图论顶点可连多个对象,能表达零售销售—PMI—CPI—国债竞售的循环依赖,态射由边线充当。
标普按时间排布,图形多行顶点值被捆绑映射到单个波动率数据点,域对象含四个循环元素,结构不对等故非同构。
本文侧重示范图形构图方法,假设准确性非主题;实盘应扩大事件集并做样本外检验,外汇贵金属高风险,结论仅具概率倾向。
可以,小布内置了日历到波动率的图形映射模块,不用自己写重构代码,切换品种就能看函子输出。
标普自身波动序列更贴交易品种跳动节奏,VIX 为恐慌指数具滞后,函子预测潜力在近端时间序列更直观。