🚀 AI 一键生成 ptrade 策略代码

如何避免在 PTrade 回测中使用 `get_fundamentals` 时引入未来数据?

ptrade | 发布时间: 2025/12/18
以下内容由 EasyQuant 生成。

问题描述

在回测中使用财务数据时,如何确保我获取到的是回测当天已经公布的数据,而不是未来的数据?

解决方案

在PTrade回测中,确保财务数据不包含“未来数据”(即Look-ahead Bias)的关键在于正确使用 get_fundamentals 函数的参数配置。

PTrade 的 get_fundamentals 接口设计逻辑中,默认行为就是**基于公告日期(publ_date)**来返回数据的,这天然符合回测的“Point-in-Time”(切片数据)要求。

以下是具体的配置原则和代码示例:

核心原则

要确保获取的是回测当天已公布的数据,需遵循以下三个参数设置原则:

  1. date 参数不要传参(或传入 None)。

    • 在回测模式下,如果不传入 date,系统会自动取当前回测周期的日期(context.blotter.current_dt)。
    • 系统会查找所有 公告日期(publ_date) <= 当前回测日期 的财报数据,并返回离当前日期最近的那一份。
  2. date_type 参数不要传参(或传入 None)。

    • 默认情况下(date_type=None),系统是以发布日期为基准进行过滤的。
    • 警告:如果设置 date_type=1,系统将以*会计周期截止日(end_date)*为基准。这会导致未来数据泄露(例如:在3月31日就能获取到实际上4月20日才发布的Q1财报数据)。
  3. merge_type 参数不要传参(或传入 None)。

    • 默认情况下(merge_type=None),系统获取的是首次发布的数据。
    • 警告:如果设置 merge_type=1,系统会获取最新修正的数据。上市公司可能会在几年后修正之前的财报,如果在回测中使用修正后的数据,也属于利用了未来信息。

代码示例

以下代码展示了正确与错误的用法对比:

def initialize(context):
    g.security = ['600570.SS']
    set_universe(g.security)

def handle_data(context, data):
    # ---------------------------------------------------------
    # 【正确做法】
    # 不传入 date, date_type, merge_type
    # 系统会自动获取截止到回测当日(context.blotter.current_dt)
    # 已经对外公告(publ_date <= current_dt)的最新一份数据
    # ---------------------------------------------------------
    
    # 获取最新的净利润
    q = get_fundamentals(
        g.security, 
        'income_statement', 
        'net_profit'
        # date 缺省:取回测当前日期
        # date_type 缺省:按公告日期查找
        # merge_type 缺省:取首次发布数据
    )
    
    if q is not None and '600570.SS' in q.index:
        log.info("当前回测时间: %s, 获取到的净利润: %s" % (
            context.blotter.current_dt, 
            q['net_profit']['600570.SS']
        ))

    # ---------------------------------------------------------
    # 【错误做法 - 会导致未来函数】
    # ---------------------------------------------------------
    
    # 错误1:date_type=1
    # 这会按会计周期查找。例如回测时间是2023-04-01,
    # 可能会取到2023-03-31的财报,但该财报实际可能在2023-04-25才发布。
    # q_wrong_1 = get_fundamentals(g.security, 'income_statement', 'net_profit', date_type=1)
    
    # 错误2:merge_type=1
    # 这会获取最新修正数据。如果公司在2025年修正了2023年的报表,
    # 回测在2023年时就用到了2025年的修正值。
    # q_wrong_2 = get_fundamentals(g.security, 'income_statement', 'net_profit', merge_type=1)

总结

在PTrade回测中,为了避免未来数据,调用 get_fundamentals 时请保持最简形式:

只传入 security(股票代码)、table(表名)、fields(字段名),其余参数保持默认即可。


Q&A

Q: 如果我想在回测中获取“去年年报”的数据,而不是“最新公告”的数据,该怎么做且不包含未来数据?

A: 你可以使用 report_types 参数配合默认的 date 逻辑。
例如,获取最近的一份年度财报(年报):

# report_types='4' 代表年报
# 系统会查找 公告日期 <= 回测日期 的所有财报中,最近的一份 report_types 为 '4' 的数据
df = get_fundamentals(g.security, 'income_statement', 'net_profit', report_types='4')

这样既指定了财报类型,又利用了系统的公告日期过滤机制,确保了数据的安全性。

Q: 为什么 merge_type=1 会导致未来数据?

A: 上市公司的财务数据在首次发布后,可能会因为会计差错更正、追溯调整等原因进行修改。merge_type=1 返回的是数据库中记录的最终版本(最新值)。在回测历史某个时间点时,那个时间点的投资者只能看到当时发布的“初版”数据,而看不到几年后修正的“终版”数据。使用修正后的数据会让策略表现虚高。

Q: get_fundamentals 返回的数据中有 publ_date 字段吗?

A: 是的,大部分表(如 balance_statement, income_statement 等)返回的 DataFrame 中都会包含 publ_date(公告日期)和 end_date(截止日期/会计周期)。你可以在策略中打印这两个字段来验证数据是否符合逻辑(即 publ_date 应该始终早于或等于你的回测当前日期)。