03-绩效归因

预计学习时间:1.5 小时

难度:⭐⭐⭐

核心问题:策略赚的钱从哪里来?Alpha 还是 Beta?选股还是择时?实盘和回测差多少?


绩效归因全景

┌────────────────────────────────────────────────────────────────┐
│                     绩效归因体系                                │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│                       绩效归因                                  │
│                          │                                     │
│       ┌──────────────────┼──────────────────┐                 │
│       │                  │                  │                  │
│   收益分解          风险归因           质量评估               │
│       │                  │                  │                  │
│  · Brinson 归因     · 因子归因          · 策略衰减            │
│  · Alpha/Beta       · Barra 风险        · 回测差异            │
│  · 选股/择时        · 风险贡献          · 换手率分析           │
│                                                                │
└────────────────────────────────────────────────────────────────┘

一、Brinson 归因模型

1.1 基本原理

Brinson 归因将组合收益与基准的差异分解为三个效应:

其中:

  • :组合实际收益
  • :基准收益
  • :用基准权重 × 组合行业权重计算的收益(资产配置效应的参照)
  • :用组合权重 × 基准行业收益计算的收益(选股效应的参照)
Brinson 归因分解示意

                 超额收益
              ┌────┴────┐
              │  R_p-R_b │
              └────┬────┘
         ┌─────────┼─────────┐
         │         │         │
    配置效应    选股效应    交互效应
   ┌──────┐  ┌──────┐  ┌──────┐
   │行业  │  │行业内│  │同时  │
   │权重  │  │选股  │  │改变  │
   │偏离  │  │能力  │  │两者  │
   └──────┘  └──────┘  └──────┘

1.2 完整实现

def brinson_attribution(portfolio_weights, portfolio_returns,
                        benchmark_weights, benchmark_returns,
                        sectors):
    """
    Brison 归因分析(单期)
 
    参数
    ----
    portfolio_weights : dict
      行业 -> 组合权重,如 {'金融': 0.30, '科技': 0.25}
    portfolio_returns : dict
      行业 -> 组合行业收益,如 {'金融': 0.05, '科技': 0.12}
    benchmark_weights : dict
      行业 -> 基准权重
    benchmark_returns : dict
      行业 -> 基准行业收益
    sectors : list
      行业列表
 
    返回
    ----
    result : dict
      归因结果
    """
    # 组合总收益
    R_p = sum(portfolio_weights[s] * portfolio_returns[s] for s in sectors)
    # 基准总收益
    R_b = sum(benchmark_weights[s] * benchmark_returns[s] for s in sectors)
    # 超额收益
    excess = R_p - R_b
 
    # 配置效应:用组合权重 × 基准收益 - 基准权重 × 基准收益
    allocation = sum(
        (portfolio_weights[s] - benchmark_weights[s]) * benchmark_returns[s]
        for s in sectors
    )
 
    # 选股效应:用基准权重 × 组合收益 - 基准权重 × 基准收益
    selection = sum(
        benchmark_weights[s] * (portfolio_returns[s] - benchmark_returns[s])
        for s in sectors
    )
 
    # 交互效应:组合权重与组合收益的"额外"贡献
    interaction = sum(
        (portfolio_weights[s] - benchmark_weights[s]) *
        (portfolio_returns[s] - benchmark_returns[s])
        for s in sectors
    )
 
    return {
        '组合收益': R_p,
        '基准收益': R_b,
        '超额收益': excess,
        '配置效应': allocation,
        '选股效应': selection,
        '交互效应': interaction,
        '配置占比': allocation / excess if excess != 0 else 0,
        '选股占比': selection / excess if excess != 0 else 0,
        '交互占比': interaction / excess if excess != 0 else 0,
    }
 
# 示例
sectors = ['金融', '科技', '消费', '医药', '能源']
 
portfolio_w = {'金融': 0.30, '科技': 0.25, '消费': 0.20,
               '医药': 0.15, '能源': 0.10}
benchmark_w = {'金融': 0.25, '科技': 0.20, '消费': 0.25,
               '医药': 0.20, '能源': 0.10}
 
