4. 坏账率定义与计算:在量化交易场景下,坏账率的业务定义与滚动计算方法

在量化交易与信贷风控的交叉场景中,坏账率是衡量资产质量恶化的核心指标。与传统的“逾期天数”定义不同,高频行情下的坏账率需要结合盯市价值(Mark-to-Market)与滚动时间窗口进行动态计算,以捕捉资产在极端行情下的瞬时违约风险。

4.1 业务定义:什么是“坏账”?

在量化风控体系中,一笔资产被标记为“坏账”需同时满足以下两个条件:

  • 条件一:盯市亏损超过阈值 —— 当前持仓市值相对于初始本金或保证金的比例跌破预设警戒线(例如:亏损 > 80%)。
  • 条件二:流动性枯竭 —— 连续 N 个交易时段内无法以合理价格平仓,或买卖价差(Bid-Ask Spread)超过正常范围。
业务提示: 在纯高频交易场景下,通常将“连续 3 个最小交易周期(如 5 分钟 K 线)内无法以市价单成交”视为流动性枯竭的判定标准。

4.2 坏账率的计算公式

坏账率定义为:在特定时间窗口内,被标记为“坏账”的资产数量(或名义本金)占该窗口期初总资产数量(或总名义本金)的比例。

基础公式:

BadDebtRate(t) = (累计坏账资产数 / 期初总资产数) × 100%

其中:

  • 累计坏账资产数:在时间窗口 [t - window, t] 内,首次触发坏账条件的资产个数。
  • 期初总资产数:窗口起始时刻 t - window 时的活跃资产总数。

4.3 滚动计算方法:为什么不用固定周期?

固定周期(如每月、每季度)计算坏账率存在严重的滞后性和边界效应。在行情剧烈波动时,固定周期无法及时反映资产质量的瞬时恶化。因此,量化风控系统普遍采用滚动窗口(Rolling Window)计算。

滚动计算的核心逻辑:

  1. 设定一个固定的时间窗口长度(例如:过去 24 小时、过去 100 个交易周期)。
  2. 每产生一个新的交易数据点(如每分钟),窗口向前滑动一个单位。
  3. 剔除窗口最旧的数据,加入最新的数据,重新计算窗口内的坏账率。
关键参数: 窗口长度(Window Size)的选择直接影响指标的敏感度。窗口越短,对瞬时波动的反应越快,但噪音也越大;窗口越长,曲线越平滑,但信号延迟越严重。

4.4 滚动坏账率计算流程

以下表格展示了滚动计算的核心步骤与数据流:

步骤 操作 输入 输出
1 获取最新行情快照 当前时间戳、持仓市值、保证金 盯市盈亏比例
2 判断是否触发坏账条件 盯市盈亏比例、流动性指标 坏账标记(0/1)
3 更新滚动窗口队列 当前坏账标记、窗口长度 N 最新 N 个周期的坏账标记序列
4 计算窗口内累计坏账数 坏账标记序列 累计坏账资产数
5 计算滚动坏账率 累计坏账数、期初总资产数 当前时刻的坏账率

4.5 滚动坏账率计算流程图

下图展示了从行情数据输入到坏账率输出的完整滚动计算链路:

滚动坏账率计算流程图 滚动坏账率计算流程图 行情数据输入 盯市盈亏计算 坏账条件判断(阈值+流动性) 滚动窗口队列更新(N周期) 输出滚动坏账率

4.6 Python 实现:滚动坏账率计算函数

以下代码实现了基于 deque 的滚动窗口坏账率计算,适用于高频数据流场景:

from collections import deque
import numpy as np

class RollingBadDebtRate:
    """
    滚动坏账率计算器
    """
    def __init__(self, window_size: int = 100):
        self.window_size = window_size
        self.bad_debt_flags = deque(maxlen=window_size)
        self.initial_asset_count = None

    def update(self, current_asset_count: int, 
               mark_to_market_loss_pct: float,
               liquidity_ok: bool) -> float:
        """
        更新滚动窗口并返回当前坏账率
        
        参数:
            current_asset_count: 当前总资产数
            mark_to_market_loss_pct: 盯市亏损比例 (0~1)
            liquidity_ok: 流动性是否正常 (True=正常, False=枯竭)
        
        返回:
            当前滚动坏账率 (百分比)
        """
        # 初始化期初资产数
        if self.initial_asset_count is None:
            self.initial_asset_count = current_asset_count

        # 判断是否触发坏账条件
        is_bad_debt = (mark_to_market_loss_pct > 0.8) and (not liquidity_ok)
        self.bad_debt_flags.append(1 if is_bad_debt else 0)

        # 计算滚动坏账率
        if len(self.bad_debt_flags) < self.window_size:
            # 窗口未满时,使用已有数据计算
            cumulative_bad = sum(self.bad_debt_flags)
            total_assets = self.initial_asset_count
        else:
            cumulative_bad = sum(self.bad_debt_flags)
            total_assets = self.initial_asset_count

        bad_debt_rate = (cumulative_bad / total_assets) * 100.0
        return round(bad_debt_rate, 4)

# 使用示例
if __name__ == "__main__":
    rbd = RollingBadDebtRate(window_size=50)
    
    # 模拟高频行情数据流
    for i in range(100):
        loss = np.random.uniform(0.7, 0.95)
        liquid = np.random.choice([True, False], p=[0.7, 0.3])
        rate = rbd.update(current_asset_count=1000, 
                          mark_to_market_loss_pct=loss,
                          liquidity_ok=liquid)
        if i % 20 == 0:
            print(f"第 {i:3d} 周期: 坏账率 = {rate:.2f}%")
注意: 在实际生产环境中,initial_asset_count 应随窗口滑动进行动态调整,而非固定为初始值。上述示例为简化教学版本,生产代码需增加“窗口内期初资产数”的滚动更新逻辑。

4.7 滚动窗口 vs 固定周期对比

对比维度 固定周期(月/季) 滚动窗口(N 周期)
数据时效性 滞后严重(月底才出结果) 实时更新(每周期输出)
边界效应 存在“月初/月末”跳跃 平滑过渡,无跳跃
对极端行情的敏感度 低(被周期内均值稀释) 高(窗口短时即可反映)
计算复杂度 低(一次性全量计算) 中(需维护队列状态)
适用场景 低频、长周期风控报告 高频、实时风控监控

4.8 坏账率漂移监控的输入输出关系

下图展示了坏账率作为 PSI 监控的输入指标,如何与后续的漂移检测模块衔接:

坏账率与PSI漂移监控关系图 坏账率 → PSI 漂移监控数据流 高频行情数据流 滚动坏账率计算 坏账率时间序列 PSI 漂移检测 反馈调整窗口参数

4.9 常见陷阱与避坑指南

  • 陷阱一:窗口长度选择不当 —— 窗口过短导致坏账率剧烈抖动,产生大量误报;窗口过长则无法及时捕捉漂移。建议通过历史回测选择使 KS 统计量最大的窗口长度。
  • 陷阱二:忽略期初资产数的动态更新 —— 若窗口内总资产数发生剧烈变化(如大规模赎回),固定期初值会导致坏账率失真。应使用窗口起始时刻的实际资产数。
  • 陷阱三:流动性判断过于简化 —— 仅用“能否成交”作为流动性标准可能误判。建议结合买卖价差、订单簿深度、成交量等综合指标。
最佳实践: 在量化风控系统中,建议同时维护多个不同窗口长度(如 1 小时、4 小时、24 小时)的滚动坏账率,形成“短-中-长”三层监控体系,以平衡敏感度与稳定性。