通过辅助指标减少内存消耗(基础篇)
📘

通过辅助指标减少内存消耗(基础篇)

第 1/3 篇

◍ 用辅助指标给内存减负

在 MT5 里直接把计算逻辑全塞进一个指标,会随着历史数据拉长迅速吃满内存。2014 年 3 月官方论坛一条阅读 2857 次、回帖 7 条的讨论指出,把部分非核心计算拆到辅助指标里,主指标只做调用,能明显压住内存曲线。 做法是让主指标通过 iCustom 或资源引用去读辅助指标的输出缓冲区,自己不再重复算一遍。这样同一套逻辑在多周期加载时,辅助指标实例可复用,避免每个图表都背着一份完整运算。 贵金属与外汇品种跳空多、tick 密,历史深度拉到几年后,单指标内存占用可能翻数倍。倾向先用辅助指标分流,再上实盘观察任务管理器的 MT5 内存变化。

复合指标的隐性内存开销

你可能已经写过或用过依赖辅助指标的 EA 和指标。以 MACD 为例,它内部跑两份 EMA 再算差值,本质上就是多个简单指标的叠加。 这类复合结构的代价很直接:MACD 占用内存和 CPU 时间约是单一 EMA 的三倍以上,因为它要为主指标和所有辅助指标的缓存分别分配空间。更复杂的情况里,辅助指标往往不止两个。 当指标跨多周期(比如同时盯几个周期的波动同步性)、做多货币、又被挂在二十多个货币对上运行时,内存开销会指数级放大。真实案例里,一台 MT5 客户端因此吃掉数 GB 内存。 内存撑不住时,客户端要么加载不了指标,要么计算错乱(代码没处理分配失败的話),甚至直接崩。所幸系统能用硬盘当虚拟内存兜底,程序不死,但卡到没法盯盘。外汇与贵金属交易本就高波动高风险,这种性能坑会放大你的操作延迟。

「多周期共振指标的隐性内存账」

想比 MACD 再往前走一步,可以搭一个跟踪趋势起点的复合指标 Trender:在 H4、H1、M15、M5、M1 五个周期上,各取 Ichimoku 与 Price_Channel 的信号做汇总。Ichimoku 里 Tenkan 红线在 Kijun 蓝线之上视为上升、之下视为下降;Price_Channel 里价格在中线之上视为上升、之下视为下降。五个周期乘两个指标,共 10 个辅助指标,加载时应挂在最小周期图表(本例为 M1)才能看到所有小趋势共振。 翻一下两个附属指标的源码会发现,每个 Ichimoku 和 Price_Channel 各建了 8 个缓存,乘 5 个时间框架再加 Trender 自身的 1 个缓存,合计 41 个缓存。MT5 客户端默认下一个缓存约装 100000 个 double 值,单值占 8 字节,所以 41 个缓存本身就要吃掉约 31 Mb 内存,还没算缓存里的服务信息。 别把 31 Mb 当小数目。外汇和贵金属交易本就属于高风险场景,当交易者同时开很多货币对、每个图表再叠这类复合指标,内存占用会成倍放大;图表本身每根 K 线还存 OHLC、时间、成交量,机器资源压力会先一步卡住盯盘流畅度。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">1
class="macro">#class="kw">property indicator_plots  class="num">1
class="macro">#class="kw">property indicator_minimum -class="num">1
class="macro">#class="kw">property indicator_maximum  class="num">1
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1  DarkTurquoise
class=class="str">"cmt">// The only buffer of the indicator
class="type">class="kw">double ExtBuffer[];
class=class="str">"cmt">// Timeframes of auxiliary indicators
ENUM_TIMEFRAMES TF[class="num">5] = {PERIOD_H4, PERIOD_H1, PERIOD_M15, PERIOD_M5, PERIOD_M1};
class=class="str">"cmt">// Handles of auxiliary indicators for all timeframes
class="type">int h_Ichimoku[class="num">5], h_Channel[class="num">5];
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
   SetIndexBuffer(class="num">0, ExtBuffer);
   ArraySetAsSeries(ExtBuffer, true);

   class=class="str">"cmt">// Create auxiliary indicators
   for (class="type">int itf=class="num">0; itf<class="num">5; itf++)
     {
      h_Ichimoku[itf] = iCustom(Symbol(), TF[itf],
                                  "TestSlaveIndicators\Ichimoku",
                                  class="num">9, class="num">26, class="num">52
                                 );
      h_Channel [itf] = iCustom(Symbol(), TF[itf],
                                  "TestSlaveIndicators\Price_Channel",
                                  class="num">22
                                 );
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime& time[],
                const class="type">class="kw">double& open[],
                const class="type">class="kw">double& high[],
                const class="type">class="kw">double& low[],
                const class="type">class="kw">double& close[],

◍ 多周期信号怎么在 OnCalculate 里累加

这段自定义指标的核心逻辑写在 OnCalculate 里:先把 time 数组设为序列排列(最新一根在索引 0),再用 prev_calculated 决定只刷新增量 bar,避免每 tick 重算全部历史。limit 的取值要么是 rates_total-1(首次加载),要么是 rates_total-prev_calculated(后续增量),这是 MT5 指标性能的基本功。 循环从 limit 倒序到 0,对每个 bar 先取当前时间 Time,然后开一个 itf 从 0 到 4 的循环,覆盖 5 个时间框架。每个框架内用 CopyClose 取收盘价,用 CopyBuffer 分别从 Ichimoku 句柄取 0/1 号缓冲(Tenkan、Kijun),从 Channel 句柄取 2 号缓冲(Mid 中线)。 信号累加很直白:Tenkan 上穿 Kijun 给 +1,下穿给 -1;收盘价站上通道中线给 +1,跌破给 -1。5 个框架跑完,单 bar 的 Signal 理论区间在 -10 到 +10。最后 ExtBuffer[bar] = Signal/10,把数值压到 -1~+1 之间,方便画线和告警。 开 MT5 自己验证时,记得 #property indicator_buffers 那两行声明了 5 和 3 共 8 个缓冲,若你实际只用了 1 个输出缓冲,编译会报缓冲数不匹配。外汇和贵金属波动大,这类多周期共振信号只代表概率倾向,实盘前务必在策略测试器跑至少 3 个月 tick 数据。

MQL5 / C++
const class="type">long& tick_volume[],
const class="type">long& volume[],
const class="type">int& spread[])
  {
  ArraySetAsSeries(time, true);

  class="type">int limit = prev_calculated ? rates_total - prev_calculated : rates_total -class="num">1;
  for (class="type">int bar = limit; bar >= class="num">0; bar--)
    {
      class=class="str">"cmt">// Time of the current bar
      class="type">class="kw">datetime Time  = time [bar];

      class=class="str">"cmt">//--- Gather signals from all timeframes
     class="type">class="kw">double Signal = class="num">0; class=class="str">"cmt">// total signal
      class="type">class="kw">double bufPrice[class="num">1], bufTenkan[class="num">1], bufKijun [class="num">1], bufMid[class="num">1], bufSignal[class="num">1];
      for (class="type">int itf=class="num">0; itf<class="num">5; itf++)
        {
         class=class="str">"cmt">//=== Bar price
         CopyClose(Symbol(), TF[itf], Time, class="num">1, bufPrice);
         class="type">class="kw">double Price = bufPrice[class="num">0];
         class=class="str">"cmt">//=== The Ichimoku indicator
         CopyBuffer(h_Ichimoku[itf], class="num">0, Time, class="num">1, bufTenkan);
         class="type">class="kw">double Tenkan = bufTenkan[class="num">0];
         CopyBuffer(h_Ichimoku[itf], class="num">1, Time, class="num">1, bufKijun );
         class="type">class="kw">double Kijun  = bufKijun [class="num">0];

         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;

         class=class="str">"cmt">//=== The channel indicator
         CopyBuffer(h_Channel [itf], class="num">2, Time, class="num">1, bufMid);
         class="type">class="kw">double Mid = bufMid[class="num">0];
         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;
         }

      ExtBuffer[bar] = Signal/class="num">10;
     }
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property indicator_buffers class="num">5
class="macro">#class="kw">property indicator_buffers class="num">3

从矩形面积看指标内存占用

给 MT5 终端加内存条能缓解爆内存,但硬件到顶或预算卡住时,就得回头砍指标自身的消耗。外汇与贵金属行情高频刷新,多指标同跑下 MT5 对内存的压力向来不低,盲目堆缓存迟早触到本地瓶颈。 回想初中几何:把一个复合指标的全部缓存想成一个实心矩形,它占的面积就是实打实吃掉的内存。这个模型很直白——你不用懂底层,只要认得面积公式就行。 矩形的宽,是指标基于多少根 K 线去绘制;高,是指标申请了多少个缓存序列。面积 = 宽 × 高,所以要么缩柱数,要么减缓存条数,内存占用就往下掉。 在 MT5 里把 IndicatorSetInteger(INDICATOR_BUFFERS, n) 的 n 压到最低必需值,再把 SetIndexBuffer 只绑用到的缓冲,往往能省掉一成以上的常驻内存,具体省多少看你原指标堆了多少冗余缓冲。

常见问题

把多个周期数据都塞进同一指标缓冲区会隐性占用大量内存,拆成辅助指标各自算更省。
它要在内部缓存多个时间框架的缓冲,周期越多隐性内存账越大,建议按需加载。
小布可扫描你加载的指标组合,提示哪些复合指标存在高内存开销并给拆分建议。
把每个缓冲区想成矩形,面积=点数×变量大小,累加即得占用量级,便于横向比。
用临时变量接收辅助指标返回值再累加,别为每个周期开长生命周期缓冲。