问题描述
处理财务数据时,我们都知道常规做法是采用公告日期作为数据变更的节点,而不是采用报告日期。这是一个典型的未来函数,也是最常规的处理方式。
但接下来的问题更为关键:当财报数据出现了修正财务报表,该如何处理?
最近一位刚入门的小伙伴就碰到了这个问题,这也正是他代码中的bug所在。我认为这对于许多初学者来说,是一个相当细节、重要却又容易被忽略的坑。因此,我专门写下这篇文章,来聊聊这类问题的处理思路和具体方法。请大家一定耐心看完。
问题解析
为什么说这类数据特别麻烦呢?核心在于,它涉及到对过去已发布数据的修正。如果不加注意,这很容易在你的交易系统回测过程中引入“未来的数据”,从而产生未来函数问题,这恰恰是很多朋友可能没有留意到的隐患。
核心原则:
在交易系统里,我们严禁修改过去时间点上已有的任何数值。唯一正确的做法是,将新的修正值看作是到其公告日那一刻才“可用”的数据。历史的数值必须保持不变。
场景与解决办法
场景一:计算依赖过去连续数据的指标(如ROE_DELTA)
假设我们之前的某个策略调用了过去5个季度的季度ROE(q_roe)作为基础数据来计算某个衍生因子。
类似这样的因子,如果今晚出现了修正财务报表,该如何更新?
按照我们“不修改历史”的原则,像roe_delta这样的因子就需要完成以下步骤:
- 在按
pub_date(公告日期)进行groupby时,这是一个关键细节:只有新的报告产生时才需要更新数据,因此分组计算应仅围绕新的pub_date展开。
- 读取该报告期(
rpt_date)及之前需要用到的所有ROE数据。
- 剔除所有公告日期(
pub_date)大于当前处理日期的财报数据,确保我们只使用“当时已知”的信息。
- 将筛选出的数据按报告期(
rpt_date)和公告日期(pub_date)从小到大排序。
- 针对同一个报告期(
rpt_date)进行去重,并保留公告日期最晚(keep='last')的一条记录,这代表了该报告期在“当前时刻”的最新有效值。
- 最后,使用交易日(
trade_date)对按pub_date分组计算出的结果进行索引重建(reindex),并用适当的方法(如向前填充fillna)处理缺失值。
具体到用Python和Pandas实现的代码片段如下:
# index为pub_date ,values 为roe_delta
fina_group= df_fina_need_group.groupby('pub_date',group_keys=False).apply(self.calculate_fina_indicator_pub,
df_fina_one_stock = df_fina,
include_groups=False)
# ============= 具体处理函数部分代码
_pub_date = group.name # 获取公告时间
df_fina_pub = df_fina_one_stock[df_fina_one_stock['pub_date'] <= _pub_date].copy() # 筛选出已发布的公告。
# 去重复,先排序,如果有最新的报告就取最新的
df_fina_pub.sort_values(['rpt_date', 'pub_date'], ascending=[True, True], inplace=True, ignore_index=True)
# 关于发布日期是正序,所以取值为最后一条
df_fina_pub.drop_duplicates(subset=['rpt_date'], keep='last', inplace=True, ignore_index=True)
场景二:计算TTM、同比、环比等跨期数据
这里的处理逻辑其实与场景一保持一致。关键在于,需要根据当前处理的报告日期,精确地找到其之前N个季度(如计算TTM需找前4个季度)的财报数据,并对这些历史数据同样应用“按公告日期筛选、排序、去重取最新”的步骤。
总结:任何需要用到过去多期历史数据参与计算的指标,都可以套用上述步骤与代码框架。
场景三:直接引用单期过去数据(最容易出错)
这种情况听起来最简单,但恰恰最容易引入未来函数。
错误做法:在按pub_date分组处理完数据后,直接对整个序列使用shift来引用上一期的数据。
为什么这是错的?
举个例子:今天收盘后发布了一份修正报告。那么,在计算“今天”的因子时,如果需要用到上一期财报数据,我们确实应该采用这份最新的修正值。但是,在计算“昨天”的因子时,你绝对不能使用今天才修正的数据,而只能使用截止到昨天所发布的历史数据。
如果你在今天的修正发生后,统一把所有历史日期的上一期数据都更新为最新值,那么在回测昨天及之前的日期时,系统就会“提前知道”未来的修正信息——这就是典型的未来函数。
因此,正确的做法是:如果你的因子需要用到过去某一报告期的数据,就必须将这个需求视为一个新的因子,严格按照场景一的流程去独立计算、保存和调用。
总结
本期内容源于一个具体的提问和代码bug,虽然问题看似简单,但却非常关键,属于量化回测中“未来函数”的经典陷阱,很多初学者容易在这里栽跟头。
我甚至发现,一些现成的因子库也可能没有进行类似的处理,这无疑会埋下隐患。具体到你使用的平台或库有没有做类似处理,你需要仔细查阅它的开发文档和相关函数的调用方式。
一个简单的判断方法是:当你调用上一期财报数据时,观察函数是否允许你传入一个“截止日期”或“时点”参数。如果没有,那基本上就有问题。理论上,财报数据在每个报告期的每个时点上,都应该是一个随时间可能变化的序列(因为未来可能有修正)。如果调用时连时间参数都没有,你就需要格外警惕了。
在Python等工具中进行严谨的数据处理是避免这类问题的关键。如果你对这类话题有更多见解或疑问,欢迎到云栈社区的“智能&数据&云”板块与大家交流探讨。