第1章 数据准备:获取行情数据(OHLCV)、数据清洗与对齐

做因子挖掘,第一步不是写模型,而是搞定数据。我见过太多人一上来就调包跑策略,结果回测漂亮得一塌糊涂,实盘一跑就崩——十有八九是数据出了问题。说白了,数据是因子挖掘的「地基」,地基不稳,上面盖什么都是危楼。

这一章,我们就来聊聊怎么把行情数据(OHLCV)拿到手,怎么洗干净,怎么对齐。嗯,这里说的「对齐」不只是时间对齐,还包括复权、除权、停牌这些坑。我当年刚入行时,就因为没处理好复权,白白亏了两个月的时间。

1.1 行情数据长什么样?

OHLCV 是四个价格加一个成交量的缩写:

  • O(Open):开盘价
  • H(High):最高价
  • L(Low):最低价
  • C(Close):收盘价
  • V(Volume):成交量

这是最基础的数据结构。你想想看,几乎所有技术指标、因子计算,都是从这五个字段衍生出来的。比如最简单的动量因子,就是拿今天的收盘价和 N 天前的收盘价比一下。

我个人习惯把数据存成 DataFrame 格式,索引是时间,列是股票代码。举个例子:

import pandas as pd
import numpy as np

# 模拟一个简单的 OHLCV 数据
dates = pd.date_range('2024-01-01', periods=5, freq='D')
data = {
    'open': [100, 102, 101, 103, 105],
    'high': [103, 104, 103, 106, 107],
    'low':  [99, 101, 100, 102, 104],
    'close':[102, 101, 103, 105, 106],
    'volume':[10000, 12000, 11000, 13000, 15000]
}
df = pd.DataFrame(data, index=dates)
print(df)

输出结果:

            open  high  low  close  volume
2024-01-01   100   103   99    102   10000
2024-01-02   102   104  101    101   12000
2024-01-03   101   103  100    103   11000
2024-01-04   103   106  102    105   13000
2024-01-05   105   107  104    106   15000

你看,结构就这么简单。但实际项目中,数据源往往不会这么规整。有的接口返回的是 JSON,有的是 CSV,有的甚至直接给你一个 Excel 文件。我建议你统一转成 DataFrame,后面处理起来会顺手很多。

1.2 数据获取:从哪里拿?

获取行情数据的渠道很多,我列几个常用的:

数据源 特点 适用场景
Tushare 免费,数据全,但有限流 个人研究、小规模回测
Wind 收费,数据质量高 机构、专业量化团队
聚宽/米筐 平台自带数据,方便回测 策略开发、快速验证
Yahoo Finance 免费,覆盖全球市场 海外股票、加密货币

我个人偏好用 Tushare 做快速验证,毕竟免费又方便。但要注意,免费接口通常有频率限制,比如每分钟只能请求 200 次。如果你要拉全 A 股的历史数据,建议写个循环加 sleep,别把人家服务器搞崩了。

举个例子,用 Tushare 获取某只股票的历史日线数据:

import tushare as ts

# 设置 token(需要去官网注册)
ts.set_token('your_token_here')
pro = ts.pro_api()

# 获取平安银行(000001.SZ)2024年1月日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20240101', end_date='20240131')
print(df.head())

输出结果:

   ts_code trade_date  open  high   low  close  vol
0  000001.SZ   20240131  10.2  10.5  10.1   10.3  123456
1  000001.SZ   20240130  10.1  10.4  10.0   10.2  110000
2  000001.SZ   20240129  10.3  10.6  10.2   10.4  130000
...

嗯,这里要注意:Tushare 返回的 trade_date 是字符串格式,建议转成 datetime 类型,方便后面做时间序列操作。

1.3 数据清洗:脏数据怎么处理?

数据拿到手,别急着用。先检查一下有没有脏数据。我在项目中遇到过最离谱的情况——某只股票某天的收盘价是 0,成交量是负数。你想想看,这要是直接拿去算因子,结果能看吗?

常见的脏数据包括:

  • 缺失值:某天没有交易数据(停牌、节假日)
  • 异常值:价格突变、成交量异常大或异常小
  • 重复值:同一时间戳出现多条记录
  • 格式错误:日期格式不统一、数字带逗号等

我的处理流程一般是这样的:

def clean_ohlcv(df):
    # 1. 删除重复行
    df = df.drop_duplicates()
    
    # 2. 处理缺失值(向前填充,或直接删除)
    df = df.fillna(method='ffill')
    
    # 3. 过滤异常值(比如价格不能为0或负数)
    df = df[(df['close'] > 0) & (df['volume'] >= 0)]
    
    # 4. 排序(按时间升序)
    df = df.sort_index()
    
    return df

