MQL5中的范畴论(第22部分):对移动平均的不同看法·进阶篇
「用自然变换给均线差异做滤波」
把范畴论里的自然变换搬进金融,基本是无人区,公开资料极少。主流相关性手段还是 Pearson 与 Spearman,本文只取 Spearman 作为信号算法的一环。 我们挂了三个函子 A、B、C,两两之间可定义自然变换,实际只看 AB 与 BC 两组。自然变换在这里等价于两函子目标对象的净差,所以直接跟踪各自均线的差值,写进两个独立数组,形成两条数据缓冲区。 对这两条缓冲区算互相的相关系数,就是信号核心。经验上,该系数转正,市场倾向走出趋势;转负,则更可能是震荡。 具体下单判定:把均线所有增量变化求和,和为正看多、为负看空;再用 AB、BC 缓冲区的相关系数过滤——每个柱都会出一个值。系统本质在问:已知各均线趋势后,追自然变换还能否补充收益,外汇与贵金属波动剧烈,此法仅降低噪声,不承诺胜率。
垂直组合怎么帮你看清两堆数据的关系
在范畴论里,“垂直组合”这个词不同文献可能和“水平组合”互换着用,但本文只取一种约定:它指共享同一个域范畴和共域范畴的两个函子之间的自然变换。换句话说,你拿两份结构相同的数据集,垂直组合负责把它们之间的映射关系显式抽出来。 这套东西看着抽象,落到交易上就是价格序列数据集和移动平均数据集的关系。价格 K 线是一个范畴,不同周期算出来的 MA 值是另一个范畴,两者域和共域一致,自然变换能在不破坏原有结构的前提下,把“价格→均线”的映射量化出来。 换个更好懂的例子:配料表是域范畴,菜单集合是共域范畴,函子把配料配到对应菜单。自然变换保留结构,还能进一步量出每道菜准备时间、成本差异。借助同构,你能在给定一组菜单时反推衍生成分。外汇与贵金属市场高风险,这类结构映射只提供关系视角,不预示方向。 在 MT5 里你虽不会直接写范畴论,但把 Price 数组和 iMA 缓冲区看成两个函子对象,垂直组合的思想能提醒你:多周期均线共振本质是同一域上的自然变换叠加,调参时应优先保证结构一致而非盲目加指标。
◍ 用自然变换相关系数抓价格方向
这套方法的核心,是把三组不同周期的移动平均做差,得到两条自然变换缓冲区(ab 与 bc),再用斯皮尔曼秩相关算它们之间的相关系数。相关系数大于零时,匹配的移动平均变化倾向给出同向信号;它不直接下单,而是喂给 CheckOpenLong / CheckOpenShort 里的 trend 与 direction 过滤器,后者专门用来剔除盘整噪音。 EA 首次启动必须先把缓冲区撑起来。m_natural_transformations_ab 和 bc 的长度由输入参数 m_transformations 决定,共域对象 m_o_average_a/b/c 则要调到该值加一,否则拿不到完整的变更增量,前几根 K 线会直接误判。 回测区间给了一个硬数据点:USDJPY 日线,1999.01.01–2020.01.01 跑出一份报告,接着用同样设置续测 2020.01.01–2023.08.01。正向权益曲线只说明“值得拉长周期继续验证”,绝非已证实可实盘——外汇和贵金属杠杆高,这类信号在跨样本期可能迅速衰减。 下面这段是 Init 与 GetDirection 的原文,逐行拆一下关键动作: // Init 里先 m_close.Refresh(-1) 刷最新收盘价;StartIndex() 取起始偏移 _x // m_o_prices 调大小为 m_functor_c*2,循环塞入收盘价作为函子基底 // 三个平均对象各调大小为 m_transformations+1,分别按 m_functor_a/b/c 窗口累除求 MA 并写入 // ArrayResize 两条自然变换数组为 m_transformations,初始化 0;倒序算 _a-_b 与 _b-_c 差值 // GetDirection 里 Refresh() 后调 MathCorrelationSpearman 算 ab 与 bc 的秩相关,返回 _r 把 m_functor_a/b/c 和 m_transformations 当外部输入,在 MT5 里改周期重跑这两段,能直接观察相关系数在趋势与震荡段的分布差异。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Get Direction function from Natural Transformations. |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> CSignalCT::Init(<span class="keyword">class="type">void</span>) { if(!m_init) { m_close.Refresh(-class="num">1); class="type">int _x=StartIndex(); m_o_prices.Cardinality(m_functor_c+m_functor_c); for(class="type">int i=class="num">0;i<m_functor_c+m_functor_c;i++) { m_e_price.Let();m_e_price.Cardinality(class="num">1);m_e_price.Set(class="num">0,m_close.GetData(_x+i));m_o_prices.Set(i,m_e_price); } m_o_average_a.Cardinality(m_transformations+class="num">1); m_o_average_b.Cardinality(m_transformations+class="num">1); m_o_average_c.Cardinality(m_transformations+class="num">1); for(class="type">int i=class="num">0;i<m_transformations+class="num">1;i++) { class="type">class="kw">double _a=class="num">0.0; for(class="type">int ii=i;ii<m_functor_a+i;ii++) { _a+=m_close.GetData(_x+ii); } _a/=m_functor_a; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_e_price.Set(class="num">0,_a);m_o_average_a.Set(i,m_e_price); class=class="str">"cmt">// class="type">class="kw">double _b=class="num">0.0; for(class="type">int ii=i;ii<m_functor_b+i;ii++) { _b+=m_close.GetData(_x+ii); } _b/=m_functor_b; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_e_price.Set(class="num">0,_b);m_o_average_b.Set(i,m_e_price); class=class="str">"cmt">// class="type">class="kw">double _c=class="num">0.0; for(class="type">int ii=i;ii<m_functor_c+i;ii++) { _c+=m_close.GetData(_x+ii); } _c/=m_functor_c; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_e_price.Set(class="num">0,_c);m_o_average_c.Set(i,m_e_price); } class=class="str">"cmt">// ArrayResize(m_natural_transformations_ab,m_transformations);ArrayInitialize(m_natural_transformations_ab,class="num">0.0); ArrayResize(m_natural_transformations_bc,m_transformations);ArrayInitialize(m_natural_transformations_bc,class="num">0.0); for(class="type">int i=m_transformations-class="num">1;i>=class="num">0;i--) { class="type">class="kw">double _a=class="num">0.0; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_o_average_a.Get(i,m_e_price);m_e_price.Get(class="num">0,_a); class="type">class="kw">double _b=class="num">0.0; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_o_average_b.Get(i,m_e_price);m_e_price.Get(class="num">0,_b); class="type">class="kw">double _c=class="num">0.0; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_o_average_c.Get(i,m_e_price);m_e_price.Get(class="num">0,_c); m_natural_transformations_ab[i]=_a-_b; m_natural_transformations_bc[i]=_b-_c; } m_init=true; } } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Get Direction function from Natural Transformations. |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">class="kw">double</span> CSignalCT::GetDirection() { <span class="keyword">class="type">class="kw">double</span> _r=<span class="number">class="num">0.0</span>; Refresh(); MathCorrelationSpearman(m_natural_transformations_ab,m_natural_transformations_bc,_r); <span class="keyword">class="kw">return</span>(_r); } m_ma_a.Refresh(-<span class="number">class="num">1</span>); m_ma_b.Refresh(-<span class="number">class="num">1</span>); m_ma_c.Refresh(-<span class="number">class="num">1</span>); <span class="keyword">class="type">int</span> _x=StartIndex(); <span class="keyword">class="type">class="kw">double</span> _trend= (m_ma_a.GetData(<span class="number">class="num">0</span>,_x)-m_ma_a.GetData(<span class="number">class="num">0</span>,_x+<span class="number">class="num">1</span>))+ (m_ma_b.GetData(<span class="number">class="num">0</span>,_x)-m_ma_b.GetData(<span class="number">class="num">0</span>,_x+<span class="number">class="num">1</span>))+ (m_ma_c.GetData(<span class="number">class="num">0</span>,_x)-m_ma_c.GetData(<span class="number">class="num">0</span>,_x+<span class="number">class="num">1</span>));
「把信号类塞进EA做权重试验」
MQL5向导能直接把这个信号类挂进新或已有的EA里,当成你平时用或想试的其他信号之一。典型输入参数形如 Signal_XXX_Weight(XXX是信号类名),取值通常在0.0到1.0之间,可以放在全部信号里做优化,找出每个信号的合适权重。 评估这类系统得按你自己的需求来,本质是增长和保全资本的拉锯。粗看的话,AHPR更像增长的参考,恢复系数偏向安全,但这只是很糙的估计,真要落地你还得自己多下功夫。 每改一次策略,都得回头跑回测和样本外验证,确认没动到原前提。没有一成不变的改良办法,只能在每次迭代里不断查前后向表现。 另一个思路是始终留一个基于该模型的跟踪类实例,只做少量改动:把均线应用价格从收盘价换成典型价甚至中间价,预测方向仍看两个自然变换缓冲区的相关系数,但把柱形区间的当前变化当“趋势”。区间收窄需要正相关(大于零)撑着,区间扩张也一样;相关性为零或负,说明模型认为这波波动变化可忽略。
把自然变换落到交易系统里
垂直组合里的自然变换,表面看像纯数学闲篇,但用常见指标验证就能看出门道。以移动平均线为例,不同周期 MA 在价格转折处的同步偏离,往往先于传统交叉信号出现确认倾向,这属于可复现的价格现象。 真正难点在橡胶上路:要把这种读信号的方式写成稳健 EA,得自己补不少工程活。MT5 公开代码库里能找到若干相关示例,足以让有耐心的交易者拆开看透逻辑。 外汇与贵金属杠杆高、滑点突发行情多,这类信号只在特定波动结构下概率占优,别当通用解。建议顺着预测逻辑往估值、风险模块探,能反过来校准你对本文信号的信任区间。
◍ 把工具请下神坛
这套范畴论信号类文件(ct_22.mqh 约 29.34 KB、TrailingCT_22.mqh 约 13.97 KB)本质只是向导组装的零件,类文件丢进 include、跟踪文件丢进 include\Expert\Trailing 就能跑,不熟向导的照着官方那篇建 EA 的文章走一遍即可。 它把数学上的自然变换塞进了 DEMA 这类常见指标里,思路新但绝非点石成金;外汇与贵金属杠杆高、滑点跳空频发,直接挂实盘等于拿概率游戏赌流动性。 真要验成色,开 MT5 把两个文件按路径放好,用向导拼个 EA 回测 2023 年 XAUUSD 的 M15,看净值曲线弯不弯得过来,比盯任何‘理论优雅度’都实在。