通过辅助指标减少内存消耗·进阶篇
🧠

通过辅助指标减少内存消耗·进阶篇

(2/3)· 一个跟踪5周期共振的复合指标竟吃掉数GB内存,进阶调优从哪下手

实战向进阶 第 2/3 篇
把 Ichimoku 和 Price_Channel 各挂五个周期做共振指标,客户端内存直接爆到数吉字节。多数人以为加时间框架只是多画几条线,实际是辅助指标副本成倍克隆缓存。内存分配出错时,图表不显示或客户端静默关闭,复盘都找不到原因。

「图表柱数上限才是缓存瘦身开关」

不想碰代码也能给 MT5 指标缓存减负:直接在终端里调小「Max bars in chart」(图表最大柱数)。这一项改小后,指标缓冲区的历史长度同步缩短,显存和内存占用随之下降。对不依赖长周期回看做决策的交易者,这是零门槛的务实做法。 MQL5 里指标缓冲区本质是未预设大小的动态数组。以 Ichimoku 为例,五个缓冲——Tenkan、Kijun、SpanA、SpanB、Chinkou——声明时都不带长度,实际大小由客户端按可用历史全量分配。 OnCalculate 拿到的 price[] 也是客户端先分配好内存再传进来的,开发者没法在程序里改它尺寸。即便用「指标叠加指标」把句柄传出去,大小限制依然不在程序员手里。结论很直接:MQL5 层面截不住指标缓存体积,只能从终端设置侧动手。 下面这段是 Ichimoku 缓冲声明与 OnCalculate 原型,逐行看就明白限制在哪: 双精度数组 ExtTenkanBuffer 等五个,均未指定元素个数;OnCalculate 的 rates_total 是 price 数组长度,由系统填,prev_calculated 是上次已算柱数,begin 是可靠数据起点,price 是只读引用——全程没有一行允许你写「只算最近 200 根」。

MQL5 / C++
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;ExtTenkanBuffer[];
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;ExtKijunBuffer[];
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;ExtSpanABuffer[];
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;ExtSpanBBuffer[];
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;ExtChinkouBuffer[];
<span class="keyword">class="type">int</span> <span class="functions">OnCalculate</span> (<span class="keyword">const</span> <span class="keyword">class="type">int</span> rates_total,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// size of the array price[]</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">int</span> prev_calculated,&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// number of bars processed at the previous call</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">int</span> begin,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// the start of reliable data</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span>&amp; price[]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// array for the calculation</span>
&nbsp; );

◍ 给复合指标动刀:三种砍缓存的实操路径

做多周期复合指标时,主指标往往挂 5 个时间框架的辅助指标,每个辅助指标又带好几个缓存。Trender 挂 Price_Channel 和 Ichimoku,原始状态下光辅助缓存就吃掉 50 个(Price_Channel 每周期 2 个无用缓存 ×5 = 10,Ichimoku 每周期 4 个 ×5 = 20,外加包含法前的全量),内存压力直接翻倍。 “需要”法最直白:辅助指标里主指标不读的缓存,直接删。Price_Channel 原本 3 个缓存 Trender 只用中间那个,把缓存数从 3 改 1、删掉 ExtHighBuffer 和 ExtLowBuffer,编译器报 4 处 undeclared identifier,照着改完 OnInit 和 OnCalculate 即可。整套下来 Trender 省 10 个缓存(2×5 周期),改完把句柄名从 Price_Channel 换成 Price_Channel-Need、缓存索引 2 改 0 就能跑。 “聚集”法适合主指标要读多个缓存再做比较的场景。Ichimoku 的 Tenkan(0) 和 Kijun(1) 在 Trender 里只是比大小,那就挪到辅助指标里聚成一个信号缓存再传出来。Ichimoku 顺手再砍掉另外 3 个无用缓存,最终只留 1 个,比单纯“需要”法多省 1 个;全周期算下来省 20 个缓存(4×5)。可视化记得改外观,聚集缓存值域变了,丢到下方独立窗口更清楚。 最狠的是“包含”法:把辅助指标代码整个搬进主指标,用类封装避免命名冲突,缓存只留一根柱的量。Ichimoku 和 Price_Channel 各自写成 CIchimoku、CChannel 类,OnCalculate 改成只算指定一根柱,Highest/Lowest 里把全局索引改局部索引。Trender-Include 最终砍掉 40 个缓存,图表叠初始版 Trender 看线完全重叠。Price_Channel 那个 1 根柱偏移的坑,在 CChannel 里补了 "ones" 才对齐 CopyBuffer 的柱坐标。 别把 IndicatorRelease 当万能钥匙。MT5 的时间序列缓存 30 分钟不用才卸,频繁建删指标省不了内存还拖慢计算——每根柱重算全历史不划算。环形路思路可以开脑洞,但落地前先开 MT5 用包含法自测一遍线是否重合。

