04-绩效评估指标

预计学习时间:1.5 小时

难度:⭐⭐⭐

核心问题:如何全面评估一个策略的表现?哪些指标最重要?


指标分类体系

┌────────────────────────────────────────────────────────────────┐
│                     绩效评估指标体系                            │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│                       绩效评估                                  │
│                          │                                     │
│      ┌───────────────────┼───────────────────┐                │
│      │                   │                   │                │
│  收益类指标          风险类指标        风险调整收益          │
│      │                   │                   │                │
│  · 累计收益          · 波动率            · Sharpe            │
│  · 年化收益          · 最大回撤          · IR                │
│  · 超额收益          · VaR/CVaR         · Sortino           │
│                      · 下行风险          · Calmar           │
│                                                                │
│      ┌───────────────────┴───────────────────┐                │
│      │                                       │                │
│  交易类指标                              收益分布             │
│      │                                       │                │
│  · 换手率                                 · 偏度/峰度         │
│  · 胜率                                   · 月度/年度分析     │
│  · 盈亏比                                                      │
│                                                                │
└────────────────────────────────────────────────────────────────┘

收益类指标

1. 累计收益率

定义:从开始到结束的总收益。

def total_return(returns):
    """
    计算累计收益率
 
    参数
    ----
    returns : Series
      日收益率序列
 
    返回
    ----
    total_ret : float
      累计收益率
    """
    return (1 + returns).prod() - 1
 
# 示例
returns = pd.Series([0.01, -0.005, 0.02, 0.015, -0.01])
print(f"累计收益率: {total_return(returns):.2%}")

2. 年化收益率

定义:将累计收益率转换为年度收益率。

其中 是交易天数。

def annual_return(returns, periods_per_year=252):
    """
    计算年化收益率
 
    参数
    ----
    returns : Series
      收益率序列
    periods_per_year : int
      每年交易周期(日频252,周频52,月频12)
 
    返回
    ----
    ann_ret : float
      年化收益率
    """
    n = len(returns)
    total_ret = total_return(returns)
    return (1 + total_ret) ** (periods_per_year / n) - 1
 
# 示例
dates = pd.date_range('2020-01-01', '2022-12-31', freq='D')
returns = pd.Series(np.random.randn(len(dates)) * 0.01, index=dates)
print(f"年化收益率: {annual_return(returns):.2%}")

3. 超额收益率

定义:策略收益超过基准收益的部分。

def excess_return(strategy_returns, benchmark_returns):
    """
    计算超额收益率
 
    参数
    ----
    strategy_returns : Series
      策略收益率
    benchmark_returns : Series
      基准收益率
 
    返回
    ----
    excess_returns : Series
      超额收益率序列
    """
    # 对齐日期
    aligned = pd.concat([strategy_returns, benchmark_returns], axis=1).dropna()
    return aligned.iloc[:, 0] - aligned.iloc[:, 1]
 
# 示例
strategy_ret = pd.Series([0.01, 0.02, -0.01, 0.015, 0.005])
benchmark_ret = pd.Series([0.008, 0.015, -0.005, 0.01, 0.003])
excess = excess_return(strategy_ret, benchmark_ret)
print(f"累计超额收益: {excess.sum():.2%}")

风险类指标

1. 波动率 (Volatility)

定义:收益率的标准差,衡量收益的不确定性。

年化波动率

def volatility(returns, periods_per_year=252):
    """
    计算年化波动率
 
    参数
    ----
    returns : Series
      收益率序列
    periods_per_year : int
      每年交易周期
 
    返回
    ----
    vol : float
      年化波动率
    """
    return returns.std() * np.sqrt(periods_per_year)
 
# 示例
returns = pd.Series(np.random.randn(252) * 0.01)
print(f"年化波动率: {volatility(returns):.2%}")

2. 最大回撤 (Maximum Drawdown)

定义:从历史最高点到最低点的最大跌幅。

def max_drawdown(returns):
    """
    计算最大回撤
 
    参数
    ----
    returns : Series
      收益率序列
 
    返回
    ----
    max_dd : float
      最大回撤
    dd_duration : int
      最大回撤持续天数
    """
    # 计算累计净值
    cumulative = (1 + returns).cumprod()
    running_max = cumulative.expanding().max()
    drawdown = (cumulative - running_max) / running_max
 
    max_dd = drawdown.min()
 
    # 计算最大回撤持续期
    max_dd_idx = drawdown.idxmin()
    max_dd_start = running_max[:max_dd_idx].idxmax()
    dd_duration = (max_dd_idx - max_dd_start).days
 
    return max_dd, dd_duration
 
