4、低波动因子的数据获取:使用Python获取股票行情数据(Tushare、AKShare)、数据清洗与预处理

做量化的人都知道一句话:数据决定策略的上限,代码决定策略的下限

低波动因子这个策略,听起来高大上,但底层逻辑其实很简单——就是买那些波动小、走势稳的股票。但问题来了:你上哪儿拿数据?拿到数据后怎么处理?

我刚开始做低波动因子回测时,就踩过一个坑。当时从某平台直接下载了CSV文件,没做任何清洗就扔进模型。结果回测收益曲线漂亮得不像话,我还以为自己发现了圣杯。后来才发现——数据里有未来函数,复权价格算错了。嗯,那感觉就像考试抄到了假答案。

所以今天这一章,咱们就踏踏实实把数据获取和预处理这块地基打牢。

4.1 数据源的选择:Tushare vs AKShare

国内做量化,绕不开两个库:TushareAKShare。我个人的习惯是:

  • Tushare:数据质量高,接口稳定,但需要注册获取token,部分数据需要积分。适合做严肃的学术研究或实盘策略。
  • AKShare:完全免费,数据源多(来自东方财富、新浪等),但偶尔会有接口变动。适合快速验证想法、做教学演示。

说白了,如果你要跑一个需要长期维护的策略,我建议用Tushare。如果你只是写个demo或者做课程作业,AKShare完全够用。

我的建议:两个库都装上。平时用AKShare做快速验证,正式回测时用Tushare做交叉验证。

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_codetrade_dateopenhighlowclosevolamount
000001.SZ202312299.859.959.829.88486732.454812345678.0
000001.SZ202312289.789.909.759.86512345.675012345678.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返回的数据列名是中文的,比如“日期”、“开盘”、“收盘”等。我个人觉得对新手更友好,但做策略时记得把列名统一成英文,方便后续处理。

注意:AKShare的接口偶尔会因为数据源变动而失效。我曾经在凌晨跑策略时遇到接口报错,排查了半天才发现是东方财富改了API。所以,生产环境一定要加异常处理和备用数据源

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 本章知识体系

说了这么多,咱们用一张图把核心逻辑串起来:

低波动因子数据获取与预处理流程 数据源选择 数据获取 数据清洗:缺失值处理 → 复权处理 → 异常值过滤 因子计算:日收益率 → 滚动标准差 → 年化波动率 Tushare / AKShare → 数据清洗 → 因子计算 → 策略回测

这张图就是咱们做低波动因子的完整数据流水线。每一步都马虎不得。

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年春节后的暴跌)

好了,数据获取和预处理这块就讲完了。记住一句话:干净的数据是策略盈利的基石。下一章咱们就用这些数据,真正开始构建低波动因子组合。


专注资料整理