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()} 个违约案例")
关键要点:上述代码展示了样本构建的完整流程——先按时效性切分窗口,再按相关性筛选变量,最后通过代表性校验进行分层调整。尾部风险覆盖通过保留足够多的违约案例来实现,确保压力测试模型能够学习到极端事件的模式。