Python量化投资入门:用Matplotlib绘制A股分时图(附完整代码)
Python量化投资实战:从零构建A股分时图分析系统
最近几年,身边越来越多的朋友开始关注量化投资这个领域。大家最初的想法都很朴素:面对市场波动,能不能用更科学、更系统的方法来做决策,而不是单纯依赖感觉或小道消息?我最初也是抱着这样的好奇心踏入这个领域的。作为一个从传统编程转向金融数据分析的开发者,我发现Python在这个领域展现出了惊人的亲和力。它不像一些专业的金融软件那样有着高昂的学习成本和许可费用,反而像一把瑞士军刀,灵活、强大且社区资源丰富。
今天,我想分享的,就是如何用Python,特别是其核心的可视化库Matplotlib,来构建一个基础的A股分时图分析工具。分时图是日内交易者最亲密的伙伴,它像是一帧一帧播放的电影,实时记录着价格与成交量的博弈。对于初学者而言,亲手绘制出一张专业的分时图,不仅是学习Python数据处理和图形绘制的绝佳实践,更是理解市场微观结构的第一步。本文将完全从实战出发,假设你已有基本的Python语法知识,我们将一起走过数据获取、清洗、可视化到美化的完整流程,并附上可直接运行的代码。我们的目标不是复现某个教程,而是打造一个你真正能用、能改、能扩展的分析起点。
1. 构建你的量化分析环境:工具与数据准备
工欲善其事,必先利其器。在开始绘制图表之前,我们需要一个稳定、高效的开发环境。对于量化分析,我强烈推荐使用Anaconda来管理Python环境。它集成了数据科学领域几乎所有的核心包,包括我们即将用到的Pandas、NumPy和Matplotlib,避免了繁琐的依赖安装和版本冲突问题。
提示:如果你在安装某些包时遇到网络问题,可以尝试更换为国内的镜像源,例如清华或阿里云的镜像,这能极大提升下载速度。
安装好Anaconda后,我们可以通过其自带的conda命令或pip来确保所有必要的库都已就位。以下是我们的核心工具栈:
- Pandas: 数据分析的基石。它将数据加载为
DataFrame格式,一种类似Excel表格的二维数据结构,支持高效的数据清洗、筛选和聚合操作。 - NumPy: 提供高性能的数组运算。许多金融计算,如收益率、波动率,底层都依赖于它。
- Matplotlib: 本次的主角,Python最经典的绘图库。它提供了极高的自由度,允许你控制图表的每一个细节。
- yfinance / akshare / tushare (可选): 这些是专门用于获取金融数据的第三方库。为了教程的完整性和可复现性,我们将主要使用
yfinance(雅虎财经接口,对A股支持有限但稳定)作为示例,并简要介绍其他获取A股数据的可行方案。
首先,让我们在Python脚本或Jupyter Notebook中导入这些库:
# 基础数据处理与可视化库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 设置Matplotlib支持中文显示(解决默认字体不包含中文的问题)
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 数据获取库(以yfinance为例)
import yfinance as yf
数据是量化分析的血液。对于A股分时图,我们需要的是日内Tick级或分钟级的交易数据,主要包括每个时间点的开盘价、最高价、最低价、收盘价和成交量。这里有一个现实挑战:免费、稳定且高质量的A股分钟级数据源并不多。yfinance主要覆盖美股,对A股仅能通过代码后缀(如000001.SZ)获取日线数据,分钟数据可能无法直接获取。
因此,我们需要考虑替代方案。以下是一个简单的对比,帮助你根据自身情况选择:
| 数据源方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 本地CSV文件 | 最稳定,无网络依赖,可自定义 | 数据非实时,需手动或定期更新 | 学习、回测历史行情 |
| 第三方库 (如akshare) | 免费,直接获取A股数据,社区活跃 | 接口可能变动,稳定性依赖源网站 | 个人研究、原型开发 |
| 专业数据API (付费) | 数据质量高,稳定,频率全,有技术支持 | 成本较高 | 实盘交易、专业研究 |
为了确保教程的顺利进行,我们将采用一个折中方案:使用本地预下载的CSV文件模拟数据获取过程。你可以从许多财经网站导出某只股票某一天的分时数据(通常为CSV或Excel格式)。假设我们有一个名为stock_data_20230510.csv的文件,包含了“平安银行”在2023年5月10日的分钟级数据。
2. 数据获取与清洗:从原始数据到规整DataFrame
拿到原始数据后,第一步永远是“清洗”。金融数据常常存在缺失值、格式不一致、时间戳不规整等问题。我们的目标是将其转化为一个干净的Pandas DataFrame,索引是规整的datetime类型,列是清晰的价格和成交量信息。
让我们模拟加载并处理这个CSV文件:
# 假设CSV文件包含以下列:'time', 'open', 'high', 'low', 'close', 'volume'
df_raw = pd.read_csv('stock_data_20230510.csv')
# 查看前几行数据结构和基本信息
print("数据预览:")
print(df_raw.head())
print("\n数据信息:")
print(df_raw.info())
常见的清洗步骤包括:
- 解析时间列:将字符串格式的时间(如
"09:30")转换为Pandas的datetime类型,并合并日期信息。 - 处理缺失值:检查是否有NaN(非数字)值,并决定是向前填充、向后填充还是删除。
- 设置索引:将时间列设置为DataFrame的索引,便于时间序列分析。
- 处理非交易时段:A股上午休市(11:30-13:00)期间没有数据,我们需要确保时间索引是连续的,但图表显示时需要断开。
下面是一个具体的清洗代码示例:
# 1. 解析时间:假设原始数据中‘date’列为‘2023-05-10’,‘time’列为‘09:30’
df_raw['datetime'] = pd.to_datetime(df_raw['date'] + ' ' + df_raw['time'])
df = df_raw.set_index('datetime')
# 2. 处理缺失值:简单起见,使用前向填充(用前一分钟的数据填充当前缺失值)
df.fillna(method='ffill', inplace=True)
# 3. 重采样与过滤(可选但重要):确保数据是规整的1分钟频率,并过滤掉非交易时间
# 首先,创建一个涵盖全天交易分钟的理论时间范围
full_day_range = pd.date_range(start='2023-05-10 09:30', end='2023-05-10 15:00', freq='1min')
# 然后,将原始数据对齐到这个范围,缺失处会生成NaN
df = df.reindex(full_day_range)
# 再次填充NaN(非交易时段在reindex后会产生大量NaN,这正是我们想要的)
df.fillna(method='ffill', inplace=True)
# 4. 标记交易时段:为后续绘图区分交易与非交易时间
df['is_trading'] = True
# 将午休时间(11:30至13:00)标记为非交易
df.loc['2023-05-10 11:30':'2023-05-10 13:00', 'is_trading'] = False
经过以上步骤,我们得到了一个索引清晰、数据完整、并标记了交易时段的DataFrame df。现在,数据已经准备好了。
3. 核心绘制:使用Matplotlib构建分时图骨架
有了干净的数据,我们就可以开始绘图了。一张标准的A股分时图通常由上、下两个子图组成:上方是价格走势线,下方是成交量柱状图。我们将使用Matplotlib的subplots函数来创建这个布局。
# 创建图形和轴对象
# fig是整个画布,axes是一个包含两个Axes对象的数组
fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True, # sharex共享X轴
gridspec_kw={'height_ratios': [3, 1]}) # 设置子图高度比 3:1
price_ax = axes[0] # 价格轴
volume_ax = axes[1] # 成交量轴
# 绘制价格线(仅绘制交易时段的数据)
trading_data = df[df['is_trading']]
price_ax.plot(trading_data.index, trading_data['close'],
color='#1f77b4', linewidth=1.5, label='收盘价')
# 绘制成交量柱状图(同样仅交易时段)
volume_ax.bar(trading_data.index, trading_data['volume'],
color=['#ff6961' if close >= open else '#2ca02c' for close, open in zip(trading_data['close'], trading_data['open'])],
width=0.0005) # 宽度需要根据时间频率微调
这段代码创建了基本的图形框架,但看起来还非常简陋。价格线是连续的,但忽略了午间休市,导致图形在11:30到13:00之间有一条不应该存在的连接线。成交量柱的颜色是统一的,没有体现“红涨绿跌”的常见视觉习惯。接下来,我们就要解决这些问题。
4. 高级美化与实战技巧:让图表专业起来
一个专业的图表,细节决定成败。我们需要处理几个关键问题:午间休市的断层显示、价格线与均线的叠加、成交量颜色区分以及整体样式的美化。
4.1 处理交易时间断层
Matplotlib本身不会自动识别并跳过非交易时间。我们需要手动“断开”这段时间的连接。一个巧妙的方法是将一天的数据分成上午和下午两个段,分别绘制。
# 分割数据
morning_session = df['2023-05-10 09:30':'2023-05-10 11:30']
afternoon_session = df['2023-05-10 13:00':'2023-05-10 15:00']
# 在价格轴上分别绘制上午和下午的线段
price_ax.plot(morning_session.index, morning_session['close'], color='#1f77b4', linewidth=1.5)
price_ax.plot(afternoon_session.index, afternoon_session['close'], color='#1f77b4', linewidth=1.5)
# 添加一条灰色的垂直线标记午休开始和结束,增强视觉效果
price_ax.axvline(x=pd.Timestamp('2023-05-10 11:30'), color='gray', linestyle='--', linewidth=0.8, alpha=0.7)
price_ax.axvline(x=pd.Timestamp('2023-05-10 13:00'), color='gray', linestyle='--', linewidth=0.8, alpha=0.7)
price_ax.text(pd.Timestamp('2023-05-10 11:30'), price_ax.get_ylim()[1]*0.95, '午间休市',
verticalalignment='top', horizontalalignment='right', fontsize=9, color='gray')
4.2 添加分时均线
分时均线是当日成交价格的平均线,能平滑价格波动,反映趋势。通常使用成交量加权平均价(VWAP) 或简单移动平均。这里我们计算并绘制5分钟简单移动平均线。
# 计算移动平均(分别对上午和下午段计算,避免跨午休计算失真)
morning_session['close_ma5'] = morning_session['close'].rolling(window=5, min_periods=1).mean()
afternoon_session['close_ma5'] = afternoon_session['close'].rolling(window=5, min_periods=1).mean()
price_ax.plot(morning_session.index, morning_session['close_ma5'], color='orange', linewidth=1.2, linestyle='-', label='5分钟均线')
price_ax.plot(afternoon_session.index, afternoon_session['close_ma5'], color='orange', linewidth=1.2, linestyle='-')
4.3 优化成交量图
成交量图通常用颜色区分上涨和下跌的分钟。我们定义:收盘价 >= 开盘价时为上涨(红色),反之为下跌(绿色)。同时,调整柱状图的宽度和间距,使其更美观。
# 为上午和下午成交量分别准备颜色列表
def get_volume_color(open_prices, close_prices):
"""根据开盘价和收盘价返回颜色列表"""
return ['#ff6961' if c >= o else '#2ca02c' for o, c in zip(open_prices, close_prices)]
morning_colors = get_volume_color(morning_session['open'], morning_session['close'])
afternoon_colors = get_volume_color(afternoon_session['open'], afternoon_session['close'])
# 清除之前统一的成交量图,重新绘制分段的彩色柱状图
volume_ax.clear()
# 计算合适的柱宽(基于时间间隔)
width_minutes = 0.6 / (24*60) # 将0.6天宽度转换为matplotlib日期单位的宽度
volume_ax.bar(morning_session.index, morning_session['volume'], width=width_minutes, color=morning_colors, edgecolor='none')
volume_ax.bar(afternoon_session.index, afternoon_session['volume'], width=width_minutes, color=afternoon_colors, edgecolor='none')
4.4 综合美化与标注
最后,我们添加标题、坐标轴标签、网格线、图例等元素,让图表信息完整、阅读舒适。
# 设置价格图
price_ax.set_title('平安银行 (000001.SZ) - 2023年5月10日 分时图', fontsize=16, pad=20)
price_ax.set_ylabel('价格 (元)', fontsize=12)
price_ax.grid(True, which='both', axis='y', linestyle='--', linewidth=0.5, alpha=0.7)
price_ax.legend(loc='upper left', fontsize=10)
# 设置成交量图
volume_ax.set_ylabel('成交量 (手)', fontsize=12)
volume_ax.set_xlabel('交易时间', fontsize=12)
volume_ax.grid(True, which='both', axis='y', linestyle='--', linewidth=0.5, alpha=0.7)
# 格式化X轴时间显示
import matplotlib.dates as mdates
# 主要刻度为小时,格式为‘HH:MM’
volume_ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
volume_ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
fig.autofmt_xdate(rotation=0) # 自动调整日期标签倾斜度
# 调整布局,防止标签重叠
plt.tight_layout()
# 显示图表
plt.show()
运行完整的代码后,你将得到一张信息丰富、视觉专业的A股分时图。它清晰地显示了价格在上午和下午两个连续区间的波动,成交量柱状图红绿分明,还有均线作为趋势参考。这张图已经超越了简单的“画线”,具备了初步的分析功能。
5. 从静态到动态:构建可复用的分时图函数
在实战中,我们不可能每次都从头写一遍绘图代码。最佳实践是将核心功能封装成函数或类,方便重复调用。下面,我们创建一个plot_intraday函数,它接受股票代码、日期和DataFrame(或从函数内部获取数据),并返回绘制好的分时图。
def plot_intraday(stock_code, target_date, df=None, data_source='local'):
"""
绘制指定股票在指定日期的分时图。
参数:
stock_code (str): 股票代码,如 '000001'
target_date (str): 日期,格式 'YYYY-MM-DD'
df (pd.DataFrame, optional): 包含分时数据的DataFrame。若为None,则尝试从data_source获取。
data_source (str): 数据源类型,'local' 或 'api'。默认为'local'。
返回:
fig, axes: Matplotlib的图形和轴对象。
"""
# 1. 数据获取逻辑(此处简化,实际需根据data_source实现)
if df is None:
if data_source == 'local':
# 模拟从本地文件加载,实际应匹配你的文件命名规则
filename = f"{stock_code}_{target_date}.csv"
try:
df = pd.read_csv(filename, parse_dates=[['date', 'time']], index_col=0)
except FileNotFoundError:
print(f"本地文件 {filename} 未找到。")
return None, None
# 可在此扩展其他数据源,如 akashare
else:
print(f"暂不支持数据源: {data_source}")
return None, None
# 2. 数据清洗与预处理(复用之前的逻辑,此处略去详细代码)
# ... (包含时间解析、重采样、标记交易时段等)
# 3. 绘图逻辑(复用第3、4部分的代码,封装进来)
fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True,
gridspec_kw={'height_ratios': [3, 1]})
price_ax, volume_ax = axes
# ... (分割数据、绘制价格线、均线、成交量等所有绘图代码)
# 4. 设置标题和标签(动态化)
price_ax.set_title(f'{stock_code} - {target_date} 分时图', fontsize=16, pad=20)
plt.tight_layout()
return fig, axes
# 使用示例
# fig, axes = plot_intraday('000001', '2023-05-10')
# if fig:
# plt.show() # 在脚本中显示
# # fig.savefig('分时图.png', dpi=300, bbox_inches='tight') # 或保存为图片
这个函数只是一个起点。你可以根据需要扩展它,例如:
- 添加更多技术指标线(如布林带、MACD)。
- 支持在价格图上标记特定事件(如大单买入点)。
- 将多个股票的分时图并列显示进行比较。
- 集成交互功能(使用
mplcursors库实现鼠标悬停显示数值)。
6. 常见问题排查与性能优化
在实际操作中,你可能会遇到一些典型问题。这里列出几个我踩过的坑及其解决方案:
问题一:图表显示乱码或中文不显示 这是Matplotlib的经典问题。解决方案已在最开始的代码中给出:通过plt.rcParams设置中文字体。确保你的系统中存在SimHei(黑体)这类中文字体。如果还有问题,可以指定字体文件的绝对路径。
问题二:成交量柱状图宽度不合适,柱子连成一片或间隔太大 柱状图的宽度width参数在时间序列图中需要根据时间频率进行换算。我们的代码中使用了基于日期单位的计算 width_minutes = 0.6 / (24*60)。这个值可能需要根据你实际的图形大小和数据显示范围进行微调。一个经验法则是,让相邻柱子之间有细微的缝隙。
问题三:数据量很大时,绘图速度慢 Matplotlib绘制大量数据点(如全天的Tick数据)时可能会变慢。可以考虑以下优化:
- 数据降采样:如果不是做超高频分析,可以先将数据聚合到5分钟或15分钟级别再绘图。
- 使用更高效的后端:在脚本开头尝试
import matplotlib; matplotlib.use('Agg')或'TkAgg'。 - 简化图形元素:暂时关闭网格、阴影等效果。
问题四:如何自动获取最新数据? 对于想每天自动运行脚本的朋友,可以将数据获取部分与定时任务(如Linux的cron或Windows的“任务计划程序”)结合。使用像akshare这样的库编写一个数据更新脚本,定时运行并保存数据,然后主绘图脚本读取最新的数据文件即可。
注意:任何依赖于第三方免费数据源的自动化脚本,都要做好异常处理(
try...except)和日志记录,因为网站接口变更或网络波动是常有的事。
走到这里,你已经拥有了一个从数据到图表的完整工作流。它可能还不是一个生产级的系统,但绝对是一个坚实可靠的起点。量化投资的学习路径很长,从可视化开始,逐步深入到策略回测、风险管理和实盘交易,每一步都需要这样的动手实践。我建议你在成功运行本文代码后,尝试更换不同的股票、不同的日期,观察图形的变化。然后,挑战自己:能否在图上增加昨日收盘价作为水平线?能否将成交量做成涨跌颜色的折线图?这些小小的改进,正是你能力增长的脚印。
更多推荐


所有评论(0)