第三章:金融数据获取与清洗——多源数据接入实战

做量化投研,数据就是你的弹药库。弹药不行,再好的策略也白搭。

这一章,咱们聊聊怎么把Tushare、AKShare、Wind这些数据源接入进来,再把这些脏数据洗干净。我个人习惯把数据获取和清洗看作一个整体流程——你想想看,数据拿进来就指望它干净?不存在的。

3.1 多源数据接入:三驾马车怎么选?

国内量化圈,数据源基本就这三家:Tushare、AKShare、Wind。我自己的经验是,不同场景用不同工具。

数据源 特点 适用场景 费用
Tushare 结构化好,文档清晰,社区活跃 个人研究、回测、因子挖掘 免费+积分制
AKShare 开源免费,覆盖广,更新快 快速原型、小规模策略 完全免费
Wind 机构级,数据质量高,接口稳定 实盘交易、机构投研 付费(较贵)

嗯,这里要注意:千万别在实盘环境用免费数据源做高频交易。我在项目中遇到过,AKShare某天突然接口变了,导致策略直接断粮。血的教训。

3.1.1 Tushare接入实战

Tushare需要先注册获取token。我个人习惯把token放在环境变量里,而不是硬编码在代码中。

import tushare as ts
import os

# 设置token(建议从环境变量读取)
ts.set_token(os.getenv('TUSHARE_TOKEN', '你的token'))
pro = ts.pro_api()

# 获取日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20240101', end_date='20240301')
print(df.head())

为什么推荐环境变量?因为代码要上Git,token泄露了你就等着被薅羊毛吧。我曾经见过有人把token直接commit到公开仓库,第二天积分就被刷光了。

3.1.2 AKShare接入实战

AKShare更轻量,不需要token,直接pip install akshare就能用。

import akshare as ak

# 获取A股日线数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily", 
                        start_date="20240101", end_date="20240301", adjust="qfq")
print(df.head())

AKShare的优点是快,缺点是接口名称经常变。我建议每次升级前先看看更新日志,不然代码跑着跑着就报错了。

3.1.3 Wind接入实战

Wind是机构标配,但需要安装Wind客户端并启动。接口是w.start()和w.wsd()。

from WindPy import w

w.start()  # 启动Wind接口

# 获取日线数据
df = w.wsd("000001.SZ", "open,high,low,close,volume", 
           "2024-01-01", "2024-03-01", "")
print(df.Data)

Wind的坑在于:如果客户端没启动,w.start()会卡住。我建议加个超时处理,或者先检查进程是否存在。

3.2 数据清洗实战:脏数据怎么治?

数据拿进来,第一件事不是分析,是清洗。你想想看,金融数据里什么妖魔鬼怪都有。

3.2.1 缺失值处理

缺失值在金融数据里太常见了。停牌、节假日、数据源漏了,都会导致NaN。

import pandas as pd
import numpy as np

# 模拟缺失数据
df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=10, freq='D'),
    'close': [100, 101, np.nan, 103, 104, np.nan, 106, 107, 108, 109]
})

# 方法1:前向填充(推荐用于日频数据)
df['close_ffill'] = df['close'].ffill()

# 方法2:线性插值(适用于短时间缺失)
df['close_interp'] = df['close'].interpolate(method='linear')

# 方法3:直接删除(缺失太多就别要了)
df_drop = df.dropna(subset=['close'])

我个人习惯:日频数据用前向填充,因为停牌后复牌,价格一般会补涨补跌。但如果是分钟级数据,我建议用插值,因为前向填充会引入滞后偏差。

避坑指南: 我曾经在回测中用了前向填充处理分钟数据,结果策略在停牌后第一天开仓,直接吃了个跌停。后来改成插值+标记缺失时段,才把这个问题解决。

3.2.2 异常值处理

异常值比缺失值更隐蔽。涨停跌停、数据录入错误、除权除息,都会产生异常值。

# 用Z-score检测异常值
from scipy import stats

df['z_score'] = np.abs(stats.zscore(df['close'].dropna()))
df['is_outlier'] = df['z_score'] > 3

# 用IQR方法(更稳健)
Q1 = df['close'].quantile(0.25)
Q3 = df['close'].quantile(0.75)
IQR = Q3 - Q1
df['is_outlier_iqr'] = (df['close'] < Q1 - 1.5*IQR) | (df['close'] > Q3 + 1.5*IQR)

嗯,这里要注意:金融数据的异常值不一定是错误。比如某天突然暴涨,可能是利好公告。我建议先标记异常值,再人工判断,而不是直接删除。

3.2.3 复权处理

复权是量化投研里最容易被忽视的坑。不复权的话,分红送股会导致价格跳空,回测结果完全失真。

复权类型 说明 适用场景
前复权 调整历史价格,保持最新价格不变 回测、因子计算
后复权 调整当前价格,保持历史价格不变 长期趋势分析
不复权 原始价格,有跳空 实盘交易(按实际价格成交)
# Tushare获取前复权数据
df = pro.daily(ts_code='000001.SZ', start_date='20240101', 
               end_date='20240301', adj='qfq')

# AKShare获取前复权数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily",
                        start_date="20240101", end_date="20240301", 
                        adjust="qfq")
个人经验: 我建议回测时用前复权数据,但计算收益率时用后复权。为什么?前复权会导致历史收益率失真,尤其是长期回测。我一般会同时保留两种复权数据,各取所需。

3.3 数据存储:Parquet vs Feather

数据洗干净了,存哪里?CSV?别闹了,几百万条数据CSV读写慢得要命。

我个人推荐Parquet和Feather,两者都是列式存储,压缩率高,读写快。

格式 压缩率 读写速度 跨语言支持 适用场景
Parquet 高(可压缩到10%-20%) 好(Spark、Hive等) 大数据、长期存储
Feather 中(约50%) 极快 一般(R/Python为主) 中间结果、快速读写
import pandas as pd

# 保存为Parquet
df.to_parquet('stock_data.parquet', compression='snappy')

# 保存为Feather
df.to_feather('stock_data.feather')

# 读取
df_parquet = pd.read_parquet('stock_data.parquet')
df_feather = pd.read_feather('stock_data.feather')

我自己的习惯:原始数据存Parquet,因为压缩率高,省硬盘。中间处理结果存Feather,因为读写快,迭代开发时省时间。

核心要点: 数据存储不是随便选个格式就行。要考虑读写频率、数据量、跨语言需求。我见过有人用CSV存了10年日线数据,每次回测光读数据就要5分钟。换成Parquet后,10秒搞定。

3.4 本章知识体系

下面这张图,是我自己梳理的数据获取与清洗流程。你照着这个框架走,基本不会漏掉关键环节。

金融数据获取与清洗流程 数据源 数据源 数据源 Tushare AKShare Wind 数据清洗 缺失值处理 异常值检测 复权处理 数据存储 Parquet(长期存储) Feather(中间结果) 因子挖掘与信号生成

说白了,数据获取与清洗就是量化投研的「地基工程」。地基没打好,上面盖多高的楼都得塌。我见过太多人花90%时间调策略,却不愿意花10%时间把数据搞干净。结果呢?回测曲线漂亮得像假的一样,实盘一跑就露馅。

嗯,这一章的内容就到这儿。记住:数据干净了,策略才有意义。


蓝海数据掘金营,专注资料整理