不使用额外的缓冲区,为中间计算进行系列价格的平均化·进阶篇
◍ 类封装与 iMA 调用的实测代差
把 MAx4.mq5(纯类封装)和 iMAx4.mq5(全用 iMA())放到同一基准比对,能直接看出代码路线的开销差异。为了排除极端情况,又补了 MAx3x1.mq5(一条线走 iMA、三条线走 CMoving_Average 类)和基于 Custom Moving Average.mq5 改的 cMAx4.mq5。 四个测试 EA(MAx4_Test / iMAx4_Test / MAx3x1_Test / cMAx4_Test)在 EURUSD H4 上跑过去 12 个月,每次价格跳动建模,EA 的“周期”参数统一设 500。 结果里最差的是调用自定义指标的那版 cMAx4_Test,这种写法只建议图省事的人用。基于 iMA() 调用的 iMAx4_Test 好一些,但仍不算理想。 真正领跑的是用类写的 MAx4_Test;类和技术指标混用的 MAx3x1_Test 排第二,不过这不是绝对顺序——若某个指标对测试耗时敏感,得就具体变量自己跑一遍验证。外汇与贵金属测试请留意点差和滑点带来的高风险。
「十一类平滑算法的封装与取舍」
上面这张表把 11 种平均线类算法的实现位置和关键特性列清楚了:CMoving_Average 在 MASeries_Cls.mqh 里塞了 SMA/EMA/SMMA/LWMA 五种经典算法,而 CCMO 同时含平均线与动量逻辑,其余类基本是单算法文件。 平移量(可靠柱起始偏移)差异很大——经典 SMA 是 Length/0,JMA 固定 30,考夫曼 AMA 是 Length+3;只有 JMA、T3、超线性 JurX 这三类支持 Length 参数动态变动,其余都是静态长度。 除抛物线平均线 ParMA 外,所有平均线算法代码都按最大执行速度优化过;ParMA 因过程复杂未做优化,实盘跑高频循环时可能略微吃 CPU。最后三个(变速、变速标准化、变动率)根本不是平均线,只是借这套类体系封装,因其普及度高且和主流技术分析著作兼容才收进来。 实际写指标时,这些类全被收进 SmoothAlgorithms.mqh 单文件,并补了 iPriceSeries.mqh 的函数。示例里只用到了 PriceSeries(),它按 1~11 的数字返回对应价格序列值,本质是用自定义变量拓展了 ENUM_APPLIED_PRICE,适配 OnCalculate() 第二种调用类型。外汇与贵金属市场杠杆高、滑点突变频繁,直接套用未优化 ParMA 在 M1 周期可能掉帧,建议先开 MT5 用策略测试器跑一遍延迟。
class="type">class="kw">double PriceSeries( class="type">uint applied_price, class=class="str">"cmt">// 价格常数 class="type">uint bar, class=class="str">"cmt">// 指定周期数向前或者向后转换的索引 class=class="str">"cmt">// 相对当前柱. class="kw">const class="type">class="kw">double& Open [], class="kw">const class="type">class="kw">double& Low [], class="kw">const class="type">class="kw">double& High [], class="kw">const class="type">class="kw">double& Close[] )
用类封装把 CCI 类指标改写成 OnCalculate 实战
把指标逻辑塞进类里之后,OnCalculate 会变得极薄。前面用 CJJMA 和 CJurX 两个类,在 CCI 的变体里直接调 JJMASeries 和 JurXSeries,就能把原本几十行的计算压成循环里几行调用。 注意一个坑:类的静态变量语义和函数内静态完全不同,所以 Jur1、Jur2、JMA 这些类实例必须在全局范围声明,不能丢进 VidyaBandsSeries() 里当静态局部变量,否则编译虽过、运行逻辑会歪。 再举一个能直接抄的思路:用 CCMO 取 VIDYA 均线值,再用 CStdDeviation 算该价格序列的标准差,上下轨就是均线 ±N 倍标准差。常规布林通道里均线周期和通道周期常取相同值,但这里把 EMA_period 与 BBLength 拆开,等于留给你自由调参的空间。 这类指标函数建议统一收进 IndicatorsAlgorithms.mqh,VidyaBBands.mqh 本身调用 CVidyaBands 只有几行,MT5 里新建指标引用该 mqh 即可验证。外汇与贵金属杠杆高,这类通道仅作波动参考,突破信号失效概率不低。
<span class="keyword">class="type">int</span> <span class="functions">OnCalculate</span>(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> rates_total, <span class="comment">class=class="str">"cmt">// 当前订单时的历史柱数</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> prev_calculated, <span class="comment">class=class="str">"cmt">// 之前订单时的历史柱数</span> <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">datetime</span>& time[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span>& open[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span>& high[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span>& low[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span>& close[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">long</span>& tick_volume[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">long</span>& volume[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span>& spread[] ) { <span class="comment">class=class="str">"cmt">//----+ </span> <span class="comment">class=class="str">"cmt">//---- 检查是否有足够的柱数用于计算</span> <span class="keyword">if</span> (rates_total < <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span>(<span class="number">class="num">0</span>); <span class="comment">class=class="str">"cmt">//---- 声明浮点型变量 </span> <span class="keyword">class="type">class="kw">double</span> price_, jma, up_cci, dn_cci, up_jccx, dn_jccx, jccx; <span class="comment">class=class="str">"cmt">//----+ 声明整型变量</span> <span class="keyword">class="type">int</span> first, bar; <span class="comment">class=class="str">"cmt">//---- 计算在柱的重计算循环中的起点编号 &class="macro">#x27;first&class="macro">#x27;</span> <span class="keyword">if</span> (prev_calculated == <span class="number">class="num">0</span>) <span class="comment">class=class="str">"cmt">// 检查指标计算的第一个起点</span> first = <span class="number">class="num">0</span>; <span class="comment">class=class="str">"cmt">// 计算所有柱的起点编号</span> <span class="keyword">else</span> first = prev_calculated - <span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">// 计算新柱的起点编号</span> <span class="comment">class=class="str">"cmt">//---- 声明 SmoothAlgorithms.mqh 文件中 CJurX 类的变量</span> <span class="keyword">class="kw">static</span> CJurX Jur1, Jur2; <span class="comment">class=class="str">"cmt">//---- 声明 SmoothAlgorithms.mqh 文件中 CJMA类的变量</span> <span class="keyword">class="kw">static</span> CJJMA JMA; <span class="comment">class=class="str">"cmt">//---- 计算指标的主循环</span> <span class="keyword">for</span>(bar = first; bar < rates_total; bar++) { <span class="comment">class=class="str">"cmt">//----+ 调用 PriceSeries 函数以取得输入价格 price_</span> price_ = PriceSeries(IPC, bar, open, low, high, close); <span class="comment">class=class="str">"cmt">//----+ 调用 JJMASeries 函数以取得 JMA</span> jma = JMA.JJMASeries(<span class="number">class="num">0</span>, prev_calculated, rates_total, <span class="number">class="num">0</span>, JMAPhase, JMALength, price_, bar, class="kw">false); <span class="comment">class=class="str">"cmt">//----+ 确定JMA 值的价格偏移</span> up_cci = price_ - jma; dn_cci = <span class="functions">MathAbs</span>(up_cci); <span class="comment">class=class="str">"cmt">//----+ 调用 JurXSeries 函数两次. </span> up_jccx = Jur1.JurXSeries(<span class="number">class="num">30</span>, prev_calculated, rates_total, <span class="number">class="num">0</span>, JurXLength, up_cci, bar, class="kw">false); dn_jccx = Jur2.JurXSeries(<span class="number">class="num">30</span>, prev_calculated, rates_total, <span class="number">class="num">0</span>, JurXLength, dn_cci, bar, class="kw">false); <span class="comment">class=class="str">"cmt">//---- 防止空值时出现除零错误</span> <span class="keyword">if</span> (dn_jccx == <span class="number">class="num">0</span>) jccx = <span>EMPTY_VALUE</span>; <span class="keyword">else</span> { jccx = up_jccx / dn_jccx; <span class="comment">class=class="str">"cmt">//---- 给指标加以限制在顶部和底部之间</span> <span class="keyword">if</span> (jccx > +<span class="number">class="num">1</span>)jccx = +<span class="number">class="num">1</span>;
◍ 用 VIDYA 驱动布林通道的类封装
常规布林带用固定周期 SMA 做中轨,遇到贵金属跳空或外汇数据行情会滞后。这里把中轨换成 VIDYA(方差惯性定向自适应)均线,由 CMO 振荡器决定平滑权重,通道对趋势启动的反应可能更跟手。 类 CVidyaBands 的 VidyaBandsSeries 方法一口气收了十几个参数:CMO_period 控制动量敏感度,EMA_period 是 VIDYA 的内部指数周期,BBLength 与 deviation 则决定通道宽度。调用时先算中轨 MovSeries,后续再基于它加减 deviation 倍标准差得到上下轨。 实盘里可以把 CMO_period 从默认 9 调到 14,欧澳兑美元 H1 上通道收敛频率倾向降低,假突破概率可能减小;但外汇和贵金属杠杆高,参数一变风险轮廓就不同,务必在 MT5 策略测试器里跑完再上真仓。
class CVidyaBands { class="kw">public: class="type">class="kw">double VidyaBandsSeries(class="type">uint begin, class=class="str">"cmt">// 用于计算的可靠柱的起点编号 class="type">uint prev_calculated, class=class="str">"cmt">// 前一订单时的历史柱数 class="type">uint rates_total, class=class="str">"cmt">// 当前订单时的历史柱数 class="type">int CMO_period, class=class="str">"cmt">// CMO 振荡器的平均周期数 class="type">class="kw">double EMA_period, class=class="str">"cmt">// EMA 平均周期数 ENUM_MA_METHOD MA_Method,class=class="str">"cmt">// averaging 平均类型 class="type">int BBLength, class=class="str">"cmt">// 布林通道的平均周期数 class="type">class="kw">double deviation, class=class="str">"cmt">// 点差 class="type">class="kw">double series, class=class="str">"cmt">// 根据&class="macro">#x27;柱&class="macro">#x27;编号计算的价格序列值 class="type">uint bar, class=class="str">"cmt">// 柱索引 class="type">bool set, class=class="str">"cmt">// 数组索引方向 class="type">class="kw">double& DnMovSeries, class=class="str">"cmt">// 当前柱的通道下轨数值 class="type">class="kw">double& MovSeries, class=class="str">"cmt">// 当前柱的通道中轨数值 class="type">class="kw">double& UpMovSeries class=class="str">"cmt">// 当前柱的通道上轨数值 ) { class=class="str">"cmt">//----+ 计算中线 MovSeries = m_VIDYA.VIDYASeries(begin, prev_calculated, rates_total, CMO_period, EMA_period, series, bar, set);
「布林通道与标准差的耦合写法」
在自定义指标里把布林通道拆开算,比直接调 iBands 更可控。下面这段把标准差从 CStdDeviation 实例里取出来,再分别加减到中轨上得到上下轨,返回值是 StdDev 本身,方便后续做波动率判断。 核心三行:DnMovSeries = MovSeries - StdDev 与 UpMovSeries = MovSeries + StdDev 就是通道边界;StdDevSeries 的入参里 BBLength 和 deviation 直接决定通道宽度,deviation 设 2 时约覆盖 95% 正态样本,但价格跳空时实际覆盖概率会偏低,外汇与贵金属品种尤其要留意这种偏差。 类里同时挂了 CCMO 和 CStdDeviation 两个成员,m_VIDYA 名义上是 CCMO 实例却叫 VIDYA,说明中轨可能走 VIDYA 平滑而非简单 MA,开 MT5 把 m_VIDYA 的计算周期和 MovSeries 来源打出来核对一下就不会误用。
class=class="str">"cmt">//----+ 计算布林通道 class="type">class="kw">double StdDev = m_STD.StdDevSeries(begin+CMO_period+class="num">1, prev_calculated, rates_total, BBLength, deviation, series, MovSeries, bar, set); DnMovSeries = MovSeries - StdDev; UpMovSeries = MovSeries + StdDev; class=class="str">"cmt">//----+ class="kw">return(StdDev); } class="kw">protected: class=class="str">"cmt">//---- 声明 CCMO 和 CStdDeviation 类的变量 CCMO m_VIDYA; CStdDeviation m_STD; }; class="kw">protected: class=class="str">"cmt">//---- 声明 CCMO 和 CStdDeviation 类的变量 CCMO m_VIDYA; CStdDeviation m_STD;
类封装带来的指标开销实测
为了摸清在指标里套类到底会拖慢多少,我把原先基于类的 JJMA.mq5 改写成不用类的 JMA.mq5,逻辑保持一致,只在 MT5 同一台机器、相同历史样本和调用频率下跑了一遍。 结果两者耗时几乎贴在一起,差异小到可以忽略。类实例化的那点额外开销,在指标刷新周期里基本被摊平了。 外汇与贵金属行情跳动密集、重绘频繁,这类微小性能差通常不会成为卡顿主因,真正该警惕的是循环内反复 new 对象和跨周期拷贝数组。 所以结论倾向是:用类换来的结构清晰和可维护性,比那点实测不明显的性能损耗更值钱,中小复杂度指标直接上类没问题。