☰
Python股票K线图源码:KDJ与MACD指标可视化实战
2026/11/4 10:43:49 网站建设 项目流程

简介:这份资源面向股票量化分析与Python可视化学习者,提供一套完整的K线图及KDJ、MACD、RSI等技术指标显示源代码,重点解决历史行情读取、股票数据切换与指标公式动态更换的问题,并兼顾Python 2与Python 3的跨版本兼容设计。压缩包共18个文件、约103KB,以10个py源码文件为核心,辅以4张jpg界面截图、2个ico图标、1份docx说明文档和1个txt自述文件,模块化拆分清晰,便于按需阅读与二次扩充。目前已有5876人学习下载,说明其在同类小工具中具备一定参考价值。读者可从中获得行情数据读取与指标计算的实现思路、界面绘制与模块划分的组织方式,以及自行扩展新指标或替换数据源的改造入口,适合具备Python基础、希望动手实践股票图表与量化指标显示的学习者参考。

1. 一套能直接跑的股票K线图源码,到底能省多少事

前阵子帮朋友看一个量化小工具,需求很朴素:把日线数据画成K线,下面挂KDJ和MACD两个副图,鼠标移上去能看到当天的开高低收。他原本打算自己从零写,结果光是处理Python2和Python3的编码差异、把日期轴对齐、让三个子图的X轴同步缩放,就折腾了两天。后来翻到一份现成的完整源码,跑起来一看,K线、KDJ、MACD三块图都在,数据接口留了扩展位,改改就能接自己的行情源。这份资源就是干这个的——一套基于Python2/3的股票K线及KDJ、MACD指标显示完整源代码,适合想快速搭出量化分析可视化界面、又不想在绘图细节上反复踩坑的开发者。它不负责帮你选股,也不内置行情接口,但把“数据进来、图出去”这条链路铺好了,剩下的就是往里填数据、往外接策略。

2. 项目定位与技术选型:为什么是这套组合

2.1 它解决的是“可视化最后一公里”

很多量化初学者卡在数据分析和画图之间:pandas里算好了指标,一到画图就发现K线不是K线、副图跟主图对不齐、缩放时日期标签乱跳。这套源码的定位很明确,就是把matplotlib那套底层绘图逻辑封装成可复用的K线组件,KDJ和MACD作为副图指标直接挂在下面,三者共享同一个时间轴。你拿到的不是一个大而全的量化框架,而是一个“画图模块”,可以嵌进自己的脚本里,也可以单独跑起来看效果。

从技术选型看,它走的是轻量路线。绘图用matplotlib,这是Python生态里最稳的选择,不依赖浏览器、不需要起服务,脚本一跑图就出来。数据处理用numpy和pandas,指标计算用向量化写法,避免逐行循环。兼容Python2/3这一点在现在看来有点老派,但考虑到不少存量量化脚本还跑在Python2环境里,这个兼容层反而省了迁移成本。常见做法是在文件头部做sys.version_info判断,统一字符串编码和range/xrange的差异,这套源码里也是这么处理的。

2.2 三个图层的职责划分

整套代码的绘图结构可以拆成三层:主图负责K线蜡烛和均线,副图一负责KDJ三线,副图二负责MACD柱状图和DIF/DEA双线。三层通过GridSpec划分高度比例,主图占大头,两个副图各占一小条。时间轴只在最下面的副图显示标签,上面两层隐藏X轴刻度但保持对齐。这种划分的好处是,你改任何一个副图的指标,不影响另外两层;坏处是三层的数据长度必须严格一致,否则会出现错位——这也是后面避坑章节要重点说的。

指标计算部分,KDJ用的是经典9,3,3参数,MACD用12,26,9,都是业内默认值。源码里把这些参数提成了模块级常量,改起来不用翻函数内部。计算逻辑本身不复杂,但要注意RSV的平滑处理:K值是RSV的移动平均,D值是K值的移动平均,J值则是3K减2D。MACD那边,DIF是快慢EMA之差,DEA是DIF的EMA,柱状图是2倍的(DIF减DEA)。这些公式在源码里都有注释,对照着看不容易算错。

2.3 数据接口的预留方式

