问题描述
df = history(400, unit='1d', field='close', security_list=g.stocks, df=True, skip_paused=True, fq='pre')
df = get_price(g.stocks[0], end_date=date, frequency='daily', fields=['close'], skip_paused=True, fq='pre', count=400)
两种方式获取的数据有什么不同吗?为什么回测结果大不一样?
解决方案
在 JoinQuant(聚宽)量化平台中,history 和 get_price 都是用于获取历史行情数据的常用 API,但它们在底层设计逻辑、对未来函数的防范以及多标的数据对齐方式上存在显著差异。这就是导致你回测结果大不一样的根本原因。
具体来说,主要有以下几个核心不同点:
1. 是否包含当天数据(未来函数风险)
history:这是专门为回测和模拟交易设计的 API。为了严格防止“未来函数”,history获取天数据时,永远不包括当天的数据(即使是在收盘后运行的函数中)。它获取的是截止到昨天为止的count个交易日的数据。get_price:这是一个通用的数据获取接口。它的截止时间由end_date参数决定。如果你传入的date是当天的日期(例如context.current_dt),get_price会返回包含当天的数据。如果在盘中(如 9:30)获取到了当天的收盘价,这就引入了未来函数,会导致回测收益异常虚高,而实盘完全无法复现。
2. skip_paused=True 导致的多标的数据错位(最致命的区别)
在你的代码中,history 传入了多只股票(g.stocks),并且设置了 skip_paused=True。这会导致极其严重的数据对齐问题:
history的行为:当获取多只股票且设置skip_paused=True时,由于不同股票在历史上的停牌日期是不一样的,为了凑齐每只股票的 400 个有效交易日,返回的 DataFrame 的行索引将不再是日期,而是简单的数字索引(0, 1, 2...)。这意味着,DataFrame 中同一行的数据,实际上可能属于完全不同的日期!如果你按行计算均线或进行横截面比较,逻辑将完全错乱。get_price的行为:在你的代码中,get_price只传入了一只股票(g.stocks[0])。对于单只股票,跳过停牌日只是简单地剔除那些没有交易的日期,返回的 DataFrame 行索引依然是真实的交易日期,数据是完全准确且时间对应的。
3. 返回的数据结构不同
history:当你指定field='close'且传入多只股票时,返回的 DataFrame 的列是股票代码,行是时间(或数字索引),里面的值全是收盘价。get_price:当你传入单只股票且fields=['close']时,返回的 DataFrame 的列是字段名('close'),行是时间。
总结与修改建议
你的回测结果大不一样,主要是因为 get_price 可能引入了未来函数,以及 history 在 skip_paused=True 时发生了多股票时间轴错位。
正确的做法建议:
- 在回测中优先使用
history或attribute_history,因为它们天然防范未来函数。 - 慎用
skip_paused=True:在获取多只股票的数据时,强烈建议保持skip_paused=False(默认值)。这样停牌日会用前一天的收盘价填充,保证所有股票在同一个日期索引下严格对齐。# 推荐的多标的获取方式(不对齐会导致严重逻辑错误) df = history(400, unit='1d', field='close', security_list=g.stocks, df=True, skip_paused=False, fq='pre') - 如果必须使用
get_price,请务必确保end_date设置为前一个交易日(context.previous_date),以避免未来函数:# 避免未来函数的 get_price 用法 df = get_price(g.stocks, end_date=context.previous_date, frequency='daily', fields=['close'], count=400, panel=False)