你的回测一天到底有几根 K 线?QuantConnect 又修了一个容易被忽略的时间陷阱
QuantConnect 又修了一个容易被忽略的时间陷阱

你的回测一天到底有几根 K 线?QuantConnect 又修了一个容易被忽略的时间陷阱
你向回测程序请求最近 100 根日 K 线。
正常理解应该很简单:
我要 100 根,你就给我 100 根。
但在某些市场里,事情没这么简单。
2026 年 9 月,QuantConnect 的开源量化引擎 LEAN 修复了一个 History 数据问题。
问题标题很直接:
Fix daily history bar counts for markets with a lunch break。
也就是:
修复存在午间休市市场的日线历史数据数量错误。
这次问题最早出现在香港期货市场相关场景中。QuantConnect 的 issue 记录显示,当算法时间刚好处于午间休市阶段时,用 History(barCount) 请求日线数据,返回的 K 线数量可能比要求的少大约 17%。
程序没有崩。
数据也不是假的。
只是你要 100 根,它可能没有真的给你 100 根。
这种问题看起来小,但如果这 100 根数据是拿来初始化均线、波动率或者机器学习特征,后面的结果就可能跟着变。
先说清楚:什么叫 Bar Count?
在 QuantConnect 里,请求历史数据有几种常见方式。
比如你可以说:
history = self.history(symbol, 100, Resolution.DAILY)
意思就是:
给我这个品种最近 100 根日线。
也可以按照时间范围请求,比如最近 30 天,或者指定开始日期和结束日期。
这两种方式看起来差不多,其实不是一回事。
“最近 100 天”是时间概念。
“最近 100 根 K 线”是数据数量。
周末没有交易,节假日没有交易,有些市场还有特殊交易时段。
所以 100 个自然日,并不等于 100 根日 K。
QuantConnect 的文档也明确区分了这两种 History 请求方式:既可以按照一段时间请求,也可以直接指定需要多少根 Bar。
如果我明确说“给我 100 根”,正常情况下就应该尽量返回最近 100 个有效交易 Bar。
这次的问题就出在这里。
午休为什么会影响“日 K 线数量”?
乍一看其实有点反常。
日 K 线不是一天一根吗?
中午休不休市,为什么会影响日线?
原因在于,回测引擎不能只看日历日期。
它还要理解交易所的完整交易时间。
有些市场从开盘一直连续交易到收盘。
但还有不少市场一天被切成几段。
比如:
上午交易
↓
午间休市
↓
下午交易
↓
收盘
问题来了。
假设现在是某个交易日中午 12 点。
上午已经交易过,但下午还没开始。
这一天算不算一个已经完成的交易日?
当然不能算。
因为当天的日 K 线还没有结束。
QuantConnect 的日线定义也是如此:日 Bar 覆盖市场从开盘到收盘的数据,只有交易日结束之后,这根日 Bar 才完整。
如果历史数据系统在推算“为了找到最近 100 根完整日线,我应该往前找多远”时,没有正确处理这段午间休市,就可能少算历史范围。
最终出现一个很奇怪的结果:
你要的是 100 根。
程序往前找的数据却不够远。
最后真正返回的可能只有八十多根。
QuantConnect 收到的 issue 就报告了这种情况:对存在 lunch break 的 HKFE 市场,在午间休市时调用基于 Bar 数量的日线 History,请求结果大约少了 17%。
2026 年 9 月 18 日,这个问题对应的修复 #9806 被合并进 LEAN。
少十几根 K 线,有那么严重吗?
得看你拿它干什么。
如果只是画一张图,可能没什么大不了。
但量化策略里,History 数据经常不是拿来看的,而是拿来初始化模型的。
比如一个很普通的策略:
用过去 100 天价格计算移动平均线。
代码可能会先请求:
100 根日线
然后把这 100 个价格送进指标。
如果实际只回来 83 根,会发生什么?
最直接的问题是:
指标可能还没准备好。
在很多量化框架里,这叫 Warm-up,也就是预热。
假设你使用:
SMA(100)
它需要 100 个历史数据点。
只有 83 个数据点时,这个指标还没有完整形成。
于是程序可能需要继续等待新的市场数据。
策略原本计划今天开始交易,结果又多等了十几个交易日。
这已经不是“少显示几根 K 线”这么简单了。
策略真正开始工作的时间变了。
再复杂一点,问题会更隐蔽
假设你不是算一条均线,而是在做机器学习。
你需要最近 250 个交易日的数据,构建这些特征:
- 20 日收益率
- 60 日波动率
- 120 日动量
- 200 日均线距离
- 成交量变化
于是程序启动时:
History(symbol, 250, Daily)
你以为每个标的都拿到了 250 根。
实际上,某些市场只返回了 210 多根。
这时候不同特征可用的数据长度可能开始不一致。
你可能继续 dropna()。
然后数据又少一截。
最终真正进入模型训练的数据,可能和你以为的数量差很多。
程序照样能跑。
模型照样能给预测结果。
回测甚至照样可以赚钱。
但研究过程已经悄悄改变了。
这种 Bug 最麻烦的地方就在这里:
它不一定制造一个明显错误,而是让你的策略和你以为的策略变得不一样。
交易日历比很多人想象中复杂
刚开始做量化时,很容易把市场时间理解成:
周一到周五
9:30 开盘
16:00 收盘
实际市场远比这个复杂。
不同资产可能有:
- 午间休市
- 夜盘
- 跨午夜交易
- 提前收市
- 节假日特殊交易时间
- 夏令时切换
- 临时休市
- 不同交易所时区
期货尤其明显。
有些期货合约的一个“交易日”,甚至并不等于自然日的 0 点到 24 点。
QuantConnect 在这次修复前后,还有另一项针对 CME 系列期货缩短交易时段和节假日时间的修改工作,也能说明交易日历维护本身就是量化基础设施里一项长期工作。
所以一个成熟的回测系统不能只知道:
今天是星期几。
它还得知道:
这个品种在哪个交易所交易?
今天是不是交易日?
今天几点开?
中间有没有休市?
几点真正结束?
现在这根 Bar 到底完成没有?
少处理其中一个环节,历史数据边界就可能算错。

