MQL5 中的范畴论 (第 2 部分)·进阶篇
「投影器数组的边界守卫与存取实现」
在 MT5 自定义类里管理投影器(projector)动态数组时,先卡一道非负且低于 INT_MAX 的边界,能避免外部传入脏值把 ArrayResize 撑爆。下面这段Setter只在 0≤Value<INT_MAX 时把 projectors 更新并重新分配数组,返回 true,否则直接返回 false。 Projectors() 仅做取值器用,返回当前投影器数量;Get() 按索引取字符串,越界就回空串;Set() 按索引写字符串,越界返回 false。这种写法把越界风险关在类内部,调用方拿不到异常引用。 构造函数把 projectors 置 0 并 ArrayFree 清数组,析构留空。外汇与贵金属行情跳变频繁,这类容器若被复用,务必确认清零逻辑,否则可能残留上一轮品种的配置。
if(Value>=class="num">0 && Value<INT_MAX) { projectors=Value; ArrayResize(projector,projectors); class="kw">return(true); } class="kw">return(false); }; class="type">int Projectors(){ class="kw">return(projectors); }; class="kw">public: class="type">class="kw">string Get(class="type">int ProjectorIndex) { class="type">class="kw">string _projector=""; if(ProjectorIndex>=class="num">0 && ProjectorIndex<Projectors()) { _projector=projector[ProjectorIndex]; } class="kw">return(_projector); } class="type">bool Set(class="type">int ValueIndex,class="type">class="kw">string Value) { if(ValueIndex>=class="num">0 && ValueIndex<Projectors()) { projector[ValueIndex]=Value; class="kw">return(true); } class="kw">return(false); } CDomain<class="type">class="kw">string> class="kw">property; CDomain<class="type">class="kw">string> cone; CComposition(class="type">void){ projectors=class="num">0;ArrayFree(projector); }; ~CComposition(class="type">void){};
用类封装把投影器扩展成字符串域
在组合类之上再派生一层 CProduct,本质是把原本数值型的投影器数量,映射成一组字符串域对象,方便后续按名索骥地挂接不同品种或周期的逻辑。 下面这段实现里,Surjectors(int Value) 先做边界判断:Value 必须落在 0 到 INT_MAX 之间,否则直接返回 false。通过后则调用父类 CComposition::Projectors(Value) 同步投影器计数,并用 ArrayResize 把 surjector 数组成员扩到同样长度。 读成员时用重载的无参 Surjectors() 返回父类里的 projectors 字段;取具体某条字符串域则走 Get(),索引越界就返回 false。外汇与贵金属市场波动剧烈、杠杆风险高,这类结构仅用于理清多标的组合关系,不代表任何方向判断。
class CProduct : class="kw">public CComposition { class="kw">protected: CDomain<class="type">class="kw">string> surjector[]; class="kw">public: class="type">bool Surjectors(class="type">int Value) { if(Value>=class="num">0 && Value<INT_MAX) { CComposition::Projectors(Value); ArrayResize(surjector,Value); class="kw">return(true); } class="kw">return(false); }; class="type">int Surjectors(){ class="kw">return(CComposition::projectors); }; class="type">bool Get(class="type">int SurjectorIndex,CDomain<class="type">class="kw">string> &Surjector) { if(SurjectorIndex>=class="num">0 && SurjectorIndex<CComposition::Projectors()) { Surjector=surjector[SurjectorIndex]; class="kw">return(true); } class="kw">return(false); }
◍ 多指标域拼装与笛卡尔积落地
这段实现把六个振荡类指标拆进三个双维值域(domain A/B/C),再用笛卡尔积合成复合产品。domain A 装 RSI 与 CCI,domain B 装 DeMarker 与 WPR,domain C 装 Stochastic(8,4,4) 与 TriX,全部共用 __product_size 作为取样长度。 CProduct 的 Set 方法只做边界检查:ValueIndex 落在 [0, Projectors()) 才把引用写入 surjector 数组并返回 true,越界直接返回 false,避免越界写坏内存。析构里 ArrayFree(surjector) 释放投影数组,句柄类资源交给 MT5 终端回收。 指标句柄创建时 Stochastic 用了固定参数 8/4/4 + MODE_SMA + STO_LOWHIGH,其余四个都吃 __product_size 和 __product_price 宏。FillDomain 连续短路与运算,任一指标取数失败整段跳过,不打印也不合成。 合成顺序先 GetProduct(_d_p_a,_d_p_b,_product,2) 打出 A&B 的二维锥,再把结果与 _d_p_c 做第二次 GetProduct(_product.cone,_d_p_c,_product,5),维度参数 2 与 5 决定投影切片粒度。开 MT5 把 __product_size 设成 30 跑一遍,终端大概率是三行 domain 打印加两行 product 打印,外汇与贵金属波动下这套域合成的高维噪声会明显放大,属高风险验证。
class="type">bool Set(class="type">int ValueIndex,CDomain<class="type">class="kw">string> &Value) { if(ValueIndex>=class="num">0 && ValueIndex<CComposition::Projectors()) { surjector[ValueIndex]=Value; class="kw">return(true); } class="kw">return(false); } CProduct(class="type">void){ ArrayFree(surjector); }; ~CProduct(class="type">void){}; class=class="str">"cmt">////////// class=class="str">"cmt">//PRODUCTS class=class="str">"cmt">////////// CDomain<class="type">class="kw">double> _d_p_a,_d_p_b,_d_p_c; _d_p_a.Cardinality(__product_size);_d_p_b.Cardinality(__product_size);_d_p_c.Cardinality(__product_size); class="type">int _rsi_handle=iRSI(_Symbol,_Period,__product_size,__product_price); class="type">int _cci_handle=iCCI(_Symbol,_Period,__product_size,__product_price); class="type">int _dmk_handle=iDeMarker(_Symbol,_Period,__product_size); class="type">int _wpr_handle=iWPR(_Symbol,_Period,__product_size); class="type">int _stc_handle=iStochastic(_Symbol,_Period,class="num">8,class="num">4,class="num">4,MODE_SMA,STO_LOWHIGH); class="type">int _trx_handle=iTriX(_Symbol,_Period,__product_size,__product_price); if ( FillDomain(_d_p_a,class="num">0,__product_size,_rsi_handle) && FillDomain(_d_p_a,class="num">1,__product_size,_cci_handle) && FillDomain(_d_p_b,class="num">0,__product_size,_dmk_handle) && FillDomain(_d_p_b,class="num">1,__product_size,_wpr_handle) && FillDomain(_d_p_c,class="num">0,__product_size,_stc_handle) && FillDomain(_d_p_c,class="num">1,__product_size,_trx_handle) ) { printf(__FUNCSIG__+" domain A: "+PrintDomain(_d_p_a,class="num">2)); printf(__FUNCSIG__+" domain B: "+PrintDomain(_d_p_b,class="num">2)); printf(__FUNCSIG__+" domain C: "+PrintDomain(_d_p_c,class="num">5)); CProduct _product; GetProduct(_d_p_a,_d_p_b,_product,class="num">2); printf(__FUNCSIG__+" A & B product: "+PrintDomain(_product.cone,class="num">2)); GetProduct(_product.cone,_d_p_c,_product,class="num">5);
「三域笛卡尔积在 USDCHF 周线上的实测输出」
把 A、B、C 三个区间域做交集扩展,核心就是笛卡尔积。上面这段日志来自 MT5 脚本对 USDCHF.ln 周线(W1)的跑数,时间锚点 2023.02.17 17:31:33,单帧内完成全部打印。 A 域返回 {(-66.67),(66.67)},B 域返回 {(-61.99),(-68.45)},C 域是微观波动区间 {(-0.00996),(-0.00628)}。A 与 B 先乘出 4 个二维组合,再与 C 乘出 8 个三维组合,日志里 A & B & C product 列出了完整 8 元组。 这种展开在外汇周线域上意义直接:把方向区间(A)、动量区间(B)、价格微扰(C)绑成一个状态空间,后续过滤只在这 8 个态里挑。贵金属与外汇杠杆品种这么玩波动会被放大,属高风险验证,参数域边界请自己回测确认。 开 MT5 把 PrintDomain 换成你自己的域函数,改 _product.cone 精度参数(示例里是 5),能立刻看到不同截断下的组合数量变化,可能显著影响后续信号稀疏度。
printf(__FUNCSIG__+" A & B & C product: "+PrintDomain(_product.cone,class="num">5)); }
用范畴域抽象金融产品与风险映射
把交易所衍生品看成「范畴里的域」,靠通用属性而非具体合约条款去定义,能直接剥离监管方、期权/期货类别等标识。这类标识不是摆设——识别清算场所与合约形态,便于估算合规成本与交易对手风险敞口,外汇与贵金属杠杆品种的高风险正藏在这些隐性成本里。 下面这段脚本把证券、交易所、期权周期、行权宽度各建为独立域:证券域基数 2(EURUSD、USDJPY),交易所域基数 7(CBOE 到 LSE),期权周期域 3 种(JAJO/FMAN/MJSD),行权宽度域 2 档(1000/1250 点)。跑起来后打印各域内容,本质是在锥域与属性域间构造同态,标记「普遍性」成立。 通用属性还能压住 CDO 这类多层结构的复杂度:把每层标的资产定义为域,再用属性捆出结构,联合风险比逐层硬算更抽象也更通用。换个角度,判定想要的通用属性去反推新产品,比从零拼合约更容易贴近市场需求。再跑一次脚本,同态会把风险水平(例中以 ATR 值指代)映射进由多证券组成的投资组合锥域。
class=class="str">"cmt">////////////////////////////// class=class="str">"cmt">//PRODUCT UNIVERSAL-PROPERTY class=class="str">"cmt">////////////////////////////// CDomain<class="type">class="kw">string> _d_security,_d_exchanges,_d_optioncycle,_d_strikewidth,_d_property; class=class="str">"cmt">// CElement<class="type">class="kw">string> _e;_e.Cardinality(class="num">1); class=class="str">"cmt">// _d_security.Cardinality(class="num">2); _e.Set(class="num">0,"EURUSD");_d_security.Set(class="num">0,_e,true); _e.Set(class="num">0,"USDJPY");_d_security.Set(class="num">1,_e,true); class=class="str">"cmt">// _d_exchanges.Cardinality(class="num">7); _e.Set(class="num">0,"Chicago Board Options Exchange(CBOE)");_d_exchanges.Set(class="num">0,_e,true); _e.Set(class="num">0,"Shanghai Stock Exchange(SSE)");_d_exchanges.Set(class="num">1,_e,true); _e.Set(class="num">0,"Shenzhen Stock Exchange(SZSE)");_d_exchanges.Set(class="num">2,_e,true); _e.Set(class="num">0,"Tokyo Stock Exchange(TSE)");_d_exchanges.Set(class="num">3,_e,true); _e.Set(class="num">0,"Osaka Exchange(OSE)");_d_exchanges.Set(class="num">4,_e,true); _e.Set(class="num">0,"Eurex Exchange");_d_exchanges.Set(class="num">5,_e,true); _e.Set(class="num">0,"London Stock Exchange(LSE)");_d_exchanges.Set(class="num">6,_e,true); class=class="str">"cmt">// _d_optioncycle.Cardinality(class="num">3); _e.Set(class="num">0,"JAJO - January, April, July, and October");_d_optioncycle.Set(class="num">0,_e,true); _e.Set(class="num">0,"FMAN - February, May, August, and November");_d_optioncycle.Set(class="num">1,_e,true); _e.Set(class="num">0,"MJSD - March, June, September, and December");_d_optioncycle.Set(class="num">2,_e,true); class=class="str">"cmt">// _d_strikewidth.Cardinality(class="num">2); _e.Set(class="num">0,"class="num">1000 points");_d_strikewidth.Set(class="num">0,_e,true); _e.Set(class="num">0,"class="num">1250 points");_d_strikewidth.Set(class="num">1,_e,true); class=class="str">"cmt">// printf(__FUNCSIG__+" securities domain: "+PrintDomain(_d_security,class="num">0)); printf(__FUNCSIG__+" exchanges domain: "+PrintDomain(_d_exchanges,class="num">0)); printf(__FUNCSIG__+" option cycle domain: "+PrintDomain(_d_optioncycle,class="num">0)); printf(__FUNCSIG__+" strike width domain: "+PrintDomain(_d_strikewidth,class="num">0)); CProduct _product_1;
◍ 把监管属性织进产品锥的映射
前面几步把证券、交易所、期权周期、行权宽度分别做成 cone 域并交叠出 _product_all,这一步要给最终产品挂上监管维度。_d_property 先设基数为 5,再依次塞入 CFTC、CSRC、FSA、BaFin、FCA 五个监管机构字符串,等于给域对象定义了 5 个离散元素。 _product_all.property 指向这块监管域,universality 的 domain/codomain 分别接 property 与 cone,相当于声明「监管方 → 具体品种」的满射框架;CMorphism<string,string> _mm 只是把这套 domain/codomain 再显式绑一遍,方便后续做关系遍历。 真正落地的逻辑在双重 for 循环:外层扫 5 个监管元素,内层扫 cone 里每个品种字符串,当监管等于 CFTC 且 cone 元素里包含 'Chicago Board Options Exchange (CBOE)' 时进入分支——这里用 StringFind(...)>=0 做子串判定,说明 CBOE 品种在模型里被显式归到 CFTC 管辖下。外汇与贵金属跨市场品种若套这套结构,监管归属错配可能导致回测域污染,实盘映射前建议在 MT5 里先 printf 出 _product_all 的 cardinality 核对。 把这段代码直接丢进 EA 的 OnInit 尾部跑一遍,终端里能看到 securities & exchanges & optioncycle & strikewidth product 的 cone 打印,顺带验证 CBOE 是否如预期挂在 CFTC 下;改 _d_property 的 5 个字符串就能切换你关心的监管口径。
GetProduct(_d_security,_d_exchanges,_product_1,class="num">0); printf(__FUNCSIG__+" securities & exchanges product: "+PrintDomain(_product_1.cone,class="num">0)); CProduct _product_2; GetProduct(_d_optioncycle,_d_strikewidth,_product_2,class="num">0); printf(__FUNCSIG__+" securities & exchanges & optioncycle product: "+PrintDomain(_product_2.cone,class="num">0)); CProduct _product_all; GetProduct(_product_1.cone,_product_2.cone,_product_all,class="num">0); printf(__FUNCSIG__+" securities & exchanges & optioncycle & strikewidth product: "+PrintDomain(_product_all.cone,class="num">0)); _d_property.Cardinality(class="num">5); _e.Set(class="num">0,"Commodity Futures Trading Commission(CFTC)");_d_property.Set(class="num">0,_e,true); _e.Set(class="num">0,"China Securities Regulatory Commission(CSRC)");_d_property.Set(class="num">1,_e,true); _e.Set(class="num">0,"Financial Services Agency(FSA)");_d_property.Set(class="num">2,_e,true); _e.Set(class="num">0,"Federal Financial Supervisory Authority(BaFin)");_d_property.Set(class="num">3,_e,true); _e.Set(class="num">0,"Financial Conduct Authority(FCA)");_d_property.Set(class="num">4,_e,true); class=class="str">"cmt">// _product_all.class="kw">property=_d_property; class=class="str">"cmt">// _product_all.universality.domain=_product_all.class="kw">property; _product_all.universality.codomain=_product_all.cone; class=class="str">"cmt">// CMorphism<class="type">class="kw">string,class="type">class="kw">string> _mm; _mm.domain=_product_all.class="kw">property; _mm.codomain=_product_all.cone; class=class="str">"cmt">// for(class="type">int c=class="num">0;c<_product_all.class="kw">property.Cardinality();c++) { CElement<class="type">class="kw">string> _e_property;_e_property.Cardinality(class="num">1); if(_product_all.class="kw">property.Get(c,_e_property) && _e_property.Get(class="num">0)!="") { for(class="type">int cc=class="num">0;cc<_product_all.cone.Cardinality();cc++) { CElement<class="type">class="kw">string> _e_cone;_e_cone.Cardinality(class="num">1); if(_product_all.cone.Get(cc,_e_cone) && _e_cone.Get(class="num">0)!="") { if(_e_property.Get(class="num">0)=="Commodity Futures Trading Commission(CFTC)") { if(StringFind(_e_cone.Get(class="num">0),"Chicago Board Options Exchange(CBOE)")>=class="num">0) {