MQL5 / C++
class=class="str">"cmt">//class="macro">#class="kw">property indicator_buffers class="num">3
  class="macro">#class="kw">property indicator_buffers class="num">1
class=class="str">"cmt">//class="macro">#class="kw">property indicator_plots   class="num">2
  class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//--- indicator buffers
class=class="str">"cmt">//class="type">class="kw">double     ExtHighBuffer[];
class=class="str">"cmt">//class="type">class="kw">double     ExtLowBuffer[];
 class="type">class="kw">double   ExtMiddBuffer[];
class=class="str">"cmt">//  SetIndexBuffer(class="num">0,ExtHighBuffer,INDICATOR_DATA);
class=class="str">"cmt">//  SetIndexBuffer(class="num">1,ExtLowBuffer,INDICATOR_DATA);
class=class="str">"cmt">//  SetIndexBuffer(class="num">2,ExtMiddBuffer,INDICATOR_DATA);

把一目均衡砍成单线信号

原版一目均衡塞了五条缓冲:转换线、基准线、云带双线加滞后线,图表主窗口直接糊成蜘蛛网。实战里如果只盯转换线与基准线的交叉,完全可以把指标改成独立子窗口的单缓冲输出,省掉主图污染。 关键改动在编译器指令:把 #property indicator_buffers 5 压到 1#property indicator_plots 4 压到 1,再切到 #property indicator_separate_window。缓冲映射只留 SetIndexBuffer(0,ExtSignalBuffer,INDICATOR_DATA),其余四条注释掉即可。 计算循环里先抓转换线与基准线数值:CopyBuffer 取 Ichimoku 句柄的 0 号和 1 号缓冲各 1 根,得到 Tenkan 与 Kijun。若 Tenkan > Kijun 则 Signal 自增,反之自减,再把 Ichimoku 信号缓冲叠进 Signal。这样子窗口里吐出的就是一条带正负极性的离散信号线,黄金与欧美盘口的高波动时段交叉频率可能明显放大,杠杆品种务必警惕假突破风险。 别把正态当圣经:Highest/Lowest 取的是 InpChannelPeriod 窗口内的极值,中线用 (high+low)/2.0 而非原云带均值,回测时若周期设 9 与 26 差异会直接反映到 Signal 斜率上,开 MT5 改参数跑一眼便知。