这里有个坑:向前填充(ffill)虽然方便,但如果你连续停牌好几天,填充出来的数据其实是有问题的。我曾经就因为没注意这个,导致因子在停牌期间「凭空」产生了交易信号,回测结果虚高。后来我加了一个判断:如果连续缺失超过 5 天,就直接删除这些数据。

1.4 数据对齐:时间、复权、除权

数据对齐是因子挖掘里最容易被忽视的环节。说白了,就是确保不同股票的数据在同一个时间点上「对齐」。比如你要算沪深 300 所有成分股的动量因子,如果 A 股和 B 股的数据日期不一致,算出来的因子值就是错的。

对齐主要分三步:

  1. 时间对齐:把所有股票的数据统一到同一个交易日历上
  2. 复权处理:消除分红、送股、配股对价格的影响
  3. 停牌处理:停牌期间的数据怎么填充或删除

先看时间对齐。A 股的交易日历不是连续的,周末和法定节假日都不交易。我建议你维护一个「交易日历表」,然后做左连接:

# 假设 trade_cal 是交易日历,df 是某只股票的数据
trade_cal = pd.DataFrame({'date': pd.date_range('2024-01-01', '2024-01-31', freq='B')})
df_aligned = trade_cal.merge(df, on='date', how='left')

这样,非交易日的数据就是 NaN,后面再填充或删除。

再来看复权。复权分为前复权和后复权。我个人习惯用前复权,因为它的价格序列是连续的,不会因为除权出现跳空。举个例子:

# 假设 df 包含复权因子(adj_factor)
df['close_adj'] = df['close'] * df['adj_factor']

这里 adj_factor 是复权因子,一般数据源会直接提供。如果没有,你可以自己算:

# 简单前复权计算(不考虑分红再投资)
def calc_adj_factor(df):
    # 以最新价格为基准
    base_price = df['close'].iloc[-1]
    df['adj_factor'] = base_price / df['close']
    return df

嗯,这个算法比较粗糙,实际项目中建议直接用数据源提供的复权因子。

最后是停牌处理。停牌期间没有交易数据,价格和成交量都是 NaN。我的做法是:

  • 如果停牌时间短(≤5天),用前一天的收盘价填充
  • 如果停牌时间长(>5天),直接删除这些数据,或者标记为「不可交易」

我曾经踩过一个坑:某只股票停牌半年,复牌后连续涨停。我用前复权填充了停牌期间的数据,结果因子计算时把停牌期间的「假价格」也算进去了,导致信号完全失真。后来我加了一个停牌标记列,在因子计算前先过滤掉停牌期间的数据。

1.5 知识体系总览

为了让你更直观地理解本章的知识结构,我画了一张流程图:

数据准备知识体系 数据获取 数据清洗 数据对齐 时间对齐 复权处理 停牌处理 干净、对齐的 OHLCV 数据

这张图把数据准备的流程串起来了。从获取原始数据开始,经过清洗、对齐,最终得到干净可用的 OHLCV 数据。每一步都有坑,每一步都需要你仔细处理。

1.6 避坑指南

最后,分享几个我踩过的坑,希望能帮你少走弯路:

⚠️ 坑1:复权因子不一致
不同数据源的复权因子可能不同。我曾经用 Tushare 和 Wind 拉同一只股票的数据,算出来的因子值差了 0.5%。后来发现是复权算法不一样。建议你固定一个数据源,不要混用。
💡 技巧:数据版本管理
我习惯给每批数据打上时间戳和版本号。比如 "ohlcv_202401_v1.parquet"。这样万一发现数据有问题,可以快速回滚到之前的版本。
⚠️ 坑2:停牌数据「假填充」
前面说过,停牌期间用前复权填充价格,会导致因子计算失真。我的建议是:在因子计算前,先过滤掉停牌期间的数据。具体做法是维护一个「停牌标记列」,停牌日标记为 1,正常交易日标记为 0。
💡 技巧:交易日历统一
如果你同时交易 A 股和港股,注意它们的交易日历不一样。A 股春节休市 7 天,港股可能只休 3 天。我建议你分别维护两个市场的交易日历,不要混在一起。

好了,数据准备这部分就讲到这里。记住一句话:数据质量决定了因子挖掘的上限。花 80% 的时间在数据上,剩下的 20% 用来写模型,这才是量化工程师该有的态度。


专注资料整理