如何在 MetaTrader 5 里快速开发并调试交易策略·进阶篇
逐笔报价怎么塞进指标缓冲区
在自定义指标里想做逐笔 Tick 统计,第一步是把三个数组清零并改成时间序列方向。ZeroMemory 把 TickPriceBuffer、SignalBuffer、SignalColors 清掉旧值,随后 ArraySetAsSeries 带 true 参数让下标 0 永远指向最新一根,写缓存时不用每次算尾索引。 核心移位逻辑在 ApplyTick 里:用 ArrayCopy 把自身从 0 拷到 1、长度减一,等于全体后移一格,再把 price 写进 [0]。这段没有循环,ArraySize 取长度后三次拷贝完成价格、信号、颜色的同步滚动。 主计算段用 lastprice=close[rates_total-1] 取当前收盘价当报价源,ticks_counter 每来一次新价自增,再调 ApplyTick 完成移位。最后 return(rates_total) 让 MT5 下次用相同总量调用,避免重算历史。 回测时若勾了「每笔 tick」,ticks_counter 在 EURUSD M1 上单根可能跳几十到上百次,缓冲区 size 设太小会丢早期样本,建议开 MT5 把 size 打印出来确认。外汇与贵金属杠杆高,tick 级信号仅作概率参考,实盘前务必在策略测试器跑够样本。
ZeroMemory(TickPriceBuffer); ZeroMemory(SignalBuffer); ZeroMemory(SignalColors); class=class="str">"cmt">//--- 序列数组向后定位, 在这种情况下最方便 ArraySetAsSeries(SignalBuffer,true); ArraySetAsSeries(TickPriceBuffer,true); ArraySetAsSeries(SignalColors,true); first=class="kw">false; } class=class="str">"cmt">//--- 使用当前的收盘价作为价格 class="type">class="kw">double lastprice=close[rates_total-class="num">1]; class=class="str">"cmt">//--- 瞬时报价计数 ticks_counter++; ApplyTick(lastprice); class=class="str">"cmt">// 执行计算和缓存区移位 class=class="str">"cmt">//--- 返回 prev_calculated 的数值用于下次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 应用瞬时报价进行计算 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ApplyTick(class="type">class="kw">double price) { class="type">int size=ArraySize(TickPriceBuffer); ArrayCopy(TickPriceBuffer,TickPriceBuffer,class="num">1,class="num">0,size-class="num">1); ArrayCopy(SignalBuffer,SignalBuffer,class="num">1,class="num">0,size-class="num">1); ArrayCopy(SignalColors,SignalColors,class="num">1,class="num">0,size-class="num">1); class=class="str">"cmt">//--- 保存最后的价格值 TickPriceBuffer[class="num">0]=price; class=class="str">"cmt">//--- } class=class="str">"cmt">//--- 只在价格变化时计算 if(lastprice!=TickPriceBuffer[class="num">0]) { ticks_counter++; class=class="str">"cmt">// 瞬时报价计数 ApplyTick(lastprice); class=class="str">"cmt">// 执行计算和缓存区移位 }
「用独立缓冲存报价差值再算标准差」
给瞬时报价指标加一个辅助缓冲区是最直接的做法:单独开一个 DeltaTickBuffer 数组,只用来装每笔 tick 的价格增量,主价格序列和信号数组不会被这类中间计算污染。上面代码片段里缓冲区总数从 3 扩到 4,第 3 号索引绑定给 DeltaTickBuffer 且类型设为 INDICATOR_CALCULATIONS,说明它不参与绘图,只供后台迭代。 初始化时把这个数组也 ZeroMemory 清掉,并用 ArraySetAsSeries(...,true) 设成时间倒序,和另外三个缓冲区的处理方式保持一致,这样在 OnCalculate 首次触发时所有序列索引方向统一,写循环不会错位。 ApplyTick 函数里用 ArrayCopy 把 DeltaTickBuffer 整体向后挪一位,再把最新差值写进 [0],相当于维护一个滚动的差值窗口。之后另写 getStdDev() 去暴力遍历这个窗口就能出标准差,逻辑层和绘图层彻底分开。 代码写完后按 F5 进 MT5 调试,终端里能直接看到红蓝圆圈的 signal 绘制和差值缓冲的联动;外汇和贵金属 tick 频率高,这类高频计算在实盘可能拖慢执行,调试阶段就该顺手查掉明显的循环冗余。
class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">4 class="macro">#class="kw">property indicator_plots class="num">2 ... class=class="str">"cmt">//--- 指标缓存区 class="type">class="kw">double TickPriceBuffer[]; class="type">class="kw">double SignalBuffer[]; class="type">class="kw">double DeltaTickBuffer[]; class="type">class="kw">double ColorsBuffers[]; ... class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓存区映射 SetIndexBuffer(class="num">0,TickPriceBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,SignalBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">2,SignalColors,INDICATOR_COLOR_INDEX); SetIndexBuffer(class="num">3,DeltaTickBuffer,INDICATOR_CALCULATIONS); ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const ...) class=class="str">"cmt">//--- 在首次调用期间将指标缓存区清零并标记为序列 if(first) { ZeroMemory(TickPriceBuffer); ZeroMemory(SignalBuffer); ZeroMemory(SignalColors); ZeroMemory(DeltaTickBuffer); class=class="str">"cmt">//--- 序列数组向后定位, 在这种情况下最方便 ArraySetAsSeries(TickPriceBuffer,true); ArraySetAsSeries(SignalBuffer,true); ArraySetAsSeries(SignalColors,true); ArraySetAsSeries(DeltaTickBuffer,true); first=class="kw">false; } ... class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 应用瞬时报价进行计算 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ApplyTick(class="type">class="kw">double price) { class="type">int size=ArraySize(TickPriceBuffer); ArrayCopy(TickPriceBuffer,TickPriceBuffer,class="num">1,class="num">0,size-class="num">1); ArrayCopy(SignalBuffer,SignalBuffer,class="num">1,class="num">0,size-class="num">1); ArrayCopy(SignalColors,SignalColors,class="num">1,class="num">0,size-class="num">1); ArrayCopy(DeltaTickBuffer,DeltaTickBuffer,class="num">1,class="num">0,size-class="num">1); class=class="str">"cmt">//--- 保存最后的价格值 TickPriceBuffer[class="num">0]=price;
◍ 用 tick 级标准差抓报价异常跳变
在 MT5 的自定义指标里,把每个即时报价(tick)的价格差存进 DeltaTickBuffer,再拿最近 ticks 个样本算标准差,就能给盘口噪声定一个动态基准。比起写死的点值阈值,这种方法在伦敦盘与亚盘波动率切换时不容易误报。 下面这段是标准差的暴力实现:先累加所有 Delta 求均值 average,再跑第二轮把每个样本与均值的差的平方求和,最后除以 (number-1) 开根号。注意分母是 number-1 而非 number,这是样本标准差,对小样本更保守。 ApplyTick 函数负责把旧数组整体后移一位(ArrayCopy 偏移 1),把新 price 塞进 [0],重算 Delta 与 stddev。当 MathAbs(DeltaTickBuffer[0]) 大于 gap*stddev 时,认为出现了异常跳变:涨用蓝色点、跌用红色点,并 PrintFormat 输出触发时的报价、变化点数、阈值点数和标准差点数,方便回看日志。 实盘里把 gap 从默认 3 调到 4,EURUSD 在常规时段触发频率可能下降约 40%,但重大数据行情仍会连发信号。外汇与贵金属杠杆高,这类信号只描述波动特征,不预示方向,验证时请在策略测试器用真实 tick 数据跑一遍。
class=class="str">"cmt">//--- 计算与前一数值的差值 DeltaTickBuffer[class="num">0]=TickPriceBuffer[class="num">0]-TickPriceBuffer[class="num">1]; class=class="str">"cmt">//--- 得到标准差 class="type">class="kw">double stddev=getStdDev(ticks); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "暴力" 计算标准差 |//+------------------------------------------------------------------+ class="type">class="kw">double getStdDev(class="type">int number) { class="type">class="kw">double summ=class="num">0,sum2=class="num">0,average,stddev; class=class="str">"cmt">//--- 统计变化之和, 并计算期望收益 for(class="type">int i=class="num">0;i<ticks;i++) summ+=DeltaTickBuffer[i]; average=summ/ticks; class=class="str">"cmt">//--- 现在计算标准差 sum2=class="num">0; for(class="type">int i=class="num">0;i<ticks;i++) sum2+=(DeltaTickBuffer[i]-average)*(DeltaTickBuffer[i]-average); stddev=MathSqrt(sum2/(number-class="num">1)); class="kw">return (stddev); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 应用瞬时报价进行计算 |//+------------------------------------------------------------------+ class="type">void ApplyTick(class="type">class="kw">double price) { class="type">int size=ArraySize(TickPriceBuffer); ArrayCopy(TickPriceBuffer,TickPriceBuffer,class="num">1,class="num">0,size-class="num">1); ArrayCopy(SignalBuffer,SignalBuffer,class="num">1,class="num">0,size-class="num">1); ArrayCopy(SignalColors,SignalColors,class="num">1,class="num">0,size-class="num">1); ArrayCopy(DeltaTickBuffer,DeltaTickBuffer,class="num">1,class="num">0,size-class="num">1); class=class="str">"cmt">//--- 保存最后的价格值 TickPriceBuffer[class="num">0]=price; class=class="str">"cmt">//--- 计算与前一数值的差值 DeltaTickBuffer[class="num">0]=TickPriceBuffer[class="num">0]-TickPriceBuffer[class="num">1]; class=class="str">"cmt">//--- 得到标准差 class="type">class="kw">double stddev=getStdDev(ticks); class=class="str">"cmt">//--- 如果价格变化超出指定的阀值 if(MathAbs(DeltaTickBuffer[class="num">0])>gap*stddev) class=class="str">"cmt">// 将会在第一个瞬时报价上显示一个信号, 保留它作为一个 "特征" { SignalBuffer[class="num">0]=price; class=class="str">"cmt">// 放置一个圆点 class="type">class="kw">string col="Red"; class=class="str">"cmt">// 圆点省缺为红色 if(DeltaTickBuffer[class="num">0]>class="num">0) class=class="str">"cmt">// 价格暴涨 { SignalColors[class="num">0]=class="num">1; class=class="str">"cmt">// 则圆点为蓝色 col="Blue"; class=class="str">"cmt">// 保存日志 } else class=class="str">"cmt">// 价格暴跌 SignalColors[class="num">0]=class="num">0; class=class="str">"cmt">// 圆点为红色 class=class="str">"cmt">//--- 输出消息至智能程序日志 PrintFormat("瞬时报价=%G 变化=%.1f pts, 触发=%.3f pts, 标准差=%.3f pts %s", TickPriceBuffer[class="num">0],DeltaTickBuffer[class="num">0]/_Point,gap*stddev/_Point,stddev/_Point,col); }
无信号时的缓冲归零处理
在自定义指标的信号计算收尾处,当价格行为不满足任何触发条件时,需要将当前柱的信号缓冲直接置零。
这一行 else SignalBuffer[0]=0; 紧接前面的 if 判断,作用是将第 0 号元素(即当前未闭合或刚闭合的柱)的信号值清为 0,避免上一根柱的残留信号污染实时显示。
从 MT5 数据窗口观察,若省略该赋值,指标线可能在信号消失后仍维持前值的视觉假信号,尤其在切换周期时更易暴露。
外汇与贵金属市场波动剧烈、杠杆风险高,这类缓冲清理虽小,却是回测与实盘一致性的一道防线,建议开 MT5 把这段贴进 OnInit 后的信号函数里跑一遍验证。
else SignalBuffer[class="num">0]=class="num">0; class=class="str">"cmt">// 无信号 class=class="str">"cmt">//--- }