2. 样本构建基础:压力测试样本的概念、样本与全量数据的区别、样本构建的基本原则

2.1 压力测试样本的概念

在金融压力测试中,样本并非简单地从全量数据中随机抽取,而是指为了评估特定极端情景下的风险暴露,从总体数据中按照特定规则筛选出的、具有代表性的数据子集。样本构建是压力测试流程的起点,其质量直接决定了风险度量的准确性与监管合规的有效性。

核心定义:压力测试样本 = 目标总体 × 情景约束 × 代表性规则 × 时效窗口

一个典型的压力测试样本通常包含以下要素:

  • 目标总体:需要评估的资产组合、交易对手或业务条线(如:对公贷款组合、零售按揭池)。
  • 情景约束:压力情景下的关键风险因子(如:GDP下降5%、失业率上升至10%)。
  • 代表性规则:确保样本在风险特征、行业分布、期限结构等方面与总体保持一致。
  • 时效窗口:样本数据的时间跨度需覆盖至少一个完整经济周期,通常包含压力期数据。
压力测试样本概念结构图 压力测试样本概念结构 压力测试样本 目标总体 情景约束 代表性规则 时效窗口 样本 = 目标总体 ∩ 情景约束 ∩ 代表性规则 ∩ 时效窗口 四者缺一不可,共同决定样本的有效性与合规性

2.2 样本与全量数据的区别

理解样本与全量数据的差异,是构建高质量压力测试样本的前提。全量数据包含所有可用的历史记录,而样本则是经过筛选、清洗和结构化后的子集。两者在多个维度上存在本质区别:

对比维度 全量数据 压力测试样本
数据规模 包含所有历史交易、客户、合约记录,通常达数亿至数十亿条 按风险特征筛选后的子集,通常为数千至数十万条
时间跨度 覆盖系统上线至今的全部历史 仅覆盖包含压力事件的关键窗口(如2008年金融危机、2020年疫情)
数据质量 包含大量缺失值、异常值、重复记录 经过严格清洗、去重、插补,满足建模要求
风险覆盖 以正常市场状态为主,极端事件占比极低 通过过采样或情景生成,确保极端风险事件有足够代表性
计算效率 直接建模耗时巨大,难以迭代 可在合理时间内完成模型训练与情景模拟
监管合规 原始数据通常不满足监管报送格式 按监管模板(如CCAR、DFAST)结构化输出
常见误区:不要认为样本越大越好。全量数据中的噪声、冗余和缺失会稀释压力情景的信号。一个精心构建的10万条样本,其风险区分能力往往优于未经处理的1000万条全量数据。

2.3 样本构建的基本原则

构建压力测试样本需遵循三大核心原则:代表性、相关性和时效性。这三者构成样本质量的“铁三角”,缺一不可。

2.3.1 代表性(Representativeness)

样本必须在关键风险维度上反映总体的分布特征,避免选择偏差。具体包括:

  • 风险特征分布:样本中不同信用评级、行业、地区的占比应与总体一致。
  • 尾部风险覆盖:通过分层抽样或重要性抽样,确保高损失、高违约的尾部案例被充分纳入。
  • 周期性平衡:样本需同时包含扩张期和衰退期的数据,避免仅选取“好年份”。

2.3.2 相关性(Relevance)

样本中的变量和事件必须与所评估的压力情景高度相关。相关性原则要求:

  • 情景驱动筛选:根据压力情景中的风险因子(如利率、汇率、失业率),选择受其影响最大的资产或交易。
  • 变量对齐:样本中的特征变量应与压力传导模型中的输入变量保持一致。
  • 业务逻辑验证:样本筛选规则需经过业务专家评审,确保符合实际风险传导路径。

2.3.3 时效性(Timeliness)

样本数据必须反映最新的风险状况和宏观经济环境。时效性原则包含:

  • 数据新鲜度:样本应包含最近一个完整季度的数据,对于快速变化的风险(如市场风险),需使用月度或周度数据。
  • 窗口对齐:样本的时间窗口应与压力测试的预测窗口一致(如前瞻性1年、3年)。
  • 动态更新:样本构建不是一次性工作,需建立定期刷新机制,确保每次压力测试都使用最新数据。
