MQL指标:理解生命周期函数、首态数据处理与尾部动态更新
环境: Meta Trader 5 指标(Indicator)有一个生命周期函数 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)数”。 OnCalculate 的 调用( invoke )时机这里强调 invoke 的原因是为了与主动调用 call 区分开来,生命周期函数通常意味着一个事件。你不应该主动去调用 OnCalculate 函数,而应该等待系统接受某种信号后自己去调用这个函数。 那么所谓信号是什么?
OnCalculate中的一些潜规则在 OnCalculate 函数 开始执行时,系统保证:
而编程者应该保证:
基于以上保证,prev_calculate即你之前处理完毕的数据数,默认为0(什么都没有处理)。 也就是说, 上一次调用 OnCalculate 所返回的值会被缓存成prev_calculated 在这次调用 OnCalculate的时候作为参数传入。 性能优化首先考虑一下 OnCalculate 函数可能处理的数据量。 市场价格更新频率: 分笔(全世界只要发生可观测交易就会变化,可能每秒都有若干笔数据)
这通常意味着在通常情况下,OnCalculate 需要完整更新整个指标序列,在1秒内计算若干次 10000~ 30000 的数据量,有时候,完成这些计算还不是常数时间代价的……更致命的是,一旦你在计算上慢了一步,商机有可能就会被错过。 很容易发现,通常情况下,过去的指标是不需要发生变化的。比如“2016年3月10日 19:00处的3小时收盘均值”是不会随着时间改变而改变的。因此我们其实并不需要重复计算这些部分。 那么问题简单了,我们需要多记录一个,哪个位置之前的数据是不需要计算的,那就是 prev_calculated。因此,在编程时,编程人员应该时刻遵守这个约定来给出返回值。 通常情况下,在函数结尾
接下来给出一个基本的范式。 int OnCalculate(const int rates_total,const int &spread[])
{
//--- selectively update
for(int i = prev_calculated; i < rates_total; i++){
//--- TODO: deal with new bars
}
//--- return value of prev_calculated for next call
return rates_total;
}
首态数据处理通常情况下,指标并不是基于一个数据2的,而是需要回溯之前的若干数据的。 如移动平均线(Moving Average),波动率(Volatility)通常基于多个时间点的数据的计算。 因此不可避免地会遇到下标越界的问题: int OnCalculate(const int rates_total,const int &spread[])
{
//--- selectively update
for(int i = prev_calculated; i < rates_total; i++){
Buffer[i] = (close[i] - close[i - 1]) / close[i - 1]; // => over range!
}
//--- return value of prev_calculated for next call
return rates_total;
}
上面的代码会遭遇越界。因为在第一次调用
所以我们需要一个特殊的逻辑来处理这个初始状态下的数据。 {
int from = prev_calculated,to = rates_total;
//--- first state
if(from <= 0){
Buffer[0] = 0; // or any value you like
from = 1; // change the value of from
}
// range update
for(int i = from; i < to; i++){
Buffer[i] = (close[i] - close[i - 1]) / close[i - 1]; // => okay
}
//--- return value of prev_calculated for next call
return rates_total;
}
由于 上述代码在实际运行时仍然会发生错误,具体请看接下来的尾部动态更新。 尾部动态更新在MT5中,接收周期内数据是不增加实际的bars数量的。 比如,在1小时周期内,最后1小时的所有数据都会被整合到
这是因为实际上系统可能是这样调用OnCalculate的: OnCalculate(3,0,...) // => return 3 (first state)
OnCalculate(4,3,...) // => return 4 (selectively update [3,4) )
OnCalculate(4,4,...) // => return 4 (receive data in period,but do nothing)
OnCalculate(4,but do nothing)
// ...
OnCalculate(5,...) // => return 5 (selectively update [4,5) )
真的什么都不要做吗? 显然,这个时候不能忽略这些数据,应当进行尾部更新 {
int from = prev_calculated,to = rates_total;
//--- first state
if(from <= 0){
Buffer[0] = 0; // or any value you like
from = 1; // change the value of from
}
// range update
for(int i = from; i < to; i++){
Buffer[i] = (close[i] - close[i - 1]) / close[i - 1]; // => okay
}
// dynamic update in tail
if(from == to){
Buffer[from - 1] = (close[from - 1] - close[from - 2]) / close[from - 2];
}
//--- return value of prev_calculated for next call
return rates_total;
}
如此,尾部的动态更新就完成了。 结语在本文中总结了编写指标的几个坑点。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |