跳转至

第 13 章:策略可视化与报表分析

数据可视化 (Data Visualization) 不仅是展示结果的手段,更是探索性数据分析 (Exploratory Data Analysis, EDA) 的核心工具。通过高质量的图表,我们可以直观地识别策略的风险特征、收益来源以及潜在的过拟合迹象。本章将介绍如何使用 akquant 及第三方工具生成专业的量化回测报告。

13.1 可视化原则与核心图表

13.1.1 权益曲线 (Equity Curve)

最基础的图表,展示账户总资产随时间的变化。

  • 线性坐标 (Linear Scale):适合短期回测。
  • 对数坐标 (Logarithmic Scale):适合长期回测。在对数坐标下,直线的斜率代表复利增长率,且能清晰展示早期的波动(避免被后期的指数增长掩盖)。

13.1.2 水下曲线 (Underwater Plot)

专门用于展示回撤 (Drawdown) 的深度和持续时间。

  • Y轴:当前净值距离历史最高点的百分比跌幅(0% ~ -100%)。
  • 阴影面积:反映了投资者承受痛苦的“时间和空间”。
  • 分析:重点关注回撤修复期 (Recovery Period)。如果修复期过长(如超过 1 年),说明策略可能已经失效。

13.1.3 收益分布图 (Return Distribution)

展示日收益率的直方图 (Histogram) 和核密度估计 (KDE)。

  • 尖峰肥尾 (Fat Tails):金融数据的典型特征。关注分布的左尾 (Left Tail),那是“黑天鹅”藏身之处。
  • 偏度 (Skewness)
    • 正偏 (Positive Skew):小亏大赚(趋势策略)。
    • 负偏 (Negative Skew):小赚大亏(套利策略/卖期权)。

13.2 高级分析图表

13.2.1 月度热力图 (Monthly Heatmap)

将收益率按年份和月份排列成矩阵,用颜色深浅表示收益高低。

  • 用途:识别季节性 (Seasonality)策略衰退
  • 特征:如果某一年份全是绿色(亏损),可能意味着市场风格发生了根本性转变。

13.2.2 滚动指标 (Rolling Metrics)

静态指标(如全周期夏普)可能掩盖局部的剧烈波动。

  • 滚动波动率:观察市场恐慌时策略的风险暴露。
  • 滚动夏普:观察策略表现的稳定性。

13.3 AKQuant 内置绘图工具

akquant 提供了简洁的 API,基于 matplotlib 生成核心图表。

13.3.1 基础绘图

import akquant as aq
import matplotlib.pyplot as plt

# 运行回测
result = aq.run_backtest(...)

# 绘制权益曲线和回撤图
aq.plot_result(result, title="Strategy Performance")
plt.show()

13.3.2 交互式图表 (Plotly)

在 Jupyter Notebook 环境中,推荐使用交互式图表,可以缩放、拖拽,查看具体某天的持仓。

# 生成 HTML 交互式报告
result.plot(engine="plotly", filename="backtest.html")

13.4 第三方工具集成:QuantStats

akquant 完美支持 QuantStats,这是一个强大的 Python 量化分析库,能生成媲美专业基金的 Tearsheet。

13.4.1 安装与使用

pip install quantstats

13.4.2 生成综合报告

import quantstats as qs

# 1. 获取收益率序列 (pd.Series)
returns = result.metrics_df.loc['daily_returns']
# 注意:akquant result 对象通常需要转换,这里假设有一个辅助方法
# 或者直接从 equity curve 计算:
returns = result.equity_curve.pct_change().dropna()

# 2. 生成 HTML 报告
qs.reports.html(
    returns,
    benchmark="000300.SH", # 对比沪深300
    output='stats.html',
    title='Alpha Strategy Report'
)

13.5 完整示例代码

下面的代码演示了如何运行策略,并分别使用 akquant 内置工具和 QuantStats 生成可视化报告。

