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/不复权)、字段单位(成交量是股还是手,成交额是元还是万元)。这些细节不一致,会在多标的对齐时引发系统性偏差。
三、清洗与对齐:让数据可用
原始行情常见问题包括:停牌导致的缺失、涨跌停造成的价格钝化、除权除息引起的价格跳变、以及多标的交易日历不一致。清洗流程建议按以下顺序执行:
- 去重与排序:按时间戳去重,确保单调递增。
- 缺失处理:停牌日不补价格,或标记为 NaN 后前向填充并记录掩码;切勿用 0 填充价格。
- 异常值检测:单日涨跌幅超过涨跌停限制(如 A 股 ±10%、ST ±5%)的,需核查是否复权错误。
- 多标的对齐:以交易日历为基准做 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%。原因在于期间多次分红除权造成价格跳空,未复权数据让均线频繁产生虚假金叉,放大了交易次数与胜率。复盘结论:任何涉及价格连续性的择时因子,必须使用复权数据;而涉及真实成交与滑点的模拟,则需回到不复权价格。
把这条流水线固化下来,后续所有因子与模型都建立在可信数据之上。数据基础不牢,再精巧的择时模型也只是在噪声上拟合。