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'  # 只向过去查找,绝不使用未来公告
)

四、常见数据源及其时间字段核查清单

不同数据源对时间字段的命名和含义差异很大,接入时必须逐一核查。以下清单可作为数据接入时的检查表:

  1. 交易所行情:检查 trade_time、timestamp 是交易所时间还是本地接收时间。
  2. Wind/Choice/聚源:区分 report_date(报告期)、announce_date(公告日)、update_date(入库更新日)。
  3. Yahoo Finance / Tushare:日线数据通常只有交易日,但复权因子可能随时间变化,需使用历史复权因子。
  4. 宏观数据库:如FRED,需关注 realtime_start 和 realtime_end,这是获取初值的关键。
  5. 另类数据供应商:必须索取 event_time 和 delivery_time 两个字段,并以后者作为可用时间。

实战建议:在数据接入层就建立统一的“可用时间”字段,所有下游特征工程和回测引擎只认这个字段。这样可以从架构上杜绝大部分未来函数。

五、案例复盘:一个典型的财报数据泄漏

某研究员构建了一个基于净利润增速的选股策略。他使用某数据接口获取了全部A股2015-2020年的财报数据,字段包括 report_date 和 net_profit,然后按 report_date 将财报数据合并到日频行情上,回测显示年化收益超过40%。

问题出在哪里?该接口返回的 net_profit 是最新修订值,且合并时使用的是报告期而非公告日。这意味着在2015年4月1日,策略就已经“看到”了2015年一季报的净利润,而实际上该财报可能到4月28日才公告。修正后,使用公告日对齐并采用初值数据,年化收益降至12%,超额收益大幅缩水。

复盘结论:数据泄漏往往不会报错,只会让回测结果“好得不真实”。建立数据时间字段的强制核查机制,比任何复杂的防泄漏算法都更有效。

金融数据时间轴与可用时间边界 事件时间 财报报告期末 采集时间 数据入库/接收 可用时间 策略可读取 安全使用区 未来函数危险区(禁止使用) 关键判断规则: • 回测当前时刻 T,只能使用可用时间 ≤ T 的数据 • 事件时间 ≠ 可用时间,两者之间可能存在数天甚至数月延迟 • 采集时间通常晚于事件时间,但可能早于或等于可用时间 时间推进方向 →