"""
第 10 章:可视化与报告 (Visualization).

本示例展示了如何将回测结果可视化,生成包含权益曲线、回撤图和日收益分布的综合图表。
AKQuant 内置了基于 `matplotlib` 的绘图工具,可以一键生成专业级报表。

依赖:
需要安装 matplotlib: `pip install matplotlib`

演示内容:
1. 运行一个简单的策略。
2. 使用 `aq.plot_result` 生成可视化图表。
3. 保存图表为图片文件。
"""

import akquant as aq
import numpy as np
import pandas as pd
from akquant import Bar, Strategy


# 模拟数据生成
def generate_mock_data(length: int = 500) -> pd.DataFrame:
    """生成模拟数据."""
    np.random.seed(42)
    dates = pd.date_range(start="2022-01-01", periods=length, freq="D")

    # 构造一个有趋势的数据,让曲线好看一些
    trend = np.linspace(100, 150, length)
    noise = np.cumsum(np.random.randn(length))
    prices = trend + noise

    df = pd.DataFrame(
        {
            "date": dates,
            "open": prices,
            "high": prices + 1,
            "low": prices - 1,
            "close": prices,
            "volume": 100000,
            "symbol": "MOCK_PLOT",
        }
    )
    return df


class PlotStrategy(Strategy):
    """可视化演示策略."""

    def __init__(self) -> None:
        """初始化策略."""
        super().__init__()
        self.ma_window = 20
        self.warmup_period = 20

    def on_bar(self, bar: Bar) -> None:
        """收到 Bar 事件的回调."""
        symbol = bar.symbol
        closes = self.get_history(
            count=self.ma_window + 1, symbol=symbol, field="close"
        )
        if len(closes) < self.ma_window + 1:
            return

        ma = closes[:-1][-self.ma_window :].mean()
        pos = self.get_position(symbol)

        # 简单的均线突破
        if bar.close > ma and pos == 0:
            self.order_target_percent(0.95, symbol)
        elif bar.close < ma and pos > 0:
            self.close_position(symbol)


if __name__ == "__main__":
    df = generate_mock_data()

    print("开始运行第 11 章可视化示例...")
    result = aq.run_backtest(
        strategy=PlotStrategy, data=df, initial_cash=100_000, commission_rate=0.0003
    )

    print("回测完成,正在生成图表...")

    try:
        # 使用 akquant 内置的绘图函数
        # filename: 如果指定,将保存为文件而不是直接弹窗显示
        aq.plot_result(
            result,
            filename="backtest_report.png",
            title="Moving Average Strategy Performance",
        )
        print("图表已保存至: backtest_report.png")

        # 如果在 Jupyter Notebook 中,可以直接显示
        # result.plot()

    except ImportError:
        print("绘图失败: 请确保安装了 matplotlib (pip install matplotlib)")
    except Exception as e:
        print(f"绘图过程中发生错误: {e}")

13.6 专业 K 线图绘制 (Professional Candlestick Charts)

虽然折线图能展示大致趋势,但量化交易员更习惯看 K 线图 (Candlestick)。Python 中最专业的库是 mplfinance

13.6.1 基础 K 线与成交量

import mplfinance as mpf

# 准备数据 (必须包含 Open, High, Low, Close, Volume 列)
df.index.name = 'Date'

# 绘制蜡烛图 + 成交量
mpf.plot(df, type='candle', volume=True, style='yahoo')

13.6.2 叠加买卖点信号

在 K 线图上标注买入 (Buy) 和卖出 (Sell) 信号,直观复盘交易逻辑。

# 生成买卖点标记
buys = [price if sig == 1 else np.nan for price, sig in zip(df['low'], df['buy_signal'])]
sells = [price if sig == -1 else np.nan for price, sig in zip(df['high'], df['sell_signal'])]

# 添加到副图
apds = [
    mpf.make_addplot(buys, type='scatter', markersize=100, marker='^', color='r'),
    mpf.make_addplot(sells, type='scatter', markersize=100, marker='v', color='g')
]

mpf.plot(df, addplot=apds)

13.7 3D 波动率曲面 (3D Volatility Surface)

对于期权交易员,仅仅看 IV 曲线是不够的,我们需要看到整个曲面(Strike x Expiry)。

