2. 金融时序数据基础

做金融时序机器学习择时,模型的上限往往不由算法决定,而由数据质量决定。同一套因子、同一个模型,喂入未复权价格与复权价格,回测结果可能天差地别。本节聚焦择时信号工程中最基础也最容易踩坑的一环:行情数据的获取、清洗、对齐与复权。

一、核心原理:四类数据与三种频率

金融时序数据按粒度可分为四类:tick 数据(逐笔成交与盘口快照)、分钟线(1min/5min/15min/30min/60min)、日线(含周线、月线)、以及由日线派生的OHLCV 结构。OHLCV 即开盘价 Open、最高价 High、最低价 Low、收盘价 Close、成交量 Volume,是绝大多数择时因子的原料。

不同频率承载的信息不同:tick 保留微观结构(买卖盘、成交方向、订单流),适合做高频择时与滑点建模;分钟线保留日内节奏,适合捕捉开盘动量、尾盘效应;日线则承载趋势与波动率的中长期结构。择时策略通常以日线为主干,用分钟线或 tick 做信号增强。

关键认知:频率越高,数据量呈指数增长,噪声与缺失也越多。1 只股票 1 年的 tick 数据可达数 GB,而日线仅约 250 行。选择频率的本质,是在信息量与工程成本之间做权衡。

二、数据获取:接口、字段与常见来源

获取渠道主要分三类:开源库(如 akshare、tushare、yfinance)、券商/交易所授权接口、以及商业数据商。以 akshare 获取 A 股日线为例:

import akshare as ak
import pandas as pd

# 获取前复权日线
df = ak.stock_zh_a_hist(
    symbol="600519",
    period="daily",
    start_date="20200101",
    end_date="20241231",
    adjust="qfq"          # qfq前复权 / hfq后复权 / ""不复权
)
df = df.rename(columns={
    "日期":"date","开盘":"open","最高":"high",
    "最低":"low","收盘":"close","成交量":"volume"
})
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date").sort_index()
print(df.head())

获取时需重点确认三件事:时间戳时区(A 股为 Asia/Shanghai,需注意夏令时)、复权方式(qfq/hfq/不复权)、字段单位(成交量是股还是手,成交额是元还是万元)。这些细节不一致,会在多标的对齐时引发系统性偏差。

三、清洗与对齐:让数据可用

原始行情常见问题包括:停牌导致的缺失、涨跌停造成的价格钝化、除权除息引起的价格跳变、以及多标的交易日历不一致。清洗流程建议按以下顺序执行:

  1. 去重与排序:按时间戳去重,确保单调递增。
  2. 缺失处理:停牌日不补价格,或标记为 NaN 后前向填充并记录掩码;切勿用 0 填充价格。
  3. 异常值检测:单日涨跌幅超过涨跌停限制(如 A 股 ±10%、ST ±5%)的,需核查是否复权错误。
  4. 多标的对齐:以交易日历为基准做 outer join,再统一填充策略。
# 多标的按交易日历对齐
calendar = pd.date_range("2020-01-01", "2024-12-31", freq="B")
panel = {}
for code, s in data.items():
    panel[code] = s.reindex(calendar)
panel = pd.DataFrame(panel)
# 停牌标记:价格缺失即为停牌
mask = panel["close"].isna()
panel["close"] = panel["close"].ffill()

对齐技巧:跨市场(A 股 + 港股 + )择时时,建议先各自清洗,再以主市场日历为基准对齐,并对非交易日做前向填充,同时保留 is_tradable 布尔列供模型区分真实交易与填充值。

四、复权处理:择时信号的生命线

股票除权除息后,价格会出现向下跳空,若不复权,均线、动量、收益率等因子会被严重污染。复权分两种:

类型调整方向适用场景注意事项
前复权 qfq以最新价为基准向前调整历史价回测、可视化、因子计算历史价随最新价变动,需每次重算
后复权 hfq以上市首日为基准向后调整长期收益、跨期比较价格数值偏大,不影响收益率
不复权保留原始成交价撮合、滑点、真实成交模拟不能直接算收益率

复权因子(adjust factor)是核心中间量,通常由除权除息事件计算得出。若数据源未直接提供,可用相邻两日收盘价比值反推:

# 由前复权与不复权价格反推复权因子
factor = df_qfq["close"] / df_raw["close"]
factor = factor / factor.iloc[-1]   # 归一化到最新为1

高频误区:用前复权价格计算成交量加权因子时,成交量未同步调整,会导致量价关系失真。正确做法是:价格用复权价,成交量用原始量,或统一使用后复权体系。

五、案例复盘:一次复权引发的回测偏差

某均线择时策略在贵州茅台 2020—2024 回测中,使用不复权数据得到年化 18%,改用前复权后降至 11%。原因在于期间多次分红除权造成价格跳空,未复权数据让均线频繁产生虚假金叉,放大了交易次数与胜率。复盘结论:任何涉及价格连续性的择时因子,必须使用复权数据;而涉及真实成交与滑点的模拟,则需回到不复权价格。

金融时序数据流水线:从原始行情到可用特征 数据源 tick / 分钟 / 日线 清洗 去重·缺失·异常 对齐 交易日历·多标的 复权 qfq / hfq 标准化 OHLCV 面板 date × symbol × [open,high,low,close,volume] 下游:因子计算 → 择时信号 → 回测

把这条流水线固化下来,后续所有因子与模型都建立在可信数据之上。数据基础不牢,再精巧的择时模型也只是在噪声上拟合。