2. 金融数据基础
在构建任何量化策略或金融研究模型之前,我们必须先回答一个看似简单却极其关键的问题:我们手中的数据到底是什么?防未来函数与防数据泄漏的根基,不在于复杂的算法,而在于对数据本身的时间属性、生成机制和来源边界的清晰认知。本节将从数据类型、时间序列特性和常见数据源三个维度,建立后续所有实战操作的认知底座。
一、核心原理:金融数据的三种时间属性
金融数据与普通业务数据最大的区别在于:每一条记录都携带至少一个时间戳,且不同时间戳代表完全不同的含义。理解这一点,是避免未来函数的第一道防线。
- 事件时间(Event Time):数据所描述的事件实际发生的时刻。例如某笔交易在交易所撮合成交的精确时间。
- 采集时间(Ingestion Time):数据被系统接收、记录或写入数据库的时刻。网络延迟、批处理调度都会导致采集时间晚于事件时间。
- 可用时间(Availability Time):数据真正可以被策略或研究员读取使用的时刻。这是防泄漏中最关键的字段,却最容易被忽略。
核心原则:任何在回测中使用的数据,其可用时间必须严格小于等于回测当前时刻。若使用采集时间或事件时间代替可用时间,就会引入未来函数。
举例来说,某上市公司财报的事件时间是报告期末(如3月31日),但采集时间可能是4月28日盘后,而可用时间可能是4月29日开盘前。如果你在4月1日就使用该财报数据,就犯了典型的“财报前视”错误。
二、金融数据的主要类型与结构
从数据结构角度,金融研究数据可分为以下几类,每类都有不同的泄漏风险点:
| 数据类型 | 典型示例 | 时间特性 | 主要泄漏风险 |
|---|---|---|---|
| 行情快照数据 | Level-1/Level-2 逐笔委托、成交 | 高频、事件驱动 | 使用未来快照价格成交 |
| K线/Bar数据 | 日线、分钟线 OHLCV | 固定周期聚合 | 用当根Bar收盘价决策并成交 |
| 基本面数据 | 财报、盈利预告、分红 | 低频、滞后发布 | 报告期与公告日混淆 |
| 另类数据 | 新闻情绪、卫星图像、APP活跃 | 非规则、采集延迟大 | 采集时间晚于事件时间 |
| 宏观数据 | GDP、CPI、利率决议 | 定期发布、修订频繁 | 使用修订后数据而非初值 |
其中,基本面数据和宏观数据的“修订”问题尤为隐蔽。例如GDP初值在季度结束后约15天发布,之后还会经历多次修订。如果你在回测中使用了最终修订值,而当时市场只能看到初值,就构成了数据泄漏。
三、时间序列特性:平稳性、自相关与对齐
金融时间序列具有几个必须掌握的特性,它们直接影响特征工程和回测设计的正确性。
- 非平稳性:价格序列通常是非平稳的,而收益率序列更接近平稳。直接对价格做回归容易产生伪回归。
- 自相关性:收益率存在弱自相关,波动率存在强自相关(波动率聚集)。这要求我们在构造特征时注意滞后阶数的选择。
- 时间对齐:不同数据源的时间戳频率不同,必须统一到同一时间轴上。对齐时只能使用“过去已发生”的数据,不能向前填充未来值。
常见误区:使用 pandas.DataFrame.fillna(method='bfill') 或 backfill 对金融时间序列做缺失值填充。向后填充会用未来数据填补当前缺失,是典型的未来函数。正确做法是使用 ffill(前向填充)或直接删除缺失样本。
下面是一个安全的时间对齐示例,展示如何将日频因子与月频财报数据合并,且确保只使用已公告的数据:
import pandas as pd
# 假设 daily_factor 索引为交易日,financial_report 包含 report_date 和 announce_date
# 错误做法:直接按 report_date 合并
# wrong = daily_factor.merge(financial_report, on='report_date')
# 正确做法:按公告日对齐,且只使用公告日 <= 当前交易日的数据
financial_report = financial_report.sort_values('announce_date')
daily_factor = daily_factor.sort_values('trade_date')
# 使用 merge_asof 实现“最近已公告”匹配
merged = pd.merge_asof(
daily_factor,
financial_report,
left_on='trade_date',
right_on='announce_date',
direction='backward' # 只向过去查找,绝不使用未来公告
)
四、常见数据源及其时间字段核查清单
不同数据源对时间字段的命名和含义差异很大,接入时必须逐一核查。以下清单可作为数据接入时的检查表:
- 交易所行情:检查
trade_time、timestamp是交易所时间还是本地接收时间。 - Wind/Choice/聚源:区分
report_date(报告期)、announce_date(公告日)、update_date(入库更新日)。 - Yahoo Finance / Tushare:日线数据通常只有交易日,但复权因子可能随时间变化,需使用历史复权因子。
- 宏观数据库:如FRED,需关注
realtime_start和realtime_end,这是获取初值的关键。 - 另类数据供应商:必须索取
event_time和delivery_time两个字段,并以后者作为可用时间。
实战建议:在数据接入层就建立统一的“可用时间”字段,所有下游特征工程和回测引擎只认这个字段。这样可以从架构上杜绝大部分未来函数。
五、案例复盘:一个典型的财报数据泄漏
某研究员构建了一个基于净利润增速的选股策略。他使用某数据接口获取了全部A股2015-2020年的财报数据,字段包括 report_date 和 net_profit,然后按 report_date 将财报数据合并到日频行情上,回测显示年化收益超过40%。
问题出在哪里?该接口返回的 net_profit 是最新修订值,且合并时使用的是报告期而非公告日。这意味着在2015年4月1日,策略就已经“看到”了2015年一季报的净利润,而实际上该财报可能到4月28日才公告。修正后,使用公告日对齐并采用初值数据,年化收益降至12%,超额收益大幅缩水。
复盘结论:数据泄漏往往不会报错,只会让回测结果“好得不真实”。建立数据时间字段的强制核查机制,比任何复杂的防泄漏算法都更有效。