为什么你的EA会在Time[0]上突然崩掉,而4066/4073才是真凶
这事我遇到过。一台VPS上跑了十一个月的EA,某天下午突然开始疯狂刷屏:
array out of range,报错的行号指向一句最简单不过的代码——Time[0]。逻辑没改。没重新编译。它就是在那个下午决定不干了。
如果你也撞上过这个,恭喜你,进了个挺冷门的俱乐部。MQL4的官方文档说
Time[0]就是“当前图表”的时间,那它怎么可能越界?我把这件事的来龙去脉拆开讲,因为修复方法跟表面看起来的完全不一样,而且官方文档根本没写清楚。到底什么东西触发了这个报错
Time[]是一个序列数组。MQL4里序列数组按时间倒序排列,索引0是最新的一根K线。这个数组由终端维护,不是你的EA维护的。你的EA调用Time[0],终端理论上应该直接把当前K线的开盘时间递给你。问题出在“理论上”和“实际上”的差别。
这里有两个错误码需要分清,而它们经常被混为一谈:
ERR_HISTORY_WILL_UPDATED。请求的历史数据正在更新中。这个是文档里写了的。MQL4参考手册明确说,当你请求其他品种或时间周期的数据时,可能会拿到4066,需要等一会儿重试。最恶心的地方在于:你的EA可能在
Time[0]上抛出“数组越界”,而4066和4073都不是直接原因。 终端的内部序列数组在历史数据重同步的那一瞬间可能是空的或者被截断的,于是Time[0]就变成了对一个长度为零的数组的访问。这在MQL4里是致命运行时错误——除零、数组越界、无效指针,程序直接终止。你不会拿到一个优雅的
GetLastError()返回值。你的EA直接死了。真正管用的修复:用iTime()
在六个月里撞了两次之后,我彻底不在
OnTick()里用Time[]了。原因很简单:
iTime()是一个函数,不是序列数组的直接访问。它在内部做了边界检查,如果那根K线不可用,它会返回0(或者一个安全的回退值),而不是让程序崩溃。数据是一样的,但访问方式不同,不会在底层缓冲区暂时为空的时候把你炸掉。``
mql4
// 不要在OnTick()里这样用,除非你有保护
datetime barTime = Time[0]; // 历史重同步时可能崩溃
// 改成这样
datetime barTime = iTime(Symbol(), Period(), 0);
if(barTime == 0)
{
// 历史数据没准备好,跳过这个tick
return;
}
`
论坛里关于这个问题的帖子不多,但口径是一致的:处理过4073的人推荐用iTime()作为更安全的封装。它不能阻止经纪商拒绝历史请求,但它能阻止你的EA在那种时候死掉。
那ArrayCopySeries()呢?
MQL4官方参考手册提供了ArrayCopySeries()作为从其他品种或周期拉数据的“正规”方法。文档里的模式是:
调用 ArrayCopySeries()。
检查 GetLastError()是不是4066。
如果是4066,用 Sleep()重试。
这个方法对跨品种或跨周期请求是合适的。但对当前图表的Time[0]来说,它是杀鸡用牛刀,而且它不解决瞬态截断的问题——ArrayCopySeries()在终端重同步中途仍然可能返回-1或者复制零个元素。
我的真实看法:如果你只是读当前图表的时间,直接用iTime()。如果你要从别的品种拉历史数据,用ArrayCopySeries()加上文档里的重试循环。别把两种方法混在一起。
一个没人提的恶心细节
还有一个二阶故障模式,我花了挺久才搞明白。
当MT4截断图表K线的时候(大多数版本硬上限是50,000根),序列数组会位移。原来在索引0的值会移动,缓冲区被重新填充,prev_calculated可能被重置为零。如果你的EA或指标缓存了上一个tick的数组索引,然后在截断事件之后继续用,你拿到的越界错误可能跟Time[0]本身没关系——但它会显示在同一行,因为逻辑链是从那里开始的。
这里的修复方案很无聊但必须做:永远不要跨tick缓存序列数组的索引而不重新验证。如果你五分钟前存了Time[5],然后假设它还是同一根K线,在任何历史截断发生之后你就是错的。
我现在用的最小保护模式
这是我每个碰价格数据的EA的OnTick()开头都会放的精简版:
`mql4
bool HistoryReady()
{
int bars = iBars(Symbol(), Period());
if(bars < 10) // 任意最小值,按你的策略调整
return false;
datetime t0 = iTime(Symbol(), Period(), 0);
if(t0 == 0)
return false;
return true;
}
void OnTick()
{
if(!HistoryReady())
{
static datetime lastLog = 0;
if(TimeCurrent() - lastLog > 3600) // 每小时最多记一次
{
Print("历史数据未就绪 — 跳过tick");
lastLog = TimeCurrent();
}
return;
}
// ... 你的实际逻辑 ...
}
`
那个static的lastLog是有原因的:连接不好的时候,你一分钟能收到几百条这种消息,日志文件直接爆掉。
如果问题根本不在历史数据上
在你动手重写所有代码之前,还有一件事要确认。
如果你在一个看起来是读Time[0]的行上拿到数组越界,但那一行实际上涉及循环或者计算出来的索引,先检查循环边界。MQL4的运行时错误列表说得很清楚:“数组越界”是致命终止。像这样的循环:
`mql4
for(int i = 0; i <= Bars; i++) // 这里的<=是错的
`
会在最后一次迭代崩溃,因为Bars是数量,不是最后一个有效索引。这是个新手错误,但它会伪装成4066/4073问题,因为报错的行看起来很无辜。
来源
MQL4参考手册:ArrayCopySeries() — 4066错误的文档重试模式
MQL4参考手册:运行时错误 — 数组越界属于致命终止
MQL5论坛关于Time[0]数组越界和iTime()`建议的讨论MQL5论坛关于50,000根K线截断和缓冲区失效的讨论
本文首发于FXEAR.com,原创内容,未经授权禁止转载。