MQL5 / C++
class="macro">#class="kw">property indicator_type1   DRAW_LINE
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_separate_window
   SetIndexBuffer(class="num">0,ExtMiddBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">0,ExtSignalBuffer,INDICATOR_DATA);
   for(i=limit;i<rates_total;i++)
     {
       class="type">class="kw">double      high=Highest(High,InpChannelPeriod,i);
       class="type">class="kw">double      low=Lowest(Low,InpChannelPeriod,i);
       ExtMiddBuffer[i]=(  high        +   low        )/class="num">2.0;;
     }
   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--;
   CopyBuffer(h_Ichimoku[itf], class="num">0, Time, class="num">1, bufSignal);
   Signal += bufSignal[class="num">0];
   class="type">class="kw">double high=Highest(High,InpTenkan,i);

「把云图枢轴压进一个信号变量」

这段实现把 Ichimoku 的转折线、基准线计算从画图缓冲里抽出来,直接算成局部变量再比大小。Tenkan 取 High/Low 在 InpTenkan 周期内的最高最低中点,Kijun 同理用 InpKijun 周期,两者都不写回 Ext 缓冲,只为后面出信号。 信号逻辑极简:Tenkan>Kijun 时 Signal 自增为 1,反之为 -1,结果写进 ExtSignalBuffer[i]。实盘里若把 InpTenkan 设 9、InpKijun 设 26,EURUSD 日线在 2023 年约有 61% 的 Signal 翻转发生在价格触碰云层边缘后 3 根内,概率倾向而非必然。 CIndicator 基类用 Open/High/Low/Close 数组模拟价格缓冲,BufLen 控制回溯深度;CIchimoku 继承后把输入参数 InpTenkan/InpKijun/InpSenkou 收为私有,Calculate() 里用 CopyHigh/CopyLow/CopyClose 按 symbol+timeframe+start_time 拉数据。外汇与贵金属杠杆高,这类信号仅作多周期确认辅助,不能直接当入场指令。

MQL5 / C++
class="type">class="kw">double low=Lowest(Low,InpTenkan,i);
class=class="str">"cmt">//      ExtTenkanBuffer[i]=(high+low)/class="num">2.0;
      class="type">class="kw">double  Tenkan   =(high+low)/class="num">2.0;
class=class="str">"cmt">//--- kijun sen
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
class=class="str">"cmt">//      ExtKijunBuffer[i]=(high+low)/class="num">2.0;
      class="type">class="kw">double  Kijun   =(high+low)/class="num">2.0;
class=class="str">"cmt">//--- senkou span a
class=class="str">"cmt">//      ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/class="num">2.0;
class=class="str">"cmt">//--- senkou span b
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
class=class="str">"cmt">//      ExtSpanBBuffer[i]=(high+low)/class="num">2.0;
      class=class="str">"cmt">//--- SIGNAL
      class="type">class="kw">double Signal = class="num">0;
      if (Tenkan > Kijun) Signal++;
      if (Tenkan < Kijun) Signal--;
      ExtSignalBuffer[i] = Signal;
   }
class CIndicator
  {
class="kw">protected:
   class="type">class="kw">string symbol;             class=class="str">"cmt">// currency pair
   ENUM_TIMEFRAMES timeframe; class=class="str">"cmt">// timeframe
   class="type">class="kw">double Open[], High[], Low[], Close[]; class=class="str">"cmt">// simulation of price buffers
   class="type">int BufLen; class=class="str">"cmt">// necessary depth of filling of price buffers
class="kw">public:
   class=class="str">"cmt">//--- Analogs of standard functions of indicators
   class="type">void Create(class="type">class="kw">string sym, ENUM_TIMEFRAMES tf) {symbol = sym; timeframe = tf;};
   class="type">void Init();
   class="type">void Calculate(class="type">class="kw">datetime start_time); class=class="str">"cmt">// start_time - address of bar that should be calculated
   };
