4、数据获取与清洗:使用Tushare/AkShare获取数据、缺失值处理、异常值检测、数据标准化

数据获取与清洗,说白了就是量化投资的「原材料处理车间」。你策略再牛,模型再炫,拿到的数据是脏的、缺的、错的,那结果就是垃圾进垃圾出。我刚开始做量化那会儿,就吃过这个亏——回测曲线漂亮得不行,实盘一跑直接崩,查了半天,原来是某只股票的分红数据没复权处理。

嗯,咱们今天就把这块彻底捋清楚。

4.1 数据源选择:Tushare vs AkShare

国内做量化,绕不开这两个库。我个人习惯是:研究阶段用AkShare,生产环境用Tushare。为什么?

对比维度 Tushare AkShare
数据质量 高,经过校验 中等,部分源不稳定
获取方式 需注册+Token 免费,直接pip
频率支持 日/分钟/ tick 日/分钟为主
文档清晰度 好,有社区 一般,更新快
适合场景 实盘、回测 快速验证、研究
我的经验:AkShare的接口有时会变,比如某天突然返回格式改了。我建议你在代码里加一层「数据适配器」,把不同源的输出统一成DataFrame标准格式。这样切换源时,只需改一行配置。

4.2 用Tushare获取数据

先注册拿到token,然后安装:

pip install tushare

获取日线数据,我一般这么写:

import tushare as ts

# 设置token
ts.set_token('你的token')
pro = ts.pro_api()

# 获取平安银行2023年日线数据
df = pro.daily(
    ts_code='000001.SZ',
    start_date='20230101',
    end_date='20231231',
    fields='trade_date,open,high,low,close,vol'
)

# 排序,Tushare默认倒序
df = df.sort_values('trade_date').reset_index(drop=True)
print(df.head())

这里有个坑:Tushare的日线数据默认是后复权吗?不是。它返回的是原始价格。你需要自己调用 pro.adj_factor() 来获取复权因子,然后手动计算。我曾经因为忘了这一步,回测结果虚高了20%。

注意:Tushare免费版有调用频率限制,每分钟最多200次。批量获取时,记得加time.sleep(0.3)控制节奏。

4.3 用AkShare获取数据

AkShare更直接,不用注册:

pip install akshare

获取同样的数据:

import akshare as ak

# 获取平安银行日线
df = ak.stock_zh_a_hist(
    symbol="000001",
    period="daily",
    start_date="20230101",
    end_date="20231231",
    adjust="qfq"  # 前复权
)

# 重命名列,统一格式
df.rename(columns={
    '日期': 'trade_date',
    '开盘': 'open',
    '收盘': 'close',
    '最高': 'high',
    '最低': 'low',
    '成交量': 'vol'
}, inplace=True)

print(df.head())

你看,AkShare直接支持复权参数,省事。但它的数据源是东方财富,偶尔会有断更。我遇到过某天下午4点数据还没更新,等到晚上才出来。所以做定时任务时,建议把获取时间设在收盘后2小时。

4.4 缺失值处理

数据拿到手,第一件事就是检查缺失。为什么会有缺失?停牌、数据源漏了、节假日不同步,原因很多。

先看看缺失情况:

# 检查缺失比例
missing_ratio = df.isnull().sum() / len(df)
print(missing_ratio[missing_ratio > 0])

处理方式,我按场景分三类:

  • 直接删除:缺失比例小于5%,且是随机缺失。用 df.dropna() 就行。
  • 向前填充:金融时间序列常用。比如停牌日,用前一天的收盘价填充。用 df.fillna(method='ffill')
  • 插值法:如果缺失的是中间某段,比如日内分钟数据,用线性插值更合理。用 df.interpolate()
核心原则:不要无脑填充。先搞清楚缺失原因。如果是停牌,向前填充没问题。如果是数据源漏了,建议去其他源补全,而不是填充。

举个例子,处理停牌:

# 假设某股票停牌5天,这5天数据全为NaN
# 用前一天的收盘价填充
df['close'] = df['close'].fillna(method='ffill')

