问题描述
get_market_data_ex函数:
在获取5分钟收盘价时,取的是上一个5分钟K线的收盘价。如在9:30:00至9:34:59 取的都是昨天的收盘价,不是未来函数。
在获取30分钟收盘价时,取的是上一个5分钟K线的收盘价,也不是未来函数。
在获取日线收盘价时,取的则是当天的收盘价,这是个未来函数,会导致回测不准确。
请问为什么这样设置?为什么不把日线和分钟线的逻辑统一一下啊。
解决方案
您好!您提到的这个问题在量化回测中非常经典,属于典型的跨周期引用导致的未来函数问题。QMT 平台这样设置并非有意制造不一致,而是由底层数据存储和检索机制决定的。以下是详细的逻辑解析及解决方案:
1. 为什么会出现这种差异?
分钟线的逻辑(同周期或小跨度周期)
当您在 9:30 到 9:34 之间运行策略(假设主图是 1 分钟或 5 分钟),此时当天的第一个 5 分钟 K 线(09:35)尚未走完。get_market_data_ex 在检索数据库时,只能找到已经闭合的上一根 5 分钟 K 线(即昨天的最后一根),因此它返回了昨天的收盘价。这符合真实的盘中情况,没有未来函数。
日线的逻辑(大跨度跨周期引用)
当您在盘中(如 9:35)请求 period='1d' 的数据时,底层数据库是按**日期标签(Date)**来检索的。只要当天开盘,数据库中就已经生成了“今天”这条日线记录。由于 get_market_data_ex 是一个底层的数据查询接口,它匹配到“今天”的日期标签后,会直接返回该标签下的完整数据(在回测环境中,这通常是当天的最终收盘价)。
为什么不统一逻辑?
平台没有在底层接口中强制对所有大周期数据做“盘中时间截断”处理,主要原因有两点:
- 性能开销:如果每次请求日线,底层都要根据当前的分钟级时间戳去动态合成一个“截至当前分钟的伪日线”,会极大消耗计算资源,拖慢回测速度。
- 接口通用性:
get_market_data_ex被设计为“所求即所得”的静态数据查询接口。它只负责根据给定的时间范围和周期去拿数据,而不负责处理复杂的跨周期对齐逻辑。
2. 如何解决并规避未来函数?
为了保证回测的准确性,在日内(分钟级别)策略中引用日线数据时,必须在代码层面手动规避当天未走完的日线。通常的做法是:获取日线数据后,强制取上一根(即昨天的日线)。
解决方案:手动偏移获取昨天的日线
在使用 get_market_data_ex 获取日线数据时,将 count 设为 2,然后取倒数第二根数据(即昨天的收盘价);或者在获取到的 DataFrame 中使用 .shift(1)。
def handlebar(ContextInfo):
# 假设当前主图是 5 分钟周期
if not ContextInfo.is_last_bar():
return
stock_code = '000300.SH'
# 获取日线数据,count=2,确保能拿到昨天的完整数据
daily_data = ContextInfo.get_market_data_ex(
fields=['close'],
stock_code=[stock_code],
period='1d',
count=2,
dividend_type='front'
)
if stock_code in daily_data and len(daily_data[stock_code]) >= 2:
# 倒数第一根 [-1] 是当天的日线(包含未来函数)
# 倒数第二根 [-2] 是昨天的日线(安全,无未来函数)
yesterday_close = daily_data[stock_code]['close'].iloc[-2]
print(f"安全的昨日收盘价: {yesterday_close}")
总结
在量化开发中,“小周期引用大周期”天然容易引入未来函数。理解了 QMT 底层按标签检索数据的逻辑后,我们在编写策略时,只要养成“跨大周期引用时主动取前一周期(如 iloc[-2])”的习惯,就可以完美解决回测不准确的问题。