资讯动态

Python+matplotlib绘制移动平均线:从原理到实战

发布时间:2026/10/17 10:09:51 来源:尧图企业网站定制
做量化的人无论你是写策略回测还是做风险监控第一件要学会的事情就是把行情和自己的思路画出来。而在所有技术指标里移动平均线几乎是入门必碰的第一个。原因很简单它既是最经典的趋势判断工具也是很多策略信号的基础。但很多新手在画均线时都会遇到一个问题——自己在行情软件里看着挺正常的图换成Python画出来要么前几个月缺一段要么中文全变方块要么日期轴挤成一团根本没法看。这篇文章就是专门解决这些问题的。我会带你走一遍完整的移动平均线绘图流程从最基础的概念梳理到环境配置再到核心代码实现最后把几个高频踩坑点整理成速查表。无论你以前有没有用过matplotlib跟着一步步做都能在十分钟内画出一张能直接放进出场报告里的均线图。1. 移动平均线到底在画什么1.1 简单移动平均与指数加权平均移动平均线的思想并不复杂把过去N天的收盘价取个平均值用这个平均值来观察价格的趋势方向。每天算一次连成线就得到了平滑后的价格轨迹。这里的核心参数就是N也就是窗口长度。窗口越大曲线越平滑但反应也越迟钝。在Python里计算两种最常见的均线代码差别不大。简单移动平均用rolling指数加权平均用ewmdf[SMA20] df[close].rolling(20).mean() df[EMA20] df[close].ewm(span20, adjustFalse).mean()这两行的差别值得细说。SMA对20天里每一天的价格一视同仁权重相同EMA则给最近的价格更高权重所以它对价格变化的反应更快滞后性更小。实际画图时EMA看起来会更“贴”价格走势在一些短线模型里更受青睐。但EMA因为对噪音更敏感假信号也会多一些。我在实际项目中通常是长短搭配用而不是只盯一种均线。1.2 窗口参数背后的交易逻辑窗口参数选多少取决于你想观察什么级别的趋势。很多人一上来就抄经典参数但并不知道为什么要这么选。实践中常用的组合是5、10、20、60这套组合覆盖了短、中、长三个周期5日和10日均线反映短期动能是短线交易的参考20日均线常被视为中期趋势的分水岭很多资金把它当作止盈止损的参考线60日均线则更接近大趋势的“压舱石”用来判断行情是否处于上行或下行结构。绘图时把这几条线叠加在一张图上肉眼就能看出价格与各周期均线的位置关系。比如价格在60日线上方运行、同时5日线在20日线上方这种“多头排列”结构在实战中常常意味着趋势较为健康。反过来均线从上到下依次排列为空头排列这时候就该警惕风险了。这些判断虽然朴素但确实是很多自动化策略的底层逻辑。1.3 一个画图前必须想明白的问题在动手画图之前我想提醒一件事均线是滞后指标。它本质上是过去N天价格的均值所以当价格快速转向时均线会晚一步才跟上来。在图上看起来好像均线“撑住”了价格其实它只是在反映已经发生的走势。这一点对绘制和分析都至关重要。如果你看一张均线图发现价格跌破20日线后又拉回来了不要急着认为均线有神奇支撑力。它拉回来可能是因为20日前的价格基数开始变得有利也可能只是短期波动。画图的价值在于帮助你系统性观察数据而不是让视觉上的“支撑感”欺骗自己。清楚这一点你画出来的线才会被用在正确的判断场景里。2. 绘图环境与数据准备2.1 工具链选型绘图这部分我用的组合是pandas加matplotlib再加一个numpy做随机数生成和数组运算。为什么不用别的库因为pandas的DataFrame结构天然适合存储日期序列和均线计算结果而matplotlib虽然底层的API略显繁琐但胜在可控性极强画出来的图可以精细到每个标注、每条网格线。还有一个方案是直接用pandas内置的df.plot()方法它封装了matplotlib代码更短适合快速出图。但如果画面里需要叠加信号点、自定义坐标轴格式、保存多种分辨率版本最终还是绕不开原生matplotlib。这篇文章以原生matplotlib为主因为它的上限更高学会了之后画任何金融图表都能得心应手。2.2 中文字体配置这一节是很多中文本地化项目容易卡住的地方。matplotlib默认的英文字体不支持中文渲染直接跑代码会出现一排排方块。解决办法是在画图前设置字体列表plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False第一行指定中文字体matplotlib会按顺序从左到右寻找系统可用的字体。第二行也很关键它用来解决坐标轴负号显示成方块的问题——这个小问题不处理图上价格负数区域就会看着很怪。实际操作时如果SimHei在你的系统上不存在换成其他常见中文字体就行放多个候选字体是为了在不同机器上都能跑通。2.3 准备可复现的模拟行情数据绘图教程最难的地方在于数据获取因为不同编程环境能访问的数据源不一样。为了让你能在本地完整跑通整个流程我这里直接生成一段模拟行情数据逻辑是随机游走加一个初始价格基准import numpy as np import pandas as pd np.random.seed(2024) dates pd.bdate_range(2023-01-02, periods250) close 100 np.cumsum(np.random.randn(250) * 0.8) df pd.DataFrame({close: close.round(2)}, indexdates)这样可以稳定复现一张类似真实走势的日线序列。250个交易日大约等于一年足够观察不同周期均线的交错关系。你在自己的项目里只需要把这个DataFrame换成从行情接口取到的真实数据即可。数据接口返回的字段通常至少有日期、开盘、收盘、最高、最低对我们画均线来说收盘价就是入参。3. 核心实现从数据到均线图的完整流程3.1 计算移动平均线并处理空值数据准备好之后第一步是计算均线。pandas里的rolling方法需要理解一个关键参数min_periods。默认情况下rolling(20).mean()要求窗口内至少20个有效值才会输出结果所以在序列的前19个位置会得到NaN。这个行为在计算和绘图时都必须注意否则容易出现“图上有缺口”的怪现象。如果你希望从第一天开始就有均线值可以设置min_periods1意味着样本不足时用已有数据算部分均值。但大多数量化场景下前几天的“小样本均值”参考意义有限所以我一般保留NaN绘图时matplotlib会自动跳过缺失部分。这样图线上前段自然空白反而清楚提示了“这一段均线还没有足够的数据支撑”。df[MA5] df[close].rolling(5, min_periods5).mean() df[MA20] df[close].rolling(20, min_periods20).mean() df[MA60] df[close].rolling(60, min_periods60).mean()3.2 绘制收盘价与均线有了三列均线数据主图就可以画出来了。我习惯先画收盘价再叠加均线并且用线宽和透明度拉开层次import matplotlib.pyplot as plt import matplotlib.dates as mdates plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(12, 6)) ax.plot(df.index, df[close], labelClose, color#34495e, linewidth1.0, alpha0.75) ax.plot(df.index, df[MA5], labelMA5, color#e67e22, linewidth1.0, alpha0.9) ax.plot(df.index, df[MA20], labelMA20, color#2980b9, linewidth1.4, alpha0.9) ax.plot(df.index, df[MA60], labelMA60, color#c0392b, linewidth1.8, alpha0.9) ax.legend(locbest, frameonFalse, ncol4) ax.set_title(移动平均线MA可视化示例, fontsize14, pad12) ax.set_ylabel(价格) ax.grid(alpha0.3, linestyle--) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) fig.autofmt_xdate() fig.savefig(ma_demo.png, dpi150, bbox_inchestight) plt.show()这里的参数选择都经过实际验证。figsize(12, 6)是横屏长宽比比起正方形更符合价格走势的阅读习惯远的趋势变化在横向空间上展开得更自然。收盘价用较浅的颜色和细线因为它是背景信息MA60用最粗的线因为长期趋势线在图中承担着“定调”的作用——人的视线会自动被更粗、饱和度更高的线吸引这正好符合主次层级的设计意图。坐标轴格式那一行也值得解释一下mdates.DateFormatter(%Y-%m)将横轴刻度格式化为年月形式避免把日期时间也显示出来造成冗余。我会再用fig.autofmt_xdate()自动旋转日期标签这样即使数据跨度再长标签之间不会彼此挤压。3.3 完整代码汇总把上面的流程合并成一个可以独立运行的脚本大概是这样的结构import numpy as np import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False # 模拟数据 np.random.seed(2024) dates pd.bdate_range(2023-01-02, periods250) close 100 np.cumsum(np.random.randn(250) * 0.8) df pd.DataFrame({close: close.round(2)}, indexdates) # 均线 df[MA5] df[close].rolling(5, min_periods5).mean() df[MA20] df[close].rolling(20, min_periods20).mean() df[MA60] df[close].rolling(60, min_periods60).mean() # 绘图 fig, ax plt.subplots(figsize(12, 6)) ax.plot(df.index, df[close], labelClose, color#34495e, linewidth1.0, alpha0.75) ax.plot(df.index, df[MA5], labelMA5, color#e67e22, linewidth1.0, alpha0.9) ax.plot(df.index, df[MA20], labelMA20, color#2980b9, linewidth1.4, alpha0.9) ax.plot(df.index, df[MA60], labelMA60, color#c0392b, linewidth1.8, alpha0.9) ax.legend(locbest, frameonFalse, ncol4) ax.set_title(移动平均线MA可视化示例, fontsize14, pad12) ax.set_ylabel(价格) ax.grid(alpha0.3, linestyle--) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) fig.autofmt_xdate() fig.savefig(ma_demo.png, dpi150, bbox_inchestight) plt.show()这个脚本我在多个环境里跑过Python 3.8以上、pandas 1.x或2.x都能正常运行。如果你的数据源返回的是真实行情唯一需要注意的就是把df里的收盘价列命名为close或者把代码里的df[close]改成你实际用的列名。3.4 样式增强的几个小技巧基础图画出来后还能再花点心思提升可读性。第一个技巧是给最后一天的价格点加标注方便读者快速找到最新值的位置。用ax.annotate可以在图中某个位置添加文本和箭头ax.annotate( f{df[close].iloc[-1]:.2f}, xy(df.index[-1], df[close].iloc[-1]), xytext(10, 10), textcoordsoffset points, fontsize10, color#34495e )第二个技巧是设置价格坐标轴的显示范围。不要让matplotlib完全自动撑满整个窗口而是根据绘图区间设置适当的边距比如ax.set_ylim(df[close].min() * 0.98, df[close].max() * 1.02)第三个小细节是保存图片时dpi的选择。屏幕显示用100到150足够但如果图要放进报告或PPT建议至少150。bbox_inchestight可以自动裁掉四周多余白边出图之后不用再手动裁剪图片。4. 进阶多均线组合与信号可视化4.1 从均线到交易信号的映射图不能只停留在“好看”这个层面。在量化分析里画均线图的核心目的是把交易信号变得可见。最常见的一个信号是“金叉”与“死叉”短期均线从下往上穿越长期均线是金叉通常视为买入信号短期均线从上往下穿越长期均线是死叉通常视为卖出或减仓信号。用pandas计算这个信号非常方便这里有一个很巧妙的处理思路df[above] (df[MA5] df[MA20]).astype(int) df[cross] df[above].diff() golden df.index[df[cross] 1] dead df.index[df[cross] -1]逻辑其实很简单MA5在MA20上方时标记为1下方标记为0diff()就是相邻两天之间状态的变化。从0变到1说明短期均线升到长期均线上方就是金叉从1变到0则是死叉。这样把文字描述的信号转换成离散数值序列是策略实现里很常用的一套技巧。然后把信号点绘制到之前的均线图上ax.scatter(golden, df.loc[golden, close], marker^, s90, color#e74c3c, labelGolden Cross, zorder5) ax.scatter(dead, df.loc[dead, close], marker^, s90, color#2980b9, labelDeath Cross, zorder5)你可能注意到这里标记的位置是信号出现当天的收盘价不是均线交点本身。严格意义上的金叉应该发生在两个移动平均线相交的那个时间点和价位但实际数据是离散的一天一根K线信号只会出现在某一天的收盘后判断里所以用当天收盘价点位来标记是量化回测中比较常见的近似方式。如果真想画交叉点需要对两条线做插值计算那样会引入更多计算量而且对信号的判断逻辑没有本质改变画图里没有必要。4.2 多周期均线组合图刚才用的5、20、60是典型参数但在不同行情下有人会改用8、21、55或者更适配自己交易风格的组合。多周期组合图的画法和单组均线没有本质区别无非是增加几行ax.plot。我更建议的是在画多周期均线时统一用“暖色代表短周期、冷色代表长周期”的配色原则这样不管是自己复盘还是给团队讲解都能第一眼判断出哪条线属于哪个周期。要特别留意的是如果图里线比较多图例就很重要。ncol4可以让图例横排显示避免竖直排列占掉太多绘图区域。我还会设置frameonFalse去掉图例边框整体更清爽。对于更复杂的多子图场景比如上面放价格和均线、下面放成交量的组合图可以用gridspec_kw控制子图高度比例一般是3比1的比例比较协调。4.3 叠加K线图的一个可行方案有经验的读者可能会问光画收盘价线能不能直接画出带均线的K线图可以。matplotlib原生画K线比较麻烦推荐用专门封装的库比如mplfinance。它的设计就是围绕金融时间序列可视化配合移动平均线参数可以直接出图import mplfinance as mpf mpf.plot( df, typecandle, mav(5, 20, 60), volumeTrue, stylecharles )但这篇文章的主线是理解绘图原理所以我把重点放在matplotlib手工实现上。等你理解了坐标轴、数据列、标注这些基础概念再切换mplfinance就只差查阅参数文档这一步。而且遇到mplfinance覆盖不了的个性化需求你仍然能回到matplotlib做深度定制。4.4 均线斜率与趋势强度的可视化尝试再分享一个进阶的玩法把均线斜率画出来。斜率的正负和大小可以作为趋势强度的一个可视化指标。计算方法很简单用当前均线值除以前一个窗口周期的均线值再减1得到的百分比就是这一段时间的斜率。你可以把它放在图下方的子图里用柱状图表示df[ma20_slope] df[MA20].pct_change(5) * 100 fig, (ax1, ax2) plt.subplots( 2, 1, figsize(12, 8), gridspec_kw{height_ratios: [3, 1]}, sharexTrue ) ax1.plot(df.index, df[close], color#34495e, linewidth1.0, alpha0.75) ax1.plot(df.index, df[MA20], color#2980b9, linewidth1.4, labelMA20) ax1.legend() ax2.bar(df.index, df[ma20_slope], color#95a5a6, width1.0) ax2.axhline(0, color#333333, linewidth0.8) ax2.set_ylabel(MA20 Slope (%)) fig.autofmt_xdate() plt.show()这个图的优势在于它能让你直观看到“价格在涨但均线斜率已经开始走平甚至向下”的背离状态。这往往是趋势转弱的前兆但在普通的价格加均线图上不容易察觉。把它可视化成斜率柱状图后判断的敏感度会提升不少。5. 常见问题与排查技巧5.1 高频问题速查表绘图过程中踩坑是必然的我把这些年遇到最多的问题整理成了一张速查表方便你遇到现象时直接对照。现象根本原因解决方案均线图前面缺一段数据rolling窗口内样本不足产生NaN不做处理或设置min_periods1从起点开始输出图中中文字符显示为方块系统缺少绘图组件可用的中文字体通过plt.rcParams[font.sans-serif]指定系统已安装字体负号显示为方块默认ASCII负号渲染异常设置plt.rcParams[axes.unicode_minus] False横轴日期标签挤成一团刻度数量过多或标签方向未旋转fig.autofmt_xdate()或用mdates自定义刻度间隔加了金叉死叉点但图上看不到点位被价格/均线图层遮住调整zorder参数让散点保持最上层绘制保存的图片周围白边太多画布尺寸和内容区域不匹配保存时加bbox_inchestight参数均线最后一天“异常拐头”这是正常的rolling响应反映最近几天价格变化属于正常现象但查看最后一段趋势时需结合原始价格判断5.2 关于NaN与断线的深入说明第一个问题值得多写几句。因为默认min_periodswindow所以MA60在前59个交易日均没有数值MA20前19天没有MA5前4天没有。用matplotlib绘制时NaN部分会被跳过表现为线从出现的那天开始。很多新手第一次画图会觉得“这条线怎么比其他线短”其实就是这个原因不是代码写错了。但如果你的数据里本身就存在缺失交易日比如停牌或节假日数据没补全均线计算结果就会受影响。这时候建议先用df.interpolate()或者df.ffill()处理缺失值再做滚动计算。我的习惯是绘图中尽量保证三个数据级别一致价格数据、均线数据、信号标记数据都基于同一套索引否则后续做信号验证时容易对不齐日期。5.3 排查案例为什么我的金叉点总是画在奇怪的位置有一次我给某个项目排查发现他画的金叉点全部出现在均线图的下方位置明显不对。后来检查发现他在计算信号时用的是调整后的列名但绘制散点时却使用了另一个没有做对齐的DataFrame切片。因为索引没有对齐散点画到了别的日期位置上。这类问题在数据量一大之后极其隐蔽肉眼不容易发现。解决办法也很简单所有中间计算结果都保留在同一个df对象里不要中途拆成多个独立Series后在画图时才去对齐。如果必须用独立切片画图前先执行pd.DataFrame.align或者用reindex保证索引一致。养成这个习惯之后你会在各种pandas操作中少踩很多坑。5.4 图形布局与输出规范最后再叮嘱一点输出规范。绘图脚本最好和数据处理脚本分开画图脚本只负责读入整理好的数据、计算出图。如果你把数据处理和绘图全塞在同一个脚本里随着策略复杂度上升调试成本会越来越高。我常用的目录结构是这样的project/ ├── data/ # 原始行情数据 ├── figure/ # 输出图片 ├── strategy/ # 策略代码 └── chart/ # 绘图代码这样的好处是复盘时只需要重新运行画图脚本不需要再去跑一遍数据清洗流程。量化分析最忌讳的就是每次看图之前都要临时改脚本参数这既浪费时间也容易引入新的错误。把绘图固定成一个输入数据文件、输出图片文件的独立模块后你的研究效率会提升很多。写在最后一个关于绘图的真实体会画均线图这件事技术难度其实不高但它背后体现的是你对数据的理解深度。刚开始做量化的时候我也觉得把线画出来就算完事了。后来复盘多了才发现真正有用的图不是“信息最多”的图而是“决策路径最清晰”的图。每一条线、每一个标记落在图上你都要想清楚它用于回答哪个问题——是当前趋势方向还是进出场时机还是风险警示。还有一个习惯我很建议培养每次画完图随手把这张图对应的行情区间和参数记录在脚本注释里。三个月后再回来看同一张均线图时你会发现自己当时对趋势的判断和现在的理解可能完全不同这种对比很有价值。移动平均线本身不会变但看图的人会随着经验增长看到更深的东西。现在就打开终端跑一遍上面的代码吧你亲手画出的第一条均线就是你量化可视化之路的开始。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