源码没有绑定任何具体行情源,而是约定了一个DataFrame结构:索引是日期,列至少包含open、high、low、close、volume五个字段。你从tushare、baostock或者本地CSV读进来,整理成这个形状就能直接喂给绘图函数。这种“约定优于配置”的做法很务实,既不让代码变重,又给了足够的灵活性。我一般会在调用前加一层数据清洗,把停牌日的空值、复权导致的异常跳变处理掉,再传给绘图模块,图会干净很多。

3. 目录结构与核心模块拆解:从数据到三张图

3.1 典型目录长什么样

这类源码通常不会拆得太碎,一个主脚本加一两个辅助模块就够了。常见的组织方式是这样:

stock_kline/ ├── main.py # 入口,读数据、调绘图 ├── kline_plot.py # K线主图绘制 ├── indicators.py # KDJ、MACD计算 ├── data_loader.py # 数据读取与清洗(可选) └── sample_data.csv # 示例数据

main.py负责串流程,kline_plot.py里是绘图函数,indicators.py只做数值计算、不碰绘图。这种分离的好处是,你想换绘图库(比如换成pyecharts做交互图),指标计算部分原封不动就能复用。如果源码是单文件版本,那大概率是把这三块揉在一个.py里,用函数分隔,读的时候按函数名找就行。

3.2 指标计算模块逐段看

先看KDJ的计算,这是副图一的数据来源:

import numpy as np import pandas as pd def calc_kdj(df, n=9, m1=3, m2=3): # 计算N周期内的最低价和最高价 low_n = df['low'].rolling(window=n, min_periods=1).min() high_n = df['high'].rolling(window=n, min_periods=1).max() # RSV:收盘价在周期高低区间中的相对位置 rsv = (df['close'] - low_n) / (high_n - low_n) * 100 rsv = rsv.fillna(50) # 除零保护,首日给中性值 # K、D做指数平滑,等价于递推的移动平均 k = rsv.ewm(com=m1-1, adjust=False).mean() d = k.ewm(com=m2-1, adjust=False).mean() j = 3 * k - 2 * d return k, d, j

这段的关键在ewm的用法。传统KDJ用递推公式K = (2/3)*前K + (1/3)*RSV,ewm(com=2, adjust=False)正好等价于这个平滑系数。adjust=False不能省,否则pandas会按整个序列做调整,结果跟通达信、同花顺对不上。fillna(50)是血泪经验:如果某天最高价等于最低价(一字板),分母为零会出inf,不处理的话整条线就断了。

MACD的计算相对直接:

def calc_macd(df, fast=12, slow=26, signal=9): ema_fast = df['close'].ewm(span=fast, adjust=False).mean() ema_slow = df['close'].ewm(span=slow, adjust=False).mean() dif = ema_fast - ema_slow # 快慢线之差 dea = dif.ewm(span=signal, adjust=False).mean() # DIF的平滑 macd_bar = 2 * (dif - dea) # 柱状图,乘2是惯例 return dif, dea, macd_bar

参数fast、slow、signal对应12、26、9,改这三个值就能调出不同灵敏度的MACD。柱状图乘2这个细节很多人会漏,导致柱子高度只有标准软件的一半,对照行情软件时容易怀疑自己算错了。

3.3 主图与副图的拼装逻辑

绘图部分的核心是GridSpec的高度分配和共享X轴:

import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec def plot_all(df, k, d, j, dif, dea, macd_bar): fig = plt.figure(figsize=(14, 9)) # 高度比例 6:2:2,主图占六成 gs = GridSpec(3, 1, height_ratios=[6, 2, 2], hspace=0.05) ax_main = fig.add_subplot(gs[0]) ax_kdj = fig.add_subplot(gs[1], sharex=ax_main) ax_macd = fig.add_subplot(gs[2], sharex=ax_main) # 主图:K线用vlines画影线,bar画实体 for i, (idx, row) in enumerate(df.iterrows()): color = 'red' if row['close'] >= row['open'] else 'green' ax_main.vlines(i, row['low'], row['high'], color=color, linewidth=0.8) ax_main.bar(i, abs(row['close']-row['open']), bottom=min(row['open'], row['close']), color=color, width=0.6) # 副图一:KDJ三线 ax_kdj.plot(range(len(k)), k, label='K') ax_kdj.plot(range(len(k)), d, label='D') ax_kdj.plot(range(len(k)), j, label='J') # 副图二:MACD柱 + 双线 ax_macd.bar(range(len(macd_bar)), macd_bar, color='red') ax_macd.plot(range(len(dif)), dif, label='DIF') ax_macd.plot(range(len(dea)), dea, label='DEA') plt.show()