样本构建铁三角原则 样本构建铁三角原则 代表性 相关性 时效性 样本质量 = 三者交集 代表性:风险分布与总体一致 相关性:变量与情景高度对齐 时效性:数据反映最新风险状况

2.4 样本构建的实践检查清单

在实际项目中,建议按照以下清单逐项验证样本质量:

检查项 对应原则 验证方法 通过标准
行业分布偏差 代表性 卡方检验或KS检验 p值 > 0.05
尾部风险覆盖 代表性 检查样本中违约/损失案例占比 占比 ≥ 总体尾部概率
变量相关性 相关性 计算样本变量与情景因子的相关系数 |r| > 0.3
数据新鲜度 时效性 检查样本中最新数据日期 距当前 ≤ 90天
周期性覆盖 时效性 确认样本包含至少1个完整经济周期 ≥ 5年数据
实战建议:在构建样本时,先使用全量数据生成一份“总体画像报告”,包含各维度的分布统计。然后构建样本后,生成同样的报告进行对比。如果某个维度的偏差超过5%,则需要调整抽样策略或进行事后加权校正。

2.5 样本构建的代码示例(Python伪代码)

以下代码展示了如何基于代表性、相关性和时效性原则,从全量数据中筛选压力测试样本:

import pandas as pd
import numpy as np
from scipy.stats import ks_2samp

def build_stress_test_sample(full_data, scenario_factors, time_window):
    """
    构建压力测试样本
    :param full_data: DataFrame, 全量数据
    :param scenario_factors: list, 压力情景相关因子列表
    :param time_window: tuple, (start_date, end_date)
    :return: DataFrame, 构建好的样本
    """
    # 1. 时效性筛选:只保留压力窗口内的数据
    mask_time = (full_data['date'] >= time_window[0]) & (full_data['date'] <= time_window[1])
    sample = full_data[mask_time].copy()
    
    # 2. 相关性筛选:保留与情景因子高度相关的变量
    relevant_cols = ['loan_id', 'default_flag', 'loss_amount'] + scenario_factors
    sample = sample[relevant_cols]
    
    # 3. 代表性校验:使用KS检验检查行业分布
    industry_dist_full = full_data['industry'].value_counts(normalize=True)
    industry_dist_sample = sample['industry'].value_counts(normalize=True)
    
    # 如果某个行业偏差超过5%,进行分层重抽样
    for industry in industry_dist_full.index:
        if abs(industry_dist_sample.get(industry, 0) - industry_dist_full[industry]) > 0.05:
            # 对该行业进行过采样或欠采样
            industry_data = sample[sample['industry'] == industry]
            target_count = int(len(sample) * industry_dist_full[industry])
            if len(industry_data) < target_count:
                # 过采样
                sample = pd.concat([sample, industry_data.sample(target_count - len(industry_data), replace=True)])
            else:
                # 欠采样
                sample = sample.drop(industry_data.sample(len(industry_data) - target_count).index)
    
    # 4. 尾部风险覆盖:确保高违约案例被保留
    high_risk_mask = sample['default_flag'] == 1
    if high_risk_mask.sum() < 100:  # 至少保留100个违约案例
        high_risk_cases = full_data[full_data['default_flag'] == 1].sample(100 - high_risk_mask.sum(), replace=True)
        sample = pd.concat([sample, high_risk_cases])
    
    return sample

# 使用示例
full_loan_data = pd.read_csv('loan_portfolio.csv')
scenario = ['gdp_growth', 'unemployment_rate', 'interest_rate']
window = ('2019-01-01', '2023-12-31')
stress_sample = build_stress_test_sample(full_loan_data, scenario, window)
print(f"样本构建完成:{len(stress_sample)} 条记录,{stress_sample['default_flag'].sum()} 个违约案例")
关键要点:上述代码展示了样本构建的完整流程——先按时效性切分窗口,再按相关性筛选变量,最后通过代表性校验进行分层调整。尾部风险覆盖通过保留足够多的违约案例来实现,确保压力测试模型能够学习到极端事件的模式。