以经济方式计算指标的原则·进阶篇
「T3 这类递归指标别乱套重算优化」
把「只算一次收盘柱」的优化硬套到所有指标上会翻车。像 T3 这种变量带递归依赖的指标,公式本质是 SomeVariable(bar) = Function(SomeVariable(bar-1)),如果当前未收盘柱被反复跳过不重算,加载历史时算出来的全线数值都会失真。 原文里 e1~e6 就是典型:每次新柱进来若不清零还原,它们会沿用上一根柱算完后的状态继续滚,导致当前柱的值在收盘前一直漂。正确做法是把上一柱的 e1~e6 存进局部静态变量,新柱第一次触及时先还原再算。 实操上,OnCalculate 里声明 static double e1_ 到 e6_,在 rates_total!=prev_calculated 且 bar==rates_total-1 时把当前值记下来;循环前再用 e1=e1_ 这类赋值倒回去。第一次启动 prev_calculated==0 时统一用 price[first] 初始化六个静态量,系数只初始化一次。 验证办法很直接:把未优化版和优化版 T3 同时挂图,逐一把恢复列表里的变量一个一个摘掉,看哪几个摘了曲线就劈叉——剩下的就是非存不可的。重绘类或看未来函数的指标走不通这套,老手一般也不碰。外汇与贵金属杠杆高,递归指标参数错一处就可能输出完全反向的信号,上 MT5 自测前别轻信任何现成版本。
e1 = w1 * series + w2 * e1; e2 = w1 * e1 + w2 * e2; e3 = w1 * e2 + w2 * e3; e4 = w1 * e3 + w2 * e4; e5 = w1 * e4 + w2 * e5; e6 = w1 * e5 + w2 * e6; class=class="str">"cmt">//---- T3 = c1 * e6 + c2 * e5 + c3 * e4 + c4 * e3; class=class="str">"cmt">//---- 声明用于存储系数有效值的静态变量 class="kw">static class="type">class="kw">double e1_, e2_, e3_, e4_, e5_, e6_; class=class="str">"cmt">//---- 在运行至当前柱之前记录变量值 if (rates_total != prev_calculated && bar == rates_total - class="num">1) { e1_ = e1; e2_ = e2; e3_ = e3; e4_ = e4; e5_ = e5; e6_ = e6; } class=class="str">"cmt">//---- 恢复变量值 e1 = e1_; e2 = e2_; e3 = e3_; e4 = e4_; e5 = e5_; e6 = e6_; class=class="str">"cmt">//---- 首先计算用于重新计算柱的起始编号 if (prev_calculated == class="num">0) class=class="str">"cmt">// 确认这是指标的第一次计算 { first = begin; class=class="str">"cmt">// 计算所有柱的起始编号 class=class="str">"cmt">//---- 开始所计算系数的初始化 e1_ = price[first]; e2_ = price[first]; e3_ = price[first]; e4_ = price[first]; e5_ = price[first]; e6_ = price[first]; }
◍ 指标调用里拖慢 MT5 的隐藏写法
从自定义指标取数,本质是把指标缓存通过 CopyBuffer() 复制到本地数组。很多例子图省事,先把整个缓存塞进中间动态数组,再用 for 循环逐个搬到最终缓冲区——这套写法在回测里会暴露出惊人代价。 用 iAMA 派生出的 dAMA 指标做对照测试,一年周期下:直接按需复制的 EA 测试耗时 43,937 ms;先整段搬进中间数组再循环的版本耗时 960,625 ms,慢了 20 倍以上。外汇与贵金属品种点差和跳空频繁,这种多余复制在实时 tick 推送下可能进一步放大延迟。 根因在于每次价格变动都把已存在的旧值重写一遍。正确做法是只复制三类位置:新出现的指标柱、已收盘指标柱、当前未收盘指标柱,其余缓存值留着别动。 下面这段代码上半部分是反面教材,下半部分演示了用 prev_calculated 算 to_copy 只搬增量、并直接写进 AMA_Buffer 的写法。局部动态数组仅用于标准方差等中间计算,不再充当全量中转站。
if (CopyBuffer(AMA_Handle, class="num">0, class="num">0, rates_total, Array) <= class="num">0) class="kw">return(class="num">0); ArrayCopy(AMA_Buffer, Array, class="num">0, class="num">0, WHOLE_ARRAY); if (CopyBuffer(AMA_Handle, class="num">0, class="num">0, rates_total, Array) <= class="num">0) class="kw">return(class="num">0); for(bar = class="num">0; bar < rates_total; bar++) { AMA_Buffer[bar] = Array[bar]; class=class="str">"cmt">/* 这里是指标计算的代码 */ } class=class="str">"cmt">//--- 计算所需复制数据的数量 class="type">int to_copy; if(prev_calculated > rates_total || prev_calculated <= class="num">0)class=class="str">"cmt">// 确认第一次进行指标计算 to_copy = rates_total - begin; class=class="str">"cmt">// 计算全部柱的数量 else to_copy = rates_total - prev_calculated + class="num">1; class=class="str">"cmt">// 仅计算新柱的数量 class=class="str">"cmt">//--- 把重新出现的数据复制到指标缓冲区 AMA_Buffer[] if (CopyBuffer(AMA_Handle, class="num">0, class="num">0, to_copy, AMA_Buffer) <= class="num">0) class="kw">return(class="num">0); class=class="str">"cmt">//---- 声明局部数组 class="type">class="kw">double dAMA_Array[], StdDev_Array[]; class=class="str">"cmt">//---- 数组的元素指数类似时间序列 ArraySetAsSeries(dAMA_Array, true); ArraySetAsSeries(StdDev_Array, true); class=class="str">"cmt">//--- 计算所需复制的数据数量 class="type">int to_copy; if(prev_calculated > rates_total || prev_calculated <= class="num">0)class=class="str">"cmt">// 确认进行第一次指标计算 to_copy = rates_total - begin; class=class="str">"cmt">// 计算全部柱的数量 else to_copy = rates_total - prev_calculated + class="num">1; class=class="str">"cmt">// 仅计算新柱的数量 class=class="str">"cmt">//--- 把新出现的数据复制到指标缓冲区和局部动态数组中 if(CopyBuffer(dAMAHandle, class="num">1, class="num">0, to_copy, AMABuffer ) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(dAMAHandle, class="num">0, class="num">0, to_copy, dAMA_Array ) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(StdDevHandle, class="num">0, class="num">0, to_copy, StdDev_Array) <= class="num">0) class="kw">return(class="num">0);
把指标计算塞进一个指标里跑测试
连续跨指标层层调用的结构太绕,想验证就得换条路:不动 AMkA 指标本身的逻辑,而是把它的计算直接搬进用户指标内部,不再向外请求其他指标。对熟手来说这不算大改——AMA.mq5 已有底子,往里补一段类似 dAMA.mq5 算 StDev 的循环即可,余下的框架和原 AMkA.mq5 几乎一致。 用 EA 跑对比测试时,内嵌版的 AMkA_.mq5 没遇到阻力,耗时也在可接受区间;而靠外部连环调用的 AMkA.mq5 慢得离谱,实测比前者慢了七倍以上。外汇与贵金属行情跳变频繁,这种延迟在高波动时段可能直接拖垮信号触发。 结论很直接:搞几个指标一个调一个的嵌套链,只适合做初步验证,真要上实盘机器人就得把计算收拢到单层。当前数据来自客户端测试版,以后优化器改动后表现可能变化,但结构冗余这件事已经实锤。 下面这段是内嵌计算的核心循环,注意它先把 AMA 差分装进数组,再算均值与均方差: //---- 计算 AMkA 指标的主循环 for(bar = first; bar < rates_total; bar++) { //---- 把AMA指标的增量载入数组用于中间计算 for(iii = 0; iii < ama_period; iii++) dAMA[iii] = AMABuffer[bar - iii - 0] - AMABuffer[bar - iii - 1]; //---- 计算AMA增量的简单平均数 Sum = 0.0; for(iii = 0; iii < ama_period; iii++) Sum += dAMA[iii]; SMAdif = Sum / ama_period; //---- 计算增量的平方差的和以及平均数 Sum = 0.0; for(iii = 0; iii < ama_period; iii++) Sum += MathPow(dAMA[iii] - SMAdif, 2); //---- 从 AMA 增量计算 StDev 均方差的最终值 StDev = MathSqrt(Sum / ama_period); 中文逐行拆解:外层 for 遍历每个 bar;内嵌第一层 for 把 AMA 缓冲区相邻差写进 dAMA 数组;接着求和除以周期得到 SMAdif 均值;再一层 for 累加 (dAMA-均值) 的平方;最后开平方得出 StDev。改 ama_period 能直接看计算量如何放大耗时。
class=class="str">"cmt">//---- 计算 AMkA 指标的主循环 for(bar = first; bar < rates_total; bar++) { class=class="str">"cmt">//---- 把AMA指标的增量载入数组用于中间计算 for(iii = class="num">0; iii < ama_period; iii++) dAMA[iii] = AMABuffer[bar - iii - class="num">0] - AMABuffer[bar - iii - class="num">1]; class=class="str">"cmt">//---- 计算AMA增量的简单平均数 Sum = class="num">0.0; for(iii = class="num">0; iii < ama_period; iii++) Sum += dAMA[iii]; SMAdif = Sum / ama_period; class=class="str">"cmt">//---- 计算增量的平方差的和以及平均数 Sum = class="num">0.0; for(iii = class="num">0; iii < ama_period; iii++) Sum += MathPow(dAMA[iii] - SMAdif, class="num">2); class=class="str">"cmt">//---- 从 AMA 增量计算 StDev 均方差的最终值 StDev = MathSqrt(Sum / ama_period);
「EA里调指标的两个提速陷阱」
EA存取指标数据的大部分优化手段和指标自身优化是通用的,但EA还多一层变量:很多EA只在柱闭合切换时才处理指标,没必要每次 tick 都跑 CopyBuffer()。把复制逻辑塞进新柱判断块里,每根柱只进一次,能省掉大量无用调用。 IsNewBar() 是个好用的过滤器,传图表编号、品种、周期进去,当前柱变了才返回真。下面这段就是它的通用版和EA里的两种用法。
<span class="keyword">class="type">bool</span> IsNewBar( <span class="keyword">class="type">int</span> Number, <span class="comment">class=class="str">"cmt">// 在EA交易程序代码中调用 IsNewBar 函数的编号</span> <span class="keyword">class="type">class="kw">string</span> symbol, <span class="comment">class=class="str">"cmt">// 进行数据计算的图表交易品种</span> <span class="keyword">ENUM_TIMEFRAMES</span> timeframe <span class="comment">class=class="str">"cmt">// 进行数据计算的图表时间框架</span> ) <span class="comment">class=class="str">"cmt">//---- 声明静态变量 - 用于储存 AMA 指标值的数组</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">double</span> AMA_Array[<span class="number">class="num">3</span>]; <span class="comment">class=class="str">"cmt">//---- 调用AMA指标,复制其数据到AMA_Array数组</span> <span class="keyword">if</span> (IsNewBar(<span class="number">class="num">0</span>, <span class="functions">Symbol</span>(), <span class="number">class="num">0</span>)) { <span class="functions">CopyBuffer</span>(AMA_Handle, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, <span class="number">class="num">3</span>, AMA_Array); } <span class="comment">class=class="str">"cmt">//---- 声明静态变量- 用于储存AMA指标值的数组</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">double</span> AMA_Array[<span class="number">class="num">3</span>]; <span class="comment">class=class="str">"cmt">//---- 声明静态变量用于保存从AMA指标复制数据的结果</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">bool</span> Recount; <span class="comment">class=class="str">"cmt">//---- 调用AMA指标,复制其数据到AMA_Array数组</span> <span class="keyword">if</span> (IsNewBar(<span class="number">class="num">0</span>, <span class="functions">Symbol</span>(), <span class="number">class="num">0</span>) || Recount) { <span class="keyword">if</span> (CopyBuffer(AMA_Handle, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, <span class="number">class="num">3</span>, AMA_Array) < <span class="number">class="num">0</span>) { Recount = <span class="keyword">true</span>; <span class="comment">class=class="str">"cmt">// 复制数据的尝试没有成功 </span> <span class="keyword">class="kw">return</span>; <span class="comment">class=class="str">"cmt">// 退出 OnTick() 函数</span> } <span class="comment">class=class="str">"cmt">//---- 所有从指标缓冲区复制数据的操作都成功完成</span> <span class="comment">class=class="str">"cmt">// 直到下一次柱改变都无需回到此区块</span> Recount = <span class="keyword">false</span>; } <span class="comment">class=class="str">"cmt">//---- 指标计算主循环</span> <span class="keyword">for</span>(bar = first; bar < rates_total; bar++) <span class="comment">class=class="str">"cmt">//---- 指标计算主循环</span> <span class="keyword">for</span>(bar = first; bar < rates_total - <span class="number">class="num">1</span>; bar++)
<span class="keyword">class="type">bool</span> IsNewBar( <span class="keyword">class="type">int</span> Number, <span class="comment">class=class="str">"cmt">// 在EA交易程序代码中调用 IsNewBar 函数的编号</span> <span class="keyword">class="type">class="kw">string</span> symbol, <span class="comment">class=class="str">"cmt">// 进行数据计算的图表交易品种</span> <span class="keyword">ENUM_TIMEFRAMES</span> timeframe <span class="comment">class=class="str">"cmt">// 进行数据计算的图表时间框架</span> ) <span class="comment">class=class="str">"cmt">//---- 声明静态变量 - 用于储存 AMA 指标值的数组</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">double</span> AMA_Array[<span class="number">class="num">3</span>]; <span class="comment">class=class="str">"cmt">//---- 调用AMA指标,复制其数据到AMA_Array数组</span> <span class="keyword">if</span> (IsNewBar(<span class="number">class="num">0</span>, <span class="functions">Symbol</span>(), <span class="number">class="num">0</span>)) { <span class="functions">CopyBuffer</span>(AMA_Handle, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, <span class="number">class="num">3</span>, AMA_Array); } <span class="comment">class=class="str">"cmt">//---- 声明静态变量- 用于储存AMA指标值的数组</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">double</span> AMA_Array[<span class="number">class="num">3</span>]; <span class="comment">class=class="str">"cmt">//---- 声明静态变量用于保存从AMA指标复制数据的结果</span> <span class="keyword">class="kw">static</span> <span class="keyword">class="type">bool</span> Recount; <span class="comment">class=class="str">"cmt">//---- 调用AMA指标,复制其数据到AMA_Array数组</span> <span class="keyword">if</span> (IsNewBar(<span class="number">class="num">0</span>, <span class="functions">Symbol</span>(), <span class="number">class="num">0</span>) || Recount) { <span class="keyword">if</span> (CopyBuffer(AMA_Handle, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, <span class="number">class="num">3</span>, AMA_Array) < <span class="number">class="num">0</span>) { Recount = <span class="keyword">true</span>; <span class="comment">class=class="str">"cmt">// 复制数据的尝试没有成功 </span> <span class="keyword">class="kw">return</span>; <span class="comment">class=class="str">"cmt">// 退出 OnTick() 函数</span> } <span class="comment">class=class="str">"cmt">//---- 所有从指标缓冲区复制数据的操作都成功完成</span> <span class="comment">class=class="str">"cmt">// 直到下一次柱改变都无需回到此区块</span> Recount = <span class="keyword">false</span>; } <span class="comment">class=class="str">"cmt">//---- 指标计算主循环</span> <span class="keyword">for</span>(bar = first; bar < rates_total; bar++) <span class="comment">class=class="str">"cmt">//---- 指标计算主循环</span> <span class="keyword">for</span>(bar = first; bar < rates_total - <span class="number">class="num">1</span>; bar++)
◍ 把这条线请下神坛
代码效率在 MT5 里是个可测量的硬指标,不是玄学。用 OnCalculate 的 prev_calculated 比对 rates_total,就能判断指标是首跑还是续算,逻辑清晰就能少算无用柱。 上面这段来自原文末节的判断式,本质是防重算的护栏:当 prev_calculated 大于总柱数或小于等于 0,说明首次启动或上下文异常。 外汇与贵金属杠杆高、滑点随机,这类底层优化只解决「算得对、算得快」,不承诺任何收益。调通它,你至多省下几十毫秒和几行冗余,仅此而已。
if(prev_calculated > rates_total || prev_calculated <= class="num">0)class=class="str">"cmt">// 检查指标计算是否首次开始