2. 坏账率基础:坏账率计算公式、滚动率与迁移率、Vintage分析在监控中的应用
坏账率是量化风控策略最核心的监控指标之一。但仅看一个静态的坏账率数字,往往无法判断风险是真实恶化还是资产周期波动所致。本章从坏账率的基础公式出发,引入滚动率、迁移率与Vintage分析,构建一套完整的坏账漂移监控知识体系。
2.1 坏账率计算公式
坏账率(Bad Rate)通常定义为逾期超过某一阈值(如M3+、M6+)的资产余额占总资产余额的比例。根据业务场景不同,存在三种主流计算口径:
| 口径 | 公式 | 适用场景 |
|---|---|---|
| 余额口径 | 逾期M3+余额 / 总在贷余额 | 存量资产质量快照 |
| 件数口径 | 逾期M3+合同数 / 总在贷合同数 | 小额分散型资产 |
| 首期逾期率 | 首期逾期金额 / 当期放款金额 | 早期风险预警 |
监控要点:余额口径受大额资产影响大,件数口径更反映客户违约比例。建议同时监控两种口径,避免单一指标失真。
2.2 滚动率与迁移率
滚动率(Roll Rate)和迁移率(Transition Rate)是描述资产在不同逾期状态之间转移概率的核心工具。滚动率衡量的是某一逾期阶段的资产在下一个观察周期进入更严重逾期阶段的比例。
滚动率矩阵示例(月度观察):
| 当前状态 | → 正常 | → M1 | → M2 | → M3+ |
|---|---|---|---|---|
| 正常 | 97.2% | 2.5% | 0.2% | 0.1% |
| M1 | 35.0% | 40.0% | 20.0% | 5.0% |
| M2 | 10.0% | 15.0% | 45.0% | 30.0% |
| M3+ | 2.0% | 3.0% | 5.0% | 90.0% |
迁移率则更关注资产从某一逾期状态向下一状态转移的净比例,常用于计算最终损失率。例如:M1→M2迁移率 = 本期M2资产中来自上期M1的金额 / 上期M1总金额。
漂移监控应用:当滚动率或迁移率出现连续2个月以上的显著上升(如M1→M2迁移率从20%升至30%),通常意味着策略或客群出现系统性风险漂移,需要立即触发预警。
2.3 Vintage分析在监控中的应用
Vintage分析(账龄分析)是坏账率漂移监控最强大的工具之一。它将不同时期(通常按月)放款的资产按账龄(MOB,Month on Book)对齐,观察同一账龄下的坏账率变化趋势。
Vintage分析的核心价值:
- 剥离时间效应:消除资产成熟度差异,使不同月份放款在相同账龄下可比。
- 早期预警:MOB3~MOB6的Vintage曲线若出现上翘,预示最终坏账率可能恶化。
- 策略效果评估:收紧策略后的Vintage曲线应整体下移,反之则上移。
常见误区:不要直接用总在贷坏账率判断策略好坏。如果近期放款量大增,即使新客质量不变,总坏账率也会因资产年轻化而被动下降,造成“虚假好转”。Vintage分析能有效避免这种幸存者偏差。
2.4 坏账率漂移监控的三大指标联动
在实际监控中,坏账率、滚动率/迁移率、Vintage曲线需要联动分析,才能准确判断风险漂移的性质和来源。
| 监控场景 | 坏账率 | 滚动率/迁移率 | Vintage曲线 | 可能原因 |
|---|---|---|---|---|
| 客群质量恶化 | 上升 | 各阶段迁移率均上升 | 近期Vintage上翘 | 获客策略放宽或渠道质量下降 |
| 催收能力下降 | 上升 | M1→M2迁移率上升,但M0→M1正常 | Vintage曲线中后期恶化 | 催收团队或策略失效 |
| 经济周期影响 | 上升 | 所有迁移率同步上升 | 所有Vintage曲线同步上移 | 宏观环境恶化,需调整风险定价 |
| 资产结构变化 | 波动 | 部分客群迁移率异常 | 分客群Vintage分化 | 产品结构调整或新客群引入 |
2.5 实战代码:计算滚动率与Vintage曲线
以下Python代码演示如何从资产明细数据中计算滚动率矩阵和Vintage坏账率曲线:
import pandas as pd
import numpy as np
# 示例数据:每笔贷款的月度状态
# 字段:loan_id, month, status (0=正常, 1=M1, 2=M2, 3=M3+)
df = pd.read_csv('loan_monthly_status.csv')
# 1. 计算滚动率矩阵
def calc_roll_rate(df, current_month, next_month):
"""计算从current_month到next_month的滚动率"""
cur = df[df['month'] == current_month].set_index('loan_id')
nxt = df[df['month'] == next_month].set_index('loan_id')
merged = cur.join(nxt, how='inner', lsuffix='_cur', rsuffix='_nxt')
roll_matrix = {}
for status in [0, 1, 2]:
subset = merged[merged['status_cur'] == status]
if len(subset) == 0:
continue
total = len(subset)
for target in [0, 1, 2, 3]:
count = len(subset[subset['status_nxt'] == target])
roll_matrix[f'{status}→{target}'] = round(count / total * 100, 1)
return roll_matrix
# 2. 计算Vintage坏账率
def calc_vintage_badrate(df, max_mob=12):
"""按月放款月份和账龄计算坏账率"""
df['vintage_month'] = df.groupby('loan_id')['month'].transform('min')
df['mob'] = df.groupby('loan_id')['month'].cumcount() + 1
vintage = df[df['mob'] <= max_mob].groupby(
['vintage_month', 'mob']
)['status'].apply(lambda x: (x >= 3).mean() * 100).reset_index()
vintage.columns = ['vintage_month', 'mob', 'bad_rate']
return vintage.pivot(index='mob', columns='vintage_month', values='bad_rate')
# 输出滚动率
print("滚动率矩阵(M1→M2迁移率):", calc_roll_rate(df, '2024-01', '2024-02'))
# 输出Vintage表
vintage_table = calc_vintage_badrate(df)
print("\nVintage坏账率表(前12期):")
print(vintage_table.head(12))
监控哨兵建议:将滚动率计算和Vintage分析封装为自动化脚本,每日/每周运行。当M1→M2迁移率连续2周超过历史均值+2倍标准差时,自动触发漂移预警,并推送至风控决策引擎。