以经济方式计算指标的原则(基础篇)
📘

以经济方式计算指标的原则(基础篇)

第 1/2 篇

让指标少算点的经济原则

在 MT5 里跑自定义指标,最容易被忽视的隐性成本是重复计算。每当价格更新,OnCalculate 会被触发,如果每次都把全部历史重算一遍,CPU 和内存都会无意义地吃紧,尤其多周期多品种同开时更明显。 所谓经济方式,核心思路是只处理新增的、变化的数据,而不是从头再来。MT5 的 OnCalculate 会传入 rates_total 与 prev_calculated,前者是当前可用的柱数,后者是上一次已计算的柱数,两者的差就是本次需要补算的增量。 以一段典型结构为例,当 prev_calculated == 0 时做全量初始化,否则只从 prev_calculated - 1 开始循环,这样能避免边界处一根柱的断裂,又省掉大量已确定结果的重算。外汇与贵金属市场高杠杆、跳空频繁,这种写法在实盘多标的监控时可能显著降低卡顿概率。

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">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
{
   class="type">int start;
   if(prev_calculated==class="num">0)
      start=class="num">0;          class=class="str">"cmt">// 首次运行,全量计算
   else
      start=prev_calculated-class="num">1; class=class="str">"cmt">// 增量计算,留一根防断层
   for(class="type">int i=start;i<rates_total;i++)
   {
      class=class="str">"cmt">// 此处写指标值计算并赋给缓冲区
   }
   class="kw">return(rates_total);
}

「为什么 EA 优化前要先抠指标代码」

做可视化交易时,代码的低效往往藏得住;一旦上了自动交易,问题就藏不住了。EA 回测与参数优化对计算量极敏感,写法稍冗余,测试周期就可能拉长到根本等不到出结果,这时候想迭代出能用的策略基本不现实。 外汇与贵金属属高杠杆高风险品种,EA 跑不实盘逻辑前先得跑通回测,而大部分 EA 在代码里反复调用自定义指标。指标算得慢,外层优化就跟着崩。 动手写策略前,先摸清哪些编程细节会拖慢 EA 的测试与优化最划算。既然多数 EA 都依赖用户指标,从指标构建的坑看起就是最直接的切入点。指标本身要记的规矩不多,顺着常见写法挨个过一遍效率最高。

◍ 当前未收盘柱上的重复计算代价

RSI、ADX、ATR、CCI 这类经典指标只在已收盘指标柱上做一次计算,新柱出现才继续算。唯一例外是尚未收盘的当前柱:每次报价跳动都会重算,直到它收盘。 想验证“在未计算柱上重算指标”是否合理,最直白的法子是在 MT5 策略测试器里,把优化版(仅算新柱)和未优化版(每次 tick 重算全部)跑同样周期比对。 我拿一个基础 SMA 指标改了一行:原本 else 分支只算新柱,被替换成无论何时都从首柱重算。得到未优化版后,配一个空 OnInit/OnTick 的 EA 去调用它,测试模式选 Every tick(每笔报价)。 结果很刺眼:未优化版测试耗时比优化版多出 500 倍以上,哪怕测试区间选得很短。EA 回测里这种算力浪费,足够让任何参数优化变得不划算。 别把超频当解药 论坛里有人用液氮压 CPU、拉内存时钟把 PC 性能榨出两三倍,但写得好的一行代码往往更省钱——经济式编码让普通办公机可能跑出顶配机的回测吞吐,虽救不了古董赛扬,但能实打实省下等待时间。外汇与贵金属回测涉及高杠杆高风险,耗时放大不代表信号更可靠,仅意味算力被空耗。

MQL5 / C++
  if (prev_calculated == class="num">0) class=class="str">"cmt">// 如果是第一次计算,重新计算所有已存在的柱
     first = MAPeriod - class="num">1 + begin;
  else first = prev_calculated - class="num">1; class=class="str">"cmt">// 在之后的计算中, 只计算新出现的柱
   first = MAPeriod - class="num">1 + Begin; class=class="str">"cmt">// 有订单时重新计算全部柱 
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                SMA_Test.mq5 |
class=class="str">"cmt">//|             Copyright class="num">2010, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                  [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="type">int Handle;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//----+
   class=class="str">"cmt">//----+ 取得指标句柄
   Handle = iCustom(Symbol(), class="num">0, "SMA");
   if (Handle == INVALID_HANDLE)
     Print(" 无法获得SMA指标句柄");
class=class="str">"cmt">//----+
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//----+
    class=class="str">"cmt">//--- 释放指标句柄
    IndicatorRelease(Handle);
class=class="str">"cmt">//----+   
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//----+
   class="type">class="kw">double SMA[class="num">1];
   class=class="str">"cmt">//----+ 使用指标句柄把指标 

把指标缓冲区搬进静态数组

指标句柄拿到之后,真正要算数的是缓冲区里的数值,而不是句柄本身。上面这段把当前最新的 SMA 值从指标缓冲区复制到预先声明好的静态数组 SMA 里,只取 0 号缓冲区、起点 0、取 1 根。 CopyBuffer 的第三个参数填 0 表示从当前柱开始,第四个参数填 1 表示只取最新一根,回测时这根就是实时收盘价对应的平滑值。你在 MT5 里把这段接在指标句柄校验通过之后,就能用 SMA[0] 直接参与后续条件判断,不用每次重新算均线。 外汇与贵金属波动受杠杆和跳空影响,SMA[0] 仅反映已闭合或当前未完成柱的倾向,实盘使用前建议在策略测试器用至少 3 个月 tick 数据验证延迟表现。

MQL5 / C++
  class=class="str">"cmt">// 缓冲区的值复制到特别准备好的静态数组中
   CopyBuffer(Handle, class="num">0, class="num">0, class="num">1, SMA);
class=class="str">"cmt">//----+

常见问题

优化前抠指标代码能避免当前未收盘柱上反复重算,减少CPU占用,回测和实盘都更稳。
每来一个tick就重算整根未收盘柱,若指标复杂度高,可能让负载翻倍,应先做已收盘判断再算。
小布可自动扫描品种页上的指标调用频率,标出未收盘柱重复计算点,你直接照提示改代码。
静态数组只初始化一次,避免每帧重建缓冲区,EA跑多周期时内存和算力都更省。
外汇贵金属波动快、高风险,少算点能让EA在滑点期不卡顿,下单决策更跟手。