3. 金融数据获取:Tushare/AkShare接口调用、数据缓存策略、多源数据合并、数据质量检查

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

这一章我们聊聊怎么搞到靠谱的金融数据。我这些年踩过的坑,基本都跟数据有关。数据源挂了、数据对不上、数据有缺失……嗯,都是血泪史。

3.1 Tushare 与 AkShare:两大主流数据源

国内做量化,绕不开两个库:TushareAkShare。我个人的习惯是,Tushare 用来拿股票、基金、指数这类标准化的金融数据,AkShare 则用来补一些另类数据,比如期货库存、宏观经济指标。

Tushare 调用示例

先注册个 token,然后就能愉快地调用了。注意,Tushare 的积分制度决定了你能拿到的数据量。我刚开始做的时候,积分不够,只能拿日线数据,后来才慢慢升级。

import tushare as ts

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

# 获取平安银行日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20230101', end_date='20231231')
print(df.head())

AkShare 调用示例

AkShare 更接地气,很多数据直接从财经网站爬下来。不过它更新频繁,有时候接口会变。我曾经遇到过,前一天还能跑的代码,第二天就报错了。所以,建议固定版本。

import akshare as ak

# 获取A股实时行情
df = ak.stock_zh_a_spot_em()
print(df.head())
我的小技巧: 两个库都装上。Tushare 做主力,AkShare 做备胎。万一 Tushare 限流了,AkShare 还能顶上。

3.2 数据缓存策略:别每次都去拉数据

你想想看,每次跑策略都去远程拉数据,网络延迟不说,还容易被限流。我个人的做法是:本地缓存 + 增量更新

缓存策略设计

缓存层级 存储方式 适用场景
内存缓存 dict / Redis 高频访问的实时数据
文件缓存 Parquet / CSV 日线、分钟线等历史数据
数据库缓存 SQLite / MySQL 多策略共享的大规模数据

我一般用 Parquet 格式存历史数据,压缩率高,读取快。CSV 虽然通用,但大了之后读写慢得让人抓狂。

import pandas as pd
import os

def get_cached_data(file_path, start_date, end_date):
    """带缓存的日线数据获取"""
    if os.path.exists(file_path):
        # 读取本地缓存
        df = pd.read_parquet(file_path)
        # 检查是否需要更新
        max_date = df['trade_date'].max()
        if max_date < end_date:
            # 增量拉取缺失数据
            new_data = fetch_from_api(start_date=max_date, end_date=end_date)
            df = pd.concat([df, new_data]).drop_duplicates()
            df.to_parquet(file_path)
        return df
    else:
        # 首次拉取全量数据
        df = fetch_from_api(start_date, end_date)
        df.to_parquet(file_path)
        return df
注意: 缓存不是万能的。如果数据源有修正(比如复权因子调整),你的缓存数据就过时了。我建议每周强制刷新一次全量数据。

3.3 多源数据合并:把碎片拼成拼图

做多因子策略时,你需要把不同来源的数据合并到一起。比如,Tushare 拿股票行情,AkShare 拿行业分类,再手动补一些财务数据。

合并的难点在于:时间对齐代码映射

时间对齐

不同数据源的交易日历可能不一样。比如,A股和港股节假日不同。我习惯先建一个统一的交易日历,然后做左连接。

# 假设 trade_cal 是统一的交易日历
trade_cal = pd.DataFrame({'trade_date': pd.date_range('2023-01-01', '2023-12-31')})

# 左连接,缺失值用前向填充
df_merged = trade_cal.merge(df_stock, on='trade_date', how='left')
df_merged = df_merged.ffill()

代码映射

同一个股票,不同数据源的代码可能不同。Tushare 用 '000001.SZ',AkShare 用 '000001'。我建了一个映射表,统一转成内部代码。

code_map = {
    '000001.SZ': '000001',
    '600000.SH': '600000',
    # ...
}
核心原则: 多源合并时,永远保留原始数据列。不要直接覆盖,万一合并错了还能追溯。

3.4 数据质量检查:别让脏数据毁了你的策略

数据质量是量化投研的命门。我见过太多人,策略回测跑得飞起,实盘一塌糊涂。查到最后,发现是数据里有脏数据。

我总结了一套数据质量检查清单:

  • 缺失值检查:有没有 NaN?占比多少?
  • 异常值检查:价格有没有负数?涨跌幅有没有超过 100%?
  • 重复值检查:有没有重复的日期-股票组合?
  • 逻辑一致性检查:开盘价、最高价、最低价、收盘价是否合理?
  • 时间连续性检查:交易日有没有跳空?
def check_data_quality(df):
    """数据质量检查"""
    issues = []
    
    # 缺失值
    missing_pct = df.isnull().sum() / len(df)
    if missing_pct.max() > 0.05:
        issues.append(f"缺失值超过5%: {missing_pct[missing_pct > 0.05].index.tolist()}")
    
    # 异常值
    if (df['close'] <= 0).any():
        issues.append("收盘价存在非正值")
    
    # 重复值
    if df.duplicated(subset=['trade_date', 'ts_code']).any():
        issues.append("存在重复的日期-股票组合")
    
    # 逻辑一致性
    if (df['high'] < df['low']).any():
        issues.append("最高价低于最低价")
    
    return issues
避坑指南: 我曾经因为复权数据没处理好,导致回测收益虚高。后来我加了一个检查:用后复权价格计算收益率,再和前复权对比。如果差异超过 0.1%,就报警。

知识体系总览

下面这张图,概括了本章的核心逻辑。从数据源到缓存,再到合并和质量检查,每一步都不能省。

金融数据获取流程 Tushare AkShare 其他数据源 数据缓存策略(内存/文件/数据库) 多源数据合并(时间对齐 + 代码映射) 数据质量检查(缺失/异常/重复/逻辑) 高质量数据集 数据源层 缓存层 合并层 质量层 输出层

说白了,数据获取这件事,七分靠工具,三分靠经验。工具选对了,经验攒够了,后面的事情就顺了。

专注资料整理