# 示例
dates = pd.date_range('2020-01-01', '2022-12-31')
returns = pd.Series(np.random.randn(len(dates)) * 0.01, index=dates)
max_dd, duration = max_drawdown(returns)
print(f"最大回撤: {max_dd:.2%}")
print(f"持续天数: {duration}天")

3. VaR (Value at Risk)

定义:在给定置信水平下,可能遭受的最大损失。

历史模拟法

参数法(正态假设)

def value_at_risk(returns, confidence=0.95, method='historical'):
    """
    计算VaR
 
    参数
    ----
    returns : Series
      收益率序列
    confidence : float
      置信水平
    method : str
      'historical' - 历史模拟法
      'parametric' - 参数法
 
    返回
    ----
    var : float
      VaR值(负数表示损失)
    """
    alpha = 1 - confidence
 
    if method == 'historical':
        return returns.quantile(alpha)
    elif method == 'parametric':
        mu = returns.mean()
        sigma = returns.std()
        from scipy.stats import norm
        z = norm.ppf(alpha)
        return mu + z * sigma
    else:
        raise ValueError(f"Unknown method: {method}")
 
# 示例
returns = pd.Series(np.random.randn(1000) * 0.01)
var_95 = value_at_risk(returns, confidence=0.95)
var_99 = value_at_risk(returns, confidence=0.99)
print(f"95% VaR: {var_95:.2%}")
print(f"99% VaR: {var_99:.2%}")

4. CVaR (Conditional VaR / Expected Shortfall)

定义:超过VaR的平均损失,也称为预期亏损。

def conditional_var(returns, confidence=0.95):
    """
    计算CVaR(条件VaR)
 
    参数
    ----
    returns : Series
      收益率序列
    confidence : float
      置信水平
 
    返回
    ----
    cvar : float
      CVaR值
    """
    var = value_at_risk(returns, confidence)
    return returns[returns <= var].mean()
 
# 示例
cvar_95 = conditional_var(returns, confidence=0.95)
print(f"95% CVaR: {cvar_95:.2%}")

风险调整收益指标

1. Sharpe Ratio (夏普比率)

定义:承担单位风险获得的超额收益。

def sharpe_ratio(returns, risk_free_rate=0.03, periods_per_year=252):
    """
    计算Sharpe比率
 
    参数
    ----
    returns : Series
      收益率序列
    risk_free_rate : float
      年化无风险利率
    periods_per_year : int
      每年交易周期
 
    返回
    ----
    sharpe : float
      年化Sharpe比率
    """
    # 转换为日频无风险利率
    daily_rf = (1 + risk_free_rate) ** (1 / periods_per_year) - 1
 
    # 计算超额收益
    excess_returns = returns - daily_rf
 
    # 年化
    ann_return = excess_returns.mean() * periods_per_year
    ann_vol = excess_returns.std() * np.sqrt(periods_per_year)
 
    return ann_return / ann_vol if ann_vol > 0 else 0
 
# 示例
returns = pd.Series(np.random.randn(252) * 0.01 + 0.0005)
sharpe = sharpe_ratio(returns)
print(f"Sharpe比率: {sharpe:.2f}")

经验标准

Sharpe评价
< 1.0较差
1.0 - 2.0良好
> 2.0优秀
> 3.0可疑(可能过拟合)

2. Information Ratio (信息比率)

定义:超额收益与跟踪误差的比值。

def information_ratio(strategy_returns, benchmark_returns):
    """
    计算信息比率
 
    参数
    ----
    strategy_returns : Series
      策略收益率
    benchmark_returns : Series
      基准收益率
 
    返回
    ----
    ir : float
      信息比率
    """
    excess = excess_return(strategy_returns, benchmark_returns)
    return excess.mean() / excess.std() * np.sqrt(252) if excess.std() > 0 else 0
 
# 示例
strategy_ret = pd.Series(np.random.randn(252) * 0.01 + 0.0005)
benchmark_ret = pd.Series(np.random.randn(252) * 0.008 + 0.0003)
ir = information_ratio(strategy_ret, benchmark_ret)
print(f"信息比率: {ir:.2f}")

3. Sortino Ratio (索提诺比率)

定义:只考虑下行风险的Sharpe比率。

其中 是下行波动率(只计算负收益)。

