MQL5中的范畴论(第23部分):对双重指数移动平均的不同看法·进阶篇
用两个均线对象的差捕获自然变换
在范畴-3里总共4个对象只取其中2个时,两个函子对的总自然变换数从理论上的6降为实际可用的2。本质上自然变换就是两个共域对象之差,落到均线上就是直接相减两条移动平均对象,得到一条可存入时间序列缓冲区的差序列。 上一篇文章把这些自然变换缓冲区之间的相关性当白噪声滤波器用;本篇只盯跟踪止损调整,所以相关性重新变成方向滤波器。缓冲区间呈负相关且趋势为负,等于给空头仓位下移止损开了绿灯;相关性为正且超过零阈值(本文取零以上任意值),则提示把多头止损往上推。 实操里大量时刻会“无信号”:要么相关性为负不触发,要么当前持仓类型与信号方向不兼容——因为止损移动本来就是按具体仓位判定的。该模型可重构为专家信号类实例,经 MT5 向导塞进 EA 里做进一步回测,验证相关阈值对止损调整的命中率。
◍ 用价格区间替代收盘价测波动
预测波动性不一定要盯收盘价。把每一根 K 线的范畴1改成「高点减低点」的价格区间序列,波动的轮廓会比单纯用收盘价更贴着行情走。 具体落地时,第2范畴放这组区间的均值,第3范畴套 DEMA 平滑,写法和前文对收盘价的处理完全一致。这样产出的波动曲线更协调,对突变的敏感程度也更高。 外汇与贵金属杠杆高、跳空频繁,区间法对影线刺穿反应快,但也可能在流动性稀薄时放大噪声,实盘前请在 MT5 用历史数据跑一遍核对。
「在MT5向导里拼装自然变换跟踪类」
把上篇的跟踪类搬进MT5,流程和本系列前几篇高度一致:在MQL5向导里把专家跟踪类实例挂到EA框架下,且必须先在向导中选好专家信号类实例。本文直接复用IDE信号库里内置的动量振荡指标信号类,不另写函子代码。 移动平均需求交给CiMA与CiDEMA内置类处理,但我们仍用CObjects<double>实例承载四个均值缓冲:m_o_average_a到m_o_average_d。声明片段如下,四个对象分别对应范畴2与范畴3的均值容器。 刷新逻辑比初始化略重。先刷收盘价指针作为范畴1基底,把对象内已有元素整体前移后再写入最新值;范畴2的两个对象同理,只不过要先算对应周期MA再移位赋值。范畴3挂了4个对象,预测只取其中2个,值来源于刚填好的对象,并按‘MA的MA’思路做轻改版DEMA。 EA与EURUSD日线数据集成后,回溯区间取2020.01.01–2023.01.01。报告里值得盯的是MAE-利润相关性与持仓期-利润相关性,这两张图在完整报告最底部,能暗示模型对回撤的容忍度。外汇与贵金属属高风险品种,上述相关倾向不代表未来收益。
CObjects<class="type">class="kw">double> m_o_average_a; CObjects<class="type">class="kw">double> m_o_average_b; CObjects<class="type">class="kw">double> m_o_average_c; CObjects<class="type">class="kw">double> m_o_average_d; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Refresh function from Natural Transformations. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTrailingCT::Refresh(class="type">void) { if(!m_init) { Init(); } else { m_close.Refresh(-class="num">1); class="type">int _x=StartIndex(); for(class="type">int i=m_functor_ab+m_functor_ab-class="num">1;i>class="num">0;i--) { m_e_price.Let();m_e_price.Cardinality(class="num">1);m_o_prices.Get(i,m_e_price);m_o_prices.Set(i-class="num">1,m_e_price); } class="type">class="kw">double _p=m_close.GetData(_x); m_e_price.Let();m_e_price.Cardinality(class="num">1);m_e_price.Set(class="num">0,_p);m_o_prices.Set(class="num">0,m_e_price); 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_ab+i;ii++) { _a+=m_close.GetData(_x+ii); } _a/=m_functor_ab; 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_cd+i;ii++) { m_e_price.Let();m_e_price.Cardinality(class="num">1);m_o_average_a.Get(i,m_e_price); class="type">class="kw">double _b_i=class="num">0.0;m_e_price.Set(class="num">0,_b_i); _b+=_b_i; } _b/=m_functor_cd;
多层均值差分在MT5里的落地写法
这段逻辑把价格做了四层平滑:先取原始值 _b 存入 average_b,再对 close 做长度为 m_functor_ab 的滑动平均得到 _c,接着对 average_c 再做一遍长度为 m_functor_cd 的滑动平均得到 _d。注意第二层循环里 _d_i 被强行置 0 再累加,所以 _d 实际恒为 0,m_o_average_d 存的是空信号,这点直接在 MT5 里跑就能验证。 最后把历史变换数组整体后移一位,腾出 [0] 放最新差值:ac 通道写 _a-_c,bd 通道写 _b-_d。若 m_functor_ab 设为 5、m_functor_cd 设为 3,那么最新柱的 ac 差反映的是近 5 根收盘价均线与首值偏移的纠缠程度,外汇与贵金属品种在此类差分下波动会被放大,属高风险信号。 想复现就复制下面代码,把 m_functor_ab、m_functor_cd 调到和你品种周期匹配的数,开 MT5 用专家日志打印 m_natural_transformations_ac[0] 看发散速度。
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_ab+i;ii++) { _c+=m_close.GetData(_x+ii); } _c/=m_functor_ab; 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">// class="type">class="kw">double _d=class="num">0.0; for(class="type">int ii=i;ii<m_functor_cd+i;ii++) { m_e_price.Let();m_e_price.Cardinality(class="num">1);m_o_average_c.Get(i,m_e_price); class="type">class="kw">double _d_i=class="num">0.0;m_e_price.Set(class="num">0,_d_i); _d+=_d_i; } _d/=m_functor_cd; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_e_price.Set(class="num">0,_d);m_o_average_d.Set(i,m_e_price); } for(class="type">int i=m_transformations-class="num">1;i>class="num">0;i--) { m_natural_transformations_ac[i]=m_natural_transformations_ac[i-class="num">1]; m_natural_transformations_bd[i]=m_natural_transformations_bd[i-class="num">1]; } class=class="str">"cmt">// 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(class="num">0,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(class="num">0,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(class="num">0,m_e_price);m_e_price.Get(class="num">0,_c); class="type">class="kw">double _d=class="num">0.0; m_e_price.Let();m_e_price.Cardinality(class="num">1);m_o_average_d.Get(class="num">0,m_e_price);m_e_price.Get(class="num">0,_d); m_natural_transformations_ac[class="num">0]=_a-_c; m_natural_transformations_bd[class="num">0]=_b-_d; } }