MQL5中的范畴论(第18部分):自然性四边形·进阶篇
(2/3)· 当ATR与布林带变化被放进同一个态射网格,预测波动不再靠肉眼比对
「用两个列表函子看清自然变换的交换性」
把范畴论里的自然变换先放下抽象定义,直接用一组具体对象跑一遍更容易抓感觉。设范畴 C 有两个对象 X={5,6,7} 与 Y={Q,R,S},态射 f: X→Y 规定 f(5)=S、f(6)=R、f(7)=R。 取两个函子 F、G 把 C 映到 D:F 把对象变成平铺列表,F(X)=[5,6,5,7,5,6,7,7];G 把对象变成列表的列表,G(X)=[[5,6],[5,7,5,6,7],[7]]。对 Y 同样作用会得到 D 中另外两组对象,这样共域里一共有四个对象可看。 自然变换 η 对每个 C 中对象给一个箭头 η_X: F(X)→G(X) 与 η_Y: F(Y)→G(Y)。把它们和函子作用在 f 上得到的两条竖箭头摆在一起,就构成自然性四边形;箭头能 commute 表示无论先走 η 再走函子,还是先走 f 再走 η,结果一致。 这背后关键不是符号游戏:NT 强制保存了结构与关系。比如两位厨师 A、B 用同套食材做同一道菜,A 的菜到 B 的菜之间的 NT 不只记录差异,还保留 A 的食谱与手法;据此还能查厨师 C 的菜离 B 差多少变换,或按饮食限制反推现有配方要改哪一步。外汇与贵金属市场里这类“结构保持”思维可用来校验多周期信号映射是否一致,但市场高风险,映射一致只代表数学关系成立,不预示方向。
◍ 用自然变换框住波动预测
把价格柱区间的下一步变化预测出来,最直接的用途是判断持仓的跟踪止损要不要动、动多少。外汇与贵金属杠杆高、跳空频繁,这类预测只作概率参考,不能当成确定性信号。 具体做法是把多层感知器(MLP)架在「平方换相」结构上:正方形的四个角 A、B、C、D 各自对应未来不同时点的区间变化预测。A 角只投射下一根价格柱的区间变动,越往 D 角走,视野越往前推;若用近期区间变化去训练连通四角的 MLP,就有机会在单根柱之外给出更远的推演。 下面的 CTransformation 类就是自然变换的载体,它不存两个函子实例,只抓函子映射到的两个域(domain 与 codomain),再挂一个 MLP 做变换。开 MT5 把这段代码塞进 EA,改 hidden_size 就能试探网络容量对预测偏移的影响。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| NATURAL TRANSFORMATION CLASS | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTransformation { class="kw">protected: class="kw">public: CDomain<class="type">class="kw">string> domain; class=class="str">"cmt">//codomain object of first functor CDomain<class="type">class="kw">string> codomain; class=class="str">"cmt">//codomain object of second functor class="type">uint hidden_size; CMultilayerPerceptron transformer; CMLPBase init; class="type">void Transform(CDomain<class="type">class="kw">string> &D,CDomain<class="type">class="kw">string> &C) { domain=D; codomain=C; class="type">int _inputs=D.Cardinality(),_outputs=C.Cardinality(); if(_inputs>class="num">0 && _outputs>class="num">0) { init.MLPCreate1(_inputs,hidden_size+fmax(_inputs,_outputs),_outputs,transformer); } } class=class="str">"cmt">// class="type">void Let()
用自然性方块把变换串成闭环
上面这段类定义把范畴论里的自然性方块(naturality square)落到了 MT5 的 C++ 风格代码里。核心思路是:四条对象 A、B、C、D 用 CDomain<string> 承载,上下两条边 AB、CD 是 CTransformation,左右两条边 BD、AC 则交给 CMultilayerPerceptron 去拟合,hidden_size_bd 与 hidden_size_ac 分别控制两边隐藏层规模。 CTransformation 的构造函数里写死了 hidden_size=1,说明这条边本身不做复杂映射,只是把 domain 和 codomain 的 Let() 依次释放,相当于把输入输出域的占用交还系统。 实际在 MT5 里验证时,你可以把 CNaturalitySquare 的 AB、CD 换成自己的指标映射,再让 BD、AC 两个感知机跑不同品种——外汇与贵金属杠杆高、跳空频繁,两边网络权重发散的概率会明显大于股票。开 MT5 新建 EA 把这段粘进去编译,先确认 hidden_size=1 的变换不会在策略 tester 里报空指针,再决定要不要扩 hidden_size。
{
this.codomain.Let();
this.domain.Let();
};
CTransformation(class="type">void){ hidden_size=class="num">1; };
~CTransformation(class="type">void){};
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| NATURALITY CLASS |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CNaturalitySquare
{
class="kw">protected:
class="kw">public:
CDomain<class="type">class="kw">string> A,B,C,D;
CTransformation AB;
class="type">uint hidden_size_bd;
CMultilayerPerceptron BD;
class="type">uint hidden_size_ac;
CMultilayerPerceptron AC;
CTransformation CD;
CMLPBase init;
CNaturalitySquare(class="type">void){};
~CNaturalitySquare(class="type">void){};
};「MT5里怎么跑这套临时训练」
把MLP塞进MT5,最棘手的不是前向推理,而是训练和权重落盘的方式。本篇和前两轮不同:每个新K线到来时,四个MLP全部重新实例化并用随机权重初始化,不读取任何历史训练结果,也不把训好的权重写回文件。 驱动这件事的是下面这段Refresh函数。它只在最近一根价格柱上,对随机初始化的网络做一轮训练。对要拿去实盘或共享代码的系统来说这显然不够稳,但作为验证「自然性方块」假设的测试手段,够用。 输入参数 m_extra_training 默认是0,也就是只训当前柱。把它往上调,预测前会多跑几轮样本外训练,EA负载会明显上升,但也更逼近真实泛化表现。这正好解释了为什么本文刻意避开了权重的读写——额外训练一开,存盘反而成了累赘。 外汇与贵金属市场高杠杆、高波动,这类临训结构在切换品种或周期时可能失效,请在策略测试器里先跑样本内再上模拟盘。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Refresh function for naturality square. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CTrailingCT::Refresh() { class="type">class="kw">double _refresh=class="num">0.0; m_high.Refresh(-class="num">1); m_low.Refresh(-class="num">1); class="type">int _x=StartIndex(); class=class="str">"cmt">// atr domains capture class="num">1 bar ranges class=class="str">"cmt">// bands&class="macro">#x27; domains capture class="num">2 bar ranges class=class="str">"cmt">// class="num">1 functors capture ranges after class="num">1 bar class=class="str">"cmt">// class="num">2 functors capture ranges after class="num">2 bars class="type">int _info_ab=class="num">0,_info_bd=class="num">0,_info_ac=class="num">0,_info_cd=class="num">0; CMLPReport _report_ab,_report_bd,_report_ac,_report_cd; CMatrixDouble _xy_ab;_xy_ab.Resize(m_extra_training+class="num">1,class="num">1+class="num">1); CMatrixDouble _xy_bd;_xy_bd.Resize(m_extra_training+class="num">1,class="num">1+class="num">1); CMatrixDouble _xy_ac;_xy_ac.Resize(m_extra_training+class="num">1,class="num">1+class="num">1); CMatrixDouble _xy_cd;_xy_cd.Resize(m_extra_training+class="num">1,class="num">1+class="num">1); CElement<class="type">class="kw">string> _e; for(class="type">int i=class="num">0;i<m_extra_training+class="num">1;i++) { ... if(i<m_extra_training+class="num">1) { _xy_ab[i].Set(class="num">0,_a);class=class="str">"cmt">//in _xy_ab[i].Set(class="num">1,_b);class=class="str">"cmt">//out _xy_bd[i].Set(class="num">0,_b);class=class="str">"cmt">//in _xy_bd[i].Set(class="num">1,_d);class=class="str">"cmt">//out _xy_ac[i].Set(class="num">0,_a);class=class="str">"cmt">//in _xy_ac[i].Set(class="num">1,_c);class=class="str">"cmt">//out _xy_cd[i].Set(class="num">0,_c);class=class="str">"cmt">//in _xy_cd[i].Set(class="num">1,_d);class=class="str">"cmt">//out } } m_train.MLPTrainLM(m_naturality_square.AB.transformer,_xy_ab,m_extra_training+class="num">1,m_decay,m_restarts,_info_ab,_report_ab); ... class=class="str">"cmt">// if(_info_ab>class="num">0 && _info_bd>class="num">0 && _info_ac>class="num">0 && _info_cd>class="num">0) { ... } class="kw">return(_refresh); }