ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Tushare免费A股数据接口实测:从注册到策略回测全流程

Tushare免费A股数据接口实测:从注册到策略回测全流程

直接说结论:A股免费行情数据源里,Tushare 属于绕不开的那一个。做 Python 量化、搞策略回测、做个股筛选,第一步就是解决数据问题。你如果不想花钱买 Wind 或者聚宽的数据终端,也不想定期手工去爬东财、新浪的接口,那 Tushare 基本是最省心的方案——注册免费、接口稳定、覆盖面广,日线、分钟线、基本面、指数成分、资金流向都能拿。这篇就把我从注册到跑通全套接口的过程完整拆出来,包括 token 怎么拿、接口报错怎么处理、积分不够怎么办、本地怎么缓存,里面全是实操中踩过的坑。

1. Tushare 到底是什么,免费额度够不够用

先说清楚 Tushare 在量化数据链路上的定位。它本质是一个聚合式的金融数据 API 服务,提供了 HTTP 接口和 Python SDK 包,你通过注册账号拿到一个 token,之后所有请求都带这个 token 去调数据。数据覆盖股票、指数、基金、期货、期权、宏观、另类数据,对个人量化玩家来说,最常用的就是 A 股日线行情、复权因子、每日指标、财务报表和指数成分股这几块。

很多人一听“免费”就担心够不够用。我直接给结论:个人做日频策略、周频调仓、筛选股票池,免费档位完全够;做高频、做分钟级 T+0、做全市场 tick 级回测,不够,得靠积分升级。Tushare 的积分体系是它区别于其他免费数据源的关键——你注册以后默认有 120 积分左右(具体以官网当前规则为准),能调用的是基础接口的基础频率。积分怎么赚?充值是一方面,但更多人是通过社区贡献、发布优质内容、邀请注册来攒积分。实际体验下来,日线接口在免费档下每分钟调用次数有限制,但只要你不是疯狂循环请求,正常写策略回测不会触发限流。

1.1 为什么强烈建议用 Tushare 而不是自己爬网页

自己爬行情数据不是不行,但成本非常高。东财、新浪的网页接口格式说变就变,而且接口返回的字段经常是拼凑的,缺复权因子、缺停牌标记、除权除息数据不完整。我曾经用爬虫方案跑了两个多月,某天接口调整,整个股票池的复权价全部错位,回测出来的盈亏曲线直接没法看。后来切换到 Tushare,最直观的感受是字段统一、文档清晰、数据源头稳定。

另一个关键点是复权处理。做策略回测必须要用复权价格,否则分红除权那天K线会跳空,导致虚假的收益信号。Tushare 的pro_bar接口直接支持qfq(前复权)和hfq(后复权),底层帮你算好复权因子,这是网页爬虫很难做到的——你爬回来的原始价格必须自己处理除权除息事件,财务数据、送转比例、分红公告要自己匹配时间点,工作量巨大。

1.2 需要避开的认知误区:Tushare 不是纯免费到底

在社区里常看到有人吐槽“Tushare 不是真正免费,高积分接口要钱”。这个说法对也不对。实际情况是:基础接口免费且无积分门槛,比如股票日线、复权因子、基础信息;高积分接口需要积分达到一定阈值才能调用,比如分钟数据、资金流向、龙虎榜这些。对新手来说,日线回测和选股已经覆盖 80% 的入门需求,用不着上来就追高频数据。

我的建议是:先把非积分接口用到极致,搞清楚数据字段含义,跑通回测流程,再根据策略需求决定要不要升级积分。而且积分可以慢慢攒,社区任务、文章贡献、邀请都是渠道,不必一上来就花钱。

2. 注册、装包、拿 Token:10 分钟跑通第一个请求

Tushare 的使用流程非常固定,你只要跑通一次,后面就是填参数调接口的事情。我按顺序过一遍完整流程,包括我实际遇到的坑。

2.1 注册账号和获取 Token