portfolio_r = {'金融': 0.05, '科技': 0.15, '消费': 0.08,
               '医药': 0.12, '能源': 0.03}
benchmark_r = {'金融': 0.04, '科技': 0.10, '消费': 0.06,
               '医药': 0.08, '能源': 0.02}
 
result = brinson_attribution(portfolio_w, portfolio_r,
                              benchmark_w, benchmark_r, sectors)
 
print("=== Brinson 归因结果 ===")
for k, v in result.items():
    if isinstance(v, float):
        print(f"  {k}: {v:.4%}")
    else:
        print(f"  {k}: {v}")

1.3 多期 Brinson 归因

实际应用中需要分析多期(如月度)归因,并进行连接处理。

def brinson_multiperiod(portfolio_weights_list, portfolio_returns_list,
                         benchmark_weights_list, benchmark_returns_list,
                         sectors):
    """
    多期 Brinson 归因
 
    参数
    ----
    portfolio_weights_list : list of dict
      每期的组合权重
    portfolio_returns_list : list of dict
      每期的组合行业收益
    benchmark_weights_list : list of dict
      每期的基准权重
    benchmark_returns_list : list of dict
      每期的基准行业收益
    sectors : list
      行业列表
 
    返回
    ----
    results_df : DataFrame
      各期及累计归因结果
    """
    all_results = []
    cumulative = {'allocation': 0, 'selection': 0, 'interaction': 0}
 
    for i, (pw, pr, bw, br) in enumerate(zip(
        portfolio_weights_list, portfolio_returns_list,
        benchmark_weights_list, benchmark_returns_list
    )):
        period_result = brinson_attribution(pw, pr, bw, br, sectors)
 
        # 累积效应(简化加法连接)
        cumulative['allocation'] += period_result['配置效应']
        cumulative['selection'] += period_result['选股效应']
        cumulative['interaction'] += period_result['交互效应']
 
        all_results.append({
            '期数': i + 1,
            '超额收益': period_result['超额收益'],
            '配置效应': period_result['配置效应'],
            '选股效应': period_result['选股效应'],
            '交互效应': period_result['交互效应'],
            '累计配置': cumulative['allocation'],
            '累计选股': cumulative['selection'],
            '累计交互': cumulative['interaction'],
        })
 
    return pd.DataFrame(all_results).set_index('期数')
 
# 示例:3 期归因
np.random.seed(42)
n_periods = 3
pw_list = [portfolio_w.copy() for _ in range(n_periods)]
pr_list = [{s: v + np.random.normal(0, 0.01) for s, v in portfolio_r.items()}
            for _ in range(n_periods)]
bw_list = [benchmark_w.copy() for _ in range(n_periods)]
br_list = [{s: v + np.random.normal(0, 0.005) for s, v in benchmark_r.items()}
            for _ in range(n_periods)]
 
results_df = brinson_multiperiod(pw_list, pr_list, bw_list, br_list, sectors)
print("\n=== 多期 Brinson 归因 ===")
print(results_df.round(4))

二、因子归因(Barra 风险模型)

2.1 基本原理

Barra 风险模型将组合收益分解为:

其中:

  • :特异收益(选股 Alpha)
  • :第 个因子的暴露
  • :第 个因子的收益
  • :残差(特质风险)
