第三章:金融数据获取——用Tushare/Akshare搞定A股数据

数据是量化模型的血液,这话一点不夸张。我见过太多人把精力全花在策略设计上,结果数据源一塌糊涂,回测跑出来全是幻觉。今天咱们就聊聊怎么用Python把A股的日线、指数、行业板块数据稳稳拿到手。

3.1 数据源选哪个?Tushare vs Akshare

先说说这两个库的区别。Tushare是老牌选手,数据质量高,但需要注册获取token,部分接口有积分限制。Akshare是后起之秀,完全免费,接口更丰富,但偶尔会有数据延迟。

我个人习惯是:生产环境用Tushare,研究探索用Akshare。为什么?因为Tushare的数据经过清洗,字段规范,适合直接喂给模型。Akshare胜在方便,不用注册就能用,适合快速验证想法。

核心结论:

  • Tushare:稳定、规范、有积分门槛
  • Akshare:免费、灵活、偶尔抽风
  • 建议两个都装,互为备份

3.2 安装与初始化

先装库,这个没什么好说的。但要注意版本,我遇到过因为版本太旧导致接口报错的情况。

# 安装
pip install tushare akshare pandas

# Tushare初始化
import tushare as ts
ts.set_token('你的token')  # 去tushare.pro注册获取
pro = ts.pro_api()

# Akshare直接导入就行
import akshare as ak

小提示:Token建议写在配置文件里,别硬编码。我曾经把token直接写在代码里上传到GitHub,结果被全网爬了一遍……

3.3 获取A股日线数据

日线数据是轮动模型的基础。说白了,就是每天的开高低收、成交量这些。咱们先看Tushare怎么拿。

# Tushare获取日线
df = pro.daily(
    ts_code='000001.SZ',  # 平安银行
    start_date='20240101',
    end_date='20240301'
)
print(df.head())

这里有个坑:ts_code的格式是代码.交易所,SZ是深交所,SH是上交所。我刚开始做的时候老搞混,后来写了个映射表才解决。

再看看Akshare的写法,更直接一些:

# Akshare获取日线
df = ak.stock_zh_a_hist(
    symbol="000001",
    period="daily",
    start_date="20240101",
    end_date="20240301",
    adjust="qfq"  # 前复权
)
print(df.head())

注意:Akshare默认返回的是未复权数据。做量化一定要用复权数据,否则分红送股会把你搞晕。我见过有人用未复权数据跑回测,结果策略收益虚高30%。

3.4 获取指数数据

指数数据用于判断市场整体走势。轮动模型里,我们经常用沪深300、中证500、创业板指这些。

Tushare获取指数日线:

# 沪深300指数
df_index = pro.index_daily(
    ts_code='000300.SH',
    start_date='20240101',
    end_date='20240301'
)

Akshare获取指数数据:

# 上证指数
df_index = ak.stock_zh_index_daily(
    symbol="sh000001"
)

嗯,这里要注意:指数代码和个股代码的规则不一样。Tushare里指数代码后面加.SH.SZ,Akshare里则是shsz前缀。你想想看,如果代码写错了,拿到的数据就是错的,模型再厉害也没用。

3.5 获取行业板块数据

行业板块数据是轮动模型的核心输入。我们得知道每个行业今天涨了多少,资金流向哪里。

Tushare提供了行业分类和板块行情:

# 获取行业分类
df_industry = pro.industry_classified(
    src='sw'  # 申万行业分类
)

# 获取行业板块日线
df_sector = pro.sector_daily(
    ts_code='801010.SI',  # 申万一级行业代码
    start_date='20240101',
    end_date='20240301'
)

Akshare的行业板块数据更丰富:

# 获取申万行业板块行情
df_sector = ak.stock_board_industry_hist(
    symbol="半导体",
    start_date="20240101",
    end_date="20240301"
)

# 获取概念板块
df_concept = ak.stock_board_concept_hist(
    symbol="人工智能",
    start_date="20240101",
    end_date="20240301"
)