打开 Tushare 官网,用手机号注册,登录后进入“个人主页”,在“接口TOKEN”页面可以看到一串字符,这就是你调用接口的凭证。切记:Token 不要提交到公开仓库,不要写在博客代码里,不要复制到聊天工具中。我有一次不小心把 token 写进一个公开的 Jupyter Notebook,不到两小时就收到异常访问提醒,吓得我当场重置。现在我都用环境变量或本地配置文件来存。

Token 相当于你身份认证的钥匙,所有数据请求都会带上它,泄露了别人就能消耗你的调用额度,甚至访问你的权限范围内的数据。建议拿到后立刻存到本机环境变量里:

export TUSHARE_TOKEN="你的token字符串"

Windows 用户可以在系统环境变量里加,也可以在代码里读本地配置文件。

2.2 安装 Tushare 并验证连通性

用 pip 安装,一条命令搞定:

pip install tushare

装完以后在 Python 里初始化并验证:

import tushare as ts import os ts.set_token(os.environ.get("TUSHARE_TOKEN")) pro = ts.pro_api() # 拉取某只股票最近5个交易日日线行情 df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20240110") print(df.head())

第一次跑通你会看到一行行包含 open、high、low、close、pre_close、change、pct_chg、volume、amount 的表格数据。这里ts_code的格式是“代码.交易所后缀”,深交所是SZ,上交所是SH。这个格式跟你在行情软件里看到的纯数字代码不一样,是 Tushare 全市场统一风格,别搞混。北交所的股票后缀是BJ,注意这一点,很多新手拿北交所代码去查日线,接口直接返回空 DataFrame。

2.3 我的实际建议:先写个通用查询封装

不要每次调用都直接pro.daily(...),建议自己包装一层,统一管理 token、超时重试、返回空数据的处理。我自己的封装核心大概是这样的:

import time import tushare as ts import pandas as pd from functools import lru_cache ts.set_token("从环境变量读取") pro = ts.pro_api() @lru_cache(maxsize=256) def fetch_daily(ts_code, start_date, end_date): for attempt in range(3): try: df = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date) if df is None or df.empty: return pd.DataFrame() return df.sort_values("trade_date") except Exception as e: print(f"请求失败,重试 {attempt + 1}/3: {e}") time.sleep(1) return pd.DataFrame()

lru_cache在这里很重要。回测里面同一只股票同一时间段会被反复查询,缓存能直接把内存命中提升几十倍。网络请求是 IO 密集操作,每次调用大概几十到几百毫秒,循环 3000 只股票全量拉取时,缓存和不缓存的耗时差距是分钟级和小时级的区别。

3. 核心接口逐个拆解:日线、复权、每日指标、财务数据

这一部分是整篇的重头戏。Tushare 的接口很多,但量化用得最密集的就那么几个。我把它们按使用频率排序,逐个讲清楚参数、返回字段和实战注意点。

3.1pro.daily日线行情接口,最基础但最常用

这是最核心的接口。参数很简单:ts_code(股票代码)、trade_date(交易日期)或start_date+end_date(日期区间)。注意日期的格式是YYYYMMDD纯数字字符串,不是YYYY-MM-DD,这个我曾经转换格式弄错,查了半天才发现接口返回空。

返回字段包括:

字段含义补充说明
ts_code股票代码带交易所后缀
trade_date交易日期格式YYYYMMDD
open开盘价单位:元
high最高价单位:元
low最低价单位:元
close收盘价单位:元
pre_close昨收价用于计算涨跌幅
change涨跌额close - pre_close
pct_chg涨跌幅百分比数值
vol成交量单位:手
amount成交额单位:千元

这里有一个容易踩坑的地方:vol的单位是“手”,不是“股”。1 手 = 100 股。如果你算换手率或者流动性指标,得先换算。另外amount的单位是“千元”,不是“元”。我第一次统计某只票的成交额,直接用amount.sum(),算出来数字比实际大了 1000 倍,后来检查单位说明才发现问题。

pro.daily默认返回的是未复权价格。做策略回测必须处理复权,见下一步。

