小记 在 MQL 中 编写自定义指标时的一些需要注意的地方。
环境: Meta Trader 5
指标(Indicator)有一个生命周期函数OnCalculate
,
其函数原型为:
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[]);
其返回值意义为“完成计算的条(Bar)数”。
其真实计算产生的数据并不通过此函数的返回值返回,而是通过全局变量 Buffer
来返回。
OnCalculate 的 调用( invoke )时机
这里强调 invoke 的原因是为了与主动调用 call 区分开来,生命周期函数通常意味着一个事件。你不应该主动去调用 OnCalculate 函数,而应该等待系统接受某种信号后自己去调用这个函数。
那么所谓信号是什么?
当交易系统接受到新的数据时,在将数据推入 time, open, high, low, close, … 数组后立即调用 OnCalculate 函数。
OnCalculate中的一些潜规则
在 OnCalculate 函数 开始执行时,系统保证:
- rates_total 与 time, open, high , … 等数组的长度一致,等于实际接收到的总数据(tick)数。
而编程者应该保证:
- 返回值为处理完毕1的总数据数,这应当是不超过 rates_total 的。
基于以上保证,prev_calculate即你之前处理完毕的数据数,默认为0(什么都没有处理)。
也就是说, 上一次调用 OnCalculate 所返回的值会被缓存成prev_calculated 在这次调用 OnCalculate的时候作为参数传入。
性能优化
首先考虑一下 OnCalculate 函数可能处理的数据量。
市场价格更新频率: 分笔(全世界只要发生可观测交易就会变化,可能每秒都有若干笔数据)
数据量:按照周期缓存(简单估算一下如果1小时缓存一个数据,每1年有5000左右个数据)
几年的数据积累下来会有10000 ~ 30000 bars 的数据,这在回测中是比较正常的。
这通常意味着在通常情况下,OnCalculate 需要完整更新整个指标序列,在1秒内计算若干次 10000~ 30000 的数据量,有时候,完成这些计算还不是常数时间代价的……更致命的是,一旦你在计算上慢了一步,商机有可能就会被错过。
很容易发现,通常情况下,过去的指标是不需要发生变化的。比如“2016年3月10日 19:00处的3小时收盘均值”是不会随着时间改变而改变的。因此我们其实并不需要重复计算这些部分。
那么问题简单了,我们需要多记录一个,哪个位置之前的数据是不需要计算的,那就是 prev_calculated。因此,在编程时,编程人员应该时刻遵守这个约定来给出返回值。
通常情况下,在函数结尾 return rates_total;
是最好的方法。
既然它已经把数据给OnCalculate了,