def sortino_ratio(returns, risk_free_rate=0.03, target_return=0, periods_per_year=252):
    """
    计算Sortino比率
 
    参数
    ----
    returns : Series
      收益率序列
    risk_free_rate : float
      无风险利率
    target_return : float
      目标收益率(默认0)
    periods_per_year : int
      每年交易周期
 
    返回
    ----
    sortino : float
      Sortino比率
    """
    daily_rf = (1 + risk_free_rate) ** (1 / periods_per_year) - 1
    excess_returns = returns - daily_rf
 
    # 下行波动率
    downside_returns = excess_returns[excess_returns < target_return]
    downside_std = downside_returns.std()
 
    ann_return = excess_returns.mean() * periods_per_year
    ann_downside_std = downside_std * np.sqrt(periods_per_year)
 
    return ann_return / ann_downside_std if ann_downside_std > 0 else 0
 
# 示例
sortino = sortino_ratio(returns)
print(f"Sortino比率: {sortino:.2f}")

4. Calmar Ratio (卡玛比率)

定义:年化收益与最大回撤的比值。

def calmar_ratio(returns, periods_per_year=252):
    """
    计算Calmar比率
 
    参数
    ----
    returns : Series
      收益率序列
    periods_per_year : int
      每年交易周期
 
    返回
    ----
    calmar : float
      Calmar比率
    """
    ann_ret = annual_return(returns, periods_per_year)
    max_dd, _ = max_drawdown(returns)
    return ann_ret / abs(max_dd) if max_dd != 0 else np.inf
 
# 示例
calmar = calmar_ratio(returns)
print(f"Calmar比率: {calmar:.2f}")

交易类指标

1. 换手率 (Turnover)

定义:组合权重变化的幅度。

def turnover(weights):
    """
    计算换手率
 
    参数
    ----
    weights : DataFrame
      每日权重 (T x N)
 
    返回
    ----
    turnover_series : Series
      每日换手率
    avg_turnover : float
      平均换手率
    """
    weight_diff = weights.diff().abs()
    daily_turnover = weight_diff.sum(axis=1) / 2
    return daily_turnover, daily_turnover.mean()
 
# 示例
dates = pd.date_range('2023-01-01', '2023-12-31')
stocks = [f'S{i}' for i in range(50)]
weights = pd.DataFrame(np.random.rand(len(dates), len(stocks)),
                      index=dates, columns=stocks)
weights = weights.div(weights.sum(axis=1), axis=0)
 
daily_to, avg_to = turnover(weights)
print(f"平均日换手率: {avg_to:.2%}")
print(f"年化换手率: {avg_to * 252:.2%}")

2. 胜率 (Win Rate)

定义:盈利交易占总交易的比例。

def win_rate(returns):
    """
    计算胜率
 
    参数
    ----
    returns : Series
      交易收益率序列
 
    返回
    ----
    win_rate : float
      胜率
    """
    wins = (returns > 0).sum()
    total = len(returns)
    return wins / total if total > 0 else 0
 
# 示例
trade_returns = pd.Series([0.05, -0.03, 0.08, -0.02, 0.06, -0.01, 0.04])
wr = win_rate(trade_returns)
print(f"胜率: {wr:.2%}")

3. 盈亏比 (Profit/Loss Ratio)

定义:平均盈利与平均亏损的比值。

def profit_loss_ratio(returns):
    """
    计算盈亏比
 
    参数
    ----
    returns : Series
      交易收益率序列
 
    返回
    ----
    pl_ratio : float
      盈亏比
    """
    profits = returns[returns > 0]
    losses = returns[returns < 0]
 
    if len(profits) == 0 or len(losses) == 0:
        return 0
 
    avg_profit = profits.mean()
    avg_loss = abs(losses.mean())
 
    return avg_profit / avg_loss if avg_loss > 0 else 0
 
# 示例
pl = profit_loss_ratio(trade_returns)
print(f"盈亏比: {pl:.2f}")

完整绩效报告

PerformanceReport 类

import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
 
