🚀 AI 一键生成 joinquant 策略代码
立即体验
👉 AI 生成代码 👈

为什么在 JoinQuant 中使用 history 和 get_price 获取数据回测结果不一样?

joinquant | 发布时间: 2026/5/29
以下内容由 EasyQuant 生成。

问题描述

histroy和get_price回测结果不一样

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(聚宽)量化平台中,historyget_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 可能引入了未来函数,以及 historyskip_paused=True 时发生了多股票时间轴错位

正确的做法建议:

  1. 在回测中优先使用 historyattribute_history,因为它们天然防范未来函数。
  2. 慎用 skip_paused=True:在获取多只股票的数据时,强烈建议保持 skip_paused=False(默认值)。这样停牌日会用前一天的收盘价填充,保证所有股票在同一个日期索引下严格对齐。
    # 推荐的多标的获取方式(不对齐会导致严重逻辑错误)
    df = history(400, unit='1d', field='close', security_list=g.stocks, df=True, skip_paused=False, fq='pre')
    
  3. 如果必须使用 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)