4、低波动因子的数据获取:使用Python获取股票行情数据(Tushare、AKShare)、数据清洗与预处理
做量化的人都知道一句话:数据决定策略的上限,代码决定策略的下限。
低波动因子这个策略,听起来高大上,但底层逻辑其实很简单——就是买那些波动小、走势稳的股票。但问题来了:你上哪儿拿数据?拿到数据后怎么处理?
我刚开始做低波动因子回测时,就踩过一个坑。当时从某平台直接下载了CSV文件,没做任何清洗就扔进模型。结果回测收益曲线漂亮得不像话,我还以为自己发现了圣杯。后来才发现——数据里有未来函数,复权价格算错了。嗯,那感觉就像考试抄到了假答案。
所以今天这一章,咱们就踏踏实实把数据获取和预处理这块地基打牢。
4.1 数据源的选择:Tushare vs AKShare
国内做量化,绕不开两个库:Tushare 和 AKShare。我个人的习惯是:
- Tushare:数据质量高,接口稳定,但需要注册获取token,部分数据需要积分。适合做严肃的学术研究或实盘策略。
- AKShare:完全免费,数据源多(来自东方财富、新浪等),但偶尔会有接口变动。适合快速验证想法、做教学演示。
说白了,如果你要跑一个需要长期维护的策略,我建议用Tushare。如果你只是写个demo或者做课程作业,AKShare完全够用。
4.2 使用Tushare获取股票行情数据
先说说Tushare。你需要先注册(https://tushare.pro),拿到你的API token。
import tushare as ts
# 设置token(记得换成你自己的)
ts.set_token('你的token')
pro = ts.pro_api()
# 获取平安银行(000001.SZ)从2020年到2023年的日线数据
df = pro.daily(
ts_code='000001.SZ',
start_date='20200101',
end_date='20231231'
)
print(df.head())
返回的数据长这样:
| ts_code | trade_date | open | high | low | close | vol | amount |
|---|---|---|---|---|---|---|---|
| 000001.SZ | 20231229 | 9.85 | 9.95 | 9.82 | 9.88 | 486732.45 | 4812345678.0 |
| 000001.SZ | 20231228 | 9.78 | 9.90 | 9.75 | 9.86 | 512345.67 | 5012345678.0 |
这里要注意:Tushare返回的数据是按日期降序排列的(最新的在最上面)。我刚开始用的时候没注意,直接拿去做计算,结果收益率算反了。你想想看,那得多尴尬。
df.sort_values('trade_date', inplace=True) 把日期排正。
4.3 使用AKShare获取股票行情数据
AKShare的用法更直接,不需要token,但接口名有点长。我一般这么写:
import akshare as ak
# 获取平安银行日线数据
df = ak.stock_zh_a_hist(
symbol="000001",
period="daily",
start_date="20200101",
end_date="20231231",
adjust="qfq" # 前复权
)
print(df.head())
AKShare返回的数据列名是中文的,比如“日期”、“开盘”、“收盘”等。我个人觉得对新手更友好,但做策略时记得把列名统一成英文,方便后续处理。
4.4 数据清洗与预处理
拿到原始数据只是第一步。真正的脏活累活,从清洗开始。
4.4.1 处理缺失值
股票数据里最常见的缺失值,就是停牌日。停牌那天没有交易数据,但日期还在。你想想看,如果直接拿这个数据算波动率,结果会怎样?
# 检查缺失值
print(df.isnull().sum())
# 处理方式1:直接删除停牌日
df.dropna(subset=['close'], inplace=True)
# 处理方式2:向前填充(用上一个交易日的价格)
df.fillna(method='ffill', inplace=True)
我个人习惯用向前填充。为什么?因为停牌期间,你实际上无法交易,但你的持仓还在。用前一天的收盘价填充,更符合实际情况。
4.4.2 复权处理
这是个大坑。股票有分红、送股、配股,如果不做复权处理,价格序列会出现断崖式下跌或跳空上涨。低波动因子对价格连续性要求很高,所以必须用复权数据。
Tushare和AKShare都支持复权参数:
- 前复权:调整历史价格,让价格连续。适合回测。
- 后复权:调整当前价格,适合看真实收益。
做低波动因子,我建议用前复权。因为我们要计算的是波动率,需要价格序列的连续性。
4.4.3 计算日收益率
低波动因子的核心是波动率,而波动率的基础是收益率。计算很简单:
# 计算对数收益率(更符合正态分布假设)
df['log_return'] = np.log(df['close'] / df['close'].shift(1))
# 或者用简单收益率
df['simple_return'] = df['close'].pct_change()
# 删除第一行的NaN
df.dropna(inplace=True)
我个人偏好对数收益率。为什么?因为对数收益率在时间上是可加的,计算多期收益率时直接用加法就行。而且它更接近正态分布,后续做统计检验时更靠谱。
4.4.4 计算波动率
低波动因子通常用过去N天的收益率标准差来衡量波动率。N取多少?
- 20天(约1个月):对短期波动敏感,适合高频调仓。
- 60天(约1个季度):更稳定,适合低频调仓。
- 252天(约1年):反映长期波动特征,但反应迟钝。
我做低波动策略时,一般用60天滚动窗口。既不会太敏感,也不会太迟钝。
# 计算60天滚动波动率(年化)
df['volatility'] = df['log_return'].rolling(window=60).std() * np.sqrt(252)
# 查看结果
print(df[['trade_date', 'close', 'volatility']].tail())
np.sqrt(252) 是为了把日波动率年化。一年大约有252个交易日。
4.5 本章知识体系
说了这么多,咱们用一张图把核心逻辑串起来:
这张图就是咱们做低波动因子的完整数据流水线。每一步都马虎不得。
4.6 完整代码示例
最后,我把整个流程封装成一个函数。你以后做低波动策略,直接调这个函数就行:
import pandas as pd
import numpy as np
import akshare as ak
def get_low_vol_data(symbol, start_date, end_date, window=60):
"""
获取并清洗股票数据,计算低波动因子
参数:
symbol: 股票代码,如 '000001'
start_date: 开始日期,如 '20200101'
end_date: 结束日期,如 '20231231'
window: 滚动窗口,默认60天
返回:
DataFrame: 包含日期、收盘价、波动率
"""
# 1. 获取数据
df = ak.stock_zh_a_hist(
symbol=symbol,
period="daily",
start_date=start_date,
end_date=end_date,
adjust="qfq"
)
# 2. 重命名列
df.rename(columns={
'日期': 'trade_date',
'收盘': 'close'
}, inplace=True)
# 3. 排序
df.sort_values('trade_date', inplace=True)
# 4. 处理缺失值
df.fillna(method='ffill', inplace=True)
# 5. 计算对数收益率
df['log_return'] = np.log(df['close'] / df['close'].shift(1))
df.dropna(inplace=True)
# 6. 计算波动率
df['volatility'] = df['log_return'].rolling(window=window).std() * np.sqrt(252)
df.dropna(inplace=True)
return df[['trade_date', 'close', 'volatility']]
# 使用示例
df_vol = get_low_vol_data('000001', '20200101', '20231231')
print(df_vol.head())
1. 多个股票同时计算(用groupby)
2. 上市不足60天的新股(会导致波动率为NaN)
3. 极端行情下的异常值(比如2020年春节后的暴跌)
好了,数据获取和预处理这块就讲完了。记住一句话:干净的数据是策略盈利的基石。下一章咱们就用这些数据,真正开始构建低波动因子组合。