def factor_attribution(returns, factor_returns, factor_exposures=None,
                        benchmark_returns=None):
    """
    因子归因分析(Barra 风险模型)
 
    参数
    ----
    returns : Series
      组合日收益率
    factor_returns : DataFrame
      因子日收益率 (T x K)
    factor_exposures : DataFrame, optional
      组合对因子的暴露 (N x K)
    benchmark_returns : Series, optional
      基准收益率
 
    返回
    ----
    attribution : dict
      因子归因结果
    """
    # 如果没有提供暴露,通过回归估计
    if factor_exposures is None:
        X = np.column_stack([np.ones(len(factor_returns)), factor_returns])
        y = returns.values
 
        # OLS 回归: r_p = alpha + sum(beta_k * f_k) + epsilon
        betas = np.linalg.lstsq(X, y, rcond=None)[0]
        alpha = betas[0]
        factor_betas = betas[1:]
 
        y_pred = X @ betas
        residual = y - y_pred
 
    # 计算各因子贡献
    factor_names = factor_returns.columns.tolist()
    factor_contrib = {}
    for i, name in enumerate(factor_names):
        # 因子贡献 = 因子暴露 × 因子收益(按时间平均)
        contrib = factor_betas[i] * factor_returns[name].mean() * 252
        factor_contrib[name] = {
            'beta': factor_betas[i],
            '年化因子收益': factor_returns[name].mean() * 252,
            '年化贡献': contrib,
        }
 
    # 总因子贡献
    total_factor = sum(v['年化贡献'] for v in factor_contrib.values())
    total_alpha = alpha * 252
 
    attribution = {
        'alpha 年化': total_alpha,
        '因子总贡献 年化': total_factor,
        '残差波动率': residual.std() * np.sqrt(252) if factor_exposures is None else None,
        'R²': 1 - residual.var() / y.var() if factor_exposures is None else None,
        '因子明细': factor_contrib,
    }
 
    return attribution
 
# 示例
np.random.seed(42)
n_days, n_factors = 500, 5
factor_names = ['Market', 'Size', 'Value', 'Momentum', 'Volatility']
factor_returns = pd.DataFrame(
    np.random.normal(0.001, 0.01, (n_days, n_factors)),
    columns=factor_names
)
 
# 真实因子暴露
true_betas = np.array([1.0, 0.3, -0.2, 0.5, -0.1])
alpha_true = 0.0002
 
# 生成组合收益
portfolio_returns = pd.Series(
    alpha_true + factor_returns.values @ true_betas +
    np.random.normal(0, 0.005, n_days)
)
 
attr = factor_attribution(portfolio_returns, factor_returns)
print("=== 因子归因结果 ===")
print(f"Alpha 年化: {attr['alpha 年化']:.2%}")
print(f"因子总贡献 年化: {attr['因子总贡献 年化']:.2%}")
print(f"R²: {attr['R²']:.4f}")
print(f"\n因子明细:")
for name, detail in attr['因子明细'].items():
    print(f"  {name}: beta={detail['beta']:.3f}, "
          f"贡献={detail['年化贡献']:.2%}")

2.2 因子归因可视化数据准备

def prepare_factor_attribution_report(attribution):
    """
    将因子归因结果整理为可制表的 DataFrame
 
    参数
    ----
    attribution : dict
      factor_attribution 的返回值
 
    返回
    ----
    df : DataFrame
      因子归因汇总表
    """
    rows = [{'因子': 'Alpha', 'Beta': '-', '年化贡献': attribution['alpha 年化']}]
    for name, detail in attribution['因子明细'].items():
        rows.append({
            '因子': name,
            'Beta': detail['beta'],
            '年化贡献': detail['年化贡献'],
        })
 
    df = pd.DataFrame(rows)
    return df
 
# 示例
report_df = prepare_factor_attribution_report(attr)
print("\n因子归因汇总表:")
print(report_df.to_string(index=False))

三、Alpha 来源分析

3.1 Alpha 分解

Alpha 可以进一步分解为:

def alpha_decomposition(strategy_returns, benchmark_returns,
                         market_factor=None):
    """
    Alpha 来源分解
 
    参数
    ----
    strategy_returns : Series
      策略收益率
    benchmark_returns : Series
      基准收益率
    market_factor : Series, optional
      市场因子收益率
 
    返回
    ----
    decomposition : dict
      Alpha 分解结果
    """
    excess = strategy_returns - benchmark_returns
 
    # 总 Alpha
    total_alpha = excess.mean() * 252
 
    # 市场 Beta 和市场择时 Alpha
    if market_factor is not None:
        X = np.column_stack([np.ones(len(market_factor)), market_factor])
        y = strategy_returns.values
        betas = np.linalg.lstsq(X, y, rcond=None)[0]
        market_beta = betas[1]
        alpha_from_market = betas[0] * 252
    else:
        X = np.column_stack([np.ones(len(benchmark_returns)),
                             benchmark_returns])
        y = strategy_returns.values
        betas = np.linalg.lstsq(X, y, rcond=None)[0]
        market_beta = betas[1]
        alpha_from_market = betas[0] * 252
 
    # 选股 Alpha(超额收益中不可被基准解释的部分)
    X_sel = np.column_stack([np.ones(len(benchmark_returns)),
                             benchmark_returns])
    y_sel = excess.values
    sel_betas = np.linalg.lstsq(X_sel, y_sel, rcond=None)[0]
    selection_alpha = sel_betas[0] * 252
 
    # 信息比率
    tracking_error = excess.std() * np.sqrt(252)
    information_ratio = total_alpha / tracking_error if tracking_error > 0 else 0
 
    decomposition = {
        '总 Alpha 年化': total_alpha,
        '选股 Alpha 年化': selection_alpha,
        '市场 Beta': market_beta,
        '跟踪误差': tracking_error,
        '信息比率 (IR)': information_ratio,
        '胜率': (excess > 0).mean(),
        '超额收益偏度': excess.skew(),
        '超额收益峰度': excess.kurtosis(),
    }
    return decomposition
 
