前几天有个刚入行的朋友问我,想做点股票数据的量化测试,第一步卡在了数据获取上。我想了想,这其实是大多数人的真实状态:策略模型可以慢慢学,但连基础数据都拿不稳,后面全是空中楼阁。我自己从最早爬网页、手工下载Excel,到后来换到Tushare Pro,这个切换差不多节省了80%的数据清洗时间。这篇文章就把我实际使用Tushare Pro股票的常用基本数据接口的经验梳理一遍,尤其适合刚准备入门、又不想在数据源上浪费太多时间的朋友。
1. 数据源的十字路口:为什么我最后选了Tushare Pro
1.1 数据干净度是第一位的,自己爬虫的隐性成本远超预期
很多人一开始都觉得“我可以自己写爬虫从财经网站抓数据”。没错,我也这么干过。但真实情况是:网页结构隔三差五就变,导致解析规则经常失效;表格里的字段一会儿是字符串一会儿是数字;除权除息的数据网站给得还不全,复权得自己算。更要命的是,抓到之后你不知道数据有没有可能被反爬策略篡改,脏数据混进去,等策略跑出诡异结果时,再回头排查数据问题,时间成本非常高。
后来我换到Tushare Pro,至少数据是结构化的,字段名稳定,类型也规范,省掉了最烦人的清洗环节。这里说的“干净”,并不仅仅是格式统一,还包括它有明确的除权除息、停复牌、涨跌停等标注,这能帮你在做事件研究时直接判断哪些交易日的价格是真实可用的。
1.2 对比其他数据平台,Tushare Pro的核心差异点
市场上能拿A股数据的渠道其实不少,我简单用过几个,列个表大家感受一下:
| 数据渠道 | 优点 | 缺点 |
|---|---|---|
| 财经网站爬虫 | 免费,无积分门槛 | 结构不稳定,字段缺失,复权处理麻烦 |
| 某些商业终端 | 数据全,体验好 | 价格高,个人用户基本劝退 |
| 开源财经数据库 | 离线可用 | 数据更新滞后,依赖社区维护 |
| Tushare Pro | 接口友好,字段规范,有社区生态 | 部分接口有积分门槛,需要学习成本 |
Tushare Pro给我的感觉是“个人开发者够用,轻度商用也能兜底”。它既有分钟线、日线、财务、资金流向等一整套接口,也有文档和社区,遇到问题搜索起来还算方便。更重要的是,它提供的是一个持续的API服务,而不是一次性下载的数据包,这对需要每日增量更新的量化研究场景非常友好。
1.3 积分的本质不是门槛,而是资源调度
很多人一上来就被“积分”两个字劝退,觉得是不是要花钱才能用。其实Tushare Pro的积分体系更像个权限管理机制:不同接口需要不同的积分等级,积分越高,每分钟能调用的次数上限也越高。
我当时注册后先完成了一些基础任务,攒到一定积分,就能调日线、股票列表、交易日历这些基础接口了。对个人做研究来说,基础积分基本够用。我的经验是:不要一上来就想着调全部接口,先用低积分的接口跑通流程,再根据需求慢慢升积分。这个思路同样适合刚入门的朋友。
2. 正式动手之前的三个准备:注册、Token、Python环境
2.1 注册和Token获取流程里最容易被忽略的一步
Tushare Pro的注册流程不复杂,去官网注册账号,登录后在“个人主页”里能找到自己的Token,是一串类似“xxxxxxxxxxxx”的密钥。这里有个细节我到现在还记得:Token就是你的身份凭证,代码里调用API时都必须带着它。千万别把这个Token上传到公开的GitHub仓库,尤其是做项目演示的时候。
我第一次用的时候,直接复制Token到代码里,后来因为项目要分享出去才意识到这个问题。建议的做法是:把Token放在环境变量里,或者放到一个单独的配置文件,然后用代码读取。这样即使代码传到公共平台,也不会泄露密钥。
2.2 本地环境配置:别在Python环境上翻车
写Python的人多少都遇到过环境混乱的问题。我目前的建议是:单独为数据项目建一个虚拟环境。你不需要用太新的Python版本,3.9到3.12之间一般都没问题。安装方式很简单:
pip install tushare pandaspandas不是必须的,但处理Tushare返回的DataFrame时几乎是标配。如果后续要存到SQLite,还需要:
pip install sqlalchemy这里有个容易踩的坑:有些机器上pip可能指向的是系统Python,装完之后在IDE里又import不到。我一般用python -m pip install tushare来确保装到当前Python环境里。装完可以顺手验证一下:
import tushare as ts print(ts.__version__)2.3 最小可用测试:第一次从接口拿到DataFrame
环境准备好之后,先跑一个最简单的调用,确认连通性:
import tushare as ts # 建议从环境变量或配置项里读取Token import os token = os.environ.get("TUSHARE_TOKEN", "你的token") pro = ts.pro_api(token) # 拉取平安银行最近一段日线数据 df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20240201") print(df.head())如果返回了一个包含开盘、收盘、成交量等字段的DataFrame,说明接口跑通了。这一步看起来很基础,但它同时验证了注册、Token、网络、Python环境四个环节,后面所有接口调用都是在这个基础上展开的。
3. 股票基本数据接口逐个拆解:从日线行情到复权因子
3.1 pro_bar()和daily()的区别:不是简单二选一
Tushare Pro里,获取日线行情有两个方式。直接调pro.daily()拿到的当日价格没有处理复权,而ts.pro_bar()是封装好的高级接口,支持adj参数设置前复权或后复权。这个差异对做策略回测相当重要。
我之所以建议刚入门的朋友多理解这两个接口,是因为很多人拿到数据后,直接拿不复权的价格去算收益率,遇到分红送股的股票,价格跳空一大截,算出来的收益都是假的。
举个例子:某股票除息日当天价格从10元跳到9.5元,表面上看跌了5%,实际上是分红了0.5元。如果直接用不复权价格,策略就会误判。所以做回测时我一般用前复权数据:
df_qfq = ts.pro_bar(ts_code="000001.SZ", adj="qfq", start_date="20240101", end_date="20240201")pro_bar()支持很多参数,adj=None时不复权,adj="qfq"是前复权,adj="hfq"是后复权。各有适用场景。短期技术分析常用前复权,长期收益率计算有时用后复权,需要结合研究目的来选。
3.2 获取股票列表:stock_basic的字段不只是“股票名字”
pro.stock_basic()是获取全市场股票基础信息最常用的接口。它能返回股票代码、名称、行业、上市日期、退市日期等。我经常用它做两件事:一是维护一个全量股票池,二是做行业分类统计。
stock_list = pro.stock_basic(exchange="", list_status="L", fields="ts_code,symbol,name,area,industry,market,list_date") print(stock_list.head())这里有个容易忽略的点:list_status参数。它支持L上市、D退市、P暂停上市三种状态。如果用默认参数,可能只返回部分状态。我习惯做历史回测时把“已退市股票”也拉进来,因为如果不含退市股,回测结果会有严重的幸存者偏差。
ts_code的格式一般长这样:000001.SZ,代表深圳市场的平安银行,后面的后缀SH或SZ代表交易所。很多接口的入参都依赖这个字段,所以造数据池时建议保留完整的ts_code,不要只存6位代码。
3.3 复权因子接口adj_factor:做收益率计算的隐藏钥匙
除了直接用pro_bar拿前复权价格,Tushare Pro还提供一个专门的复权因子接口pro.adj_factor()。这个接口返回每只股票在每个交易日的复权因子:
adj = pro.adj_factor(ts_code="000001.SZ", start_date="20240101", end_date="20240201") print(adj.head())复权因子的作用是什么?简单说,某一天的复权因子代表这一天相对基准日的市值调整比例。有了它,你可以自己实现复权计算,而不是依赖封装好的接口。
实际项目里,我会同时下载日线行情和复权因子,在本地合并计算。这样做的好处是:当你需要从后复权切换到前复权,或者计算自定义区间收益时,不需要重新请求网络接口,离线就能算。
3.4 交易日历trade_cal:时间序列分析绕不开的基础表
做金融时间序列研究,最怕的就是把非交易日当成交易日。比如周末、节假日,A股不开市,但日历时间还在走。pro.trade_cal()可以返回一段时间的交易日历,并且标记是否开盘。
cal = pro.trade_cal(exchange="SSE", start_date="20240101", end_date="20240601") print(cal.head())我通常用它来生成一个“自然日到交易日”的映射表。比如计算持仓天数、判断是否错过交易窗口,或者把财务数据对齐到最近的有效交易日。这个接口看起来不起眼,但一旦你开始做稍复杂一点的数据聚合,它就变得非常重要。
用交易日历还有个小技巧:把日历表存到本地,每次增量更新数据之前,先查本地日历,判断今天是不是交易日。如果是,再调用行情接口,这样能省掉不少无效请求。
3.5 每日指标与停复牌信息:做筛选时很有用
除了上述几个接口,pro.daily_basic()也是我常用的基本接口之一。它包含每日的PE、PB、总市值、流通市值、换手率等指标。用来做股票池初筛非常方便:
basic = pro.daily_basic(ts_code="000001.SZ", start_date="20240101", end_date="20240201", fields="ts_code,trade_date,pe,pb,total_mv,circ_mv,turnover_rate")需要注意的是,daily_basic的积分要求通常会比daily高一点,但个人研究一般也能接受。如果你第一次调用报权限不足,优先看官方文档的最新权限要求,别急着怀疑代码。
4. 实战:把基本数据接口串成一个“日线数据本地化”小工具
4.1 目标与模块划分:单一职责,方便排错
接口一个个熟悉之后,接下来就是组合使用。这里我分享一下我实际搭建过一个“日线数据本地化”小工具的思路。目标很简单:每天自动拉取股票池的日线行情、复权因子、每日指标,存到本地数据库,供后续策略研究调用。
我把它拆成三个模块:
- 股票池管理:从
stock_basic获取股票列表,维护当前关注的ts_code集合。 - 数据拉取模块:根据股票代码和日期范围,调用
daily、adj_factor、daily_basic。 - 存储模块:把数据写入SQLite,同时记录每次更新到的日期,方便增量更新。
模块之间尽量保持独立。比如存储模块只负责写库,不关心数据来自哪个接口;拉取模块只返回DataFrame,不负责落盘。真出问题的时候,定位起来很快。
4.2 增量更新逻辑与日期边界处理
增量更新的核心是:上一次更新到哪天,这次就从那天之后开始拉。省时间,又不会重复请求。
import pandas as pd import tushare as ts pro = ts.pro_api(token) def get_last_date_from_db(table_name, conn): # 这里简化处理:查表里最大的trade_date query = f"SELECT MAX(trade_date) FROM {table_name}" return pd.read_sql(query, conn).iloc[0, 0] def fetch_daily_incremental(ts_code, start_date, end_date): df = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date) if df is not None and not df.empty: df = df.sort_values("trade_date") return df这段代码有个关键点:daily接口的起始日期可以沿用上一次更新的日期,但要注意除权除息可能会修正历史价格,所以如果你存储的是不复权数据,最好每隔一段时间做一次全量刷新,否则历史数据可能被复权因子更新波及。
4.3 存储格式的选择:CSV还是SQLite
刚开始做本地化时,我用的是CSV文件,因为直观、方便检查。但股票多了以后,文件小且多,管理起来很分散。后来我切到了SQLite,单文件、查询方便、支持SQL,个人项目完全够用。
对比一下:
| 存储方式 | 优势 | 劣势 |
|---|---|---|
| CSV | 简单,可读性强,Excel能打开 | 数据量大后读写慢,多表关联困难 |
| SQLite | 单文件,支持SQL,查询快 | 需要一点SQL基础,并发写入有限制 |
我的选择是:历史研究数据存SQLite,临时看数用CSV。各有各的用途,不必非此即彼。
4.4 完整代码骨架与运行效果
一个简化版本如下:
import os import sqlite3 import pandas as pd import tushare as ts token = os.environ.get("TUSHARE_TOKEN", "你的token") pro = ts.pro_api(token) conn = sqlite3.connect("stock_data.db") # 初始化股票池 stock_list = pro.stock_basic(exchange="", list_status="L", fields="ts_code,name,industry,list_date") stock_list.to_sql("stock_basic", conn, if_exists="replace", index=False) # 拉取某只股票的全部日线数据示例 code = "000001.SZ" df = pro.daily(ts_code=code, start_date="20200101", end_date="20240201") df = df.sort_values("trade_date") df.to_sql("daily_" + code.replace(".", "_"), conn, if_exists="replace", index=False) # 拉取复权因子 adj = pro.adj_factor(ts_code=code, start_date="20200101", end_date="20240201") adj.to_sql("adj_" + code.replace(".", "_"), conn, if_exists="replace", index=False) print("数据已写入SQLite数据库") conn.close()实际运行中,我会把单只股票的拉取放在一个循环里,加上time.sleep()控制频率,避免触发接口限流。to_sql的if_exists参数也很重要:第一次创建表用replace,增量更新时用append,这是数据管理的基本功。
5. 高频报错的排查链路:不是每个报错都是积分问题
5.1 报错“抱歉,您没有访问该接口的权限”的完整排查步骤
几乎每个人都会遇到类似提示。看到这个报错,我的第一反应不是去查代码,而是按顺序排查:
- 当前账号积分是否满足该接口要求:Tushare官网可以看各接口的最低积分要求,直接和你的积分对比。
- Token是否出错:检查代码里是否真的读取到了Token,有没有多空格、少字符。
- 是否有企业接口和个人接口的区分:个别接口只对特定类型账号开放。
- 是否最近规则调整:积分门槛不定期更新,老教程里的接口可能已经调整。
我踩过一次坑:把Token写死在代码里,后来换了账号,忘记更新Token,导致一直报权限错误,排查半天才发现是Token问题。所以最好把Token读出来打印一遍,确认无误。
5.2 请求超时和连接中断:先找网络,别怪接口
有时候报错不是权限,而是超时。遇到这种情况,我通常先做两件事:第一,确认网络能访问公网;第二,试着用浏览器访问Tushare官网,看是不是官网本身也慢。
如果在某些网络环境下访问不稳定,可以在代码里设置超时重试。最简单的重试机制可以用retry库,或者自己写个循环。
import time def call_with_retry(func, retries=3, **kwargs): for i in range(retries): try: return func(**kwargs) except Exception as e: print(f"第{i+1}次调用失败:{e}") time.sleep(2) raise Exception("多次调用仍然失败,请检查网络和参数")这个函数配合pro.daily之类的方法用,能在一定程度上应对网络抖动。
5.3 接口调用被限流:别让程序把自己封了
Tushare Pro对接口有频率限制,不同积分档位对应的每分钟调用次数不同。如果你快速循环调用上千只股票,很容易触发限流。
我一般会在循环里加一个简短的延时:
import time codes = ["000001.SZ", "000002.SZ", "600000.SH"] for code in codes: df = pro.daily(ts_code=code, start_date="20240101", end_date="20240201") # 处理数据... time.sleep(0.5)0.5秒对个人项目来说通常够用,但还是要根据自己的积分档位和官方文档调整。如果触发了限流,一般会收到明确的错误提示,这时把休眠时间加大到2秒或3秒,再继续。
5.4 字段类型和数据质量问题:字符串还是数字,必须心里有数
Tushare Pro返回的DataFrame字段类型大体是规范的,但偶尔也会有需要处理的地方。比如某些字段是空值,某些数字列在特定情况下会变成字符串。
我习惯在拿到DataFrame后先统一做类型检查:
df["trade_date"] = df["trade_date"].astype(str) df["close"] = pd.to_numeric(df["close"], errors="coerce")这一步可以避免后续计算时报错。另外,start_date和end_date这类参数格式必须是YYYYMMDD,字符串别带横线,这个低级错误我见过不止一次。
6. 长期使用下来的几点心得和一个自定义扩展方向
6.1 把接口文档变成自己的“数据字典”
Tushare Pro的接口文档字段比较齐全,但字段名太多,遇到不常用的接口时容易记混。我的做法是:把常用接口的返回字段整理成一份本地数据字典,注明每个字段的含义和类型,尤其是那些容易混淆的字段。
比如vol和amount:前者是成交量(手),后者是成交额(千元)。不同接口单位可能不同,不查文档真的容易算错。建立这份字典后,每次写新策略时翻一下,能省很多时间。
6.2 注意接口版本迭代:老代码一年后可能跑不通
Tushare Pro的接口规则会不定期调整,积分要求、参数名、返回字段都有可能变。我见过不少老帖子里的代码,稍微跑一下就会报错。所以我养成了一个习惯:每隔一段时间去官网看更新日志,确认自己常用的接口有没有改动。
版本升级后,旧代码可能只是某个参数被废弃,也可能是返回字段名称变化。建议把项目用到的接口封装成独立函数,这样即使规则变了,只需要改函数内部逻辑,不需要全局搜索替换。
6.3 后续可扩展的方向:结合分钟线、财务数据做深度研究
基本数据接口跑通之后,自然会产生更多需求。比如做更精细的事件研究需要分钟线数据;做基本面选股需要财务数据;做资金面分析需要资金流向数据。这些在Tushare Pro里都有对应接口,但积分和复杂度更高。
我的建议是:先把日线、复权因子、股票列表、交易日历这四个基础模块用熟,建立一个稳定的本地数据仓库,再去碰更高阶的接口。数据仓库稳定后,你会发现后续接什么接口都顺手很多。这也是为什么我选择把“股票基本数据接口”作为系列第一篇的原因,地基打不牢,后面全是返工活。