Python Tushare库实战:获取A股历史行情数据全流程
1. 使用Tushare获取A股历史行情数据实战指南作为一名量化研究员我深知获取高质量金融数据的重要性。今天我将分享如何通过Python的Tushare库获取A股历史行情数据的完整流程包含从环境配置到数据清洗的全套解决方案。1.1 为什么选择Tushare在量化研究初期数据获取往往是最令人头疼的问题。市面上虽然有Wind、Choice等专业金融终端但它们的费用对个人开发者并不友好。经过多方比较我最终选择了Tushare原因如下免费额度充足基础版每天可调用500次接口完全能满足个人研究需求数据质量可靠相比其他免费数据源Tushare的数据经过专业清洗接口设计友好返回标准Pandas DataFrame格式与Python生态完美兼容数据种类丰富除行情数据外还提供财务、宏观、新闻等多元数据提示虽然Tushare免费版已足够强大但如果需要更高频的数据或更长的历史数据可以考虑升级到付费版年费约2000元1.2 准备工作清单在开始前请确保准备好以下内容有效的Tushare账号注册地址https://tushare.proPython 3.6环境基础Python库pandas, numpy, matplotlib稳定的网络连接部分接口需要访问境外服务器2. 环境配置与基础设置2.1 安装必要库首先通过pip安装核心依赖库pip install tushare pandas numpy matplotlib我推荐使用conda创建独立的Python环境避免与其他项目产生依赖冲突conda create -n quant python3.8 conda activate quant pip install tushare pandas numpy matplotlib2.2 中文显示配置在绘制含有中文的图表时需要特别设置matplotlib的中文字体否则会出现乱码import matplotlib.pyplot as plt import matplotlib # 设置中文字体 plt.rcParams[font.sans-serif] [SimHei] # Windows系统 # plt.rcParams[font.sans-serif] [Arial Unicode MS] # Mac系统 plt.rcParams[axes.unicode_minus] False # 解决负号显示问题2.3 Tushare Token配置获取API Token是使用Tushare的第一步访问Tushare官网注册账号登录后进入个人中心在接口TOKEN处复制你的专属Token配置Token的Python代码如下import tushare as ts # 替换为你的实际Token TUSHARE_TOKEN 你的token_here # 设置Token并初始化接口 ts.set_token(TUSHARE_TOKEN) pro ts.pro_api() print(Tushare接口初始化成功)重要Token相当于你的账户密码切勿在公开场合泄露。建议将Token存储在环境变量中而不是直接写在代码里。3. 核心数据获取实战3.1 获取单只股票日线数据以下是获取单只股票历史行情的基础函数def get_stock_daily(ts_code, start_date20100101, end_date20231231): 获取单只股票的日线行情数据 参数 ts_code: 股票代码格式如000001.SZ平安银行 start_date: 开始日期格式YYYYMMDD end_date: 结束日期格式YYYYMMDD 返回 pandas DataFrame格式的日线数据 try: # 调用Tushare接口获取日线数据 df pro.daily( ts_codets_code, start_datestart_date, end_dateend_date ) # 数据清洗 df[trade_date] pd.to_datetime(df[trade_date]) df.set_index(trade_date, inplaceTrue) df.sort_index(inplaceTrue) # 按时间升序排列 print(f成功获取 {ts_code} 从 {start_date} 到 {end_date} 的日线数据) return df except Exception as e: print(f获取数据失败: {e}) return None使用示例# 获取贵州茅台(600519.SH)2022年数据 df get_stock_daily(600519.SH, 20220101, 20221231) print(df.head())3.2 数据字段解析Tushare返回的日线数据包含以下关键字段字段名说明类型ts_code股票代码strtrade_date交易日期datetimeopen开盘价floathigh最高价floatlow最低价floatclose收盘价floatpre_close前收盘价floatchange涨跌额floatpct_chg涨跌幅(%)floatvol成交量(手)floatamount成交额(千元)float3.3 获取多只股票数据实际研究中我们通常需要获取多只股票的数据。以下是批量获取的优化方案def get_multiple_stocks(stock_list, start_date, end_date): 批量获取多只股票历史数据 参数 stock_list: 股票代码列表如[000001.SZ, 600000.SH] start_date: 开始日期 end_date: 结束日期 返回 字典形式的数据集key为股票代码value为对应的DataFrame data_dict {} for code in stock_list: try: df get_stock_daily(code, start_date, end_date) if df is not None: data_dict[code] df print(f{code} 数据获取成功) else: print(f{code} 数据获取失败) except Exception as e: print(f获取 {code} 数据时出错: {e}) return data_dict使用示例# 获取三只龙头股2022年数据 stocks [600519.SH, 000858.SZ, 601318.SH] data get_multiple_stocks(stocks, 20220101, 20221231)4. 高级功能与数据增强4.1 复权处理原始价格数据未考虑分红送股的影响需要进行复权处理才能反映真实收益。Tushare提供了复权因子接口def get_adj_factor(ts_code): 获取复权因子数据 参数 ts_code: 股票代码 返回 复权因子DataFrame try: df pro.adj_factor(ts_codets_code) df[trade_date] pd.to_datetime(df[trade_date]) df.set_index(trade_date, inplaceTrue) return df.sort_index() except Exception as e: print(f获取复权因子失败: {e}) return None复权计算函数def calculate_adj_price(daily_df, adj_df): 计算复权价格 参数 daily_df: 日线数据DataFrame adj_df: 复权因子DataFrame 返回 包含复权价格的DataFrame merged daily_df.join(adj_df, howleft) merged[adj_factor].fillna(1, inplaceTrue) # 填充缺失值为1 # 计算复权价格 base_factor merged[adj_factor].iloc[-1] # 最新复权因子 merged[adj_close] merged[close] * merged[adj_factor] / base_factor merged[adj_open] merged[open] * merged[adj_factor] / base_factor merged[adj_high] merged[high] * merged[adj_factor] / base_factor merged[adj_low] merged[low] * merged[adj_factor] / base_factor return merged4.2 数据可视化良好的可视化能帮助我们快速理解数据特征def plot_stock_trend(df, title): 绘制股票价格趋势图 参数 df: 包含复权价格的DataFrame title: 图表标题 plt.figure(figsize(12, 6)) plt.plot(df.index, df[adj_close], label收盘价, colorb) plt.title(title) plt.xlabel(日期) plt.ylabel(价格(元)) plt.grid(True) plt.legend() plt.show()使用示例# 获取复权数据并绘图 adj_factor get_adj_factor(600519.SH) full_data calculate_adj_price(df, adj_factor) plot_stock_trend(full_data, 贵州茅台(600519.SH)复权价格走势)5. 常见问题与解决方案5.1 接口调用限制Tushare免费版有以下限制每分钟最多调用500次每天最多调用500次单次最多返回5000条记录解决方案添加适当的延时如time.sleep(0.1)分批获取数据按年份或季度使用try-except处理异常优化后的获取函数import time def safe_get_data(ts_code, start_date, end_date): 带错误处理和延时的安全获取函数 max_retry 3 for i in range(max_retry): try: df pro.daily( ts_codets_code, start_datestart_date, end_dateend_date ) time.sleep(0.1) # 添加100ms延时 return df except Exception as e: print(f尝试 {i1} 次失败: {e}) time.sleep(1) # 失败后等待1秒 return None5.2 数据质量问题常见数据问题及处理方法问题类型检测方法解决方案缺失值df.isnull().sum()向前填充或删除异常值(df[pct_chg] 20)设置为NaN或使用均值替代日期不连续检查日期差补充交易日历价格异常(df[high] df[low])使用前后数据修正数据清洗示例def clean_stock_data(df): 基础数据清洗函数 # 处理缺失值 df.fillna(methodffill, inplaceTrue) df.fillna(methodbfill, inplaceTrue) # 修正异常价格 mask (df[high] df[low]) | (df[open] df[high]) | (df[open] df[low]) df.loc[mask, [open, high, low]] df[close] # 处理异常涨跌幅 df.loc[df[pct_chg].abs() 20, pct_chg] 0 return df5.3 性能优化技巧当处理大量股票数据时性能成为关键考虑因素批量请求使用Tushare的批量接口如pro.daily_basic并行处理使用multiprocessing或concurrent.futures数据缓存将获取的数据保存到本地CSV或数据库增量更新只获取最新数据与历史数据合并并行获取示例from concurrent.futures import ThreadPoolExecutor def parallel_fetch(stock_list, start_date, end_date): 并行获取多只股票数据 with ThreadPoolExecutor(max_workers5) as executor: futures { executor.submit(get_stock_daily, code, start_date, end_date): code for code in stock_list } results {} for future in concurrent.futures.as_completed(futures): code futures[future] try: results[code] future.result() except Exception as e: print(f{code} 获取失败: {e}) return results6. 完整案例构建本地股票数据库最后我将分享一个实战案例 - 构建本地股票数据库import os import pandas as pd class LocalStockDB: def __init__(self, data_dirstock_data): self.data_dir data_dir os.makedirs(data_dir, exist_okTrue) def save_stock_data(self, ts_code, df): 保存单只股票数据到CSV file_path os.path.join(self.data_dir, f{ts_code}.csv) df.to_csv(file_path) print(f{ts_code} 数据已保存到 {file_path}) def load_stock_data(self, ts_code): 从CSV加载股票数据 file_path os.path.join(self.data_dir, f{ts_code}.csv) if os.path.exists(file_path): df pd.read_csv(file_path, index_coltrade_date, parse_datesTrue) return df else: print(f{file_path} 不存在) return None def update_database(self, stock_list, start_date, end_date): 更新本地数据库 for code in stock_list: # 检查本地是否有数据 old_df self.load_stock_data(code) if old_df is not None: # 获取最新数据 last_date old_df.index[-1].strftime(%Y%m%d) new_df get_stock_daily(code, last_date, end_date) # 合并数据 if new_df is not None: combined pd.concat([old_df, new_df]) self.save_stock_data(code, combined) else: # 全新获取 df get_stock_daily(code, start_date, end_date) if df is not None: self.save_stock_data(code, df)使用示例# 初始化本地数据库 db LocalStockDB() # 定义关注的股票列表 watch_list [600519.SH, 000858.SZ, 601318.SH, 000333.SZ] # 首次获取数据 db.update_database(watch_list, 20100101, 20221231) # 后续增量更新 db.update_database(watch_list, 20230101, 20231231)这个本地数据库方案有以下优势减少API调用次数数据持久化存储支持增量更新快速加载历史数据在实际使用中我发现这套方案能显著提高研究效率特别是在需要反复测试不同策略时避免了每次都从网络获取数据的等待时间。