🧩
MQL5 无额外缓冲区的平滑算法类库
平滑算法类库性能
一、为什么不用额外缓冲区
经典的做法是先算好中间结果放进“指标缓冲区”,再基于该缓冲区搜索做平均——这就需要额外的指标级缓冲区,代码又臭又长,而且每次调用 iMA 之类做中间计算并不快。本文的思路是:把中间状态保存在函数内部的类成员变量/小数组里,用递推方式在单周期内完成平均,完全不需要指标级额外缓冲区。
二、核心思路:内部状态递推
传统平均要取前 n 个值,必须开缓冲。改进后把“选值+累减”放进函数自身:当前柱的新值写入状态,最老的值移除,只保留“算一个柱所需”的精确数据量。关键是——在算当前柱之前,必须已在所有前柱上调用过该函数,状态才连续。这本质是用类成员+内部动态数组替代指标缓冲区,类似 EMA 的递推 EMA[i]=α·price[i]+(1-α)·EMA[i-1] 只需前一值。
三、平滑算法家族
文章把多种平滑封装成类(继承 CMovSeriesTools),并支持“可靠柱起始平移”与部分“动态改 Length”:
- CMoving_Average:SMA/EMA/SMMA/LWMA,其中 EMASeries 的 Length 可为 double。
- CJJMA(JMA 平滑)、CT3(T3)、CJurX(超线性):支持动态 Length。
- CStdDeviation、CCMO(钱德动量)、CAMA(考夫曼)、CParMA、CMomentum 等。
- SmoothAlgorithms.mqh 汇总所有类,并提供 PriceSeries() 取价格序列。
四、OnCalculate 里的递推调用
#include <MASeries_Cls.mqh>
static CMoving_Average MA1, MA2, MA3, MA4; // 静态:跨调用保留状态
int first = (prev_calculated==0)? 0 : prev_calculated-1;
for(int bar=first; bar<rates_total; bar++)
{
double m1 = MA1.MASeries(0, prev_calculated, rates_total, Length1, MODE_SMA, price[bar], bar, false);
double m2 = MA2.MASeries(0, prev_calculated, rates_total, Length2, MODE_EMA, m1, bar, false);
double m3 = MA3.MASeries(0, prev_calculated, rates_total, Length3, MODE_SMMA, m2, bar, false);
double m4 = MA4.MASeries(0, prev_calculated, rates_total, Length4, MODE_LWMA, m3, bar, false);
MAx4[bar] = m4 + dPriceShift;
}
五、相比标准指标的优势
- 省内存:函数内部数组只存一个柱所需数据,不占指标缓冲区。
- 无未来函数:递推只用当前及之前柱,所列算法均无预览未来。
- 性能好:替代反复调用指标,纯类实现最快。
- 灵活:算法多样,可连套(MAx4 套 4 次),可用于任意价格序列或指标值。
六、坑
⚠ 顺序与声明是命门
1) 必须顺序调用:想在当前柱取平均值,必须先在所有前柱上调用过,否则内部状态错。2) 静态声明:类变量须在全局或 OnInit 声明为 static,不能在成员函数内声明静态类变量。3) 起点偏移:不同算法平移不同(如 LWMA 非末端要 +Length+1),不清晰时取大再递减。4) 除零防护:分母可能为 0 时赋 EMPTY_VALUE。5) 部分算法 Length 运行时不可改(EMA 参数为 double 但固定),只有 JMA/T3/超线性支持动态 Length。
七、各类平滑算法对比
平滑是把噪音滤掉、凸显趋势。SMA 简单但滞后等权;EMA 给近期更大权重、反应更快;SMMA(Wilder)更平滑适合慢线;还有各种自定义平滑核。选哪种取决于你要多快反应、能接受多大滞后。
没有最好的平滑,只有最适合信号的。短线信号用快平滑(EMA),长线趋势用慢平滑(SMMA),混用才是常态。
八、用 CSeries 类取平滑序列
标准库的 CSeries 系列(如 CiMA、CiSmooth)封装了「计算某指标的平滑序列」的逻辑,你直接拿缓冲值,不必自己写递推。换算法只改构造参数,代码更干净。
CiMA ma; ma.Create(_Symbol, PERIOD_M15, 14, 0, MODE_EMA, PRICE_CLOSE); double v = ma.Main(0); // 最新 EMA 值
✦ 用类别手搓递推
CSeries 类封装了平滑计算与缓冲管理,换算法只改参数。手搓递推容易在边界(前 N 根)出 bug。
九、平滑的滞后权衡
平滑越强、滞后越大:噪音滤干净了,但信号也慢半拍,容易买在趋势中段、卖在回吐时。弱平滑反应快但易被噪音骗。权衡点在「你的策略靠不靠速度」。
实用做法:快慢两条平滑线配合——快线出信号、慢线定方向,既过滤噪音又不全丢速度。这比单条线鲁棒。
十、小结
✦ 小结
平滑滤噪音、凸显趋势:SMA 等权滞后、EMA 快、SMMA 更慢更平滑,按策略速度需求选。用 CSeries 类取序列最稳,换算法只改参数。核心权衡是滞后 vs 噪音,快慢双线配合比单线鲁棒。
常见问题
不是不要状态,而是不要“指标级缓冲区”。EMA 递推只需要前一个 EMA 值,这个值存在类成员变量里就行,不必开一个 INDICATOR_DATA/CALCULATIONS 缓冲。文章用类内部小数组滚动存储,达到同样效果但更省内存、更好复用。
因为递推状态是跨柱累积的(类似 EMA 依赖上一根的值)。如果你只从某根开始算,前面的状态是空的,结果会从错误基准出发,整条线都偏。所以初始化阶段也要从第一根开始把函数跑一遍把状态建起来。
对“在指标内部反复做中间平均”的场景,能且更快——省去反复调用 iMA 的开销。但若只是要一条标准 MA 线,直接用 iMA 句柄+CopyBuffer 更简单。文章的价值在于把多种平滑(JMA/T3/CCMO 等)做成可链式调用的类库。