
做量化分析的人都知道数据是一切策略的地基。我最初接触 Tushare Pro 的时候第一反应是“这玩意儿注册怎么这么麻烦”但真正常态化用起来之后才发现它确实值回门槛费。Tushare Pro 是 Tushare 团队在旧版基础上重构的金融数据接口平台覆盖股票、基金、期货、债券、宏观经济等全品类数据接口按权限分级开放靠积分机制控制访问量。这篇文章就写给那些准备入坑、或者已经在坑里挣扎的开发者聊聊怎么把这套接口用顺、用稳绕开我踩过的那些坑。无论是纯小白还是有一定基础的 Python 开发者都可以照着这份笔记走一遍。我一直觉得Tushare Pro 本质上不是“一个库”而是一套HTTP API规范加数据服务协议。它的核心价值不只是“能拿到数据”而是数据经过清洗、复权、对齐之后可以直接喂给策略引擎省掉了自己到处爬公告、修财务数据的脏活累活。下面这份笔记是我从环境准备到接口调用、从增量更新到本地入库的完整梳理。1. 准备阶段注册、积分与 Token 的正确打开方式1.1 注册和实名认证的细节Tushare Pro 的注册流程比一般的开放平台稍微麻烦一点因为需要实名认证。用手机号注册之后进入个人中心会看到一个“证书”或者“实名认证”的入口需要上传身份证照片。这一步不是为了刁难用户而是因为金融数据涉及合规要求平台必须确保数据使用者的身份可追溯。我遇到过很多人卡在“实名认证审核不通过”这一步。实际上最常出问题的地方是照片不够清晰、反光、或者边缘被裁剪。我第二次才过后来总结的经验是找个光线均匀的地方把身份证平放在深色背景上拍尽量让四角完全出镜不要用美颜和滤镜。审核一般在几分钟到几小时之内完成遇到高峰期可能需要等半天。认证通过之后系统会给一个 Token 字符串类似40码长的一串字母数字组合。这个 Token 就是你调用所有接口的钥匙。我建议妥善保存不要随手粘在公开仓库里因为 Token 直接关联你的积分权限和调用额度。1.2 积分体系怎么影响你写代码Tushare Pro 的积分体系是新手最容易懵的地方。积分从哪里来主要有两个途径注册送一部分基础分完善个人信息比如绑定邮箱、手机号会再给一些。更高的积分需要充值或者通过推广等方式获取。积分的核心作用是把接口分成不同权限等级。举个例子基础积分比如 120 分往往只能访问 stock_basic 这类低频、基础的数据。中等积分比如 2000 分可以访问日线行情、财务数据等。更高积分比如 5000 分以上才有分钟线、资金流等高级数据的权限。这里的逻辑有点像“数据分层”设计。为什么平台要这么搞因为免费的行情数据如果不做限制服务器很容易被打爆数据质量也会下降。用一个不太恰当的类比就像银行 VIP 窗口和普通窗口的区别——高级接口给高频调用者基础接口给低频学习者。关键问题是你的代码要根据积分选择接口不然辛辛苦苦写完一调用发现抱歉您没有权限心态容易崩。我最早写脚本时习惯一股脑把所有接口都试一遍后来老老实实去文档页看每个接口的积分要求才明白什么叫“对症下药”。1.3 Python 环境下的基础调用模板无论你用什么框架最终调用 Tushare Pro 的底层都是 HTTP 请求。官方推荐的方式是通过tushare这个 Python 包底层封装了 token 传递和 JSON 解析。安装很简单pip install tushare但是版本要注意官方建议用最新版因为老版本可能没有包含最新接口。比如有些新接口在 1.2 以上版本才有网上很多旧教程里的pro.bar()在现在的版本里行为已经发生了变化。调用模板大概是这样的import tushare as ts # 设置 token个人开发阶段建议放在环境变量里 ts.set_token(你的token字符串这里替换) pro ts.pro_api() # 拉取平安银行日线数据示例 df pro.daily(ts_code000001.SZ, start_date20240101, end_date20240601) print(df.head())注意ts_code的格式A 股一般要带上后缀.SZ或者.SH。不然后台无法区分是深市还是沪市的股票这点特别容易踩坑。你如果直接传000001运气好能返回数据运气不好直接报错或者取错证券。2. 数据接口的完整拆解从 stock_basic 到 daily2.1 stock_basic一切股票的起点我第一次接触 Tushare Pro 时最先跑通的接口就是stock_basic。它的作用是获取全量股票列表包含代码、名称、行业、上市日期、退市日期等基础信息。你可以把它理解成“股票界的户口本”——后续所有个股维度的数据几乎都要依附在这个列表上。接口基本参数df pro.stock_basic(exchange, list_statusL, fieldsts_code,symbol,name,area,industry,list_date)这里list_status是核心参数L表示上市状态D表示退市P表示暂停上市。大部分场景下你要的是L但如果做回测历史股票池也要包含退市股否则会有严重的幸存者偏差。我有一个亲身体会刚开始做量化回测时只用了当前上市的股票列表导致那些已经退市的股票没有被纳入历史数据回测收益虚高。后来把所有状态的数据都拉下来并且根据交易日期动态过滤股票池结果曲线瞬间“真实”了很多。2.2 daily日线行情才是主战场daily接口是使用频率最高的接口之一。它的返回字段包括开高低收、成交量、成交额、振幅、涨跌幅、涨跌额、换手率等。对于大多数中低频策略来说这些字段已经够用了。接口调用示例df pro.daily(ts_code600519.SH, start_date20240101, end_date20240301)反回的数据是按日期倒序排列的也就是最新的日期在最上面。如果你习惯了pandas的默认递增索引一定要在使用前排个序df df.sort_values(trade_date)很多人会忽略这个细节导致后续计算均线或者收益率时方向反了看起来信号完全“错位”。这不是数据问题是排序问题。日线接口还有一个重要限制单个接口单次请求最多返回一定行数比如 6000 行。这意味着如果你拉几十年数据可能需要分多次请求配合循环完成。这个问题我后面讲增量更新时专门展开。2.3 财务数据与股票列表的组合玩法在选股策略中财务数据是重要的因子来源。Tushare Pro 里的财务接口有很多比如income利润表、balancesheet资产负债表、cashflow现金流量表。这些接口都需要用到ts_code和period来定位报告期。实用技巧不要每次都拉全市场所有股票的财务数据因为数据量太大而且大部分你根本用不上。更好的策略是先用daily或index_weight筛出一个候选池然后只对候选池拉财务数据。我曾经忽略过ann_date公告日期和end_date报告期的区别。end_date是报告期截止日ann_date是实际披露日期对于避免未来函数来说必须使用ann_date来过滤数据的可用时间点。也就是说某公司 2024 年一季报虽然是 3 月 31 日截止但实际可能在 4 月底才公告如果你的策略假设你在 4 月 1 日就看到了这组数据那就是典型的未来函数。3. 实战过程增量更新与本地数据存储的完整方案3.1 为什么本地存储是刚需虽然 Tushare Pro 提供了在线接口但在回测场景里每次都实时拉取是不现实的。一来是网络IO开销大二来是接口限流会导致回测中断三来是历史数据反复请求浪费积分。最优解是做一次全量初始化然后每天增量更新把数据落在本地。存储方案常见有两种CSV/Parquet文件或者SQLite/MySQL数据库。我个人偏好在中小规模项目里使用 SQLite因为零配置、单文件、随手备份。核心表结构建议如下CREATE TABLE IF NOT EXISTS daily_data ( ts_code TEXT, trade_date TEXT, open REAL, high REAL, low REAL, close REAL, pre_close REAL, change REAL, pct_chg REAL, vol REAL, amount REAL, PRIMARY KEY (ts_code, trade_date) );主键用(ts_code, trade_date)联合主键这是最重要的设计。有了主键后续做INSERT OR REPLACE时天然防重不会因为重复跑脚本导致数据翻倍。3.2 初始化与增量更新的代码拆解先说全量初始化。最简单的方式是根据stock_basic拿到所有股票代码然后循环调用daily。但这里有个性能问题A 股几千只股票就算每只股票只拉一次也要几千次 HTTP 请求。这时候要合理利用 Tushare Pro 的接口特性。实际上daily接口也支持不带ts_code只传trade_date来获取某一天的全市场行情。利用这个特性初始化时可以按自然日从上市日期到现在逐日拉取也可以用以下思路简化# 不传 ts_code而是按交易日循环 for trade_date in trade_cal_list: df pro.daily(trade_datetrade_date) # 入库这里有两种模式按股票循环适合少量个股的精细化维护能控制请求次数。按日期循环适合全市场历史数据初始化每次请求拿到当日全部股票。我实测下来按日期循环更符合 Tushare Pro 服务端的性能模型且更容易做断点续传。比如某一天的数据因为网络超时没拉到只需要重跑这一天就行了不会影响其他数据。增量更新则可以固定在每个交易日收盘后运行# 获取最近一个交易日的日期 from tushare.util import trade_cal cal pro.trade_cal(exchangeSSE, start_date20240101, end_date20240401) trade_days cal[cal.is_open 1][cal_date].tolist() latest_day trade_days[-1] # 增量拉取这一天数据并入库 df pro.daily(trade_datelatest_day)这里trade_cal接口很重要。它不仅告诉你哪些是交易日还能避免你在周末、节假日白跑一趟。3.3 用 token 管理请求频率Tushare Pro 对每分钟请求次数有限制不同积分不同限制。如果代码是循环调用非常容易触发限流表现为接口偶尔报每分钟最多访问XX次。常规解法是加入time.sleep(0.5)之类的暂停但更好的方案是加一个简单的令牌桶或者直接用循环中的计数器和时间戳控制。我写过一个极简版import time class RateLimiter: def __init__(self, max_calls5, period1): self.max_calls max_calls self.period period self.calls [] def wait_if_needed(self): now time.time() self.calls [t for t in self.calls if t now - self.period] if len(self.calls) self.max_calls: time.sleep(self.period - (now - self.calls[0]) 0.01) self.calls.append(time.time())实测下来这个限速器能明显降低报错率。尤其是做全市场数据初始化时一天跑几千次请求没有限速器基本不可能一次性完成。3.4 数据结构对齐和类型转换Tushare 返回的 DataFrame 中日期字段通常是YYYYMMDD字符串比如20240101。为了保证时间序列分析顺利入库之前最好统一转成datetime类型。df[trade_date] pd.to_datetime(df[trade_date], format%Y%m%d)再有vol成交量的单位是“手”不少新手误以为是“股”。转换成“股”需要乘以 100。如果你把单位弄错算出来的换手率、资金流向指标都会失真。还有一个细节daily接口返回的amount单位是千元不是元。做成交额因子时要乘以 1000 才是元。这些单位细节我第一次全忽略了后来和行情软件核对数据才发现问题。4. 常见问题与排查技巧实录4.1 积分不足导致的各类报错最经典的报错是{code: 2002, msg: 抱歉您没有权限}看到这类报错先别急着怀疑代码大概率是积分不够。解决办法不是写代码而是去个人中心看权限分配。有很多接口文档上标注的积分只是门槛实际还有每分钟调用次数的差别。举例daily接口 2000 积分可访问但 5000 积分可以提升每分钟调用频率。这类隐性差异不写在显眼地方需要自己翻接口详情页。4.2 返回数据为空但又不报错这是比报错更隐性的坑。你调daily(ts_code300001.SZ)返回空DataFrame程序没有抛异常。大多数情况下原因是指定时间区间内该股票停牌或者尚未上市。比如某股票 2024 年 5 月刚上市你从 2024 年 1 月开始拉数据那自然是空的。这时候要查上市日期。用stock_basic的list_date字段就能定位。还有一类情况是指定日期是非交易日。比如节假日调休日虽然日历上显示是工作日但股市不开盘。务必用trade_cal来确认。4.3 字段命名不一致和大小写问题Tushare Pro 的接口字段命名在不同接口间并非完全统一。比如日线接口里pct_chg表示涨跌幅而有些接口叫pct_change。如果你把接口数据直接 concat很容易因为列名不同报错。我的做法是写一个统一映射函数def normalize_columns(df, mapping): return df.rename(columnsmapping)比如把pct_chg统一映射为pct_change。这样后续因子计算时只要记住一套标准的 DataFrame schema 就可以了。4.4 网络超时和重试机制经常跑数据的人肯定遇到过ConnectionError或者Timeout。Tushare 的接口服务虽然稳定但网络环境总有波动。尤其是长时间循环请求时偶发的超时几乎是必然。建议在网络请求外面包一层重试装饰器def retry(func, retries3, delay2): for i in range(retries): try: return func() except Exception as e: if i retries - 1: raise e time.sleep(delay)这个装饰器看不起来不高大上但实际效果非常显著。我在一次性初始化 5000 只股票的日线数据时没有重试机制的成功率大概是 90%加上重试之后基本能 100% 跑完。4.5 常见问题速查表现象可能原因解决思路报错“没有权限”积分不足或接口权限不够查看接口文档积分要求升级积分或换接口返回空 DataFrame股票未上市/停牌/区间无数据查询上市日期和停牌记录用trade_cal确认交易日数据翻倍或重复未处理主键或重复运行入库脚本使用INSERT OR REPLACE或去重逻辑请求频繁报限流超出了一分钟请求次数限制加入限速器降低请求频率或提高积分收益率算反了未按交易日期正序排列强制df.sort_values(trade_date)成交额数值不对单位理解错误千元 vs 元核对字段单位说明必要时*10005. 更进阶的玩法复权因子、指数成分与交易日历的组合5.1 复权因子的正确打开方式做量化的人都知道股票分红送转会导致价格不连续。如果不做复权处理历史数据就像被“剪断”了一样很多策略信号都会失真。Tushare Pro 提供adj_factor接口来获取复权因子。计算后复权价格的方式是df pro.adj_factor(ts_code000001.SZ, start_date20240101, end_date20240601) # 后复权收盘价 实际收盘价 * 复权因子 df[close_adj] df[close] * df[adj_factor]为什么用后复权而不是前复权因为后复权价格不会随着新分红事件改变历史数值更适合回测。前复权虽然看起来连续但每次新分红后历史价格都会整体变化如果你的回测框架是基于历史的 snapshots那前复权会造成未来函数。这是我在实际对比中踩过最深的坑之一。5.2 指数成分股与股票池管理index_weight接口可以获取指数的成分股和权重。常见的沪深300、中证500均有覆盖。在构建股票池时这个接口比手动维护名单靠谱得多。调用方式df pro.index_weight(index_code000300.SH, start_date20240101, end_date20240131)需要注意成分股列表是按公告日披露的不是按交易日实时变化的。做回测时应该用ann_date而不是trade_date来确定某一天成分股名单。这也是避免未来函数的重要一环。5.3 交易日历在调度系统里的妙用trade_cal接口返回的数据包括交易日和非交易日标记。它的核心应用场景不只是“防呆”而是做任务调度。比如你的增量程序部署在服务器上如果每天固定 18:00 运行遇到节假日就是白跑。正确姿势是每次运行时先拉一下trade_cal判断当日是不是交易日如果是再继续拉行情。这样既节省积分也不会产生多余请求。5.4 数据质量验证的几个土办法确认拿到数据之后怎么判断数据质量我有几个土办法第一抽查头部数据。用行情软件随便挑几只耳熟能详的股票比对最新收盘价。如果数字对不上多半是单位问题或者获取时日期不对。第二检查数据中的缺失日期。把某只股票的交易日期序列与交易日历取差集看是否有遗漏。第三检查极端值。比如某只股票的涨跌幅超过 20%部分板块除外但大部分股票单日涨跌幅限制在 10% 左右可能有数据错误或者未复权导致的价格跳变。这套土办法虽然不严谨但能挡住 90% 的低级错误。我自己曾经因为拉数据时用了错误的start_date导致整个 2023 年的数据全部缺失而程序既不报错也不提示。如果不是做验证比对根本发现不了。6. 从数据到策略的小经验数据这关过了后面写策略其实会顺很多。见过不少人一上来就写复杂的因子模型但底层数据都是乱的因子计算出来全是噪音回测再怎么漂亮都是自欺欺人。我个人倾向于把数据层当成独立的工程模块来做设计好存储结构、更新频率和校验逻辑这样策略研究员对接时只需要像查数据库一样取数而不是天天去和接口打交道。还有一点想特别提醒Tushare Pro 的字段文档更新比较频繁接口新增字段或者废弃老字段都是常有的事建议每次大版本更新后跑一遍自己的现有脚本看看有没有报错。我前阵子就遇到过daily接口新增了一个字段导致我自己的列名映射表不匹配程序直接宕掉。从实际体验来看Tushare Pro 在整个中文金融数据开源生态里算是维护最积极、接口最稳定的平台之一。但工具终究是工具真正能拉开差距的还是你怎么去设计自己的数据管线、怎么把数据变成可复现的策略信号。希望这份笔记能帮你少走一些弯路。最后再分享一个小技巧频繁使用 Tushare Pro 的开发者建议去关注一下官方文档站点里每一次变更公告因为很多行为变化比如某个接口的默认字段调整不会单独通知到个人你不主动跟进很可能某天脚本就坏了一处而你完全不知情。用的时候多留个心眼准没错。