# 示例
np.random.seed(42)
n_days = 500
benchmark = pd.Series(np.random.normal(0.0005, 0.012, n_days))
strategy = pd.Series(
    benchmark + 0.0003 + np.random.normal(0, 0.008, n_days)
)
 
decomp = alpha_decomposition(strategy, benchmark)
print("=== Alpha 分解 ===")
for k, v in decomp.items():
    print(f"  {k}: {v:.4f}" if isinstance(v, float) else f"  {k}: {v}")

3.2 滚动 Alpha 监控

def rolling_alpha_monitor(strategy_returns, benchmark_returns,
                           window=60):
    """
    滚动窗口 Alpha 监控
 
    参数
    ----
    strategy_returns : Series
      策略收益率
    benchmark_returns : Series
      基准收益率
    window : int
      滚动窗口
 
    返回
    ----
    rolling_stats : DataFrame
      滚动统计量
    """
    excess = strategy_returns - benchmark_returns
 
    rolling = pd.DataFrame({
        '滚动 Alpha 年化': excess.rolling(window).mean() * 252,
        '滚动 IR': (excess.rolling(window).mean() * np.sqrt(252) /
                    excess.rolling(window).std()),
        '滚动胜率': excess.rolling(window).apply(
            lambda x: (x > 0).mean()),
        '滚动跟踪误差': excess.rolling(window).std() * np.sqrt(252),
    })
 
    return rolling.dropna()
 
# 示例
rolling_stats = rolling_alpha_monitor(strategy, benchmark)
print(f"\n滚动 Alpha 监控(最近 {len(rolling_stats)} 期):")
print(rolling_stats.tail(5).round(4))

四、策略衰减监控

4.1 衰减信号检测

策略性能随时间下降是常见现象。需要及时检测衰减信号。

衰减类型表现可能原因
Alpha 衰减超额收益持续下降Alpha 被套利殆尽
波动率变化实际波动偏离预期市场结构变化
容量下降收益随规模递减策略容量有限
相关性上升与其他策略相关性增大策略同质化
def decay_detector(returns, rolling_window=60, z_threshold=2.0):
    """
    策略衰减检测器
 
    参数
    ----
    returns : Series
      策略日收益率
    rolling_window : int
      滚动窗口
    z_threshold : float
      Z-score 阈值(超过视为衰减信号)
 
    返回
    ----
    signals : DataFrame
      衰减信号列表
    """
    # 滚动 Sharpe
    rolling_mean = returns.rolling(rolling_window).mean() * 252
    rolling_std = returns.rolling(rolling_window).std() * np.sqrt(252)
    rolling_sharpe = rolling_mean / rolling_std
 
    # 滚动最大回撤
    cumret = (1 + returns).cumprod()
    rolling_peak = cumret.rolling(rolling_window).max()
    rolling_drawdown = (cumret - rolling_peak) / rolling_peak
 
    # 与全样本统计量的 Z-score
    full_mean = returns.mean() * 252
    full_std = returns.std() * np.sqrt(252)
    rolling_mean_z = (rolling_mean - full_mean) / (
        returns.rolling(rolling_window).std() * np.sqrt(252))
 
    signals = pd.DataFrame({
        '滚动 Sharpe': rolling_sharpe,
        '滚动年化收益': rolling_mean,
        '滚动最大回撤': rolling_drawdown,
        '收益 Z-score': rolling_mean_z,
    })
 
    # 标记衰减信号
    signals['Alpha 衰减'] = rolling_mean_z < -z_threshold
    signals['波动率异常'] = rolling_std > 2 * full_std
 
    return signals.dropna()
 
