通过辅助指标减少内存消耗(基础篇)
◍ 用辅助指标给内存减负
在 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、时间、成交量,机器资源压力会先一步卡住盯盘流畅度。
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 数据。
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 只绑用到的缓冲,往往能省掉一成以上的常驻内存,具体省多少看你原指标堆了多少冗余缓冲。