避坑指南:我曾经用Akshare获取行业数据时,发现某些板块在特定日期返回空值。后来排查发现是接口数据更新延迟。建议做数据校验:如果某天数据为空,用前一天的数据填充,或者直接跳过。

3.6 数据存储与缓存

每次跑策略都去拉数据,效率太低了。我建议把数据存到本地,用的时候直接读。

import os
import pandas as pd

def save_data(df, filename):
    """保存数据到本地"""
    path = f'./data/{filename}'
    os.makedirs(os.path.dirname(path), exist_ok=True)
    df.to_parquet(path)  # parquet格式比csv快10倍

def load_data(filename):
    """从本地加载数据"""
    path = f'./data/{filename}'
    if os.path.exists(path):
        return pd.read_parquet(path)
    return None

为什么用parquet?因为量化数据动辄几百万行,csv读写太慢。我试过用csv存3年的全A股日线数据,加载要30秒,换成parquet只要2秒。

3.7 数据质量检查

数据拿到手,别急着用。先做一轮质量检查,这是血的教训换来的经验。

def check_data_quality(df):
    """数据质量检查"""
    issues = []
    
    # 检查缺失值
    if df.isnull().sum().sum() > 0:
        issues.append(f"存在{df.isnull().sum().sum()}个缺失值")
    
    # 检查重复日期
    if df['trade_date'].duplicated().any():
        issues.append("存在重复日期")
    
    # 检查价格异常
    if (df['close'] <= 0).any():
        issues.append("存在非正价格")
    
    # 检查涨跌幅异常
    if (df['pct_chg'].abs() > 20).any():
        issues.append("存在涨跌幅超过20%的异常数据")
    
    return issues

我曾经遇到过某只股票连续5天数据完全一样,明显是接口返回了缓存数据。如果不做检查,模型就会学到「这只股票不会波动」的错误规律。

3.8 知识体系总览

下面这张图概括了本章的核心逻辑:从数据源到存储,再到质量检查的完整流程。

金融数据获取流程 数据源 Tushare / Akshare 数据类型 • A股日线 • 指数数据 • 行业板块 本地存储 Parquet / CSV 质量检查 缺失/异常/重复 数据流向:从数据源获取 → 分类存储 → 质量校验 → 进入模型 关键注意事项 1. 使用复权数据,避免分红送股影响 2. 建立数据缓存机制,避免重复请求 3. 每次使用前做数据质量检查

3.9 实战:一键获取全量数据

最后,我分享一个自己常用的函数,把日线、指数、板块数据一次性拉下来:

def get_all_data(start_date, end_date):
    """一键获取轮动模型所需全部数据"""
    data = {}
    
    # 1. 获取沪深300成分股日线
    hs300 = pro.index_weight(ts_code='000300.SH')
    for code in hs300['con_code'].head(50):  # 先取前50只
        df = pro.daily(ts_code=code, start_date=start_date, end_date=end_date)
        data[f'stock_{code}'] = df
    
    # 2. 获取主要指数
    indices = ['000300.SH', '000905.SH', '399006.SZ']
    for idx in indices:
        data[f'index_{idx}'] = pro.index_daily(ts_code=idx, start_date=start_date, end_date=end_date)
    
    # 3. 获取申万一级行业
    sectors = pro.industry_classified(src='sw')
    for sector in sectors['industry_code'].unique()[:10]:
        data[f'sector_{sector}'] = pro.sector_daily(ts_code=sector, start_date=start_date, end_date=end_date)
    
    return data

这个函数我用了快两年,基本没出过问题。唯一要注意的是,Tushare有调用频率限制,如果数据量大,记得加个time.sleep()

本章核心要点:

  • Tushare和Akshare各有优劣,建议双持
  • 日线、指数、板块数据是轮动模型的三大支柱
  • 数据质量比策略本身更重要
  • 本地缓存能大幅提升效率

专注资料整理