不使用额外的缓冲区,为中间计算进行系列价格的平均化(基础篇)
📘

不使用额外的缓冲区,为中间计算进行系列价格的平均化(基础篇)

第 1/3 篇

◍ 无缓冲区的序列价格均值化思路

在 MT5 自定义指标里,很多人习惯开一个额外缓冲区存中间结果再做平均,但其实可以省掉这一步。直接对序列价格本身做滑动窗口的平均化,既能减少内存占用,也避开了多余缓冲区在 OnCalculate 里同步刷新的麻烦。 核心做法是利用已传入的 price 数组,按当前索引 i 向前取 N 个值累加后除以 N,把结果写回主缓冲区。下面这段演示了不声明额外中间缓冲、只在 plot 缓冲里完成均值的写法。 外汇与贵金属行情跳空频繁,这种轻量计算在 M1 或 Tick 级可能更稳,但仍受点差与滑点影响,回测表现仅代表历史概率,不构成方向保证。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[])
{
   class="type">int start = (prev_calculated > class="num">0) ? prev_calculated - class="num">1 : begin;
   for(class="type">int i = start; i < rates_total; i++)
   {
      class="type">class="kw">double sum = class="num">0.0;
      class="type">int count = class="num">10; class=class="str">"cmt">// 窗口大小
      for(class="type">int j = class="num">0; j < count; j++)
         sum += price[i - j]; class=class="str">"cmt">// 向前取序列价格
      ExtBuffer[i] = sum / count; class=class="str">"cmt">// 直接写主缓冲,无中间缓冲
   }
   class="kw">return rates_total;
}

「指标里跑中间计算未必最快」

在早前关于指标经济计算原则的测试里,我做过一组有说服力的对比:代码里每次去调用自定义或技术指标做中间计算,并不一定是该指标内部执行中间运算的最优路径。把中间计算直接写进指标本体,实测执行速度往往比最终调用方式快出一截。 这种把计算塞进指标、顺手用额外缓冲区存中间结果的写法,初看很省事,尤其逻辑简单时诱惑很大。但它会演变成一种并发症——指标描述被额外缓冲区拖累,结构越来越重。 中间计算花样很多,不过交易者最常用到的还是各类平均线算法。针对它们,用一组简单、通用的自定义函数就能大幅简化编写任务。本文接下来就拆解这类函数的创建和调用过程。

柱上均值计算的两种缓冲区思路

在 MT5 自定义指标里,对中间缓冲区某个当前柱做平均,经典写法是先填好必要数据,再按平均周期 n 去框定此前 n 根柱的取值区间,最后套公式求均值。 老式写法展开是这样:SmoothVar(bar) = Function(Var(bar-(n-1)), Var(bar-(n-2)), ... Var(bar))。这里 bar 是当前计算柱编号,n 是参与平均的柱数。这种做法会冒出两个计算周期——第一周期把原始数据算好塞进中间缓冲区,第二周期再按公式从指标缓冲区里翻出那些单元格做二次平均,开销明显。 如果把选取逻辑直接折叠进函数内部,式子就瘦成 SmoothVar(bar) = Function(Var(bar), bar)。新值写进当前柱,最老的 Var(bar-n) 被踢出选择集,全程只需一个计算周期,且不用额外开指标缓冲区;函数内数组只存算一根柱所需的精确数据量,不是整段历史。 要注意一点:想让当前柱直接调这个内置均值函数,必须先在全部前柱上跑过它,否则选择集是空的。外汇与贵金属行情跳空频繁,这类递推均值在高波动段可能滞后,实盘前请在 MT5 策略测试器用真实点差验证。

◍ 用类封装均值函数避开内存冲突