sharex=ax_main是让三个子图联动缩放的关键,少了这行,放大主图时副图不动,看指标就失去了意义。hspace=0.05把子图间距压小,视觉上更像行情软件的连续布局。K线实体用bar画、影线用vlines画,这是matplotlib里最省事的组合,比用mplfinance少一层依赖,但代价是逐根循环,数据量上万根时会慢——后面改造章节会讲怎么优化。

4. 跑起来:环境、数据与第一次出图

4.1 环境准备与依赖安装

这套源码的依赖不多,核心就三个:matplotlib、pandas、numpy。Python2和Python3都能跑,但建议直接用Python3,省得编码问题反复折腾。安装命令:

pip install matplotlib pandas numpy

如果用的是Python2,把pip换成pip2,另外注意matplotlib版本别装太新,2.x系列对Python2支持更好。装完之后先跑一个最小验证,确认绘图后端没问题:

import matplotlib print(matplotlib.get_backend())

输出如果是Agg,说明当前是无界面环境,plt.show()不会弹窗,需要改成plt.savefig('test.png')存文件看。这个坑在服务器上跑脚本时特别常见,本地能弹图、上了服务器就“没反应”,其实是后端的问题。

4.2 准备一份能喂进去的数据

源码约定DataFrame的列名是小写的open、high、low、close、volume,索引是日期。如果你手头是CSV,读进来后先做一次列名规整:

import pandas as pd df = pd.read_csv('sample_data.csv', parse_dates=['date'], index_col='date') df.columns = [c.lower() for c in df.columns] # 统一小写 df = df[['open', 'high', 'low', 'close', 'volume']] # 只保留需要的列 df = df.sort_index() # 确保日期升序

parse_dates和index_col一起用,日期直接变成索引,后面画图时X轴位置就用range(len(df)),标签再单独映射回日期。sort_index()别省,数据源倒序导出的情况很常见,不排序的话K线会画反。数据量建议先拿两三百根日线试,太多的话首次出图会等得有点久。

4.3 串起计算与绘图

把前面的模块拼起来,主流程大概是这样:

from indicators import calc_kdj, calc_macd from kline_plot import plot_all k, d, j = calc_kdj(df) dif, dea, macd_bar = calc_macd(df) plot_all(df, k, d, j, dif, dea, macd_bar)

跑通之后应该能看到一张三层的图:上面红绿K线,中间KDJ三线,下面MACD柱子和双线。如果KDJ的J值冲出了0到100的范围,别慌,J值本来就没有上下界,行情软件里也一样。如果MACD柱子颜色全是红的,检查一下是不是没按正负分色——标准做法是正值红、负值绿,源码里如果只写了一种颜色,改bar的color参数按条件传列表就行。

提示:首次运行如果报KeyError,九成是列名大小写或字段缺失,打印df.columns对一下约定字段。

5. 避坑与排查:那些让图“看起来不对”的细节

5.1 现象:副图和主图错位,指标线整体偏移

原因通常是数据长度不一致。比如你在计算指标前做了dropna(),而绘图用的是原始df,两边行数对不上,X轴位置就错开了。解决方法是把指标计算和绘图绑在同一份清洗后的数据上,计算前统一df = df.dropna().copy(),之后所有步骤都用这个df。另一个隐蔽原因是索引重复,同一天出现两条记录,range(len(df))会把它们当成两个点,图就挤在一起了。用df = df[~df.index.duplicated(keep='first')]去重。

5.2 现象:KDJ的K、D值跟行情软件差几个点

先查ewm的adjust参数。默认adjust=True,pandas会按整个序列做加权调整,前期值和递推公式对不上;改成adjust=False才等价于传统递推。再查min_periods,如果设成n,前8天是NaN,fillna的时机不同结果也不同。我一般把min_periods=1,让第一天就有值,虽然前几天不准,但至少曲线连续,不会出现断头。

5.3 现象:K线实体颜色反了,涨跌看起来颠倒

A股习惯红涨绿跌,但有些数据源的close和open顺序或者复权方式会导致判断反掉。检查color = 'red' if row['close'] >= row['open'] else 'green'这一行,如果数据里open和close被交换过,颜色自然反。另外注意平盘(close等于open)归到哪边,归红归绿都行,但要统一,否则十字星的颜色会跳来跳去。

