Tushare免费A股数据接口实测:从注册到策略回测全流程 直接说结论A股免费行情数据源里Tushare 属于绕不开的那一个。做 Python 量化、搞策略回测、做个股筛选第一步就是解决数据问题。你如果不想花钱买 Wind 或者聚宽的数据终端也不想定期手工去爬东财、新浪的接口那 Tushare 基本是最省心的方案——注册免费、接口稳定、覆盖面广日线、分钟线、基本面、指数成分、资金流向都能拿。这篇就把我从注册到跑通全套接口的过程完整拆出来包括 token 怎么拿、接口报错怎么处理、积分不够怎么办、本地怎么缓存里面全是实操中踩过的坑。1. Tushare 到底是什么免费额度够不够用先说清楚 Tushare 在量化数据链路上的定位。它本质是一个聚合式的金融数据 API 服务提供了 HTTP 接口和 Python SDK 包你通过注册账号拿到一个 token之后所有请求都带这个 token 去调数据。数据覆盖股票、指数、基金、期货、期权、宏观、另类数据对个人量化玩家来说最常用的就是 A 股日线行情、复权因子、每日指标、财务报表和指数成分股这几块。很多人一听“免费”就担心够不够用。我直接给结论个人做日频策略、周频调仓、筛选股票池免费档位完全够做高频、做分钟级 T0、做全市场 tick 级回测不够得靠积分升级。Tushare 的积分体系是它区别于其他免费数据源的关键——你注册以后默认有 120 积分左右具体以官网当前规则为准能调用的是基础接口的基础频率。积分怎么赚充值是一方面但更多人是通过社区贡献、发布优质内容、邀请注册来攒积分。实际体验下来日线接口在免费档下每分钟调用次数有限制但只要你不是疯狂循环请求正常写策略回测不会触发限流。1.1 为什么强烈建议用 Tushare 而不是自己爬网页自己爬行情数据不是不行但成本非常高。东财、新浪的网页接口格式说变就变而且接口返回的字段经常是拼凑的缺复权因子、缺停牌标记、除权除息数据不完整。我曾经用爬虫方案跑了两个多月某天接口调整整个股票池的复权价全部错位回测出来的盈亏曲线直接没法看。后来切换到 Tushare最直观的感受是字段统一、文档清晰、数据源头稳定。另一个关键点是复权处理。做策略回测必须要用复权价格否则分红除权那天K线会跳空导致虚假的收益信号。Tushare 的pro_bar接口直接支持qfq前复权和hfq后复权底层帮你算好复权因子这是网页爬虫很难做到的——你爬回来的原始价格必须自己处理除权除息事件财务数据、送转比例、分红公告要自己匹配时间点工作量巨大。1.2 需要避开的认知误区Tushare 不是纯免费到底在社区里常看到有人吐槽“Tushare 不是真正免费高积分接口要钱”。这个说法对也不对。实际情况是基础接口免费且无积分门槛比如股票日线、复权因子、基础信息高积分接口需要积分达到一定阈值才能调用比如分钟数据、资金流向、龙虎榜这些。对新手来说日线回测和选股已经覆盖 80% 的入门需求用不着上来就追高频数据。我的建议是先把非积分接口用到极致搞清楚数据字段含义跑通回测流程再根据策略需求决定要不要升级积分。而且积分可以慢慢攒社区任务、文章贡献、邀请都是渠道不必一上来就花钱。2. 注册、装包、拿 Token10 分钟跑通第一个请求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_code000001.SZ, start_date20240101, end_date20240110) 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(maxsize256) def fetch_daily(ts_code, start_date, end_date): for attempt in range(3): try: df pro.daily(ts_codets_code, start_datestart_date, end_dateend_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_dateend_date日期区间。注意日期的格式是YYYYMMDD纯数字字符串不是YYYY-MM-DD这个我曾经转换格式弄错查了半天才发现接口返回空。返回字段包括字段含义补充说明ts_code股票代码带交易所后缀trade_date交易日期格式YYYYMMDDopen开盘价单位元high最高价单位元low最低价单位元close收盘价单位元pre_close昨收价用于计算涨跌幅change涨跌额close - pre_closepct_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_code000001.SZ, adjqfq, start_date20230101, end_date20240101)参数adj有三个取值None不复权、qfq前复权、hfq后复权。回测一般用前复权qfq因为前复权价格能直接反映当前价格水平跟真实交易价格接近。如果用后复权价格会非常大只适合计算收益率序列不适合直接对比当前股价。有一点值得说明前复权数据是动态的每次新增除权除息事件前面的历史价格都会跟着变化。所以做研究时一旦确定了数据抓取时间最好把复权数据落盘存下来避免以后重新计算结果不一样导致回测结果没法复现。我自己是每次回测跑批前固定用同一个数据快照研究记录里注明“数据截至某日”。3.3pro.daily_basic每日指标选股必备除了行情价格选股策略还需要每天变化的指标市盈率、市净率、换手率、总市值、流通市值、量比等等。这就是daily_basic接口的用途。df pro.daily_basic(ts_code000001.SZ, start_date20240101, end_date20240110, fieldsts_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_code000001.SZ, start_date20230101, end_date20240101)这个接口返回的字段特别多包括几十个指标。新手容易犯的错误是直接用最新一期报表数据而忽略“公告日期”和“报告期”的区别。end_date是报告期比如 20231231 表示三季报还是年报要看实际公告ann_date是公告日期。你在回测时要用的是“当时能拿到的最新数据”也就是ann_date小于交易日的报表而不是期末日期小于交易日的报表。这叫做point-in-time 回测避免未来函数。Tushare 里两份数据都有但需要你自己在代码里做按公告日对齐的操作。3.5 指数成分和权重组合构建必备做组合策略、行业轮动需要知道指数成分股和权重。Tushare 提供index_weight接口df pro.index_weight(index_code000300.SH, start_date20240101, end_date20240131)沪深 300 的代码是000300.SH中证 500 是000905.SH上证 50 是000016.SH。这个接口返回成分股代码和权重注意权重是百分比数值不是小数。我在一次计算指数收益时直接用权重乘以个股收益求和结果偏了 100 倍就是这地方出的问题。还有一个建议指数成分股会定期调整如果你想严格模拟指数应该用每个调样日对应的最新权重并且调样前后的切换日期要与实际一致。Tushare 的index_weight是按公告日返回某个日期的权重快照具体日期选择在做历史回测时要想清楚。4. 单次拉全市场数据的高效实操循环、限流、缓存三板斧当你开始写真正的策略不可能是只针对一两只股票。你需要的是某个时间截面全市场的行情、指标、财务数据然后做横截面筛选。这时候就涉及“如何高效拉取全市场数据”的问题。4.1 先拿股票列表再逐票拉数据第一步永远是拿到全市场的股票基础列表stock_list pro.stock_basic(exchange, list_statusL, fieldsts_code,symbol,name,area,industry,list_date)list_statusL表示当前上市状态的股票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_date20240101, end_date20240115) 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_code600519.SH, adjqfq, start_date20210101, end_date20241201) df df.sort_values(trade_date).reset_index(dropTrue) # 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并设置adjqfq财务数据用起来有未来函数直接用报告期 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 算是把门槛降得非常低的一条路。个人体会是先把免费接口用透做出一版完整的回测框架后面所有策略迭代都建立在这个框架上效率提升是几何级的。希望这篇能帮你少踩点坑。