ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+vnpy+Tushare:A股历史数据获取与复权实战

Python+vnpy+Tushare:A股历史数据获取与复权实战 做量化这两年我最大的体会不是策略多难写而是数据这关太容易翻车。很多朋友一上来就扑进策略里指标写得花团锦簇回测曲线也漂亮得吓人结果一拉到实盘环境要么数据对不上要么接口限流要么复权方式没搞明白回测收益直接变成一场幻觉。今天聊的这个组合——Python vnpy Tushare就是专门解决“股票历史数据到底从哪来、怎么拿回来、怎么在框架里跑起来”这一串最基础也最关键的问题。无论你是刚入门的量化小白还是已经在写策略但被数据源折腾到崩溃的开发者这篇文章都能给你一套能直接落地的方案并且附上完整可跑的代码。1. 为什么是vnpy Tushare而不是其他方案1.1 量化数据路上的真实痛点先泼一盆冷水历史数据这件事远没有想象中简单。你去网上随便找一个免费的股票接口可能拉下来数据缺胳膊少腿——有的少复权因子有的日期错乱有的分钟线只给最近一周。更麻烦的是不同数据源的字段口径还不一样这家叫close那家叫adj_close等你要做多因子、做回测的时候光是清洗和适配就能消耗你大半天的精力。我以前试过自己写爬虫去抓行情网站倒也能抓但有几个致命问题第一是目标网站改版后你的解析逻辑全部作废维护成本极高第二是很多网站有访问频率限制批量拉几千只股票的历史数据很容易触发封禁第三是数据可信度存疑没有经过专业的清洗和校验你根本不知道哪一天的收盘价是程序漏采后的错误值。说白了个人爬虫这条路只适合练手不适合真正跑量化研究。1.2 TushareA股数据源里的“老牌靠谱选手”Tushare这个项目在量化圈里名气很大核心优势是覆盖面和稳定度。它提供了沪深京三市的股票列表、日线、分钟线、复权因子、财务指标、资金流向、板块概念等一堆常用数据接口而且字段设计得很规范基本上是照着实盘交易数据的标准来做的。你通过ts.pro_api()拿到的是经过整理的结构化数据直接用pandas就能开工。跟其他免费数据源比Tushare的接口协议清晰文档例子多社区的问答积累也比较厚。你们在搜索引擎里看到的大量量化教程很多底层数据就是用它拉的。它还有积分制度新注册用户默认有一定的访问权限常用的daily日线接口和adj_factor复权因子接口都支持调用只要按官方规则做实名认证和积分升级个人研究和学习完全够用。1.3 vnpy从研究到落地的框架衔接vnpy是一套开源的量化交易框架定位不是简单的数据脚本而是“数据管理 策略研究 回测仿真 实盘交易”的完整链路。我最早用vnpy是看中它内置的数据库管理和回测引擎后来发现它的数据服务模块也很有意思——你可以把Tushare配置成vnpy的数据源然后在vnpy的界面上直接下载数据下载完自动进它的数据库回测的时候无缝取用。这种“数据源插件化”的设计对我的工作流帮助特别大。以前我在Jupyter Notebook里拉一份数据存成CSV然后在自己的回测脚本里读文件流程很散换个数据源就得改一堆代码。现在通过vnpy的数据服务抽象层我把取数、入库、回测这几件事串成了一个整体前端是统一的调用接口底层数据源随时可以换。这也是我推荐这套组合的核心原因它不是解决“今天能拉到数据”的临时方案而是给你一个可持续扩展的研究基建。2. 动手之前的环境准备2.1 Python版本怎么选这里先列个硬经验不要一上来就装最新版Python更不要装特别老的版本。vnpy 3.x版本官方对Python 3.10以上的支持已经比较成熟我个人用3.10和3.11都跑过没碰到大坑如果你项目里还要兼容vnpy 2.x时代的旧代码那Python 3.8到3.9会更稳。总体建议是新项目直接用Python 3.10或3.11别拿3.12以上的版本去冒险因为有些第三方金融库的预编译包可能还没跟上。安装Python时有一个容易被忽略的点——把“Add Python to PATH”勾上。Windows用户如果漏了这一步后面在命令行敲python会提示找不到命令非常折磨。Linux和macOS的话建议用系统自带的包管理器或者官方安装包装完在终端里验证一下python --version pip --version如果pip命令提示不存在或者版本太老先升级一下pippython -m pip install --upgrade pip2.2 安装vnpy和Tushare数据模块环境准备好之后核心安装命令其实只有两条。我用的是虚拟环境方案推荐你也这么做避免跟系统里其他Python项目互相污染python -m venv quant_env # Windows激活 quant_env\Scripts\activate # Linux/macOS激活 source quant_env/bin/activate pip install vnpy pip install vnpy_tusharevnpy的包比较大依赖也比较多在国内网络环境下直接安装可能会卡住。这个问题的解决办法很成熟——用国内PyPI镜像源。我个人习惯用清华源速度快而且稳定pip install -i https://pypi.tuna.tsinghua.edu.cn/simple vnpy pip install -i https://pypi.tuna.tsinghua.edu.cn/simple vnpy_tushare装完之后可以快速试一下导入确认没有报错import vnpy from vnpy_tushare import TushareService print(ok)如果这一步顺利通过说明环境基本搭好了。tushare的Python库本身也要装虽然vnpy_tushare会作为依赖带进来但为了后面写独立脚本方便也可以显式确认一下pip install tushare2.3 获取Tushare的接入凭证Tushare Pro版接口需要用token完成身份认证。流程是这样先到Tushare官网注册账号登录后在“个人主页 → 接口TOKEN”页面可以看到你的专属token一串类似xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx的字符串。这个token相当于你调用Tushare接口的钥匙每个请求都会校验它。拿到token之后在脚本里有两种方式使用。一种是放到代码开头直接设置import tushare as ts ts.set_token(你的token) pro ts.pro_api()另一种是通过环境变量管理好处是不会把密钥硬编码在代码文件里适合放进Git仓库而不担心泄露export TUSHARE_TOKEN你的tokenimport os import tushare as ts ts.set_token(os.environ.get(TUSHARE_TOKEN)) pro ts.pro_api()关于Tushare的积分制度这里多说一句。Pro版接口不是所有都对你开放的像最基础的stock_basic股票列表和daily日线行情新注册用户就有权限但一些高级接口或者高频数据接口比如分钟线和财务明细会要求一定的积分。新用户注册后通常会送初始积分做完实名认证这些之后能解锁大部分研究场景。所以当你调用某个接口报错提示“没有权限”或者“积分不足”时不要慌先去官网看文档里这个接口的最低积分要求再根据提示做相应的认证或者积分升级即可。3. 核心实战拉取股票历史数据3.1 第一步先拿到全市场股票列表很多教程一上来就教你拉某只股票的数据但实际做量化研究时你往往需要先构建一个股票池搞清楚当前市场上到底有哪些股票在交易。Tushare的stock_basic接口就是干这个的。它会返回上市公司的基本信息比如股票代码、名称、所属行业、上市日期等等。我的完整代码是这么写的import tushare as ts import pandas as pd # 设置token并初始化接口 ts.set_token(你的token) pro ts.pro_api() # 拉取A股股票列表 stock_list pro.stock_basic( exchange, # 空字符串代表沪深京三市 list_statusL, # L代表上市状态 fieldsts_code,symbol,name,area,industry,market,list_date ) print(stock_list.shape) print(stock_list.head())字段说明一下ts_codeTushare的股票代码格式比如600519.SH由6位数字代码加交易所后缀组成.SH代表上交所.SZ代表深交所.BJ代表北交所symbol不带交易所后缀的6位代码name股票名称area所在地域industry所属行业market市场板块比如主板、创业板、科创板list_date上市日期。这个接口返回的是DataFrame你可以直接对它做筛选、分组和去重。比如我经常需要把所有科创板股票单独拎出来就过滤ts_code中包含.SH且代码以688开头的记录。这个股票列表我建议一次拉下来后存个快照后面跑批量任务时拿来当遍历清单用。3.2 第二步拉取个股日线数据并计算复权现在到了今天的重头戏股票历史日线数据。很多人拿pro.daily()直接把原始行情拉下来就用但这里藏着一个大坑——daily接口返回的是“不复权”的原始价格。什么是不复权就是股票分红、送股、拆股之后股价会发生一个跳空不复权数据里这个跳空是真实存在的但它会严重干扰技术指标的计算尤其是均线、MACD这类依赖连续价格序列的指标。正确的做法是拉取复权因子adj_factor然后自己计算前复权价格。前复权的逻辑是让历史价格按照最新的复权因子进行缩放这样最近的价格是最真实的历史价格被统一调整消除了跳空缺口技术指标算出来才是有意义的。计算的核心公式是某一天的复权价 原始价 × 当天复权因子 / 最新复权因子。完整代码如下import tushare as ts import pandas as pd ts.set_token(你的token) pro ts.pro_api() ts_code 600519.SH # 贵州茅台 start_date 20200101 end_date 20231231 # 1. 原始日线行情 df_daily pro.daily( ts_codets_code, start_datestart_date, end_dateend_date ) df_daily df_daily.sort_values(trade_date).reset_index(dropTrue) # 2. 复权因子 df_adj pro.adj_factor( ts_codets_code, start_datestart_date, end_dateend_date ) df_adj df_adj.sort_values(trade_date).reset_index(dropTrue) # 3. 合并 df pd.merge(df_daily, df_adj, ontrade_date, howleft) # 4. 计算前复权价格 latest_adj df[adj_factor].iloc[-1] for col in [open, high, low, close]: df[f{col}_qfq] df[col] * df[adj_factor] / latest_adj # 5. 保留常用字段 df df[[ts_code, trade_date, open_qfq, high_qfq, low_qfq, close_qfq, vol, amount, adj_factor]] print(df.head())整个过程并不复杂但有几个细节值得注意第一复权因子整体都在变化。adj_factor不是固定的每当股票有新的分红送股所有历史点的复权因子都会整体按比例调整所以“最新复权因子”会随着时间改变。你今天计算的前复权价格和三个月后重新计算的结果可能不一样这属于正常现象。所以最好在拉数据时就把当天的adj_factor也存下来方便追溯计算口径。第二daily和adj_factor的日期不一定完全对齐。有些极端情况下某一天有复权因子却没有日线行情或者反过来。merge的时候用howleft会导致某些行的adj_factor为NaN后续计算前复权价时自然也是NaN。我的处理方式是在merge之前先对df_adj做一次按日期的去重和排序再检查一下有没有缺失日期。如果缺失不多可以用前一个交易日的因子填充如果缺失严重那就要考虑是不是接口返回了脏数据。第三成交量不需要复权。很多新手会问成交量和成交额要不要也调整答案是不用。复权只针对价格因为价格跳空是分红送股造成的名义变化而成交量代表的是实际股份交割数量不受除权除息影响。你看到的vol单位是“手”amount单位是“千元”直接用即可。3.3 第三步数据清洗与对齐细节拉回来的数据直接扔进策略前一定要做一次彻底的体检。我的习惯是整理出一套固定的清洗流程每次拿到新数据都跑一遍。首先是缺失值检查。用df.isnull().sum()扫一遍重点看收盘价和复权因子有没有空值。如果发现某一天收盘价为空但同时期的其他字段都正常那大概率是数据源本身漏了需要重新拉取。如果是复权因子缺失可以用前后两天的取平均或者用ffill()向前填充凑合一下。其次是重复值检查。Tushare理论上不会返回重复日期但如果你多次拼接数据或者从不同接口合并数据就很容易出现同一交易日的多行记录。处理方式是df df.drop_duplicates(subsettrade_date, keeplast)再一个是日期索引。拿到数据后我习惯把trade_date转成datetime类型并设为索引df[trade_date] pd.to_datetime(df[trade_date]) df df.set_index(trade_date).sort_index()这样做的好处是后面做时间切片非常方便比如df[2021-01-01:2021-06-30]就能直接取到半年的数据。还有停牌问题。A股股票经常因为各种原因停牌停牌期间没有交易自然也就没有日线记录。如果你要做截面分析比如某一天对全市场股票做排序那些停牌股票直接会缺失在当天数据里。这种情况一般有两种处理方式一是用ffill()把最近的收盘价填充进去代表“停牌期间保持上一收盘价”二是干脆剔除缺失股票。具体用哪种取决于你的策略假设。如果只是做个股历史回测建议直接忽略停牌日因为策略在停牌日根本无法撮合交易。3.4 第四步把数据保存到本地数据拉下来了清洗好了接下来的问题是——放哪如果是研究阶段的临时数据直接存CSV最省事df.to_csv(600519_daily_qfq.csv, encodingutf-8-sig)utf-8-sig是为了让Excel打开CSV时不乱码。但如果你要跑多股票、多年份的数据CSV的管理就非常痛苦。我个人更推荐存SQLite轻量、单文件、支持SQL查询非常适合个人量化项目import sqlite3 conn sqlite3.connect(stock_data.db) df.to_sql(daily_qfq, conn, if_existsappend, indexTrue) conn.close()存表时把trade_date也写进去查询时用WHERE ts_code? AND trade_date BETWEEN ? AND ?取数速度非常快。等到策略越来越复杂、数据量越来越大的时候再考虑迁移到MySQL或者PostgreSQL也不迟。初期不要上来就上重型数据库容易把自己劝退。4. 将数据接入vnpy框架4.1 在vnpy中配置Tushare数据服务如果你只用Tushare拉数据、用pandas做分析其实也用不上vnpy。但一旦你想用vnpy的回测引擎、CTA策略模块或者实盘交易网关就需要把数据接入vnpy的统一数据接口。vnpy里数据服务的接入方式是安装对应插件。前面我们已经装好了vnpy_tushare接下来要做的是在vnpy配置文件中指明“数据服务用Tushare”。vnpy的配置文件在用户目录下的.vntrader文件夹里文件名是vt_setting.json打开后添加数据服务的相关配置。如果你是用vnpy的图形界面启动可以这样操作命令行进入虚拟环境后执行python -m vnpy打开界面后点击菜单栏里的“配置”在里面找到数据服务相关设置填上Tushare的token。保存后重启vnpy数据服务就被激活了。之后在图形界面里打开“数据管理”功能就能看到基于Tushare的下载选项你可以直接输入股票代码、选择周期和起止日期点下载vnpy会把数据写进它内置的数据库。4.2 用代码完成历史数据下载与读取图形界面适合交互式操作但批量化、自动化还是要写代码。vnpy提供了一套统一的取数接口核心类是HistoryRequest和BarData。from datetime import datetime from vnpy.trader.constant import Exchange, Interval from vnpy.trader.object import HistoryRequest from vnpy.trader.datafeed import get_datafeed # 获取当前配置的数据服务 datafeed get_datafeed() req HistoryRequest( symbol600519, exchangeExchange.SSE, intervalInterval.DAILY, startdatetime(2020, 1, 1), enddatetime(2023, 12, 31) ) bars datafeed.query_bar_history(req)返回的bars是一个BarData对象的列表每个对象包含open_price、high_price、low_price、close_price、volume、datetime等属性这是vnpy统一封装的数据结构。它和被Tushare直接返回的DataFrame不同但信息完全一致。拿到这个列表后你可以遍历它或者转成DataFrame做分析。这里有一个很容易踩的坑vnpy的接口对“交易所代码”有严格要求。比如贵州茅台的Tushare代码是600519.SH但在vnpy里调用query_bar_history时symbol传600519exchange传Exchange.SSE不要直接传字符串SH也不要传600519.SH这种整个代码。否则vnpy在解析时会匹配不到交易所直接返回空数据。这个细节我在第一次用时踩过查了半天才发现是这里不对。4.3 回测引擎如何从数据库取数数据下载除重之后vnpy回测引擎取数就非常简单了。它本质上也是调用query_bar_history只不过在策略回测中你不需要手动拼HistoryRequest而是直接把数据交给BacktestingEnginefrom vnpy_ctastrategy.backtesting import BacktestingEngine engine BacktestingEngine() engine.set_parameters( vt_symbol600519.SSE, interval1d, startdatetime(2020, 1, 1), enddatetime(2023, 12, 31), rate0.0003, # 手续费率 slippage0.5, # 滑点 size100, # 合约乘数 pricetick0.01, # 最小价格跳动 capital1_000_000, # 初始资金 ) engine.add_strategy(MyDoubleMaStrategy, {}) # 你自己的策略类 engine.load_data() engine.run_backtesting()这里vt_symbol用的是vnpy交易合约格式600519.SSE表示上交所的贵州茅台。load_data()内部就会通过数据服务接口去拉取历史K线拉到后缓存到内存中再喂给策略引擎。所以只要你把前面的数据服务配置对回测这一环的数据链路就全部打通了。更妙的是vnpy的数据下载入库和回测取数是一体的。你用图形界面下载完数据后回测时load_data()会优先读本地数据库没有的K线才会去请求数据服务。这样你在断网环境下也能复跑回测不用每次都去Tushare重复拉数据。5. 高频踩坑记录与排查速查表5.1 安装与启动问题问题1pip安装vnpy非常慢或直接卡死。这个太常见了尤其在国内网络环境下。解决办法就是用国内镜像源前面已经提到。如果你用的是Pycharm或者VSCode的终端记得先激活虚拟环境再安装否则可能装到系统全局Python里。问题2导入vnpy时报DLL加载失败或者提示缺少VC运行时。Windows下最常遇到。vnpy依赖的一些底层库比如PyQt、pandas、numpy需要VC运行库支持。去微软官网装一下最新的Visual C Redistributable重启终端基本就能解决。另外Python 3.12以上的版本更容易出现这类兼容问题所以前面我建议用3.10/3.11是有原因的。问题3命令行执行python -m vnpy没反应。先确认虚拟环境是否激活再确认vnpy是否真的装进了当前环境。一个快速检验方式是pip show vnpy如果显示找不到包说明当前环境不对。5.2 数据接口与限频问题问题1调用pro.daily()报“抱歉您没有访问该接口的权限”。这个报错通常不是token写错了而是积分不足。去Tushare文档查一下daily接口的最低积分要求通常新用户初始积分不够调用某些高级参数。解决方案就是注册完成后做实名认证同时多看官方文档里“积分获取”那部分按照指引做相应的升级。个人学习的积分门槛并不高不要太焦虑。问题2批量拉取时频繁报“每分钟接口调用次数超限”。Tushare Pro有频率限制不同积分等级对应的每分钟调用次数不同。如果你要遍历几十只股票一定要控制节奏。我的习惯是在循环里加一个sleepimport time for code in code_list: df pro.daily(ts_codecode, start_datestart_date, end_dateend_date) # 处理数据... time.sleep(0.5) # 控制请求频率如果你积分等级比较高可以把0.5改成0.2积分低就调到1以上。不要贪快宁可慢一点也不能账号被临时限制。问题3数据接口返回结果时有时无网络不稳定。Tushare官方接口确实偶发超时或错误。稳健一点的写法是加一个重试机制def fetch_with_retry(func, retries3, **kwargs): for i in range(retries): try: return func(**kwargs) except Exception as e: print(f第{i1}次尝试失败: {e}) time.sleep(1) raise Exception(重试多次仍然失败)5.3 复权与数据质量坑问题1前复权价格可能出现负值吗不会。前复权是把历史价格按照最新复权因子缩放哪怕股票早期价格非常低缩放后也不会变成负数只可能无限趋近于0。如果你算出来有负值或者异常值检查一下合并时是不是把复权因子对错了行。问题2拉到的日线数据里为什么某一天的close和“行情软件里看到的不一样”大概率是复权方式不同。行情软件默认显示的是前复权而且它的前复权可能是按“当前交易日”动态计算的你拉到的daily原始数据是不复权的。所以跟软件对不上很正常不要纠结。真正要关心的是你策略里用的是哪种复权口径并保持一致。问题3有些小市值股票历史早期的adj_factor波动特别大计算结果看起来“很奇怪”这是正常的。小股票送转频繁复权因子跳变就大。这也是为什么要存因子、留计算口径的原因。不要试图手工“修正”这种数据用统一公式处理即可。问题4股票停牌期间没有数据回测会不会出问题会。如果你的策略在停牌期间还试图交易回测引擎可能因为该日K线缺失而出现空窗。vnpy的BacktestingEngine本身对K线缺失的处理方式是跳过但你的策略逻辑里如果依赖“连续交易日”建议在用数据之前先把交易日历补齐。可以直接拉Tushare交易日历然后把缺失日期填充为前值。5.4 排查速查表现象可能原因处理建议安装vnpy时报错Python版本过高或过低切换Python 3.10/3.11导入vnpy失败缺少VC运行库安装最新Visual C Redistributablepro.daily()无权限积分不足实名认证、按文档提升积分批量请求被限频超过每分钟配额循环中增加sleep查询结果为空symbol和exchange传递错误使用600519Exchange.SSE前复权价格异常复权因子合并错位检查merge键和排序与行情软件对不上复权口径不同统一使用自己计算的前复权停牌日数据缺失属于正常现象按策略需求决定填充或剔除写在最后的一个小技巧做过几轮数据清洗之后你会发现把“取数 清洗 入库”封装成一个固定函数能省掉未来无数重复劳动。我的项目里维护了一个data_service.py里面只放几个函数get_stock_list()、get_daily_with_adj()、save_to_db()、load_from_db()所有策略和回测脚本都调它来取数从不去直接拼Tushare的原生接口。这样一来哪天Tushare接口字段有变动我只需要改这一个文件策略代码完全不受影响。这也是我从v1版本一路迭代到v8版本之后觉得最值得分享的一点经验。数据层看似不起眼却是整个量化系统里最值得花时间加固的部分。希望这篇文章能帮你少走我当年走过的弯路把更多精力留给真正有挑战的策略研究上。
返回列表