Python金融数据分析入门:用Tushare获取股票历史行情(附完整代码示例)
对于刚接触金融数据分析的Python爱好者来说,最令人兴奋又略带困惑的第一步,往往就是如何获取可靠、结构化的市场数据。市面上充斥着各种数据源,有的需要付费订阅,有的界面复杂,有的数据质量参差不齐。如果你渴望一种更“程序员友好”的方式——通过几行代码就能将海量历史行情数据拉取到本地,进行自由的分析和建模,那么Tushare这个工具库绝对值得你深入了解。它并非一个简单的数据下载器,而是一个将金融数据API化的桥梁,让你能够像调用普通函数一样,获取从股票、基金到宏观经济等一系列金融数据。这篇文章,我将从一个实践者的角度,带你从零开始,手把手掌握Tushare的核心用法,并分享一些我实际使用中总结出的数据处理技巧和避坑指南。
1. 环境准备与Tushare初探
在开始编写任何代码之前,一个稳定、隔离的Python环境是高效工作的基石。我强烈建议使用conda或venv创建独立的虚拟环境,这能避免不同项目间的依赖冲突。假设你已经安装了Python(建议3.7及以上版本),那么环境搭建可以这样开始:
# 创建并激活一个名为‘finance’的虚拟环境(以conda为例) conda create -n finance python=3.9 conda activate finance # 安装核心依赖 pip install tushare pandas numpy matplotlib这里,pandas和numpy是后续数据处理的左膀右臂,matplotlib则用于基础的可视化。安装完成后,你首先需要访问Tushare的官网进行注册,以获取一个唯一的token。这个token是你的身份凭证,所有数据请求都基于它。注册过程完全免费,对于个人学习和研究来说,获取基础数据(如日线行情)的权限已经足够。
拿到token后,我们进行初始化。不要把token硬编码在脚本里,尤其是如果你打算将代码分享到GitHub等平台。一个更安全的做法是将其存储在环境变量中。
import tushare as ts import pandas as pd import os # 方式一:直接在代码中设置(仅用于临时测试,不推荐用于正式项目) ts.set_token('你的token字符串') # 方式二:从环境变量读取(推荐) # 假设你在终端中执行了 `export TUSHARE_TOKEN='你的token'` token = os.getenv('TUSHARE_TOKEN') if token: ts.set_token(token) else: print("请设置环境变量 TUSHARE_TOKEN") exit() # 初始化Pro接口 pro = ts.pro_api()初始化pro对象后,你就拥有了打开金融数据宝库的钥匙。Tushare的数据通过不同的API函数提供,每个函数对应一类数据。在开始调用前,花几分钟浏览官网的数据字典,了解有哪些数据可用,以及每个API所需的参数,这会让你后续的探索事半功倍。
2. 核心API调用实战:获取个股历史行情
获取数据是第一步,但如何高效、准确地获取所需数据,里面有不少门道。Tushare Pro接口提供了daily、weekly、monthly等函数来获取不同周期的行情数据。我们以最常用的日线数据daily()为例。
最基本的调用只需要股票代码和日期范围。A股股票的代码通常以.SZ(深交所)或.SH(上交所)结尾。
# 获取贵州茅台(600519.SH)2023年全年的日线数据 df = pro.daily(ts_code='600519.SH', start_date='20230101', end_date='20231231') print(df.head()) print(f"共获取到{len(df)}条数据")执行这段代码,你会得到一个DataFrame,包含日期、开盘价、最高价、最低价、收盘价、成交量等关键字段。但直接这样调用可能会遇到两个常见问题:一是数据量很大时返回较慢,二是默认的字段顺序可能不符合你的分析习惯。
注意:
trade_date字段是整数格式(如20230101),在后续进行时间序列分析时,需要将其转换为标准的datetime类型。
一个更健壮、功能更丰富的调用示例应该包含错误处理和参数优化:
def fetch_stock_daily(ts_code, start_date, end_date, fields=None): """ 获取股票日线行情数据,并做基础处理。 参数: ts_code: 股票代码,如 '000001.SZ' start_date: 开始日期,格式 'YYYYMMDD' end_date: 结束日期,格式 'YYYYMMDD' fields: 指定返回的字段列表,为None则返回所有字段 返回: 处理后的 pandas DataFrame """ try: # 调用API df = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date, fields=fields) if df.empty: print(f"未获取到股票 {ts_code} 在指定时间段的数据。") return df # 1. 将交易日期转换为datetime类型,并设为索引 df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d') df.set_index('trade_date', inplace=True) df.sort_index(inplace=True) # 按时间升序排列 # 2. 重命名列,使其更易读(可选) column_mapping = { 'open': '开盘价', 'high': '最高价', 'low': '最低价', 'close': '收盘价', 'pre_close': '前收盘价', 'change': '涨跌额', 'pct_chg': '涨跌幅(%)', 'vol': '成交量(手)', 'amount': '成交额(千元)' } df.rename(columns=column_mapping, inplace=True) return df except Exception as e: print(f"获取数据时发生错误: {e}") return pd.DataFrame() # 返回空DataFrame # 使用函数获取中国平安(601318.SH)2024年第一季度数据 pingan_df = fetch_stock_daily('601318.SH', '20240101', '20240331') if not pingan_df.empty: print(pingan_df[['开盘价', '收盘价', '成交量(手)']].head())通过封装函数,我们不仅实现了代码复用,还内置了数据清洗和转换的步骤,让获取到的数据立刻处于“分析就绪”状态。fields参数非常有用,当你只需要其中几列数据时,指定它可以减少网络传输的数据量,提高效率。
3. 进阶技巧:批量获取与数据管理
分析单只股票只是起点,真正的力量在于对比和组合分析。这就需要我们批量获取多只股票的数据。直接写循环调用API虽然可行,但效率不高,且可能触发频率限制。更优雅的方式是利用Tushare支持的批量代码查询,以及合理的缓存策略。
批量获取同一时间段多只股票数据:daily()接口的ts_code参数支持批量传入,代码间用逗号分隔。
# 一次性获取三只股票的数据 stock_codes = '600519.SH,000858.SZ,300750.SZ' batch_df = pro.daily(ts_code=stock_codes, start_date='20240101', end_date='20240331') print(batch_df['ts_code'].unique()) # 查看包含哪些股票返回的DataFrame会包含所有股票的数据,并通过ts_code列区分。为了方便后续分析,我们经常需要将数据转换为面板数据格式,即每个股票单独成一个DataFrame,并以股票代码为键存储在字典中。
# 将批量获取的数据按股票代码拆分成字典 panel_data = {} for code in batch_df['ts_code'].unique(): panel_data[code] = batch_df[batch_df['ts_code'] == code].copy() # 对每个股票的数据进行同样的日期转换和索引设置 panel_data[code]['trade_date'] = pd.to_datetime(panel_data[code]['trade_date'], format='%Y%m%d') panel_data[code].set_index('trade_date', inplace=True) panel_data[code].sort_index(inplace=True) # 现在可以方便地访问任意一只股票的数据 if '600519.SH' in panel_data: maotai = panel_data['600519.SH'] print(maotai[['close', 'vol']].tail())数据缓存与本地存储:反复从网络请求相同的数据是低效的。对于历史数据,一旦获取,就应该保存到本地。我常用的策略是使用pickle或parquet格式存储DataFrame,因为它们能很好地保留数据类型和索引。
import os from pathlib import Path def save_stock_data(df, code, data_dir='./stock_data'): """将单只股票数据保存到本地""" Path(data_dir).mkdir(parents=True, exist_ok=True) file_path = Path(data_dir) / f"{code}.parquet" df.to_parquet(file_path) print(f"数据已保存至: {file_path}") def load_stock_data(code, data_dir='./stock_data'): """从本地加载单只股票数据""" file_path = Path(data_dir) / f"{code}.parquet" if file_path.exists(): df = pd.read_parquet(file_path) # 确保索引是datetime类型 if not isinstance(df.index, pd.DatetimeIndex): df.index = pd.to_datetime(df.index) return df else: print(f"本地文件 {file_path} 不存在。") return None # 使用示例:先尝试从本地加载,如果没有则从网络获取并保存 code = '000001.SZ' local_df = load_stock_data(code) if local_df is None: print("本地无缓存,从Tushare获取...") online_df = fetch_stock_daily(code, '20230101', '20231231') if not online_df.empty: save_stock_data(online_df, code) local_df = online_df建立这样的本地数据仓库后,你的分析脚本启动速度会快很多,并且对Tushare API的依赖也降低了,在网络不稳定或API维护时仍能工作。
4. 基础数据分析与可视化实战
数据到手后,真正的乐趣才开始。我们使用pandas进行快速的数据洞察和特征计算。假设我们已经有了df这个包含某股票日线数据的DataFrame,且索引已是日期。
计算简单技术指标:移动平均线是最基础也最常用的指标之一。
# 计算5日、20日、60日简单移动平均线(SMA) df['SMA_5'] = df['收盘价'].rolling(window=5).mean() df['SMA_20'] = df['收盘价'].rolling(window=20).mean() df['SMA_60'] = df['收盘价'].rolling(window=60).mean() # 计算每日收益率 df['日收益率'] = df['收盘价'].pct_change() # 计算滚动波动率(以20日为例) df['波动率_20d'] = df['日收益率'].rolling(window=20).std() * (20**0.5) # 年化波动率粗略估算制作一个简单的价格与均线图:可视化能让我们直观感受趋势。这里使用matplotlib。
import matplotlib.pyplot as plt plt.style.use('seaborn-v0_8-darkgrid') # 使用一个好看的样式 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True) # 子图1:价格与均线 ax1.plot(df.index, df['收盘价'], label='收盘价', linewidth=1.5, alpha=0.8) ax1.plot(df.index, df['SMA_20'], label='20日均线', linewidth=1.5, linestyle='--') ax1.plot(df.index, df['SMA_60'], label='60日均线', linewidth=1.5, linestyle=':') ax1.set_ylabel('价格 (元)') ax1.set_title('股票价格与移动平均线') ax1.legend() ax1.grid(True, alpha=0.3) # 子图2:成交量 ax2.bar(df.index, df['成交量(手)'], color='skyblue', alpha=0.7, label='成交量') ax2.set_xlabel('日期') ax2.set_ylabel('成交量 (手)') ax2.set_title('成交量') ax2.legend() ax2.grid(True, alpha=0.3) # 自动调整日期标签格式,避免重叠 fig.autofmt_xdate() plt.tight_layout() plt.show()进行简单的数据汇总统计:pandas的describe()函数可以快速给出分布概况,但我们也可以自定义更贴合金融场景的统计。
# 基础描述性统计 print(df[['收盘价', '日收益率', '成交量(手)']].describe()) # 计算一些关键金融统计量 annual_return = df['日收益率'].mean() * 252 # 粗略年化收益率 annual_volatility = df['日收益率'].std() * (252**0.5) # 年化波动率 sharpe_ratio = annual_return / annual_volatility if annual_volatility != 0 else None # 夏普比率(无风险利率假设为0) stats_summary = pd.DataFrame({ '统计量': ['年平均收益率', '年化波动率', '夏普比率(零无风险利率)'], '值': [f"{annual_return:.2%}", f"{annual_volatility:.2%}", f"{sharpe_ratio:.3f}" if sharpe_ratio else 'N/A'] }) print(stats_summary)通过这几个步骤,你已经完成了从数据获取、清洗、存储到基础分析和可视化的完整闭环。这为你后续进行更复杂的策略回测、因子分析或机器学习建模打下了坚实的基础。记住,金融数据分析的核心在于提出正确的问题,并用数据去验证或探索答案,工具只是帮你更高效地完成这个过程。在接下来的探索中,你可能会对Tushare的其他数据接口(如财务数据、宏观经济数据)产生兴趣,也可以将pandas的威力发挥到更大,结合scikit-learn或statsmodels等库进行预测模型构建。