5.4 现象:数据量一大,出图卡到怀疑人生

逐根循环画K线是O(n)次绘图调用,五千根以上就明显慢。临时办法是减少显示根数,只画最近两三百根;根治办法是用matplotlib.collections.LineCollection批量画影线,实体用两次bar(涨一次、跌一次)批量画,把循环降到常数级。这个改造在下一章会给出具体写法。

5.5 现象:Python2下中文标签变方块

matplotlib默认字体不含中文,Python2环境里更容易撞上。加两行配置:plt.rcParams['font.sans-serif'] = ['SimHei']和plt.rcParams['axes.unicode_minus'] = False,前者指定中文字体,后者让负号正常显示。如果系统里没有SimHei,换成Microsoft YaHei或者任何已安装的中文字体名,用matplotlib.font_manager查一下可用字体列表就行。

6. 二次开发:把这份源码改成自己的看盘工具

6.1 先做性能改造,再谈功能扩展

原始逐根绘图的写法在数据量上来后是瓶颈,我一般第一步就换成批量绘制。影线用LineCollection一次性塞进去,实体拆成涨跌两组bar:

from matplotlib.collections import LineCollection def plot_kline_fast(ax, df): x = np.arange(len(df)) # 影线:每根一个[(x, low), (x, high)]线段 segments = [[(i, lo), (i, hi)] for i, lo, hi in zip(x, df['low'], df['high'])] ax.add_collection(LineCollection(segments, colors='black', linewidths=0.6)) # 实体:涨跌分开批量画 up = df['close'] >= df['open'] ax.bar(x[up], (df['close']-df['open'])[up], bottom=df['open'][up], color='red', width=0.6) ax.bar(x[~up], (df['open']-df['close'])[~up], bottom=df['close'][~up], color='green', width=0.6) ax.set_xlim(-1, len(df))

改完之后五千根K线的出图时间从十几秒降到一秒内。set_xlim要手动设,因为add_collection不会自动调整坐标范围,不设的话图是空的——这个坑我踩过,找了半天以为数据没进去。

6.2 加交互:鼠标悬停看数值

静态图看完总想点一下看具体数值,用mplcursors最省事:

import mplcursors cursor = mplcursors.cursor(ax_main, hover=True) @cursor.connect("add") def on_add(sel): i = int(sel.target[0]) row = df.iloc[i] sel.annotation.set_text(f"{df.index[i].date()}\n开{row['open']:.2f} 收{row['close']:.2f}")

装一个pip install mplcursors就行,不用改绘图主体。悬停时sel.target[0]是X坐标,取整后回查df拿当天数据。注意索引越界,鼠标移到图边缘时i可能超出范围,加个if 0 <= i < len(df)保护。

6.3 指标参数外置与多周期切换

把KDJ和MACD的参数从函数默认值提到配置文件或命令行参数,改起来不用动代码。一个简单做法是用argparse:

import argparse parser = argparse.ArgumentParser() parser.add_argument('--kdj-n', type=int, default=9) parser.add_argument('--macd-fast', type=int, default=12) args = parser.parse_args() k, d, j = calc_kdj(df, n=args.kdj_n)

多周期切换则是把日线df按周或月resample,重采样后重新算指标再画。df.resample('W').agg({'open':'first','high':'max','low':'min','close':'last','volume':'sum'})这一行就能把日线聚成周线,注意first和last的顺序不能反,否则周K的开收盘就错了。

6.4 验证改造是否正确的笨办法

改完指标或绘图逻辑后,别只看图“像不像”,拿几只票跟行情软件逐日对数值。我习惯抽最近20个交易日,把K、D、J、DIF、DEA打印出来,跟软件里的数字对到小数点后两位。对不上就回到ewm参数和fillna时机上找,九成问题出在这两处。另外改造前后各存一张图,肉眼对比K线位置和指标形态,批量绘制改完后如果X轴范围没设对,图会整体偏移,这种错误光看代码看不出来,必须出图验证。

从那以后我每次改绘图逻辑,都强制先跑一遍“20日数值对照 + 改造前后截图对比”这两步,再往下加功能。这套源码本身不复杂,价值在于把三层图的骨架搭好了,你省下的是对齐和调试的时间,花出去的是接数据、调参数、做交互的功夫。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询