量化交易新手必看:如何用vnpy+Tushare轻松获取A股历史数据(附完整代码)

📅 发布时间:2026/7/10 12:29:21 👁️ 浏览次数:
量化交易新手必看:如何用vnpy+Tushare轻松获取A股历史数据(附完整代码)
量化交易数据基石从零构建你的A股历史数据获取系统很多朋友刚接触量化交易时总想着先找个“圣杯”策略恨不得马上开始回测和实盘。但折腾一圈下来往往发现最基础的环节——获取可靠、格式统一的历史数据——反而成了最大的拦路虎。我自己刚开始用vn.py做策略开发时也在这个环节踩了不少坑。数据源五花八门格式千奇百怪清洗和转换的工作量有时甚至超过了策略研究本身。今天我们不谈复杂的策略就聚焦于这个最基础、也最关键的环节如何搭建一个稳定、高效、可复用的A股历史数据获取系统。我们将以vn.py这个国内主流的开源量化框架为基础结合Tushare这个强大的数据接口手把手带你构建一套从环境配置、数据获取到格式转换的完整流程。无论你是想回测一个简单的均线策略还是为更复杂的多因子模型准备数据这套系统都能成为你量化工具箱里最趁手的那件工具。1. 环境搭建与核心工具选择在开始敲代码之前我们需要一个干净、可复现的Python环境。我强烈建议使用Anaconda或Miniconda来管理你的项目环境这能有效避免不同项目间的包版本冲突。1.1 创建并激活专属环境打开你的终端Windows用户用Anaconda Prompt或PowerShell执行以下命令来创建一个名为quant_data的新环境并指定Python版本为3.8这是目前与vn.py等主流量化库兼容性较好的版本。conda create -n quant_data python3.8 -y conda activate quant_data环境激活后你的命令行提示符前通常会显示(quant_data)这表示你已进入该独立环境。1.2 安装核心依赖包接下来安装我们需要的三个核心库vn.py、tushare以及数据处理必备的pandas。这里需要注意vn.py的安装名称是vnpy全小写。pip install vnpy pandas对于Tushare由于其数据接口需要令牌token认证我们安装其Pro版本基础功能免费对个人学习和初期开发完全足够。pip install tushare安装完成后可以通过以下命令快速验证安装是否成功python -c import vnpy; import tushare; import pandas; print(所有核心库导入成功)1.3 Tushare Pro令牌获取与初始化Tushare Pro需要注册并获取一个唯一的令牌token才能调用API。这个过程完全免费。访问 Tushare Pro 官网 (tushare.pro) 并注册账号。登录后在个人主页的“接口TOKEN”页面你可以找到系统分配给你的token它是一串类似‘你的token字符串’的字符。在Python中初始化你的token这通常只需要在程序开始时执行一次。提示请妥善保管你的token不要将其直接硬编码在可能会公开的脚本或GitHub仓库中。最佳实践是将其存储在环境变量或配置文件中。2. 理解数据获取的核心逻辑与对象模型在动手封装代码前我们需要理解vn.py的数据模型和Tushare API的返回格式这是两者顺利“对话”的关键。2.1 vn.py 的 BarData 对象在vn.py的世界里一根K线无论周期被抽象为一个BarData对象。这个对象包含了这根K线的所有核心信息。理解它的结构是我们将外部数据转换为vn.py内部数据的第一步。BarData的主要属性包括属性名数据类型说明symbolstr标的代码如000001exchangeExchange (Enum)交易所如Exchange.SZSE(深交所)intervalInterval (Enum)K线周期如Interval.DAILY(日线)datetimedatetimeK线的时间点对于日线通常是该交易日0点open_pricefloat开盘价high_pricefloat最高价low_pricefloat最低价close_pricefloat收盘价volumefloat成交量股数open_interestfloat持仓量期货常用对于股票我们常用来存放成交额gateway_namestr数据来源网关的名称用于区分数据源我们的核心任务就是从Tushare获取到原始的DataFrame数据然后遍历每一行构造出对应的BarData对象并放入一个列表中。2.2 Tushare API 的数据格式Tushare Pro的daily、weekly、monthly等接口返回的是一个PandasDataFrame。以日线数据为例其列名通常是这样的ts_code: 股票代码带交易所后缀如000001.SZtrade_date: 交易日期格式为YYYYMMDD的字符串open,high,low,close: 开盘、最高、最低、收盘价vol: 成交量手amount: 成交额千元这里有两个关键点需要注意代码格式转换Tushare使用000001.SZ格式而vn.py内部通常使用000001symbol Exchange.SZSEexchange的组合。我们需要一个转换函数。数据顺序Tushare默认返回的数据是按交易日期升序排列从过去到现在。而在量化分析和绘图时我们更习惯时间降序从最新到最早或者至少保证顺序一致。因此在数据处理时通常需要进行反转。3. 构建可复用的数据获取引擎理解了底层逻辑后我们将构建一个封装良好的类TushareDataClient。这个类将负责所有与Tushare的通信、数据格式转换和错误处理为上层应用提供一个干净、统一的接口。3.1 客户端类的骨架与初始化我们首先定义这个类并在初始化方法中设置Tushare Pro的token。这里采用类属性来存储Tushare API实例方便所有类方法调用。import tushare as ts import pandas as pd from datetime import datetime, timedelta from typing import List, Optional from vnpy.trader.constant import Exchange, Interval from vnpy.trader.object import BarData class TushareDataClient: Tushare Pro数据获取客户端用于获取A股日线、周线、月线数据并转换为vn.py的BarData格式。 # 类属性存储tushare pro_api实例 _pro_api None classmethod def init(cls, token: str): 初始化Tushare Pro API需传入在官网获取的token。 ts.set_token(token) cls._pro_api ts.pro_api() print(TushareDataClient 初始化成功。) classmethod def _get_api(cls): 获取API实例确保已初始化。 if cls._pro_api is None: raise RuntimeError(TushareDataClient 未初始化请先调用 init() 方法设置token。) return cls._pro_api3.2 核心方法一按天数获取近期数据这是最常用的场景之一获取某只股票最近N个交易日的K线数据。我们定义一个query_bars_by_days方法。classmethod def query_bars_by_days( cls, symbol: str, exchange: Exchange, interval: Interval Interval.DAILY, days: int 100 ) - List[BarData]: 获取指定股票最近N个交易周期的K线数据。 参数 symbol: 股票代码如 000001 exchange: 交易所如 Exchange.SZSE interval: K线周期默认为日线 days: 需要获取的周期数量非自然日指交易日或K线根数 返回 一个按时间从旧到新排列的BarData列表。 # 1. 转换代码格式 ts_code cls._convert_to_ts_code(symbol, exchange) # 2. 根据周期调用不同的Tushare API api cls._get_api() if interval Interval.WEEKLY: df api.weekly(ts_codets_code, fieldsts_code,trade_date,open,high,low,close,vol,amount) elif interval Interval.MONTHLY: df api.monthly(ts_codets_code, fieldsts_code,trade_date,open,high,low,close,vol,amount) else: # 默认为日线 df api.daily(ts_codets_code) # 3. 检查数据是否为空 if df is None or df.empty: print(f警告未获取到 {ts_code} 的{interval.value}数据。) return [] # 4. 取最近days条数据并反转顺序Tushare返回的是升序 df df.head(days).iloc[::-1] df.reset_index(dropTrue, inplaceTrue) # 5. 转换为BarData列表 bar_list cls._dataframe_to_bars(df, symbol, exchange, interval) return bar_list这个方法里用到了一个辅助函数_convert_to_ts_code和一个核心转换函数_dataframe_to_bars。我们先来实现它们。3.3 辅助函数代码转换与数据转换staticmethod def _convert_to_ts_code(symbol: str, exchange: Exchange) - str: 将vn.py格式的代码和交易所转换为Tushare格式的ts_code。 if exchange Exchange.SSE: suffix .SH # 上海证券交易所 elif exchange Exchange.SZSE: suffix .SZ # 深圳证券交易所 else: raise ValueError(f不支持的交易所类型: {exchange}) return f{symbol}{suffix} classmethod def _dataframe_to_bars( cls, df: pd.DataFrame, symbol: str, exchange: Exchange, interval: Interval ) - List[BarData]: 将Tushare返回的DataFrame转换为BarData列表。 bar_list [] for _, row in df.iterrows(): # 解析日期字符串Tushare日期格式为YYYYMMDD try: # 注意这里将日期解析为datetime对象并设置时间为0点。 # 对于日线这代表交易日对于周/月线代表该周期结束的日期。 bar_datetime datetime.strptime(str(row[trade_date]), %Y%m%d) except ValueError as e: print(f日期解析错误: {row[trade_date]}跳过该条数据。) continue # 创建BarData对象 bar BarData( symbolsymbol, exchangeexchange, intervalinterval, datetimebar_datetime, open_pricefloat(row[open]), high_pricefloat(row[high]), low_pricefloat(row[low]), close_pricefloat(row[close]), volumefloat(row[vol]) * 100, # Tushare的vol单位是‘手’乘以100转换为‘股’ open_interestfloat(row.get(amount, 0)), # 成交额单位千元这里原样存储 gateway_nameTushare_Pro, # 标明数据来源 ) bar_list.append(bar) print(f成功转换 {len(bar_list)} 条{interval.value}数据。) return bar_list注意Tushare的vol成交量单位是“手”1手100股而vn.py的BarData.volume字段通常期望单位是“股”。因此我们在转换时进行了乘以100的操作。成交额amount的单位是“千元”你可以根据后续分析需求决定是否转换。3.4 核心方法二按日期范围获取历史数据另一个常见需求是获取指定起止日期内的所有历史数据。我们实现query_bars_by_date方法。classmethod def query_bars_by_date( cls, symbol: str, exchange: Exchange, start_date: str, # 格式YYYYMMDD end_date: str, # 格式YYYYMMDD interval: Interval Interval.DAILY ) - List[BarData]: 获取指定股票在特定日期范围内的K线数据。 参数 start_date: 开始日期格式20230101 end_date: 结束日期格式20231231 ts_code cls._convert_to_ts_code(symbol, exchange) api cls._get_api() # 根据周期选择API if interval Interval.WEEKLY: df api.weekly(ts_codets_code, start_datestart_date, end_dateend_date, fieldsts_code,trade_date,open,high,low,close,vol,amount) elif interval Interval.MONTHLY: df api.monthly(ts_codets_code, start_datestart_date, end_dateend_date, fieldsts_code,trade_date,open,high,low,close,vol,amount) else: df api.daily(ts_codets_code, start_datestart_date, end_dateend_date) if df is None or df.empty: print(f警告未获取到 {ts_code} 在 {start_date} 至 {end_date} 期间的{interval.value}数据。) return [] # 按时间从旧到新排序 df df.iloc[::-1] df.reset_index(dropTrue, inplaceTrue) bar_list cls._dataframe_to_bars(df, symbol, exchange, interval) return bar_list4. 实战演练从获取数据到初步应用现在我们的数据获取引擎已经搭建完毕。让我们通过几个具体的例子看看如何在实际中调用它并简单地将数据用于分析或可视化。4.1 示例一获取贵州茅台近期日线数据假设我们想分析贵州茅台600519.SH最近200个交易日的走势。# 首先在程序入口处初始化客户端token替换为你自己的 TushareDataClient.init(你的tushare_pro_token_字符串) # 获取贵州茅台最近200个交易日的日线数据 from vnpy.trader.constant import Exchange, Interval symbol 600519 exchange Exchange.SSE interval Interval.DAILY maotai_bars TushareDataClient.query_bars_by_days( symbolsymbol, exchangeexchange, intervalinterval, days200 ) print(f共获取到 {len(maotai_bars)} 条数据。) if maotai_bars: latest_bar maotai_bars[-1] # 列表最后一条是最新数据 print(f最新交易日: {latest_bar.datetime.date()}收盘价: {latest_bar.close_price})4.2 示例二获取宁德时代特定年份的周线数据如果我们想研究宁德时代300750.SZ在2023年全年的周度表现。symbol 300750 exchange Exchange.SZSE interval Interval.WEEKLY start_date 20230101 end_date 20231231 catl_weekly_bars TushareDataClient.query_bars_by_date( symbolsymbol, exchangeexchange, start_datestart_date, end_dateend_date, intervalinterval ) print(f获取到 {len(catl_weekly_bars)} 条周线数据。) # 可以计算2023年每周的平均成交额 if catl_weekly_bars: avg_amount sum(bar.open_interest for bar in catl_weekly_bars) / len(catl_weekly_bars) print(f2023年周平均成交额千元: {avg_amount:.2f})4.3 数据落地与初步分析获取到的BarData列表可以直接用于vn.py框架内的其他模块比如回测引擎。但更多时候我们可能需要将其保存下来或者用pandas进行快速分析。将数据转换为Pandas DataFrame进行分析import pandas as pd # 假设我们已经有了 maotai_bars 这个列表 def bars_to_dataframe(bar_list: List[BarData]) - pd.DataFrame: 将BarData列表转换为更易于分析的Pandas DataFrame。 data [] for bar in bar_list: data.append({ datetime: bar.datetime, open: bar.open_price, high: bar.high_price, low: bar.low_price, close: bar.close_price, volume: bar.volume, # 单位股 amount: bar.open_interest, # 单位千元 symbol: bar.symbol, interval: bar.interval.value }) df pd.DataFrame(data) df.set_index(datetime, inplaceTrue) # 将日期设为索引 return df maotai_df bars_to_dataframe(maotai_bars) print(maotai_df.head()) # 查看前几行 print(maotai_df[[close, volume]].describe()) # 查看收盘价和成交量的统计摘要简单计算技术指标例如20日简单移动平均线# 计算20日简单移动平均线SMA maotai_df[SMA_20] maotai_df[close].rolling(window20).mean() # 查看最近几天收盘价与20日均线的位置关系 recent_data maotai_df[[close, SMA_20]].tail(10) print(recent_data)5. 进阶技巧与排错指南在实际使用中你可能会遇到各种问题。这里分享几个我踩过坑后总结的进阶技巧和常见问题解决方法。5.1 处理限流与网络异常Tushare Pro的免费接口有调用频率限制。在批量获取多只股票数据时很容易触发限流。一个简单有效的策略是在请求间增加短暂的随机延迟。import time import random def safe_query_multiple_stocks(symbol_exchange_list, days100): 安全地批量查询多只股票数据避免触发限流。 all_results {} for symbol, exchange in symbol_exchange_list: try: bars TushareDataClient.query_bars_by_days(symbol, exchange, daysdays) all_results[f{symbol}.{exchange.value}] bars print(f已获取 {symbol} 数据。) # 在每次请求后等待一个随机时间0.5秒到2秒之间 time.sleep(0.5 random.random() * 1.5) except Exception as e: print(f获取 {symbol} 数据时出错: {e}) all_results[f{symbol}.{exchange.value}] [] return all_results5.2 数据完整性校验从网络API获取的数据偶尔会有缺失或异常。在将数据投入严肃的回测或分析前进行基本的校验是必要的。检查缺失日期对于日线数据检查是否存在非交易日的日期或者日期序列是否连续。检查价格合理性确保open,high,low,close满足low open, close high的基本逻辑。检查成交量为正正常情况下成交量应为正数。def validate_bar_data(bar_list: List[BarData]) - bool: 对BarData列表进行基本的数据有效性校验。 if not bar_list: return False for i, bar in enumerate(bar_list): # 检查价格逻辑 if not (bar.low_price bar.open_price bar.high_price and bar.low_price bar.close_price bar.high_price): print(f第{i}条数据价格逻辑错误: {bar.datetime}) return False # 检查成交量非负 if bar.volume 0: print(f第{i}条数据成交量为负: {bar.datetime}) return False # 简单检查是否有极端异常值例如股价为0 if bar.close_price 0: print(f第{i}条数据收盘价异常: {bar.datetime}) return False # 检查日期顺序我们提供的数据应该是从旧到新 for i in range(1, len(bar_list)): if bar_list[i].datetime bar_list[i-1].datetime: print(f第{i}条数据日期不递增: {bar_list[i].datetime}) return False return True5.3 将数据持久化到本地数据库对于需要反复使用的数据每次都从API拉取既低效又受限于调用次数。将数据保存到本地数据库如SQLite是标准做法。vn.py原生支持使用PeeweeORM将BarData对象保存到数据库。from vnpy.trader.database import BaseDatabase, get_database from vnpy.trader.object import BarData # 1. 初始化数据库这里以SQLite为例vn.py已封装 database: BaseDatabase get_database() # 2. 假设我们已经获取了 bar_list def save_bars_to_database(bar_list: List[BarData], collection_name: str stock_bars): 将BarData列表保存到数据库。 if not bar_list: return try: # 使用vn.py数据库接口的save_bar_data方法 # 注意此方法可能因vn.py版本不同而有差异请查阅官方文档 database.save_bar_data(bar_list) print(f成功将 {len(bar_list)} 条数据保存到数据库。) except Exception as e: print(f保存数据到数据库时出错: {e}) # 一个备选方案使用pandas保存到CSV df bars_to_dataframe(bar_list) csv_filename f{collection_name}_{bar_list[0].symbol}_{bar_list[0].interval.value}.csv df.to_csv(csv_filename) print(f数据已作为备选方案保存到CSV文件: {csv_filename})构建一个可靠的数据管道是量化研究万里长征的第一步也是最不能马虎的一步。今天这套基于vn.py和Tushare的方案优势在于结构清晰、与主流框架无缝对接并且免费。它可能不是获取数据最快或最全的方式但对于绝大多数个人研究者和策略原型开发来说完全够用且能让你把精力更集中在策略逻辑本身而不是没完没了地处理数据格式问题。在实际项目中你可能会根据需求对这个TushareDataClient类进行扩展比如增加对分钟级数据的支持、集成其他数据源如akshare、或者添加更完善的数据缓存和更新机制。但它的核心设计模式——将数据获取、格式转换、错误处理封装在独立的服务层——是通用的。把这个基础打牢后续无论策略怎么变数据这一块都能稳如磐石。