class PerformanceReport:
    """完整的绩效评估报告"""
 
    def __init__(self, strategy_returns, benchmark_returns=None,
                 risk_free_rate=0.03):
        """
        参数
        ----
        strategy_returns : Series
          策略收益率序列
        benchmark_returns : Series, optional
          基准收益率序列
        risk_free_rate : float
          无风险利率
        """
        self.strategy_returns = strategy_returns
        self.benchmark_returns = benchmark_returns
        self.rf = risk_free_rate
 
    def generate_metrics(self):
        """生成所有绩效指标"""
        metrics = {}
 
        # 收益指标
        metrics['累计收益率'] = total_return(self.strategy_returns)
        metrics['年化收益率'] = annual_return(self.strategy_returns)
 
        # 风险指标
        metrics['年化波动率'] = volatility(self.strategy_returns)
        metrics['最大回撤'], metrics['最大回撤天数'] = max_drawdown(self.strategy_returns)
        metrics['95% VaR'] = value_at_risk(self.strategy_returns, 0.95)
        metrics['95% CVaR'] = conditional_var(self.strategy_returns, 0.95)
 
        # 风险调整收益
        metrics['Sharpe比率'] = sharpe_ratio(self.strategy_returns, self.rf)
        metrics['Sortino比率'] = sortino_ratio(self.strategy_returns, self.rf)
        metrics['Calmar比率'] = calmar_ratio(self.strategy_returns)
 
        # 与基准对比
        if self.benchmark_returns is not None:
            excess = excess_return(self.strategy_returns, self.benchmark_returns)
            metrics['超额收益率'] = annual_return(excess)
            metrics['信息比率'] = information_ratio(
                self.strategy_returns, self.benchmark_returns
            )
 
        return pd.Series(metrics)
 
    def plot_performance(self, save_path=None):
        """绘制绩效图表"""
        # 计算累计净值
        strategy_cum = (1 + self.strategy_returns).cumprod()
 
        fig = plt.figure(figsize=(15, 10))
        gs = GridSpec(3, 2, figure=fig)
 
        # 1. 累计收益曲线
        ax1 = fig.add_subplot(gs[0, :])
        ax1.plot(strategy_cum.index, strategy_cum.values,
                label='策略', linewidth=2)
 
        if self.benchmark_returns is not None:
            benchmark_cum = (1 + self.benchmark_returns).cumprod()
            ax1.plot(benchmark_cum.index, benchmark_cum.values,
                    label='基准', linewidth=2, alpha=0.7)
 
        ax1.set_title('累计净值曲线', fontsize=14, fontproperties='SimHei')
        ax1.legend(prop={'family': 'SimHei'})
        ax1.grid(True, alpha=0.3)
 
        # 2. 回撤图
        ax2 = fig.add_subplot(gs[1, 0])
        running_max = strategy_cum.expanding().max()
        drawdown = (strategy_cum - running_max) / running_max
        ax2.fill_between(drawdown.index, drawdown.values, 0,
                         color='red', alpha=0.3)
        ax2.plot(drawdown.index, drawdown.values, color='red', linewidth=1)
        ax2.set_title('回撤', fontsize=12, fontproperties='SimHei')
        ax2.grid(True, alpha=0.3)
 
        # 3. 滚动Sharpe
        ax3 = fig.add_subplot(gs[1, 1])
        rolling_sharpe = self.strategy_returns.rolling(60).apply(
            lambda x: sharpe_ratio(x, self.rf)
        )
        ax3.plot(rolling_sharpe.index, rolling_sharpe.values, linewidth=1)
        ax3.axhline(y=1, color='gray', linestyle='--', alpha=0.5)
        ax3.set_title('滚动Sharpe比率(60日)', fontsize=12, fontproperties='SimHei')
        ax3.grid(True, alpha=0.3)
 
        # 4. 月度收益
        ax4 = fig.add_subplot(gs[2, :])
        monthly_returns = self.strategy_returns.resample('M').apply(lambda x: (1 + x).prod() - 1)
 
        colors = ['green' if x > 0 else 'red' for x in monthly_returns.values]
        ax4.bar(range(len(monthly_returns)), monthly_returns.values, color=colors, alpha=0.7)
        ax4.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
        ax4.set_xticks(range(len(monthly_returns)))
        ax4.set_xticklabels([d.strftime('%Y-%m') for d in monthly_returns.index],
                           rotation=45, ha='right', fontsize=8)
        ax4.set_title('月度收益率', fontsize=12, fontproperties='SimHei')
        ax4.grid(True, alpha=0.3, axis='y')
 
        plt.tight_layout()
 
        if save_path:
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
        plt.show()
 
    def generate_report(self, save_path=None):
        """生成完整报告"""
        print("=" * 60)
        print("策略绩效评估报告".center(50))
        print("=" * 60)
 
        # 打印指标
        metrics = self.generate_metrics()
        print("\n【绩效指标】")
        for name, value in metrics.items():
            if isinstance(value, float):
                if '率' in name or '比率' in name or 'VaR' in name or 'CVaR' in name:
                    print(f"  {name:15s}: {value:10.2%}")
                elif '天数' in name:
                    print(f"  {name:15s}: {int(value):10}天")
                else:
                    print(f"  {name:15s}: {value:10.4f}")
 
        # 绘图
        self.plot_performance(save_path)
 
        return metrics
 
