1. 为什么我想写量化交易系列却先把数据源这件事讲透很多人开始接触量化交易时第一个目标往往是写出某个策略比如双均线交叉、动量排序或者网格交易。我自己刚入行时也这样满脑子都是策略逻辑怎么设计参数怎么优化结果第一版回测跑出来曲线好看得吓人但仔细一核对发现数据本身就有问题某只股票停牌了半年我的数据库里却少了那半年另一只股票经历了多次送转除权我用的价格却是未复权数据导致收益率计算完全失真。那一刻我才明白策略代码和数据其实是同一件事数据不可靠再精致的策略也只是在错误的地基上盖楼。这篇是量化交易系列的第一篇我会把财经数据社区 Tushare 从头讲清楚包括它到底解决什么问题、怎么注册、能拿哪些数据、日常取数时有哪些坑以及我实际是怎么用它搭起本地数据环境的。无论你是完全没写过代码、想用现成工具拉数据的人还是已经写过一些脚本、但始终被数据源折腾的初学者这篇文章都能帮上忙。1.1 策略、回测、实盘每个环节都在和同一份数据打交道量化交易本质上是一个用历史数据做决策验证的过程。你建一个选股模型要回顾过去十年每天哪些股票满足条件、它们的后续收益如何你写一个择时策略要用历史K线去计算均线、动量、波动率你跑完回测想上实盘盘中判断也要依赖实时行情盘后复盘更要核对成交回报。这些环节看似独立但底层都是同一套数据的流转。实际做下来你会发现最难的不是写策略而是保证数据从头到尾口径一致。比如你日线用的是第一个来源财务数据用的是第二个来源指数行情又来自第三个来源那么时间戳对不上、复权规则不同、缺失值处理方式也不一样最终叠加出来的回测结果根本没有参考价值。我自己的原则是尽量选择一个主数据源把行情、复权、财务、指数、行业分类都从同一套体系里拿外部的数据只做对账或补充不在策略主链路上混用。1.2 我对够用的数据源有一个很具体的验收清单很多教程只会说数据要干净、要完整但落到实际操作里什么叫干净什么叫完整我给自己定了一个顺序明确的验收清单交易日历是否完整。如果某一天实际上开市但本地数据里没有它回测时间轴会从那天开始断裂后续所有指标计算都会错位。是否有老股票、退市股票的记录。选股回测最忌幸存者偏差只用现在还活着的股票看起来业绩很好实盘根本买不到已经退市的那些。复权因子是否准确。做长周期回测时分红送转对收益的影响非常大不复权或复权错误10年曲线可能差出一倍。字段单位是否统一。成交量到底按手还是按股成交额按元还是千元这些小事不确认过滤条件一写错就是灾难。这套清单我建议你也抄下来每次拿到新数据源先花半小时把这几项检查一遍再开始写策略。省下的时间绝对比投入的多。2. Tushare的社区属性它不只是一个简单的取数HTTP接口如果你在搜索引擎里找Tushare会发现它被介绍成金融数据库开源接口Python财经数据工具包。这些说法都对但都少了一层最重要的人情味——它本身是一个围绕财经数据成长起来的社区。我最初接触时也以为它只是个在线API文档后来才发现它的价值一半在数据另一半在沉淀下来的讨论和踩坑记录。2.1 从早期开源项目到数据接口文档论坛的复合体Tushare最初的形态是开发者为了自己取数据方便而写的一套爬虫和解析工具后来慢慢开放给别人用逐渐扩展成现在看到的Tushare Pro体系。所谓社区社区的体现就是它不只给你一个传输数据的端点配套的还有完整的接口文档、字段说明、更新记录、讨论区以及大量前人在使用过程中留下的问题解答。遇到字段含义不清楚、接口返回为空、权限不足这类问题搜索历史帖子的命中率高得吓人这比一个冷冰冰的API文档要友好太多。另外它对新用户也相对友好。普通注册用户通过完成一些基础任务就能积累积分日线行情、财务指标、指数成分、宏观数据这些主力接口都会开放。对个人学习和初期研究来说基本不需要额外付费这点在当前能稳定提供历史A股数据的方案里非常难得。2.2 积分、token、权限第一次用之前必须搞清楚的三件事我用Tushare时踩过一个非常典型的坑——拿着刚注册的token直接去调分钟线接口结果返回一个空DataFrame我当时还以为是网络问题。后来才明白不同数据接口有积分门槛。这里简单说一下它的规则逻辑注册后通过每日登录、完成个人信息、参与社区等操作可以获得一定的积分。积分达到不同档位能解锁的数据深度和调用频率就不同。基础积分段覆盖日线、周线、财务、指数、宏观等常用范围而分钟线、实时资金流、逐笔成交这类数据需要更高的积分档位。对于刚开始做日线级策略的人来说用基础权限足够跑通整个流程没必要急着充积分。等策略需求明确后再按照所需接口去评估是否升级。token是你个人的访问凭证相当于密码。它会在所有请求中用来识别你的账号和权限所以千万不要把token硬编码在共享代码里更别随手推到公开仓库。我习惯把它单独放在本机的配置文件或环境变量里面代码只负责读取。2.3 数据全景从行情、财务到宏观覆盖范围比想象中大Tushare当前的接口不是单一功能而是按数据类别组织的。我的使用经验里至少可以把它们分成这几组基础行情日线、周线、月线、分钟线以及复权因子、涨跌停价格。证券信息与状态股票列表、行业分类、上市状态、上市日期、退市日期。财务与业绩财务指标、利润表、资产负债表、现金流量表、业绩快报、业绩预告。估值与交易热度市盈率、市净率、总市值、换手率、量比、资金流向。指数与板块指数日线、指数成分、行业板块、概念板块、交易所公告的公开数据。另类与宏观宏观月度指标、存款准备金率、人口数据、龙虎榜、分红送配信息。刚开始完全不需要把这些都接一遍。我建议先抓取最核心的基础部分——全市场日线、复权因子、交易日历、股票基础信息、指数日线、财务指标就够了。后面需要什么再加什么数据表不是越全越好而是越符合策略需要越好。3. 半小时跑通注册、Token和第一个Python取数脚本很多教程会把取数过程讲得很玄乎但实际上Tushare的上手路径是很固定的。我下面按自己第一次成功调通接口的完整流程写出来照着做半小时内就能把第一份数据下载到本地。3.1 注册、保存Token这一步决定后面所有请求能不能被识别先去Tushare官网注册一个账号注册后在个人主页里能找到自己的token。token是一串长字符串建议拿到后就复制保存到一个安全的地方。我最推荐的做法是在本地项目目录下建一个config.ini内容如下[tushare] token 你的token字符串然后在代码里用configparser读取而不是直接把token写死在.py文件里。这样即使代码备份到其他地方token也不会跟出去。3.2 安装tushare库并调用第一个接口环境方面只要你的机器上有Python 3.8以上版本直接装包就行。pip install tushare安装完成后打开Python交互环境或写一个脚本import tushare as ts # 方式一临时设置token ts.set_token(你的token) # 初始化pro接口 pro ts.pro_api()第一次调用我强烈建议先请求交易日历而不是直接拉行情。交易日历结构简单、返回稳定用它验证权限和网络连接最直观。cal pro.trade_cal(exchangeSSE, start_date20240101, end_date20240131) print(cal.head(10)) print(f总记录数: {len(cal)})正常返回后你会看到类似这样的列exchange cal_date is_open pretrade_date SSE 20240102 1 20231229 SSE 20240103 1 20240102 ...is_open为1表示交易日为0表示休市。别小看这张表后面做回测时间轴、过滤非交易日它都要频繁出场。3.3 顺手拉一份股票列表理解代码规范交易日历能跑通后再把股票基础信息接口也试一下。这个接口返回全市场上市公司的代码、名称、行业、上市状态等算是建立选股池的第一块基石。basic pro.stock_basic( listed_statusL, fieldsts_code,name,industry,market,list_date ) print(basic.sample(10))重点关注ts_code这种编码形式比如000001.SZ、600000.SH。A股代码末尾都会带交易所后缀SZ代表深交所SH代表上交所。很多人第一次使用时不带后缀直接传000001结果返回空数据这是非常常见的入门错误。如果你运气好上面这段执行后就能看到几行股票信息。如果返回内容为空请先检查是否触发了积分权限限制或者把fields参数换成全部字段再看一次我遇到过的多数情况都是因为权限或字段名拼写问题导致的。4. 回测里出场率最高的几类数据接口我逐个拆给你看通过初步验证后就可以进入真正的高频使用环节了。我的量化工作流里这几类接口出现在绝大多数策略代码中建议你优先熟悉它们。4.1 日线行情想省心就直接用pro_bar别在复权问题上自找麻烦刚开始用Tushare时我用pro.daily()取日线每次都拿到未复权价格然后自己用复权因子算。后来发现还有个更直接的方法ts.pro_bar()它把复权逻辑封装好了不折腾人。# 直接获取前复权日线 df ts.pro_bar( ts_code000001.SZ, adjqfq, start_date20200101, end_date20240101 ) print(df[[trade_date, open, high, low, close, vol]].tail())adj参数有三种选择不填表示不复权qfq表示前复权hfq表示后复权。做中短线策略我常用前复权因为价格更贴近当前市场状态做长期趋势研究或分红套利时后复权更合适。如果你在回测框架里已经自己实现了复权流程那也可以用pro.daily()配合pro.adj_factor()去做底层数据灵活度更高但初学者直接用pro_bar更稳。pro_bar还支持月线、周线、分钟线等周期参数。周线和月线做中长线择时非常方便不用自己再从日线做聚合。4.2 全市场截面按交易日拉快照比按股票循环高效得多很多初学者拿到接口后习惯遍历股票代码去取数据。如果只是取10只股票问题不大但要建一个全市场选股策略上千只股票逐个调接口效率低不说还容易触发频率限制。我的做法是反过来按交易日拉截面数据。每天收盘后全市场所有股票的日线在一张表里返回一次调用就把当天所有股票的收盘、涨跌幅、成交量都拿到。代码大概是这样的snapshot pro.daily( trade_date20240115, fieldsts_code,open,high,low,close,pct_chg,vol ) print(snapshot.shape) print(snapshot.head())用trade_date参数而不是ts_code一次返回可能是上千行。之后再把每个交易日的快照纵向拼接起来就得到了一个非常规整的全市场日线数据库。这个方法在网络请求次数上优化了一个数量级强烈推荐。4.3 估值与财务指标建立基本面过滤器的关键一环如果你的策略不止看价格还要看市盈率、市净率、市值这类指标那就需要daily_basic接口。daily_basic pro.daily_basic( trade_date20240115, fieldsts_code,turnover_rate,volume_ratio,pe,pb,total_mv,circ_mv ) print(daily_basic.head())这个接口返回的是某一天全市场的估值快照字段包括换手率、量比、市盈率、市净率、总市值、流通市值等。用它做股票池过滤非常直观比如只保留总市值大于某一区间的股票或者剔除市盈率为负的股票。财务指标方面fina_indicator接口覆盖了大量财务分析指标例如净资产收益率、毛利率、资产负债率等。取数时要注意它的时间参数它支持按报告期取也支持按公告日期范围取。回测里如果按报告期取要注意未来函数问题——财务报告实际披露日往往滞后于报告期结束日策略只能在披露以后才能使用这份数据。4.4 指数与板块成分选股池的第一道过滤做指数增强或行业轮动策略时指数日线和成分股列表必不可少。指数日线接口的用法和个股日线很接近index_data pro.index_daily( ts_code000300.SH, start_date20240101, end_date20240131 ) print(index_data.head())指数成分股在index_member这类接口里维护。拿到成分股列表后再和本地全市场日线做内连接就能生成一个只含指数成分的选股池。板块和概念数据的接口名称相对分散单看文档可能会被复杂结构吓到但实际上原理差不多先查板块列表再查板块下的成分股。我通常只在需要用概念过滤时才会去翻文档平时做通用选股池时用行业分类字段就足够了。5. 数据到手只是开始清洗、拼接和落盘的实战经验我一直觉得从接口取数并不难真正拉开差距的是数据到手后怎么整理。下面这些内容是我在多次构建本地数据库过程中总结出来的经验踩过不少坑才整理清楚。5.1 Tushare默认返回格式里有几个隐藏规则首先所有日期字段默认都是字符串并不是datetime类型格式是YYYYMMDD。做时间序列拼接时建议统一转成datetime再排序。其次单位问题容易被忽略。日线接口里的vol单位通常是手而不是股amount单位是千元。如果你拿来和某些行情软件的显示值对比不统一单位的话会以为数据错了。另外字段缺失的情况也很多。某只股票当天停牌或者刚上市还没有某项财务指标接口就不会返回对应数值而是表现为行数少一截或字段为空。处理缺失数据时一定要先理解缺失原因再决定是删掉、补零还是前向填充。盲目填零会对指标计算产生严重干扰。5.2 清洗、拼接与复权推荐一套可复用的组装流程我常用的流程是先建一张全市场日线表再在策略中按需筛选。基本代码骨架如下import time import pandas as pd pro ts.pro_api() # 假设已经有一个交易日列表trade_dates all_data [] for d in trade_dates: try: tmp pro.daily( trade_dated, fieldsts_code,trade_date,open,high,low,close,pct_chg,vol ) all_data.append(tmp) time.sleep(0.5) # 主动限速避免触发频控 except Exception as e: print(f{d} 失败: {e}) continue full_table pd.concat(all_data, ignore_indexTrue) print(full_table.shape)这里加time.sleep是为了自我保护很多频率限制是连续高并发触发的慢一点反而稳定。如果是长历史回测建议用每日的截面快照拼接而不是逐只股票循环。拼接之后如果需要跑面板类型策略可以再pivot成宽表行是股票列是日期或者反过来。5.3 落盘小数据量用SQLite大数据量用Parquet数据在内存里操作完一定要落盘保存否则每次回测都要重新调接口时间和调用次数都浪费。对于几千只股票、几年日线这种规模我推荐干脆存成Parquet文件压缩率高、读取速度快而且能保留数据类型。如果还会反复更新可以按年份拆分文件比如daily_2023.parquet、daily_2024.parquet每次只更新最新一年的文件。如果只是个人学习、数据量不大或者想随时用SQL查询SQLite也是很好的选择。一张daily表字段建好索引后按ts_code或trade_date查询都很方便。我自己的习惯是历史数据用Parquet保存每天增量用SQLite或按日期追加各有各的用处。6. 横向对比Tushare不是唯一选择但为什么我留了它为了客观起见我也想说说市面上其他数据方案的取舍。毕竟没有哪个工具是万能的关键是搞清楚不同方案适合什么场景。6.1 券商客户端导出数据适合看盘不适合批量回测部分券商软件支持把个股历史行情导成Excel或者提供简单的公式接口。如果你只是验证一个非常小的样本比如只研究两三只股票这确实能应付。但一旦策略涉及全市场选股这种导出方式的弊端就出来了一是手动操作太频繁几十只股票还能接受上千只就完全不可持续二是不同券商对复权、单位、字段的定义可能不一样导出之后还得自己做大量格式转换三是数据覆盖范围偏窄财务指标、宏观数据往往没有。所以我只会用它做临时性肉眼核对不会把它放进自动化的回测流程里。6.2 爬公开网页多一点自由但也多一箩筐维护成本有些开源方案会提供爬虫工具去抓取门户网站的行情或财务数据。初期玩一玩会觉得很灵活想抓什么抓什么但实际用下来痛点也很明显网页栏目调整一下字段名就变了爬虫代码要跟着改。防爬机制升级后频繁访问会被拒绝被迫加代理、加延迟、折腾验证码。被爬取的公开页面通常只保留最近几年数据做长周期回测就会缺历史。数据质量缺少校验某天页面字段异常你的本地表不察觉就会被污染。这些成本在数据量小的时候感觉不到等到策略依赖它自动运行就变成天天提心吊胆的事情。我的建议是爬虫适合学习和临时小规模获取不适合作为量化数据主链路。6.3 Tushare的定位把研究效率放在第一优先级相比之下Tushare把很多基础工程问题给标准化了。它不追求给你最原始的实时推送流而是提供一个称手的、稳定的、有文档的历史数据和常规行情方案。对个人研究和小团队来说把一个接口的参数调通比自己去维护一套爬虫系统省心太多。当然它也不是没缺点。更高频的分钟数据需要更高权限部分实时性要求极高的策略确实得不到完全满足。但对于大多数日线级量化研究场景它已经覆盖了我九成以上的需求。我更建议你在策略还没有稳定之前别在数据获取上过度建设先把主数据源固定下来跑通回测有了明确瓶颈再去优化数据链路。7. 最后聊聊我认为做数据准备时最重要的一件事如果只让我留一条经验给新人那一定是拉数据不复杂验证数据才复杂。 刚开始我拿到一份看起来正常的行情表直接就喂给回测框架结果某个策略在历史收益曲线上出现了很多不该有的跳变。后来排查到原因一只股票在送转日附近的价格出现了断崖而我用的是未复权数据复权因子没有算进去。所以现在每次更新数据后我都会强制执行几个最小验证随机挑两只股票将本地日线的区间涨幅和行情软件上的复权涨幅做对比差异超过0.5%就要停用数据排查原因。用交易日历里的is_open统计当年交易日数量再和本地日线表里实际出现的日期数量做对比多一天或少一天都是问题。财务数据入库时先检查每次发布的公告日期顺序不让未来数据提前混入回测窗口。这几个动作加起来只花十来分钟但能让整个回测结果的可信度上一个台阶。个人实践下来数据准备阶段宁可慢一点、也不要侥幸因为一个隐藏的错误往往比几十行策略Bug更致命。这篇先把Tushare的来龙去脉和数据获取链路讲清楚了下一篇我计划直接基于这份数据跑一个全市场动量因子的完整回测到时候再继续分享实战中的细节。