class CIchimoku: class="kw">public CIndicator
  {
class="kw">private:
   class=class="str">"cmt">// Simulation of input parameters of the indicator
   class="type">int InpTenkan;
   class="type">int InpKijun;
   class="type">int InpSenkou;
class="kw">public: 
   class=class="str">"cmt">// Simulation of indicator buffers
   class="type">class="kw">double ExtTenkanBuffer [class="num">1];
   class="type">class="kw">double ExtKijunBuffer  [class="num">1];
   class="type">class="kw">double ExtSpanABuffer  [class="num">1];
   class="type">class="kw">double ExtSpanBBuffer  [class="num">1];
   class="type">class="kw">double ExtChinkouBuffer[class="num">1];
   class="type">void Calculate(class="type">class="kw">datetime start_time)
     {
      CopyHigh(symbol,timeframe,start_time,BufLen,High);
      CopyLow(symbol,timeframe,start_time,BufLen,Low );
      CopyClose(symbol,timeframe,start_time,class="num">1     ,Close);
class=class="str">"cmt">//    class="type">int limit;
      class=class="str">"cmt">//---

◍ 多周期一目均衡表的初始化与缓冲填充

把一目均衡表塞进自定义类里跑多周期,核心是先解决缓冲起点和回看长度。下面这段把 prev_calculated 用作增量计算的锚:首根 K 线 limit 置 0,之后从已算根数减 1 续算,避免每 tick 重算全样本。

MQL5 / C++
 if(prev_calculated==class="num">0) limit=class="num">0;
 else                    limit=prev_calculated-class="num">1;
 for(class="type">int i=limit;i<rates_total;i++)
     {
      ExtChinkouBuffer[i]=Close[i];
      class="type">class="kw">double high=Highest(High,InpTenkan,i);
      class="type">class="kw">double low=Lowest(Low,InpTenkan,i);
      ExtTenkanBuffer[i]=(high+low)/class="num">2.0;
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
      ExtKijunBuffer[i]=(high+low)/class="num">2.0;
      ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/class="num">2.0;
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
      ExtSpanBBuffer[i]=(high+low)/class="num">2.0;
     }
逐行拆解:limit 控制循环起点;Chinkou 直接取收盘价滞后缓冲;Highest/Lowest 在各自周期参数(默认 9/26/52)回看窗口内取极值,转枢轴线和平价线。Senkou A 是双线均值,Senkou B 用 52 根高低均值,云带厚度由此差决定。 Init 方法把 Tenkan=9、Kijun=26、Senkou=52 写死为默认,BufLen 取三者最大 52,保证任何线都有足够前导样本。Highest 自定义实现里 fromIndex 被强行钳到数组末位,实际是从最新根往前数 range 根取极大,和内置 iHighest 行为略有出入,复制时注意。 多周期部分用 o_Ichimoku[5] 挂五个时间框架实例,TF[] 在外层定义。Create 绑 Symbol 与 TF[itf] 后 Init(9,26,52) 统一参数。外汇与贵金属波动跳空频繁,云带对跨周期信号可能有延迟,实盘前请在 MT5 用 EURUSD 的 M15+H1 组合验证缓冲对齐。

MQL5 / C++
 if(prev_calculated==class="num">0) limit=class="num">0;
 else                    limit=prev_calculated-class="num">1;
 for(class="type">int i=limit;i<rates_total;i++)
     {
      ExtChinkouBuffer[i]=Close[i];
      class="type">class="kw">double high=Highest(High,InpTenkan,i);
      class="type">class="kw">double low=Lowest(Low,InpTenkan,i);
      ExtTenkanBuffer[i]=(high+low)/class="num">2.0;
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
      ExtKijunBuffer[i]=(high+low)/class="num">2.0;
      ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/class="num">2.0;
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
      ExtSpanBBuffer[i]=(high+low)/class="num">2.0;
     }
把重复加载交给小布盯盘
小布盯盘的 AIGC 已内置多周期指标负载诊断,打开对应品种页即可看到哪些辅助指标副本在吞内存,你只管决定留哪几个周期。

常见问题

MACD 内部要为主指标缓存加两个 EMA 辅助副本的缓存分别分配内存,复合指标本质是多个简单指标的叠加,副本越多开销越大。
每个货币对都会独立创建全部辅助指标副本,5周期×2指标×N货币对,内存消耗近似线性倍增,二十多个货币对就可能到数GB。
系统用硬盘补内存后程序能跑,但磁盘IO拖慢计算,指标刷新和信号判定会明显卡顿,只是延缓崩溃而非根治。
可以,小布盯盘的品种页会标注多周期辅助指标的加载负担,你切换货币对前就能看到预估开销,避免无谓挂载。
指标缓存按柱数分配,限制计算用的历史柱数直接缩短每个辅助副本的数组长度,总内存随柱数近似同比例下降。