# 成交量填0
df['vol'] = df['vol'].fillna(0)

4.5 异常值检测

异常值,说白了就是「一眼假」的数据。比如某股票突然一天涨了1000%,或者成交量突然比平时大了100倍。这通常是数据错误,不是市场行为。

我常用的检测方法:

  1. 3σ原则:假设数据服从正态分布,超出均值±3倍标准差的就是异常。适合收益率这类数据。
  2. 箱线图法:用四分位数,超出1.5倍IQR的算异常。适合价格、成交量。
  3. 固定阈值:比如A股涨跌幅限制±10%,超出这个范围的直接标记。

代码实现:

# 3σ法检测收益率异常
df['return'] = df['close'].pct_change()
mean = df['return'].mean()
std = df['return'].std()
df['return_outlier'] = (df['return'] > mean + 3*std) | (df['return'] < mean - 3*std)

# 查看异常
outliers = df[df['return_outlier']]
print(f"发现 {len(outliers)} 个异常值")
注意:不要一发现异常就删除。先看看是不是真实事件。比如某公司突然发布重大利好,涨停10%是正常的。我建议你建立一个「白名单」,把已知的重大事件日期排除在外。

4.6 数据标准化

标准化,就是把不同量纲的数据拉到同一个尺度。比如股价是几十块,成交量是几百万手,直接放一起建模,模型会忽略成交量。

常用的两种方法:

方法 公式 适用场景
Z-score标准化 (x - μ) / σ 数据近似正态分布,如收益率
Min-Max归一化 (x - min) / (max - min) 数据有明确边界,如RSI指标

代码实现:

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Z-score标准化
scaler = StandardScaler()
df['close_z'] = scaler.fit_transform(df[['close']])

# Min-Max归一化
scaler = MinMaxScaler()
df['close_mm'] = scaler.fit_transform(df[['close']])

这里有个细节:标准化参数要在训练集上拟合,然后应用到测试集。否则会造成未来信息泄露。我见过有人对整个数据集做标准化,然后做回测,结果过拟合得一塌糊涂。

我的习惯:把标准化器保存下来,用pickle或joblib。这样实盘时,直接用同样的参数处理新数据,保证一致性。

4.7 完整流程:从获取到清洗

把上面所有步骤串起来,形成一个函数:

def get_clean_data(ts_code, start, end):
    # 1. 获取数据
    df = pro.daily(ts_code=ts_code, start_date=start, end_date=end)
    df = df.sort_values('trade_date').reset_index(drop=True)
    
    # 2. 缺失值处理
    df['close'] = df['close'].fillna(method='ffill')
    df['vol'] = df['vol'].fillna(0)
    
    # 3. 异常值检测(涨跌幅限制)
    df['return'] = df['close'].pct_change()
    df = df[df['return'].between(-0.1, 0.1)]  # 剔除异常涨跌
    
    # 4. 标准化
    scaler = StandardScaler()
    df['close_std'] = scaler.fit_transform(df[['close']])
    
    return df, scaler

这个流程,我用了好几年。每次新项目,直接复制粘贴,改改参数就行。你想想看,如果每次都要从头写一遍,多浪费时间。

4.8 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

数据获取与清洗流程 数据获取 Tushare / AkShare 缺失值处理 删除 / 填充 / 插值 异常值检测 3σ / 箱线图 / 阈值 数据标准化 Z-score / Min-Max 输出清洗后数据 用于回测 / 建模 每个步骤都可能需要回溯,比如异常值检测后可能需要重新处理缺失值 迭代优化:发现问题后返回上一步

你看,整个流程不是线性的。实际工作中,你经常需要来回调整。比如标准化后发现异常值更明显了,那就回去重新调异常检测的参数。这很正常,别怕麻烦。

总结一下:数据清洗没有银弹。我的建议是——先跑通一个最小流程,然后根据数据特点逐步优化。别想着一步到位,那不可能。

好了,数据获取与清洗就讲到这里。下一章咱们聊聊因子构建,那才是真正出彩的地方。


公众号:蓝海资料掘金营,微信deep3321