把均值计算写成普通函数,变量在两次调用间容易丢值;同一段代码里若反复用不同参数的单型均线,还会抢共享内存。正解是把这类函数做成类,我用的 CMoving_Average 就是从 CMovSeriesTools 基类派生的,基类管了周期合法性检查等通用方法,衍生类不用到处复制。 CMoving_Average 内含 5 个单型均线函数,MASeries() 是聚合入口,靠 MA_Method 参数切到 SMA/EMA/SMMA/LWMA;为对齐指标原生变量,参数里补了 begin、prev_calculated、rates_total、set,set 决定 price series 的索引方向是否跟变量数组一致。 重点:类里所有均线函数的 Length 在运行期锁死不能改,EMASeries() 的 Length 还是 double 类型。要在指标里用,先 #include 全局引入 MASeries_Cls.mqh,再按取均值的次数在 OnCalculate() 循环前声明对应数量的静态 CMoving_Average 变量——静态是为保住跨调用的值。 示例里跑 SMA→EMA→SMMA→LWMA 四个连续流程,前一个输出喂给下一个,末值进指标缓冲区。最容易翻车的是可靠柱起点索引的初始化:若 LWMA 不是最后一环,起点偏移得是 Length4+1 而非 Length4;拿不准就先取大数,再往下递减试。外汇与贵金属波动剧烈,这类多均线嵌套对历史柱数敏感,实盘前务必在 MT5 用复盘数据验证逻辑。

MQL5 / C++
class CMoving_Average : class="kw">public CMovSeriesTools
  {
class="kw">public: 
   class="type">class="kw">double     MASeries(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 Length,                class=class="str">"cmt">// 平均周期数
                         ENUM_MA_METHOD MA_Method, class=class="str">"cmt">// 平均方法 (MODE_SMA, MODE_EMA, MODE_SMMA, MODE_LWMA)
                         class="type">class="kw">double series,             class=class="str">"cmt">// 用于计算编号为 &class="macro">#x27;bar&class="macro">#x27; 的价格序列值
                         class="type">uint bar,                 class=class="str">"cmt">// 柱索引
                         class="type">bool set                  class=class="str">"cmt">// 数组索引方向.
                         );
   class="type">class="kw">double     SMASeries(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 Length,               class=class="str">"cmt">// 平均周期数
                         class="type">class="kw">double series,            class=class="str">"cmt">// 用于计算编号为 &class="macro">#x27;bar&class="macro">#x27; 的价格序列值
                         class="type">uint bar,                 class=class="str">"cmt">// 柱索引
                         class="type">bool set                 class=class="str">"cmt">// 数组索引方向.
                         );
  };

「均线序列函数的接口参数拆解」

在 MT5 自定义指标里,EMA、SMMA、LWMA 三类均线通常封装成独立的序列计算函数,参数结构高度一致,区别只在平滑权重逻辑。 三个函数的首参都是 begin(可靠柱起点索引),用来跳过尚未形成完整历史的柱;prev_calculated 和 rates_total 则分别交代上一 tick 与当前已加载的历史柱数量,避免重复计算。 Length 决定平均周期数,EMA 用 double 允许小数周期,SMMA/LWMA 用 int 只接整数。series 传入指定 bar 的价格序列值,bar 是目标柱索引,set 控制数组索引方向(true 为时间正序)。 实盘接这些数据前,先在 EURUSD 的 M15 上用 Length=20 跑一遍 EMASeries,观察 begin 跳过的前几根柱返回值是否为空,能直接验证你的索引边界有没有写错。外汇与贵金属杠杆高,参数误用可能放大滑点风险。

MQL5 / C++
class="type">class="kw">double EMASeries(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">class="kw">double Length, class=class="str">"cmt">// 平均周期数
 class="type">class="kw">double series, class=class="str">"cmt">// 用于计算编号为 &class="macro">#x27;bar&class="macro">#x27; 的价格序列值
 class="type">uint bar, class=class="str">"cmt">// 柱索引
 class="type">bool set class=class="str">"cmt">// 数组索引方向.
 );

class="type">class="kw">double SMMASeries(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 Length, class=class="str">"cmt">// 平均周期数
 class="type">class="kw">double series, class=class="str">"cmt">// 用于计算编号为 &class="macro">#x27;bar&class="macro">#x27; 的价格序列值
 class="type">uint bar, class=class="str">"cmt">// 柱索引
 class="type">bool set class=class="str">"cmt">// 数组索引方向.
 );