这和上一篇 Look-ahead Bias 有什么关系?
上一篇我们讲 QuantConnect 修复了一个前视偏差问题。
那个问题是:
回测程序拿到了本来还不应该知道的数据。
这一次恰好相反。
程序应该给你的历史数据,却可能没给够。
一个是:
拿多了。
一个是:
拿少了。
但背后其实是同一个问题。
时间。
量化回测不只是:
价格 + 策略公式
中间还藏着一整套时间系统:
交易所日历
→ 交易时段
→ 数据时间戳
→ Bar 开始时间
→ Bar 结束时间
→ History 查询
→ 指标预热
→ 交易决策
任何一步错一点,最后的回测结果都有可能变化。
History 数据还有一个容易误解的地方
K 线通常不是以“开始时间”来判断你什么时候知道它。
更重要的是它什么时候结束。
QuantConnect 的 History 数据会按照 Bar 的 EndTime 来索引,官方文档也专门提醒过这一点。
比如一根日 K 线包含了:
Open
High
Low
Close
Volume
在今天收盘之前,你不可能知道今天最终的:
High
Low
Close
Volume
所以一根完整的日线,只有交易结束之后才真正存在。
这也是为什么午间休市这个看起来很普通的时间段,会让日线历史查询变得复杂。
中午的时候:
今天已经交易了一部分。
但今天又还没有结束。
程序必须知道:
“今天”现在到底算不算一个完整 Bar。
自己做回测时,可以检查什么?
以后在策略里大量使用 History 时,我建议先做一个很简单的检查。
比如:
history = self.history(symbol, 100, Resolution.DAILY)
self.debug(len(history))
不要默认:
我请求 100 根
=
我肯定拿到 100 根
先看看实际返回多少。
然后再检查:
- 第一根数据是什么日期?
- 最后一根是什么日期?
- 有没有缺失交易日?
- 当前交易日有没有被错误算进去?
- 指标是不是已经 Ready?
- 不同市场返回的数据数量是否一致?
- 回测和实盘请求历史数据时结果是否一致?
尤其当策略涉及:
海外市场、期货、不同交易所、多资产组合
这些检查很有必要。
因为不同市场根本不是同一套交易时间。
回测里,时间经常比公式更危险
很多人研究量化策略,会把大部分精力花在这些事情上:
用 SMA 还是 EMA?
参数用 20 还是 30?
Sharpe 能不能再提高 0.2?
但真正能让整个结果失真的,有时候不是公式。
可能只是:
少了十几根 K 线。
上一篇 QuantConnect 修复的是“未来的数据提前进来了”。
这一次修复的是“历史数据没有拿够”。
它们都说明了一件事:
一条回测曲线的背后,不只是策略逻辑。
还藏着数据、时间戳、交易日历、交易时段、历史数据查询和指标初始化。
所以下次写:
history(symbol, 100)
别只把它理解成一句取数据的代码。
最好再确认一下:
你要的 100 根,程序真的给了你 100 根吗?