# 示例使用
if __name__ == "__main__":
    # 生成模拟数据
    np.random.seed(42)
    dates = pd.date_range('2020-01-01', '2023-12-31')
 
    strategy_returns = pd.Series(
        np.random.randn(len(dates)) * 0.015 + 0.0003,
        index=dates
    )
 
    benchmark_returns = pd.Series(
        np.random.randn(len(dates)) * 0.012 + 0.0002,
        index=dates
    )
 
    # 生成报告
    report = PerformanceReport(strategy_returns, benchmark_returns)
    metrics = report.generate_report()

月度收益热力图

def plot_monthly_returns(returns, ax=None):
    """
    绘制月度收益热力图
 
    参数
    ----
    returns : Series
      日收益率序列
    ax : matplotlib Axes, optional
      子图对象
    """
    # 计算月度收益
    monthly = returns.resample('M').apply(lambda x: (1 + x).prod() - 1)
 
    # 创建年月矩阵
    monthly_df = pd.DataFrame({
        'year': monthly.index.year,
        'month': monthly.index.month,
        'return': monthly.values
    })
 
    pivot = monthly_df.pivot(index='year', columns='month', values='return')
 
    # 绘图
    if ax is None:
        fig, ax = plt.subplots(figsize=(12, 6))
 
    im = ax.imshow(pivot.values, cmap='RdYlGn', aspect='auto',
                   vmin=-0.1, vmax=0.1)
 
    # 设置刻度
    ax.set_xticks(range(12))
    ax.set_xticklabels(['1月', '2月', '3月', '4月', '5月', '6月',
                       '7月', '8月', '9月', '10月', '11月', '12月'])
    ax.set_yticks(range(len(pivot.index)))
    ax.set_yticklabels(pivot.index)
 
    # 添加数值标签
    for i in range(len(pivot.index)):
        for j in range(12):
            text = ax.text(j, i, f'{pivot.values[i, j]:.1%}',
                          ha='center', va='center',
                          color='black', fontsize=8)
 
    ax.set_title('月度收益率热力图', fontproperties='SimHei', fontsize=14)
 
    # 添加colorbar
    cbar = plt.colorbar(im, ax=ax)
    cbar.set_label('收益率', fontproperties='SimHei')
 
    plt.tight_layout()
    return ax

收益分布分析

偏度 (Skewness)

定义:衡量收益分布的不对称性。

from scipy.stats import skew
 
def skewness(returns):
    """计算偏度"""
    return skew(returns.dropna())
 
# 解释
# skew > 0: 正偏(右尾更长)
# skew < 0: 负偏(左尾更长,风险更大)
# skew = 0: 对称分布

峰度 (Kurtosis)

定义:衡量收益分布的尖峰程度。

from scipy.stats import kurtosis
 
def kurt(returns):
    """计算峰度(超额峰度)"""
    return kurtosis(returns.dropna())
 
# 解释
# kurt > 0: 尖峰分布(极端风险更高)
# kurt < 0: 平坦分布
# kurt = 0: 正态分布

核心知识点总结

┌────────────────────────────────────────────────────────────────┐
│                 04-绩效评估指标 核心要点                         │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  1. 收益指标                                                   │
│     累计收益 → 年化收益 → 超额收益                             │
│                                                                │
│  2. 风险指标                                                   │
│     波动率、最大回撤、VaR、CVaR                                 │
│                                                                │
│  3. 风险调整收益                                               │
│     Sharpe > 1 良好 > 2 优秀                                   │
│     IR 衡量相对基准的表现                                       │
│     Sortino 只考虑下行风险                                      │
│                                                                │
│  4. 交易指标                                                   │
│     换手率影响成本                                             │
│     胜率 + 盈亏比 = 综合评估                                   │
│                                                                │
│  5. 可视化                                                     │
│     净值曲线、回撤图、滚动Sharpe、月度热力图                    │
│                                                                │
└────────────────────────────────────────────────────────────────┘

思考题

  1. 为什么Sharpe > 3 的回测结果需要警惕?
  2. 最大回撤和波动率,哪个对投资者更重要?
  3. 如何判断一个策略是”靠运气”还是”靠能力”?

下一步

现在你知道如何评估策略表现了。但回测中最重要的是识别陷阱——下一节将学习回测中的各种偏差和防范方法。

前往:05-回测陷阱与防范