3.2ts.pro_bar复权行情,回测必用

ts.pro_bar是 Tushare 封装好的高级接口,可以在一个函数里完成复权计算,比先拉原始数据再自己算复权因子方便得多。

df = ts.pro_bar(ts_code="000001.SZ", adj="qfq", start_date="20230101", end_date="20240101")

参数adj有三个取值:None(不复权)、qfq(前复权)、hfq(后复权)。回测一般用前复权qfq,因为前复权价格能直接反映当前价格水平,跟真实交易价格接近。如果用后复权,价格会非常大,只适合计算收益率序列,不适合直接对比当前股价。

有一点值得说明:前复权数据是动态的,每次新增除权除息事件,前面的历史价格都会跟着变化。所以做研究时一旦确定了数据抓取时间,最好把复权数据落盘存下来,避免以后重新计算结果不一样导致回测结果没法复现。我自己是每次回测跑批前固定用同一个数据快照,研究记录里注明“数据截至某日”。

3.3pro.daily_basic每日指标,选股必备

除了行情价格,选股策略还需要每天变化的指标:市盈率、市净率、换手率、总市值、流通市值、量比等等。这就是daily_basic接口的用途。

df = pro.daily_basic(ts_code="000001.SZ", start_date="20240101", end_date="20240110", fields="ts_code,trade_date,pe_ttm,pb,ps_ttm,turnover_rate,total_mv,circ_mv")

常用字段:

  • pe_ttm:滚动市盈率,比静态市盈率更及时
  • pb:市净率
  • turnover_rate:换手率,单位是百分比
  • total_mv:总市值,单位是万元
  • circ_mv:流通市值,单位是万元

这个接口在选股策略里非常关键,比如低市盈率选股、小市值策略、换手率过滤等。注意市值单位是“万元”,不是“元”。如果你要跟其他数据源对数字,先统一单位。

3.4 财务报表数据,基本面策略核心

做基本面量化,三大报表数据是刚需。Tushare 提供了income(利润表)、balancesheet(资产负债表)、cashflow(现金流量表),还有fina_indicator(财务指标,已经算好 ROE、毛利率、负债率等)。

df = pro.fina_indicator(ts_code="000001.SZ", start_date="20230101", end_date="20240101")

这个接口返回的字段特别多,包括几十个指标。新手容易犯的错误是直接用最新一期报表数据而忽略“公告日期”和“报告期”的区别。end_date是报告期(比如 20231231 表示三季报还是年报要看实际公告),ann_date是公告日期。你在回测时要用的是“当时能拿到的最新数据”,也就是ann_date小于交易日的报表,而不是期末日期小于交易日的报表。这叫做point-in-time 回测,避免未来函数。Tushare 里两份数据都有,但需要你自己在代码里做按公告日对齐的操作。

3.5 指数成分和权重,组合构建必备

做组合策略、行业轮动,需要知道指数成分股和权重。Tushare 提供index_weight接口:

df = pro.index_weight(index_code="000300.SH", start_date="20240101", end_date="20240131")

沪深 300 的代码是000300.SH,中证 500 是000905.SH,上证 50 是000016.SH。这个接口返回成分股代码和权重,注意权重是百分比数值,不是小数。我在一次计算指数收益时直接用权重乘以个股收益求和,结果偏了 100 倍,就是这地方出的问题。

还有一个建议:指数成分股会定期调整,如果你想严格模拟指数,应该用每个调样日对应的最新权重,并且调样前后的切换日期要与实际一致。Tushare 的index_weight是按公告日返回某个日期的权重快照,具体日期选择在做历史回测时要想清楚。

4. 单次拉全市场数据的高效实操:循环、限流、缓存三板斧

当你开始写真正的策略,不可能是只针对一两只股票。你需要的是某个时间截面全市场的行情、指标、财务数据,然后做横截面筛选。这时候就涉及“如何高效拉取全市场数据”的问题。

4.1 先拿股票列表,再逐票拉数据

第一步永远是拿到全市场的股票基础列表:

stock_list = pro.stock_basic(exchange="", list_status="L", fields="ts_code,symbol,name,area,industry,list_date")

list_status="L"表示当前上市状态的股票,D表示已退市,P表示暂停上市。如果你做回测需要包含已退市股票以避免幸存者偏差,就得把L和D都拉上,再按list_date筛选。这里如果只拉当前存在的股票,回测结果会“幸存者偏差”拉满——你只会看到活下来的票,那些退市暴跌的票根本没参与历史模拟,策略收益会虚高。

4.2 控制请求频率,避免触发限流

Tushare 免费档位对不同接口有权限范围限制,官网“积分说明”页面写得很清楚。我们不能贴具体数值,因为规则会更新,但经验法则是:连续请求时加 0.3 到 1 秒的 sleep,单次批量任务总请求数控制在几百以内比较安全。我自己的策略是每一次循环之间time.sleep(0.5),拉 1000 只股票大约需要 8-10 分钟,这个时间完全可以接受。

触发限流的表现有两种。一种是直接抛异常提示“抱歉,您每分钟最多访问该接口X次”;另一种是返回空数据,不报错。第二种特别坑,你以为这只股票停牌了,其实是限流。所以我的封装函数会检查返回 DataFrame 的行数,如果多只股票连续出现空数据,主动放慢速度。

4.3 增量更新与本地缓存,越跑越快的核心思路

全量拉数据只做一次,以后就做增量。你不需要每天把几千只股票从头拉一遍,只需要拉最近几个交易日的增量数据,然后合并进本地持久化存储里。最简单的方式是用to_csv或to_parquet落盘,也可以用 SQLite。我常用 Parquet 格式,读取速度快,占用空间小。

# 拉增量数据并合并到本地 new_df = fetch_daily("000001.SZ", start_date="20240101", end_date="20240115") old_df = pd.read_parquet("daily_000001.parquet") merged = pd.concat([old_df, new_df]).drop_duplicates(subset=["ts_code", "trade_date"]).sort_values("trade_date") merged.to_parquet("daily_000001.parquet")

去重逻辑是关键,drop_duplicates必须按ts_code和trade_date两个字段同时去重,不能只按 trade_date。跑久了你会发现数据更新策略的核心思路就两句话:全量初始化一次,增量每日更新,历史数据永不重拉。

5. 实战:基于 Tushare 数据写一个最简单的双均线策略回测

前面讲了一堆接口,现在串起来跑一个完整的例子。不做复杂的风控、不做仓位优化,只展示“从 Tushare 拿数据 -> 算信号 -> 模拟交易 -> 算收益”这一条完整链路。

5.1 拉数据并计算均线信号

import pandas as pd import numpy as np import tushare as ts ts.set_token("你的token") pro = ts.pro_api() # 1. 拉前复权日线 df = ts.pro_bar(ts_code="600519.SH", adj="qfq", start_date="20210101", end_date="20241201") df = df.sort_values("trade_date").reset_index(drop=True) # 2. 计算双均线 df["ma5"] = df["close"].rolling(5).mean() df["ma20"] = df["close"].rolling(20).mean() # 3. 生成交易信号:金叉买入,死叉卖出 df["signal"] = 0 df.loc[df["ma5"] > df["ma20"], "signal"] = 1 df["position"] = df["signal"].diff()

注意这里position取值为 1 表示“今天从空仓变成满仓”,-1 表示“从满仓变成空仓”。为什么用diff()而不是直接用 signal?因为回测模拟的是持仓状态的变化,不是每天的持仓建议。很多人第一次写回测就是在这里绕晕。

5.2 简单回测收益计算

# 4. 计算策略每日收益 df["ret"] = df["close"].pct_change() df["strategy_ret"] = df["position"].shift(1) * df["ret"] df["strategy_nav"] = (1 + df["strategy_ret"]).cumprod() df["benchmark_nav"] = (1 + df["ret"]).cumprod() # 5. 输出核心绩效指标 total_return = df["strategy_nav"].iloc[-1] - 1 annual_return = (1 + total_return) ** (252 / len(df)) - 1 print(f"策略累计收益: {total_return:.2%}") print(f"策略年化收益: {annual_return:.2%}")

