2. 核心概念:坏账率定义、高频行情特征、PSI(群体稳定性指标)数学原理
在构建高频行情坏账率监控体系之前,必须统一三个核心概念的定义与数学基础。坏账率是监控的目标变量,高频行情是监控的数据环境,PSI 是监控的量化工具。三者缺一不可。
2.1 坏账率定义与计算口径
坏账率(Bad Rate)在量化风控中通常指逾期超过特定天数的资产占总资产的比例。但在高频行情场景下,坏账率的计算窗口需要从“月”压缩到“小时”甚至“分钟”。
| 口径类型 | 计算公式 | 适用场景 | 高频化改造 |
|---|---|---|---|
| 滚动坏账率 | 滚动坏账率 = 当前逾期 ≥ M 的订单数 / 当前在贷订单数 | 日频、周频监控 | 按分钟切片,计算滑动窗口内的坏账占比 |
| Vintage 坏账率 | Vintage 坏账率 = 某放款批次逾期 ≥ M 的订单数 / 该批次总放款数 | 批次效果评估 | 按小时批次划分,缩短观察期至 24h |
| 即期坏账率 | 即期坏账率 = 当前逾期 ≥ M 的订单数 / 当前应还订单数 | 实时风险预警 | 每 5 分钟计算一次,用于触发漂移告警 |
实操建议:高频监控中优先使用即期坏账率作为 PSI 计算的目标变量,因为它对行情变化最敏感,且计算延迟最低。
2.2 高频行情特征
高频行情(High-Frequency Quote)区别于传统日频行情,具有以下显著特征,这些特征直接决定了 PSI 监控的采样策略和计算频率。
- 高数据密度:每秒产生数千笔报价与成交记录,数据量是日频行情的 10⁴~10⁶ 倍。
- 强自相关性:相邻 tick 的价格变化极小,但累积效应显著,容易造成 PSI 的“伪漂移”。
- 非均匀时间间隔:交易活跃时段数据密集,清淡时段稀疏,需要按事件驱动而非固定时间窗采样。
- 微观结构噪声:买卖价差、订单簿深度变化、大单冲击等微观结构因素会干扰坏账率的真实分布。
- 多品种联动:股票、期货、期权等品种之间的价差与相关性在高频下更加紧密。
关键认知:高频行情下的 PSI 监控不能直接套用日频监控的“按月/按周”分箱策略,必须引入自适应分箱和滑动窗口机制,否则 PSI 会因数据密度波动而频繁误报。
2.3 PSI(群体稳定性指标)数学原理
PSI(Population Stability Index)用于衡量两个样本群体在某个特征或评分上的分布差异。在坏账率监控中,PSI 衡量当前时段坏账率分布与基准时段坏账率分布的偏移程度。
2.3.1 标准 PSI 计算公式
PSI 的计算基于等宽分箱或等频分箱后的分布对比,公式如下:
PSI = Σ (Actual_i - Expected_i) × ln(Actual_i / Expected_i)
其中:
- Actual_i = 当前时段第 i 个分箱的样本占比
- Expected_i = 基准时段第 i 个分箱的样本占比
- i = 分箱编号(通常 10~20 箱)
- ln = 自然对数
2.3.2 分箱策略对比
| 分箱方式 | 原理 | 高频行情适用性 | PSI 稳定性 |
|---|---|---|---|
| 等宽分箱 | 按坏账率数值等距划分(如 0-5%, 5-10%...) | 较差,高频数据分布偏态严重时多数样本集中在少数箱 | 低,易出现空箱导致 PSI 计算异常 |
| 等频分箱 | 按样本量等比例划分(每箱约 10% 样本) | 较好,能自适应数据密度变化 | 高,推荐用于高频场景 |
| 自定义分箱 | 根据业务经验设定坏账率阈值边界 | 中等,需定期维护分箱边界 | 中等,依赖专家判断 |
2.3.3 PSI 阈值判定标准
- PSI < 0.1:分布无显著变化,监控通过。
- 0.1 ≤ PSI < 0.25:轻度漂移,需关注并排查原因。
- PSI ≥ 0.25:严重漂移,触发告警并启动模型或策略重审。
高频场景特殊说明:在高频行情下,由于数据波动剧烈,建议将阈值适当放宽。例如:PSI < 0.15 视为正常,0.15~0.35 为关注,≥ 0.35 为告警。具体阈值需根据历史回测结果调整。
2.4 高频场景下 PSI 计算的 Python 实现
以下代码展示了如何在 Python 中实现高频行情坏账率的 PSI 计算,包含等频分箱和滑动窗口机制。
import numpy as np
import pandas as pd
def calculate_psi_highfreq(actual, expected, bins=10, method='quantile'):
"""
高频行情 PSI 计算函数(支持等频分箱)
Parameters:
-----------
actual : array-like
当前时段坏账率样本
expected : array-like
基准时段坏账率样本
bins : int
分箱数量,默认 10
method : str
分箱方法,'quantile' 等频分箱(推荐),'uniform' 等宽分箱
Returns:
--------
psi : float
PSI 值
breakdown : pd.DataFrame
每个分箱的详细占比与贡献
"""
if method == 'quantile':
# 等频分箱:基于基准分布的分位数
percentiles = np.linspace(0, 100, bins + 1)
bin_edges = np.percentile(expected, percentiles)
# 处理边界重复(高频数据常见)
bin_edges = np.unique(bin_edges)
else:
# 等宽分箱
bin_edges = np.linspace(min(expected.min(), actual.min()),
max(expected.max(), actual.max()),
bins + 1)
# 分箱计数
expected_counts, _ = np.histogram(expected, bins=bin_edges)
actual_counts, _ = np.histogram(actual, bins=bin_edges)
# 计算占比(加平滑避免除零)
epsilon = 1e-6
expected_pct = expected_counts / len(expected) + epsilon
actual_pct = actual_counts / len(actual) + epsilon
# 计算 PSI
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
# 构建明细表
breakdown = pd.DataFrame({
'bin_start': bin_edges[:-1],
'bin_end': bin_edges[1:],
'expected_count': expected_counts,
'actual_count': actual_counts,
'expected_pct': expected_pct - epsilon,
'actual_pct': actual_pct - epsilon,
'psi_contrib': (actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
})
return psi, breakdown
# 示例:模拟高频行情坏账率数据
np.random.seed(42)
expected_badrate = np.random.beta(2, 98, 10000) # 基准分布,均值约 2%
actual_badrate = np.random.beta(2.5, 97.5, 10000) # 当前分布,均值约 2.5%
psi_value, psi_df = calculate_psi_highfreq(actual_badrate, expected_badrate, bins=10)
print(f"PSI 值: {psi_value:.4f}")
print(psi_df.head())
代码要点:
- 使用
np.percentile实现等频分箱,适应高频数据的偏态分布。 - 加入
epsilon平滑项,避免因空箱导致的对数计算错误。 - 返回明细 DataFrame 便于后续排查哪个分箱贡献了主要漂移。
2.5 核心概念总结
| 概念 | 定义要点 | 高频场景关键调整 | 与 PSI 的关系 |
|---|---|---|---|
| 坏账率 | 逾期 ≥ M 的订单占比 | 使用即期口径,分钟级计算 | PSI 计算的目标变量 |
| 高频行情 | 高密度、强自相关、非均匀、多噪声 | 自适应分箱、滑动窗口、事件驱动采样 | 决定 PSI 的采样与分箱策略 |
| PSI | 分布偏移量化指标 | 阈值放宽、等频分箱、平滑处理 | 坏账率漂移的量化工具 |