以经济方式计算指标的原则(基础篇)
让指标少算点的经济原则
在 MT5 里跑自定义指标,最容易被忽视的隐性成本是重复计算。每当价格更新,OnCalculate 会被触发,如果每次都把全部历史重算一遍,CPU 和内存都会无意义地吃紧,尤其多周期多品种同开时更明显。 所谓经济方式,核心思路是只处理新增的、变化的数据,而不是从头再来。MT5 的 OnCalculate 会传入 rates_total 与 prev_calculated,前者是当前可用的柱数,后者是上一次已计算的柱数,两者的差就是本次需要补算的增量。 以一段典型结构为例,当 prev_calculated == 0 时做全量初始化,否则只从 prev_calculated - 1 开始循环,这样能避免边界处一根柱的断裂,又省掉大量已确定结果的重算。外汇与贵金属市场高杠杆、跳空频繁,这种写法在实盘多标的监控时可能显著降低卡顿概率。
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 性能榨出两三倍,但写得好的一行代码往往更省钱——经济式编码让普通办公机可能跑出顶配机的回测吞吐,虽救不了古董赛扬,但能实打实省下等待时间。外汇与贵金属回测涉及高杠杆高风险,耗时放大不代表信号更可靠,仅意味算力被空耗。
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 数据验证延迟表现。
class=class="str">"cmt">// 缓冲区的值复制到特别准备好的静态数组中 CopyBuffer(Handle, class="num">0, class="num">0, class="num">1, SMA); class=class="str">"cmt">//----+