class="type">class="kw">double LWMASeries(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 Length, class=class="str">"cmt">// 平均周期数
 class="type">class="kw">double series, class=class="str">"cmt">// 用于计算编号为 &class="macro">#x27;bar&class="macro">#x27; 的价格序列值
 class="type">uint bar, class=class="str">"cmt">// 柱索引

四类均线嵌套的落地写法

把 SMA、EMA、SMMA、LWMA 串成一条四级流水线,核心在 MASeries 的链式调用:上一级的输出价直接喂给下一级的 price 参数。上面这段把 MA1 到 MA4 声明为静态类变量,避免每根 K 线重算时对象被反复构造,MT5 里能少掉一些不必要的开销。 主循环里 bar 从 first 跑到 rates_total,四次 MASeries 分别用 Length1~Length4 和四种平滑模式;ma4_ 最后叠了 dPriceShift 写进 MAx4 缓冲。注意 start 变量的偏移:start3 沿用 Length1+begin 是因为 EMA 本身不后移可靠信息起点,而 start4 要再叠 Length3,否则 LWMA 段会读到未初始化区域。 外汇与贵金属杠杆高、滑点随机,这套嵌套在 M15 以上周期回测相对稳,但实盘仍可能因点差跳变导致前两根 bar 的 ma4_ 偏离历史值,上机前先把 begin 和 dPriceShift 打出来逐根核对。

MQL5 / C++
class="type">bool set             class=class="str">"cmt">// 数组索引方向.
);
class="kw">protected:
  class="type">class="kw">double       m_SeriesArray[];
  class="type">int          m_Size_, m_count, m_weight;
  class="type">class="kw">double       m_Moving, m_MOVING, m_Pr;
  class="type">class="kw">double       m_sum, m_SUM, m_lsum, m_LSUM;
};
class="macro">#include <MASeries_Cls.mqh> 
class=class="str">"cmt">//---- 声明MASeries_Cls.mqh文件中CMoving_Average类的变量
class="kw">static CMoving_Average MA1, MA2, MA3, MA4;
class=class="str">"cmt">//---- 指标计算主循环
for(bar = first; bar < rates_total; bar++)
 {
 class=class="str">"cmt">//----+ 调用四次 MASeries  
 ma1_ = MA1.MASeries(start1, prev_calculated, rates_total, Length1, MODE_SMA,   price[bar], bar, class="kw">false);
 ma2_ = MA2.MASeries(start2, prev_calculated, rates_total, Length2, MODE_EMA,   ma1_,       bar, class="kw">false);
 ma3_ = MA3.MASeries(start3, prev_calculated, rates_total, Length3, MODE_SMMA, ma2_,       bar, class="kw">false);
 ma4_ = MA4.MASeries(start4, prev_calculated, rates_total, Length4, MODE_LWMA, ma3_,       bar, class="kw">false);
 class=class="str">"cmt">//----      
 MAx4[bar] = ma4_ + dPriceShift;
}
class=class="str">"cmt">//---- 初始化可靠信息起点变量
start1 = class="num">0 + begin;
start2 = Length1 + begin;
start3 = Length1 + begin; class=class="str">"cmt">// 之前的 EMA 平均没有改变可靠信息的起点
start4 = Length1 + Length3 + begin;

常见问题

可在当前柱循环里累加 close 等价格再除以计数,用局部变量存和,不调用 iMA 类函数也能拿到均值。
每根柱重算时都要遍历历史,缺少缓存;只在数据量小或逻辑极简时无缓冲思路才划算。
小布可读取你的指标代码,标出类封装中未隔离的全局变量,并提示改用局部或成员变量的写法。
period 决定取样根数,shift 控制向右偏移几根取历史值;调错 shift 会让信号整体滞后。
先封单类均值函数,再在主计算里按小周期到大周期依次调用,每类结果存独立变量避免覆盖。