from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(111, projection='3d')

# X: 行权价, Y: 到期时间, Z: 隐含波动率
ax.plot_surface(X, Y, Z, cmap='viridis')

ax.set_xlabel('Strike Price')
ax.set_ylabel('Time to Expiry')
ax.set_zlabel('Implied Volatility')
观察要点

  • Smile/Skew:沿 Strike 轴的弯曲程度。
  • Term Structure:沿 Time 轴的倾斜程度。

13.8 交易分析图表 (Trade Analysis)

13.8.1 MAE/MFE 散点图

最大不利偏离 (MAE) 与最大有利偏离 (MFE) 的散点图是优化止盈止损的神器。

  • X轴:MAE(最大浮亏)。
  • Y轴:最终盈亏 (PnL)。
  • 分析
    • 如果大量盈利交易的 MAE 都很小(如 < 1%),说明入场点非常精准。
    • 如果亏损交易的 MAE 很大,说明止损设置过宽,或者执行拖沓。
    • 黄金法则:截断亏损 (Cut Loss),让利润奔跑 (Let Profit Run)。这就意味着在图上,左下角的点(止损单)应该密集且受控,右上角的点(盈利单)应该发散且无上限。

13.6 专业 K 线图绘制 (Professional Candlestick Charts)

虽然折线图能展示大致趋势,但量化交易员更习惯看 K 线图 (Candlestick)。Python 中最专业的库是 mplfinance

13.6.1 基础 K 线与成交量

import mplfinance as mpf

# 准备数据 (必须包含 Open, High, Low, Close, Volume 列)
df.index.name = 'Date'

# 绘制蜡烛图 + 成交量
mpf.plot(df, type='candle', volume=True, style='yahoo')

13.6.2 叠加买卖点信号

在 K 线图上标注买入 (Buy) 和卖出 (Sell) 信号,直观复盘交易逻辑。

# 生成买卖点标记
buys = [price if sig == 1 else np.nan for price, sig in zip(df['low'], df['buy_signal'])]
sells = [price if sig == -1 else np.nan for price, sig in zip(df['high'], df['sell_signal'])]

# 添加到副图
apds = [
    mpf.make_addplot(buys, type='scatter', markersize=100, marker='^', color='r'),
    mpf.make_addplot(sells, type='scatter', markersize=100, marker='v', color='g')
]

mpf.plot(df, addplot=apds)

13.7 3D 波动率曲面 (3D Volatility Surface)

对于期权交易员,仅仅看 IV 曲线是不够的,我们需要看到整个曲面(Strike x Expiry)。

from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(111, projection='3d')

# X: 行权价, Y: 到期时间, Z: 隐含波动率
ax.plot_surface(X, Y, Z, cmap='viridis')

ax.set_xlabel('Strike Price')
ax.set_ylabel('Time to Expiry')
ax.set_zlabel('Implied Volatility')
观察要点

  • Smile/Skew:沿 Strike 轴的弯曲程度。
  • Term Structure:沿 Time 轴的倾斜程度。

13.8 交易分析图表 (Trade Analysis)

13.8.1 MAE/MFE 散点图

最大不利偏离 (MAE) 与最大有利偏离 (MFE) 的散点图是优化止盈止损的神器。

  • X轴:MAE(最大浮亏)。
  • Y轴:最终盈亏 (PnL)。
  • 分析
    • 如果大量盈利交易的 MAE 都很小(如 < 1%),说明入场点非常精准。
    • 如果亏损交易的 MAE 很大,说明止损设置过宽,或者执行拖沓。
    • 黄金法则:截断亏损 (Cut Loss),让利润奔跑 (Let Profit Run)。这就意味着在图上,左下角的点(止损单)应该密集且受控,右上角的点(盈利单)应该发散且无上限。

本章小结: 一张好的图表胜过千言万语。通过多维度的可视化分析,我们不仅能向投资者展示策略的收益能力,更能诚实地剖析策略的风险特征。记住:不要试图用图表掩盖风险,而要用图表揭示风险