3. 数据获取与预处理:用Tushare/AkShare搞定指数数据
做量化策略,数据就是你的原材料。原材料不干净,再好的策略也白搭。这一章,咱们就聊聊怎么把沪深300、中证500这些宽基指数的数据搞到手,再把它收拾得服服帖帖。
3.1 数据源的选择:Tushare vs AkShare
国内做量化,绕不开两个库:Tushare和AkShare。我个人的习惯是,小规模研究用AkShare,因为它免费、不用注册,拿来就能用。但如果是做产品级的策略,我建议用Tushare Pro,数据质量更稳定,字段也更全。
说白了,AkShare像一把瑞士军刀,啥都能干,但偶尔会有点小毛病。Tushare Pro更像专业工具,稳定可靠,但需要积分。我在项目中遇到过AkShare某天突然返回空数据的情况,排查了半天才发现是对方接口升级了。嗯,从那以后,关键数据我都会双源校验。
3.2 安装与初始化
先装好这两个库。代码很简单,但有个坑——Tushare需要token,别忘了配置。
# 安装
# pip install tushare akshare pandas numpy
import tushare as ts
import akshare as ak
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
# Tushare初始化(需要你的token)
ts.set_token('你的token')
pro = ts.pro_api()
# AkShare不需要初始化,直接调用函数即可
3.3 获取沪深300指数数据
沪深300,代码是'000300.SH'。我一般取最近5年的日线数据,太短了不够分析,太长了市场结构可能已经变了。
# 用Tushare获取沪深300日线
def get_index_data_ts(code='000300.SH', start='20190101', end='20231231'):
df = pro.index_daily(
ts_code=code,
start_date=start,
end_date=end,
fields='trade_date,open,high,low,close,pre_close,change,pct_chg,vol,amount'
)
df['trade_date'] = pd.to_datetime(df['trade_date'])
df.sort_values('trade_date', inplace=True)
df.reset_index(drop=True, inplace=True)
return df
# 用AkShare获取(备用方案)
def get_index_data_ak(symbol='沪深300', start='20190101', end='20231231'):
df = ak.stock_zh_index_daily(symbol=f"sh{code_map[symbol]}")
df['date'] = pd.to_datetime(df['date'])
df = df[(df['date'] >= start) & (df['date'] <= end)]
df.sort_values('date', inplace=True)
df.reset_index(drop=True, inplace=True)
return df
# 实际调用
hs300_ts = get_index_data_ts()
hs300_ak = get_index_data_ak()
print(f"沪深300数据量:{len(hs300_ts)} 条")
print(hs300_ts.head())
你看,两个库返回的字段名不一样。Tushare用'trade_date',AkShare用'date'。这就是数据对齐的第一个麻烦。
3.4 数据清洗:别让脏数据毁了你的策略
数据拿到手,别急着用。先检查几个关键点:
- 缺失值: 指数数据一般不会缺,但节假日会停牌。检查一下有没有非交易日被错误填充。
- 异常值: 比如某天涨跌幅超过20%,这基本是数据错误。我遇到过某只指数一天涨了50%,后来发现是除权除息没处理。
- 重复值: 同一个交易日出现两条数据,取最后一条或者直接删除。
def clean_index_data(df, date_col='trade_date'):
"""数据清洗通用函数"""
# 1. 删除重复行
df = df.drop_duplicates(subset=[date_col])
# 2. 按日期排序
df = df.sort_values(date_col).reset_index(drop=True)
# 3. 检查缺失值
missing = df.isnull().sum()
if missing.any():
print(f"发现缺失值:\n{missing[missing > 0]}")
# 用前向填充处理(指数数据一般可以用前一天的值)
df = df.fillna(method='ffill')
# 4. 检查异常涨跌幅(超过15%视为异常)
if 'pct_chg' in df.columns:
abnormal = df[abs(df['pct_chg']) > 15]
if len(abnormal) > 0:
print(f"发现异常涨跌幅数据:{len(abnormal)} 条")
print(abnormal[[date_col, 'pct_chg']])
# 我一般直接删除异常值
df = df[abs(df['pct_chg']) <= 15]
return df
hs300_clean = clean_index_data(hs300_ts)
print(f"清洗后数据量:{len(hs300_clean)} 条")
3.5 数据对齐:多指数联合分析的基础
做指数增强策略,经常要同时分析沪深300和中证500。这两个指数的交易日不完全一致,比如某天沪深300有交易,中证500可能因为成分股调整停牌了。这时候就需要对齐。
对齐的核心思想:只保留所有指数都有数据的交易日。说白了,就是取交集。
def align_indices(df_list, date_col='trade_date'):
"""对齐多个指数数据,只保留共同交易日"""
# 提取每个指数的日期集合
date_sets = [set(df[date_col].unique()) for df in df_list]
# 取交集
common_dates = sorted(list(set.intersection(*date_sets)))
print(f"对齐前:各指数数据量分别为 {[len(s) for s in date_sets]}")
print(f"对齐后:共同交易日 {len(common_dates)} 天")
# 筛选共同日期
aligned_dfs = []
for df in df_list:
aligned = df[df[date_col].isin(common_dates)].copy()
aligned.sort_values(date_col, inplace=True)
aligned.reset_index(drop=True, inplace=True)
aligned_dfs.append(aligned)
return aligned_dfs
# 获取中证500数据
zz500_ts = get_index_data_ts(code='000905.SH')
zz500_clean = clean_index_data(zz500_ts)
# 对齐沪深300和中证500
hs300_aligned, zz500_aligned = align_indices([hs300_clean, zz500_clean])
print(f"沪深300对齐后:{len(hs300_aligned)} 条")
print(f"中证500对齐后:{len(zz500_aligned)} 条")
你想想看,如果不对齐,直接用原始数据算相关性或者做回归,结果可能完全失真。我曾经吃过这个亏,回测表现很好,实盘一跑就崩。后来发现是数据没对齐,多算了几个假的高收益交易日。
3.6 数据预处理流程总览
下面这张图,是我做数据预处理的标准流程。每次拿到新数据,都按这个步骤走一遍。
3.7 实战:完整的数据获取与预处理脚本
最后,我把上面的步骤整合成一个完整的函数。以后每次做策略,直接调用就行。
def get_and_preprocess_indices(codes=['000300.SH', '000905.SH'],
start='20190101', end='20231231'):
"""
一站式获取并预处理多个指数数据
返回:对齐后的DataFrame字典
"""
result = {}
for code in codes:
print(f"正在处理 {code}...")
# 获取数据
df = get_index_data_ts(code, start, end)
# 清洗
df = clean_index_data(df)
# 计算一些常用指标
df['log_ret'] = np.log(df['close'] / df['close'].shift(1))
df['ma5'] = df['close'].rolling(5).mean()
df['ma20'] = df['close'].rolling(20).mean()
df['volatility'] = df['log_ret'].rolling(20).std() * np.sqrt(252)
result[code] = df
# 对齐
dfs = list(result.values())
aligned = align_indices(dfs)
for i, code in enumerate(codes):
result[code] = aligned[i]
return result
# 执行
data_dict = get_and_preprocess_indices()
print("数据预处理完成!")
print(f"沪深300最后5条数据:")
print(data_dict['000300.SH'].tail())
数据搞定了,下一步就是基于这些干净的数据,构建波动率控制模型。嗯,这部分咱们下一章再聊。