3. 坏账率计算模块:逐笔交易坏账标记、滚动窗口坏账率计算、加权坏账率

坏账率是量化策略回测中衡量信用风险的核心指标。本模块从三个层次构建坏账率计算体系:逐笔交易坏账标记(微观判定)、滚动窗口坏账率计算(时序聚合)、加权坏账率(风险暴露调整)。三者形成从单笔到组合、从静态到动态的完整计算链路。

3.1 逐笔交易坏账标记

坏账标记是后续所有计算的基石。每笔交易在生命周期结束后,根据逾期状态和回收情况判定是否标记为坏账。判定规则如下表:

判定条件 坏账标记值 说明
逾期天数 >= 90 且未全额回收 1(坏账) 符合监管对不良贷款的认定标准
逾期天数 < 90 或已全额回收 0(正常) 未达到坏账阈值或风险已化解
交易未到期(仍在还款中) 0(暂不标记) 仅对已结清或已逾期终止的交易判定
关键实践:坏账标记必须与策略的持有期对齐。对于滚动策略,建议在交易退出(平仓或到期)后延迟一个观察窗口再标记,避免因回收延迟导致误判。

以下代码实现逐笔交易的坏账标记逻辑:

def mark_bad_debt(trades_df, overdue_threshold=90):
    """
    逐笔交易坏账标记
    trades_df: 包含 'overdue_days', 'recovery_rate', 'status' 列的 DataFrame
    """
    import numpy as np
    conditions = [
        (trades_df['overdue_days'] >= overdue_threshold) & (trades_df['recovery_rate'] < 1.0),
        (trades_df['overdue_days'] < overdue_threshold) | (trades_df['recovery_rate'] == 1.0),
        (trades_df['status'] == 'active')
    ]
    choices = [1, 0, 0]
    trades_df['bad_debt_flag'] = np.select(conditions, choices, default=0)
    return trades_df

3.2 滚动窗口坏账率计算

滚动窗口坏账率用于捕捉坏账率随时间的变化趋势,避免静态截面指标掩盖风险积累过程。核心参数包括窗口长度、步长和聚合方式。

计算步骤:

  1. 确定窗口参数:窗口长度(如 30 天、90 天)、滑动步长(如 7 天)。
  2. 窗口内聚合:对每个窗口内的交易,计算坏账笔数 / 总笔数(笔数口径)或坏账金额 / 总金额(金额口径)。
  3. 时间对齐:将窗口坏账率对齐到窗口结束日期,形成时序序列。
推荐口径:金额口径坏账率更能反映风险暴露的真实影响,尤其当单笔交易金额差异较大时。笔数口径适合作为辅助监控指标。

滚动窗口坏账率计算示例:

def rolling_bad_debt_rate(trades_df, window_days=90, step_days=7):
    """
    滚动窗口坏账率计算(金额口径)
    """
    trades_df = trades_df.sort_values('exit_date')
    results = []
    max_date = trades_df['exit_date'].max()
    min_date = trades_df['exit_date'].min()
    current_start = min_date

    while current_start + pd.Timedelta(days=window_days) <= max_date:
        window_end = current_start + pd.Timedelta(days=window_days)
        window_data = trades_df[
            (trades_df['exit_date'] >= current_start) &
            (trades_df['exit_date'] < window_end)
        ]
        if len(window_data) > 0:
            bad_amount = window_data.loc[window_data['bad_debt_flag'] == 1, 'amount'].sum()
            total_amount = window_data['amount'].sum()
            rate = bad_amount / total_amount if total_amount > 0 else 0.0
            results.append({
                'window_end': window_end,
                'bad_debt_rate': rate,
                'trade_count': len(window_data)
            })
        current_start += pd.Timedelta(days=step_days)
    return pd.DataFrame(results)
滚动窗口坏账率计算流程 窗口 1(第 1-90 天) 窗口 2(第 8-97 天) 窗口 3(第 15-104 天) …… 窗口 N 步长 = 7 天 输出:窗口结束日期 → 坏账率(金额/笔数)→ 时序序列 用于趋势监控、阈值告警、回测绩效归因

3.3 加权坏账率

加权坏账率在滚动窗口基础上引入风险暴露权重,解决不同时期、不同产品风险敞口不均等的问题。常见加权方式包括:

  • 余额加权:以窗口内每日未偿还余额为权重,反映资金占用期间的真实风险。
  • 时间加权:以交易存续天数为权重,长周期交易对坏账率贡献更大。
  • 风险敞口加权:以授信额度或风险价值(VaR)为权重,适用于组合层面风险度量。
加权方式 权重因子 适用场景 计算复杂度
余额加权 每日未偿还余额 循环贷、信用卡分期 中
时间加权 交易存续天数 固定期限贷款、债券 低
风险敞口加权 授信额度 / VaR 组合风控、压力测试 高
避坑提示:加权坏账率不能直接与简单坏账率比较。当风险敞口集中在高坏账率区间时,加权坏账率会显著高于简单坏账率,这是正常现象,反映风险集中度。在哨兵告警阈值调优时,建议对加权坏账率设置更宽松的初始阈值。

余额加权坏账率计算实现:

def weighted_bad_debt_rate(trades_df, daily_balance_df, window_days=90):
    """
    余额加权坏账率计算
    daily_balance_df: 包含 'date', 'trade_id', 'balance' 的每日余额表
    """
    # 标记坏账交易
    trades_df = mark_bad_debt(trades_df)
    bad_trade_ids = trades_df[trades_df['bad_debt_flag'] == 1]['trade_id'].unique()

    # 计算窗口内每日坏账余额占比
    daily_balance_df['is_bad'] = daily_balance_df['trade_id'].isin(bad_trade_ids).astype(int)
    daily_balance_df['bad_balance'] = daily_balance_df['balance'] * daily_balance_df['is_bad']

    # 滚动窗口聚合
    daily_agg = daily_balance_df.groupby('date').agg(
        total_balance=('balance', 'sum'),
        total_bad_balance=('bad_balance', 'sum')
    ).reset_index()

    daily_agg['weighted_rate'] = daily_agg['total_bad_balance'] / daily_agg['total_balance']
    daily_agg['weighted_rate'] = daily_agg['weighted_rate'].fillna(0)

    # 窗口平均
    daily_agg['rolling_weighted_rate'] = (
        daily_agg['weighted_rate']
        .rolling(window=window_days, min_periods=1)
        .mean()
    )
    return daily_agg[['date', 'rolling_weighted_rate']]
加权坏账率计算架构 逐笔交易坏账标记 bad_debt_flag = 0/1 每日余额表 trade_id + date + balance 权重因子配置 余额/时间/敞口 加权聚合引擎 坏账余额 / 总余额 → 日度加权率 → 滚动窗口平均 输出:加权坏账率时序 用于哨兵告警阈值设定与回测绩效评估

3.4 三种坏账率的对比与选择

指标 计算粒度 时间维度 权重调整 告警灵敏度 推荐用途
逐笔坏账标记 单笔交易 静态(交易结束时) 无 低(离散信号) 数据清洗、基础统计
滚动窗口坏账率 窗口聚合 动态(时序滑动) 无 中(趋势敏感) 日常监控、阈值告警
加权坏账率 窗口聚合 动态(时序滑动) 有(余额/时间/敞口) 高(暴露敏感) 组合风控、压力测试
最佳实践:在哨兵告警系统中,建议同时监控滚动窗口坏账率(趋势)和加权坏账率(风险暴露),设置双层阈值。当加权坏账率超过滚动坏账率 1.5 倍时,触发风险集中度预警。