# 示例:模拟包含衰减的收益序列
np.random.seed(42)
n = 500
# 前期正常,后期衰减
early = np.random.normal(0.001, 0.015, n // 2)
late = np.random.normal(0.0002, 0.015, n // 2)  # Alpha 下降
all_returns = pd.Series(np.concatenate([early, late]))
 
signals = decay_detector(all_returns)
decay_periods = signals[signals['Alpha 衰减'] | signals['波动率异常']]
print(f"=== 策略衰减监控 ===")
print(f"检测到衰减信号: {len(decay_periods)} 个周期")
if len(decay_periods) > 0:
    print(f"\n衰减期统计:")
    print(f"  衰减期平均 Sharpe: {decay_periods['滚动 Sharpe'].mean():.2f}")
    print(f"  衰减期平均回撤: {decay_periods['滚动最大回撤'].mean():.2%}")

4.2 策略容量估算

def capacity_estimate(daily_volume, max_participation=0.05,
                      avg_trade_value=None):
    """
    估算策略容量
 
    参数
    ----
    daily_volume : Series
      日成交量(金额)
    max_participation : float
      最大参与率(占日成交量的比例)
    avg_trade_value : float
      平均单笔交易金额
 
    返回
    ----
    capacity : float
      估算的策略容量
    details : dict
      容量明细
    """
    avg_volume = daily_volume.mean()
    capacity = avg_volume * max_participation
 
    # 按不同参与率估算
    details = {}
    for pct in [0.01, 0.03, 0.05, 0.10]:
        cap = avg_volume * pct
        details[f'{pct:.0%} 参与率'] = cap
 
    return capacity, details
 
# 示例
np.random.seed(42)
daily_volume = pd.Series(
    np.random.lognormal(20, 0.5, 252)  # 模拟日成交量
)
 
cap, details = capacity_estimate(daily_volume)
print(f"策略容量估算: {cap:,.0f} 元")
for k, v in details.items():
    print(f"  {k}: {v:,.0f} 元")

五、实盘 vs 回测差异分析

5.1 差异来源

实盘表现与回测结果之间往往存在显著差异,这是量化交易中最常见也最棘手的问题。

回测 vs 实盘差异来源

┌─────────────────────────────────────────────────┐
│                  差异来源                         │
├──────────┬──────────┬──────────┬────────────────┤
│  执行成本  │  滑点     │  流动性   │  模型假设      │
│          │          │          │               │
│ · 佣金    │ · 市价冲击│ · 涨跌停  │ · 幸存偏差    │
│ · 印花税  │ · 延迟执行│ · 停牌   │ · 前视偏差    │
│ · 过户费  │ · 部分成交│ · 闪崩   │ · 过度拟合    │
│          │          │          │  · 制度变化    │
└──────────┴──────────┴──────────┴────────────────┘
class PerformanceAttribution:
    """
    绩效归因与实盘差异分析工具类
 
    功能:Brinson 归因、因子归因、Alpha 分解、衰减监控、
          实盘差异分析
    """
 
    def __init__(self, backtest_returns, live_returns=None,
                 benchmark_returns=None, factor_returns=None):
        """
        参数
        ----
        backtest_returns : Series
          回测收益率
        live_returns : Series
          实盘收益率
        benchmark_returns : Series
          基准收益率
        factor_returns : DataFrame
          因子收益率
        """
        self.bt_returns = backtest_returns
        self.live_returns = live_returns
        self.bm_returns = benchmark_returns
        self.factor_returns = factor_returns
 
    # ---- 基本绩效指标 ----
    def basic_metrics(self, returns):
        """计算基本绩效指标"""
        if returns is None or len(returns) == 0:
            return {}
 
        annual_factor = 252
        mean_annual = returns.mean() * annual_factor
        vol_annual = returns.std() * np.sqrt(annual_factor)
        sharpe = mean_annual / vol_annual if vol_annual > 0 else 0
 
        # 最大回撤
        cumret = (1 + returns).cumprod()
        peak = cumret.cummax()
        drawdown = (cumret - peak) / peak
        max_dd = drawdown.min()
 
        # Calmar 比率
        calmar = mean_annual / abs(max_dd) if max_dd != 0 else 0
 
        # 胜率
        win_rate = (returns > 0).mean()
 
        # 偏度和峰度
        skew = returns.skew()
        kurt = returns.kurtosis()
 
        return {
            '年化收益': mean_annual,
            '年化波动率': vol_annual,
            'Sharpe': sharpe,
            '最大回撤': max_dd,
            'Calmar': calmar,
            '胜率': win_rate,
            '偏度': skew,
            '峰度': kurt,
        }
 
    # ---- 实盘差异分析 ----
    def live_vs_backtest(self):
        """
        分析实盘与回测的差异
 
        返回
        ----
        comparison : dict
          差异分析结果
        """
        if self.live_returns is None:
            print("警告:未提供实盘数据")
            return None
 
        bt_metrics = self.basic_metrics(self.bt_returns)
        live_metrics = self.basic_metrics(self.live_returns)
 
        comparison = {
            '指标': list(bt_metrics.keys()),
            '回测值': list(bt_metrics.values()),
            '实盘值': list(live_metrics.values()),
        }
 
        # 计算差异
        diff = {}
        for key in bt_metrics:
            bt_val = bt_metrics[key]
            live_val = live_metrics.get(key, 0)
            # 相对差异
            if bt_val != 0 and abs(bt_val) > 1e-6:
                rel_diff = (live_val - bt_val) / abs(bt_val)
            else:
                rel_diff = 0
            diff[key] = {
                '回测': bt_val,
                '实盘': live_val,
                '绝对差异': live_val - bt_val,
                '相对差异': rel_diff,
            }
 
        return diff
 
    # ---- 收益差异归因 ----
    def return_gap_attribution(self, costs_bps=5, slippage_bps=3):
        """
        将回测与实盘收益差异归因到不同来源
 
        参数
        ----
        costs_bps : float
          估计交易成本(基点/天)
        slippage_bps : float
          估计滑点成本(基点/天)
 
        返回
        ----
        gap_analysis : dict
          差异归因
        """
        if self.live_returns is None:
            return None
 
        # 对齐日期
        common_idx = self.bt_returns.index.intersection(
            self.live_returns.index)
        bt = self.bt_returns.loc[common_idx]
        live = self.live_returns.loc[common_idx]
 
        # 总差异
        total_gap = (bt.mean() - live.mean()) * 252 * 10000  # 基点
 
        # 估算各项成本
        estimated_cost = costs_bps  # 基点/天
        estimated_slippage = slippage_bps  # 基点/天
        unexplained = total_gap - estimated_cost - estimated_slippage
 
        gap_analysis = {
            '回测年化收益(bps)': bt.mean() * 252 * 10000,
            '实盘年化收益(bps)': live.mean() * 252 * 10000,
            '总差异(bps)': total_gap,
            '交易成本(bps)': estimated_cost,
            '滑点成本(bps)': estimated_slippage,
            '未解释部分(bps)': unexplained,
            '成本解释比例': (
                (estimated_cost + estimated_slippage) / total_gap
                if total_gap != 0 else 0
            ),
        }
        return gap_analysis
 
    # ---- 综合报告 ----
    def full_report(self):
        """生成完整的绩效归因报告"""
        print("=" * 60)
        print("          绩效归因综合报告")
        print("=" * 60)
 
        # 基本指标
        bt_metrics = self.basic_metrics(self.bt_returns)
        print("\n--- 回测绩效指标 ---")
        for k, v in bt_metrics.items():
            print(f"  {k:12s}: {v:.4f}" if isinstance(v, float) else f"  {k:12s}: {v}")
 
        # 实盘对比
        if self.live_returns is not None:
            comparison = self.live_vs_backtest()
            print("\n--- 实盘 vs 回测差异 ---")
            for metric, vals in comparison.items():
                print(f"  {metric:12s}: "
                      f"回测={vals['回测']:.4f}, "
                      f"实盘={vals['实盘']:.4f}, "
                      f"差异={vals['相对差异']:.1%}")
 
            gap = self.return_gap_attribution()
            print("\n--- 收益差异归因 ---")
            for k, v in gap.items():
                if isinstance(v, float):
                    print(f"  {k:20s}: {v:.1f} bps" if v > 10 else f"  {k:20s}: {v:.2%}")
                else:
                    print(f"  {k:20s}: {v}")
 
        # 因子归因
        if self.factor_returns is not None:
            attr = factor_attribution(
                self.bt_returns, self.factor_returns)
            print("\n--- 因子归因 ---")
            print(f"  Alpha 年化: {attr['alpha 年化']:.2%}")
            print(f"  因子总贡献: {attr['因子总贡献 年化']:.2%}")
            print(f"  R²: {attr['R²']:.4f}")
 
        # 衰减监控
        signals = decay_detector(self.bt_returns)
        n_decay = signals['Alpha 衰减'].sum()
        print(f"\n--- 策略衰减 ---")
        print(f"  衰减信号数: {n_decay}")
 
        print("\n" + "=" * 60)
 
# 使用示例
np.random.seed(42)
n_days = 500
dates = pd.bdate_range('2023-01-01', periods=n_days)
 
# 回测收益
bt_returns = pd.Series(
    np.random.normal(0.001, 0.015, n_days), index=dates
)
 
# 实盘收益(扣除滑点和成本)
live_returns = pd.Series(
    bt_returns.values - 0.0003 - np.random.normal(0, 0.002, n_days),
    index=dates
)
 
# 基准收益
bm_returns = pd.Series(
    np.random.normal(0.0005, 0.012, n_days), index=dates
)
 
# 因子收益
factor_ret = pd.DataFrame(
    np.random.normal(0.001, 0.008, (n_days, 4)),
    columns=['Market', 'Size', 'Value', 'Momentum'],
    index=dates
)
 
pa = PerformanceAttribution(
    bt_returns, live_returns, bm_returns, factor_ret
)
pa.full_report()

六、关键指标速查表

指标公式/定义良好范围含义
Brinson 配置效应正值为优行业权重偏离的贡献
Brinson 选股效应正值为优行业内选股能力
Information Ratio> 0.5 为优单位跟踪误差的超额
Alpha 衰减 Z-score滚动均值偏离全样本|z| < 2 正常Alpha 稳定性
回测差异越小越好执行质量
容量参与率交易量 / 总成交量< 5% 安全流动性风险

七、实战检查清单

在上线策略后,定期进行以下检查:

┌──────────────────────────────────────────────────────────┐
│                  绩效归因检查清单                          │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  □ 每周:                                                │
│    □ 本周 Alpha vs 上周                                   │
│    □ 各因子贡献变化                                       │
│    □ 回测与实盘收益差异                                    │
│                                                          │
│  □ 每月:                                                │
│    □ Brinson 归因分析                                     │
│    □ 因子暴露偏离监控                                     │
│    □ 滚动 Sharpe / IR 趋势                                │
│    □ 策略容量检查                                        │
│                                                          │
│  □ 每季度:                                              │
│    □ 策略衰减综合评估                                     │
│    □ 交易成本 vs 预期                                     │
│    □ 与同类策略相关性                                     │
│    □ 策略参数是否需要重训练                                │
│                                                          │
└──────────────────────────────────────────────────────────┘

总结

归因维度核心工具关键洞察
收益分解Brinson 模型区分配置 vs 选股贡献
风险来源因子归因识别真正的 Alpha
Alpha 质量IR / 衰减监控Alpha 稳定性比大小更重要
执行质量实盘差异分析成本是实盘的头号敌人
持续监控滚动指标早发现早处理

核心原则:不知道钱从哪里来的收益,是最危险的收益。定期归因是策略长期生存的保障。

模块完结:恭喜你完成了风险管理模块的全部学习。回顾 模块首页 巩固知识,或前往其他模块继续学习。