
【导语】上一篇我们用均线策略回测了宁德时代的收益,虽然算出了15%的收益率,但一堆数字总让人觉得不够直观——买卖点是不是踩在关键位置?收益曲线有没有一路向上?今天就教大家用matplotlib这个“画图神器”,把“股价+均线+买卖信号+收益曲线”整合到一张图上,策略效果好坏,一眼就能看清楚。
一、为什么必定要做策略可视化?
许多新手只盯着“总收益率”这个数字,却忽略了可视化的重大性,这实则是个大误区。可视化的核心价值在于“让问题显性化”,主要有三个作用:
- 直观验证信号有效性:金叉是不是出目前股价启动前?死叉有没有避开大跌?画图后信号的“及时性”和“准确性”一目了然,避免被虚假收益数字误导。
- 快速定位策略漏洞:如果收益曲线突然大幅下跌,结合股价图能立刻看出是“信号延迟”还是“假突破”导致的,比对着表格找问题效率高10倍。
- 便于优化和分享:画好的图表可以直接用来分析策略,无论是自己复盘还是分享给同行,都比纯文字更有说服力。
小提醒:可视化不是“花架子”,而是量化交易的“刚需工具”。专业的量化团队,回测报告里图表占比能达到60%以上,新手从一开始就要养成画图的习惯。
二、10分钟准备:可视化环境搭建
我们用Python的matplotlib库画图,搭配pandas处理数据,环境搭建超简单,和上一篇的工具无缝衔接。
1. 安装matplotlib库
打开命令提示符(Windows)或终端(Mac),粘贴下面的命令,按回车等待安装完成:
pip install matplotlib # 安装画图库
pip install matplotlib-inline # Jupyter环境专用,PyCharm可忽略
2. 解决中文显示问题
matplotlib默认不支持中文,直接画图会出现乱码,需要在代码里添加几行配置。不用死记,每次画图前复制进去就行,后面会给出完整示例。
三、核心实操:4类关键图表,代码直接抄
我们基于上一篇的均线策略数据,制作4类最实用的图表:① 股价+均线+买卖点图;② 收益曲线走势图;③ 最大回撤分析图;④ 交易信号分布直方图。每类图表都带完整代码,新手复制后修改路径就能用。
先补充:数据预处理(衔接上一篇策略)
第一需要把上一篇的回测数据整理好,这里直接给出“数据获取+策略回测+数据整理”的完整前置代码,确保画图数据连贯:
import akshare as ak
import pandas as pd
import numpy as np
# 1. 获取宁德时代2024年日K数据
stock_df = ak.stock_zh_a_daily(
symbol="300750", start_date="2024-01-01", end_date="2024-10-31", adjust="qfq"
)
stock_df = stock_df.reset_index()
stock_df.columns = ["日期", "开盘价", "最高价", "最低价", "收盘价", "成交量", "成交额"]
# 2. 计算均线和交易信号(和上一篇完全一致)
stock_df["5日均线"] = stock_df["收盘价"].rolling(window=5).mean()
stock_df["20日均线"] = stock_df["收盘价"].rolling(window=20).mean()
stock_df["金叉信号"] = (stock_df["5日均线"].shift(1) < stock_df["20日均线"].shift(1)) & (stock_df["5日均线"] > stock_df["20日均线"])
stock_df["死叉信号"] = (stock_df["5日均线"].shift(1) > stock_df["20日均线"].shift(1)) & (stock_df["5日均线"] < stock_df["20日均线"])
stock_df["交易信号"] = 0
stock_df.loc[stock_df["金叉信号"], "交易信号"] = 1
stock_df.loc[stock_df["死叉信号"], "交易信号"] = -1
# 3. 执行回测并计算资产净值(新增:计算每日资产净值,用于画收益曲线)
cash = 100000
hold_shares = 0
stock_df["资产净值"] = 0.0 # 每日资产总价值(现金+持仓市值)
for i in range(len(stock_df)):
close_price = stock_df.loc[i, "收盘价"]
signal = stock_df.loc[i, "交易信号"]
if signal == 1 and hold_shares == 0:
hold_shares = cash // close_price
cash -= hold_shares * close_price
elif signal == -1 and hold_shares > 0:
cash += hold_shares * close_price
hold_shares = 0
# 计算每日资产净值
stock_df.loc[i, "资产净值"] = cash + hold_shares * close_price
# 4. 整理买卖点数据(方便画图时标记)
buy_signals = stock_df[stock_df["交易信号"] == 1][["日期", "收盘价"]] # 买入点
sell_signals = stock_df[stock_df["交易信号"] == -1][["日期", "收盘价"]] # 卖出点
图表1:股价+均线+买卖点(核心图表)
这是最基础也最实用的图表,能清晰看到股价在均线上下的波动,以及买卖信号是否精准。买入点用红色向上箭头标记,卖出点用绿色向下箭头标记。
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
# 解决中文显示问题(固定配置,直接复制)
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans'] # 中文支持
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 1. 创建画布和子图(figsize控制图片大小,单位是英寸)
fig, ax = plt.subplots(figsize=(12, 6))
# 2. 转换日期格式(matplotlib需要特定格式的日期)
stock_df["日期"] = pd.to_datetime(stock_df["日期"])
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) # 日期显示格式
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=2)) # 每2个月显示一个日期
# 3. 绘制股价和均线
ax.plot(stock_df["日期"], stock_df["收盘价"], label="宁德时代收盘价", color="#1f77b4", linewidth=1.5)
ax.plot(stock_df["日期"], stock_df["5日均线"], label="5日均线", color="#ff7f0e", linewidth=1.2)
ax.plot(stock_df["日期"], stock_df["20日均线"], label="20日均线", color="#2ca02c", linewidth=1.2)
# 4. 标记买卖点(买入=红色上箭头,卖出=绿色下箭头)
ax.scatter(buy_signals["日期"], buy_signals["收盘价"],
color="red", marker="^", s=100, label="买入信号", zorder=5) # zorder确保箭头在最上层
ax.scatter(sell_signals["日期"], sell_signals["收盘价"],
color="green", marker="v", s=100, label="卖出信号", zorder=5)
# 5. 图表美化
ax.set_title("宁德时代5日-20日均线策略买卖点示意图(2024年)", fontsize=14, pad=20)
ax.set_xlabel("日期", fontsize=12)
ax.set_ylabel("价格(元)", fontsize=12)
ax.legend(loc="upper left") # 图例位置
ax.grid(True, alpha=0.3) # 网格线,alpha控制透明度
# 6. 旋转x轴日期标签,避免重叠
plt.xticks(rotation=45)
# 7. 保存图片(保存到桌面,dpi控制清晰度)
plt.tight_layout() # 自动调整布局,避免标签被截断
plt.savefig("C:/Users/Admin/Desktop/买卖点示意图.png", dpi=300)
plt.close() # 关闭画布,释放内存
print("买卖点示意图已保存到桌面!")
运行后会在桌面生成一张高清图片,能清晰看到:如果某笔买入信号出目前股价跌破均线后,大致率是假信号;如果卖出信号刚好在股价大跌前,说明策略有效。
图表2:收益曲线走势图
收益曲线能直观展示“资产净值”的变化过程,比单纯的“总收益率”更有参考价值——同样是15%的收益,“稳步上涨”和“暴涨暴跌”的风险完全不同。
import matplotlib.pyplot as plt
# 1. 计算基准收益(对比沪深300,看策略是否跑赢大盘)
# 获取沪深300指数数据
index_df = ak.stock_zh_index_daily(symbol="000300", start_date="2024-01-01", end_date="2024-10-31")
index_df = index_df.reset_index()
index_df.columns = ["日期", "开盘价", "最高价", "最低价", "收盘价", "成交量", "成交额"]
index_df["日期"] = pd.to_datetime(index_df["日期"])
# 计算指数收益率(以初始值为100000)
index_df["基准净值"] = 100000 * (index_df["收盘价"] / index_df["收盘价"].iloc[0])
# 2. 合并策略收益和基准收益数据(按日期对齐)
profit_df = pd.merge(stock_df[["日期", "资产净值"]], index_df[["日期", "基准净值"]], on="日期", how="inner")
# 3. 绘制收益曲线
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制策略收益和基准收益
ax.plot(profit_df["日期"], profit_df["资产净值"], label="均线策略资产净值", color="#ff4500", linewidth=2)
ax.plot(profit_df["日期"], profit_df["基准净值"], label="沪深300基准净值", color="#4169e1", linewidth=2, linestyle="--")
# 标记初始本金线
ax.axhline(y=100000, color="gray", linestyle=":", alpha=0.8, label="初始本金(10万元)")
# 图表美化
ax.set_title("均线策略 vs 沪深300收益曲线对比(2024年)", fontsize=14, pad=20)
ax.set_xlabel("日期", fontsize=12)
ax.set_ylabel("资产净值(元)", fontsize=12)
ax.legend(loc="upper right")
ax.grid(True, alpha=0.3)
# 旋转x轴日期
plt.xticks(rotation=45)
# 保存图片
plt.tight_layout()
plt.savefig("C:/Users/Admin/Desktop/收益曲线对比.png", dpi=300)
plt.close()
# 计算超额收益
excess_return = (profit_df["资产净值"].iloc[-1] - profit_df["基准净值"].iloc[-1]) / profit_df["基准净值"].iloc[-1] * 100
print(f"策略超额收益率:{excess_return:.2f}%")
print("收益曲线对比图已保存到桌面!")
通过这张图,能立刻判断策略是否跑赢大盘:如果策略曲线始终在基准曲线上方,说明策略有超额收益;如果长期低于基准,就要思考优化策略或换标的。
图表3:最大回撤分析图
最大回撤是衡量策略风险的核心指标,画图能清晰看到回撤发生的时间段和恢复情况——同样是10%的回撤,“1个月恢复”比“6个月恢复”的策略更好。
import matplotlib.pyplot as plt
# 1. 计算最大回撤(专业版:每日回撤率)
# 先计算每日的峰值资产(从开始到当前的最大值)
stock_df["峰值资产"] = stock_df["资产净值"].cummax()
# 计算每日回撤率((峰值资产-当日资产)/峰值资产)
stock_df["回撤率"] = (stock_df["峰值资产"] - stock_df["资产净值"]) / stock_df["峰值资产"] * 100
# 2. 找到最大回撤的区间(开始和结束日期)
max_drawdown = stock_df["回撤率"].max()
max_drawdown_start = stock_df[stock_df["回撤率"] == max_drawdown]["日期"].iloc[0]
# 回撤结束日期:最大回撤后,资产净值回到之前峰值的日期
peak_before = stock_df[stock_df["日期"] < max_drawdown_start]["峰值资产"].max()
max_drawdown_end = stock_df[(stock_df["日期"] > max_drawdown_start) & (stock_df["资产净值"] >= peak_before)]["日期"].iloc[0]
# 3. 绘制回撤图
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制回撤率曲线
ax.plot(stock_df["日期"], stock_df["回撤率"], color="#dc143c", linewidth=1.5, label="每日回撤率")
# 标记最大回撤区间(用阴影填充)
ax.axvspan(max_drawdown_start, max_drawdown_end, color="orange", alpha=0.2, label=f"最大回撤区间({max_drawdown:.2f}%)")
# 标记最大回撤点
max_drawdown_point = stock_df[stock_df["回撤率"] == max_drawdown]
ax.scatter(max_drawdown_point["日期"], max_drawdown_point["回撤率"], color="red", s=80, zorder=5)
# 图表美化
ax.set_title("均线策略最大回撤分析(2024年)", fontsize=14, pad=20)
ax.set_xlabel("日期", fontsize=12)
ax.set_ylabel("回撤率(%)", fontsize=12)
ax.legend(loc="upper right")
ax.grid(True, alpha=0.3)
ax.set_ylim(bottom=0) # 回撤率最低为0,不显示负数
# 旋转x轴日期
plt.xticks(rotation=45)
# 保存图片
plt.tight_layout()
plt.savefig("C:/Users/Admin/Desktop/最大回撤分析.png", dpi=300)
plt.close()
print(f"最大回撤率:{max_drawdown:.2f}%")
print(f"最大回撤发生时间:{max_drawdown_start.strftime('%Y-%m-%d')}")
print(f"最大回撤恢复时间:{max_drawdown_end.strftime('%Y-%m-%d')}")
print("最大回撤分析图已保存到桌面!")
图表4:交易信号分布直方图
这个图表能看出交易信号的时间分布,避免策略“过度交易”——如果某几个月交易次数特别多,可能是那段时间市场波动大,产生了大量假信号。
import matplotlib.pyplot as plt
# 1. 整理交易信号的月份分布
stock_df["月份"] = stock_df["日期"].dt.to_period("M") # 按月份分组
# 统计每月的买入和卖出次数
signal_count = stock_df.groupby("月份")["交易信号"].value_counts().unstack(fill_value=0)
# 重命名列(如果没有对应信号,列可能不存在,需要判断)
if 1 in signal_count.columns:
signal_count["买入次数"] = signal_count[1]
else:
signal_count["买入次数"] = 0
if -1 in signal_count.columns:
signal_count["卖出次数"] = signal_count[-1]
else:
signal_count["卖出次数"] = 0
# 转换月份格式为字符串(便于显示)
signal_count.index = signal_count.index.astype(str)
# 2. 绘制直方图
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制分组柱状图
x = range(len(signal_count))
width = 0.35 # 柱子宽度
ax.bar([i - width/2 for i in x], signal_count["买入次数"], width, label="买入次数", color="#ff6b6b")
ax.bar([i + width/2 for i in x], signal_count["卖出次数"], width, label="卖出次数", color="#4ecdc4")
# 图表美化
ax.set_title("均线策略交易信号月度分布(2024年)", fontsize=14, pad=20)
ax.set_xlabel("月份", fontsize=12)
ax.set_ylabel("交易次数", fontsize=12)
ax.set_xticks(x)
ax.set_xticklabels(signal_count.index, rotation=45)
ax.legend()
ax.grid(True, alpha=0.3, axis="y")
# 保存图片
plt.tight_layout()
plt.savefig("C:/Users/Admin/Desktop/交易信号分布.png", dpi=300)
plt.close()
# 计算平均每月交易次数
avg_monthly_trade = (signal_count["买入次数"].sum() + signal_count["卖出次数"].sum()) / len(signal_count)
print(f"平均每月交易次数:{avg_monthly_trade:.1f}次")
print("交易信号分布图已保存到桌面!")
四、可视化后的策略优化:从图表中找问题
画图不是目的,而是为了优化策略。结合上面4张图,新手可以快速定位问题并调整,举3个常见场景:
- 场景1:买入信号总在股价冲高后出现——从买卖点图发现,金叉信号出目前股价连续上涨后,说明短期均线反应太快,可把5日均线换成8日均线,过滤追高信号。
- 场景2:收益曲线长期低于大盘——从收益对比图发现,策略在大盘上涨时跟不上,可增加“沪深300指数站上60日均线”作为附加条件,只在大盘趋势向上时交易。
- 场景3:某段时间回撤大且恢复慢——从回撤分析图发现,回撤发生在行业利空期间,可增加“行业指数(如新能源指数)均线过滤”,避免在行业下跌时交易。
五、下一篇预告:策略自动化,让代码帮你盯盘
学会了回测和可视化,下一步就是“自动化盯盘”——下一篇我们教大家用Python写一个实时盯盘脚本,当股票出现金叉/死叉信号时,自动发送邮件或微信提醒,不用再24小时盯盘,轻松抓住交易机会。
【互动】你画出来的策略收益曲线跑赢大盘了吗?评论区晒出你的可视化图表截图,抽3人送《Python量化可视化大全》,包含10+类专业图表代码!






老师,怎么没了量化入门第四篇?
量化入门第4篇:策略自动化,代码帮你7×24小时盯盘 https://www.toutiao.com/article/7580194259555451402/量化入门第5篇:从回测到实盘,策略落地的最后1公里 https://www.toutiao.com/article/7580638237367091739/