问题描述
解决方案
在PTrade回测中,确保财务数据不包含“未来数据”(即Look-ahead Bias)的关键在于正确使用 get_fundamentals 函数的参数配置。
PTrade 的 get_fundamentals 接口设计逻辑中,默认行为就是**基于公告日期(publ_date)**来返回数据的,这天然符合回测的“Point-in-Time”(切片数据)要求。
以下是具体的配置原则和代码示例:
核心原则
要确保获取的是回测当天已公布的数据,需遵循以下三个参数设置原则:
-
date参数:不要传参(或传入None)。- 在回测模式下,如果不传入
date,系统会自动取当前回测周期的日期(context.blotter.current_dt)。 - 系统会查找所有 公告日期(publ_date) <= 当前回测日期 的财报数据,并返回离当前日期最近的那一份。
- 在回测模式下,如果不传入
-
date_type参数:不要传参(或传入None)。- 默认情况下(
date_type=None),系统是以发布日期为基准进行过滤的。 - 警告:如果设置
date_type=1,系统将以*会计周期截止日(end_date)*为基准。这会导致未来数据泄露(例如:在3月31日就能获取到实际上4月20日才发布的Q1财报数据)。
- 默认情况下(
-
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 应该始终早于或等于你的回测当前日期)。