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())

常见的清洗步骤包括:

  1. 解析时间列:将字符串格式的时间(如"09:30")转换为Pandas的datetime类型,并合并日期信息。
  2. 处理缺失值:检查是否有NaN(非数字)值,并决定是向前填充、向后填充还是删除。
  3. 设置索引:将时间列设置为DataFrame的索引,便于时间序列分析。
  4. 处理非交易时段: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)和日志记录,因为网站接口变更或网络波动是常有的事。

走到这里,你已经拥有了一个从数据到图表的完整工作流。它可能还不是一个生产级的系统,但绝对是一个坚实可靠的起点。量化投资的学习路径很长,从可视化开始,逐步深入到策略回测、风险管理和实盘交易,每一步都需要这样的动手实践。我建议你在成功运行本文代码后,尝试更换不同的股票、不同的日期,观察图形的变化。然后,挑战自己:能否在图上增加昨日收盘价作为水平线?能否将成交量做成涨跌颜色的折线图?这些小小的改进,正是你能力增长的脚印。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