position.shift(1)是防止未来函数的关键一步。今天收盘算出金叉信号,最快也要明天开盘才能买入,所以当天的收益不该被算进策略收益里,要把持仓信号滞后一天。不 shift 的回测结果会虚高,而且是那种看着很美、实盘必亏的回测。

5.3 这个例子的现实意义

上面这个例子虽然简单,但已经具备完整的数据流水线:复权行情 = 数据层,均线信号 = 因子层,持仓调整 = 策略层,净值计算 = 绩效层。你以后写任何更复杂的策略——多因子选股、行业轮动、统计套利——底层都是这一套流程的加强版。

6. 常见问题速查:接口报错、数据为空、积分不够

实操过程中会遇到一些高频问题,我按“问题-原因-解决”的方式整理成速查表。

6.1 问题速查表

问题现象最常见原因解决方案
调用接口报错:权限不足当前积分不满足该接口门槛查看官网积分说明,换用基础接口或攒积分
返回空 DataFrame1. 日期范围没有交易数据 2. 股票代码格式错误 3. 触发了限流检查代码后缀,确认日期是否交易日,降低请求频率
数据算出来收益特别高没有用复权价格改用ts.pro_bar并设置adj="qfq"
财务数据用起来有未来函数直接用报告期 end_date 对齐,没有用公告日 ann_date回测时用ann_date <= 交易日期筛选可得数据
重复拉数据导致耗时长每次都全量重拉落盘到本地 parquet 或 SQLite,增量更新
成交量看起来不对vol 单位是手,amount 单位是千元换算成股或元之后再计算

6.2 关于积分升级,我的实际取舍经验

网上很多教程一上来就劝你充值升级积分,我的看法不同。最开始写策略,先用免费档把日线、日线指标、财务指标这些基础接口吃透,完全可以做出有实际意义的策略。等到你确实需要分钟线做日内策略,或者每天要拉全市场资金流、龙虎榜数据做事件驱动,再考虑升级也不迟。而且最高积分档位对个人玩家经常用不满,不如按需升级。

还有一个小技巧:部分接口有“基础版”和“增强版”之分,基础版字段少但免费,增强版字段全但要求积分。如果你只需要 close、volume 这些核心字段,基础版就够了,没必要死磕增强版。

6.3 数据质量问题:Tushare 也不是完美的

最后说句公道话。Tushare 免费数据在绝大多数场景下质量是可信的,但偶尔会出现个别股票某天数据缺失、复权因子更新滞后、财务数据调整后没有同步修改历史值的情况。我的经验是:不要默认数据是 100% 正确的,回测前抽查几只有代表性的股票,用行情软件人工核对一下价格和成交量。等你的策略上了实盘,数据核对应该进入日常流程,每天盘后跑一次数据完整性检查,发现异常及时定位。

7. 最后分享两个实用细节

基于我长期使用的体验,补两个对效率提升非常明显的细节。

第一个是建议你把常用的数据获取都封装到一个统一的数据模块里,然后所有策略脚本都 import 这个模块。这样当 Tushare 的接口参数或返回字段发生变化时,你只需要改一个文件,而不是翻遍所有策略代码去替换。接口升级是常态,我经历过几次字段改名,统一封装的收益非常明显。

第二个是建议每次批量拉取数据后,把数据快照的日期范围记录到一个 manifest 文件里,比如 JSON 格式。下次跑策略前先检查本地数据和当前日期的差距,如果缺口超过某阈值再触发增量更新。这样既省流量,又保证策略研究始终基于最新的可用数据。

A 股量化数据坑不少,Tushare 算是把门槛降得非常低的一条路。个人体会是:先把免费接口用透,做出一版完整的回测框架,后面所有策略迭代都建立在这个框架上,效率提升是几何级的。希望这篇能帮你少踩点坑。

返回列表