第19章:压力测试与情景分析:历史情景、假设情景、极端市场条件模拟
做市商这行,说白了就是跟不确定性打交道。你设计的定价模型再漂亮,希腊值对冲得再完美,一旦市场出现极端行情,所有假设都可能瞬间崩塌。我见过太多团队,平时回测曲线漂亮得不行,结果一次黑天鹅事件就把几年的利润全吐回去了。
所以,压力测试和情景分析不是可选项,而是必修课。今天我就把这块的核心逻辑和实操方法掰开揉碎讲清楚。
19.1 为什么压力测试是做市商的“安全气囊”?
先问个问题:你的模型在正常市场里跑得好好的,凭什么要花精力做压力测试?
原因很简单——市场不会永远正常。2015年瑞郎黑天鹅、2020年原油负价格、2023年硅谷银行事件,每一次都让大批做市商爆仓。我自己的团队在2020年3月就吃过亏,当时VIX飙到80以上,我们持有的期权头寸Delta对冲策略完全失效,因为流动性枯竭了,你根本没法按理论价格平仓。
压力测试的核心目的就三个:
- 识别脆弱点:哪些参数变动会要了你的命?
- 量化极端损失:最坏情况下亏多少?能不能扛住?
- 优化风控参数:止损线、保证金比例、头寸限额该怎么设?
核心观点:压力测试不是为了预测未来,而是为了确保你在最坏情况下还能活着。
19.2 三种情景类型:历史、假设、极端
我个人习惯把情景分成三类,每一类的用途和构建方法都不一样。
19.2.1 历史情景
历史情景最简单——直接拿过去发生过的极端行情来测试。比如:
- 2008年雷曼倒闭时的市场波动
- 2011年欧债危机时的信用利差飙升
- 2020年3月新冠疫情引发的流动性危机
具体做法是:把历史某段时间内所有资产的价格变动、波动率曲面变化、相关性矩阵全部提取出来,然后施加到当前持仓上。
# 伪代码示例:历史情景回测
def historical_scenario(portfolio, date_range):
# 提取历史数据
hist_data = load_market_data(date_range)
# 对每个时间点计算组合损益
pnl_series = []
for t in hist_data:
new_prices = apply_shock(portfolio.current_prices, t.returns)
pnl = calculate_pnl(portfolio, new_prices)
pnl_series.append(pnl)
# 找出最大回撤和VaR
max_drawdown = min(pnl_series)
var_95 = np.percentile(pnl_series, 5)
return max_drawdown, var_95
这里有个坑——历史不会简单重复。2008年的危机模式,放到今天的市场结构下可能完全不一样。所以我建议把历史情景当作“最低标准”,而不是全部。
19.2.2 假设情景
假设情景更灵活,你可以自己定义“如果发生X,会怎样?”这类问题。比如:
- 如果美联储突然加息200个基点?
- 如果某只股票被恶意做空,波动率翻倍?
- 如果两个高度相关的资产突然脱钩?
构建假设情景时,我通常会考虑三个维度:
| 维度 | 典型参数 | 冲击幅度 |
|---|---|---|
| 价格 | 标的资产价格 | ±10% ~ ±50% |
| 波动率 | 隐含波动率曲面 | +20% ~ +200% |
| 相关性 | 资产间相关系数 | 从0.8变为-0.3 |
| 流动性 | 买卖价差 | 扩大5~20倍 |
我的经验:假设情景不要只做“单因子”测试。真实市场里,价格暴跌往往伴随着波动率飙升和流动性枯竭。我习惯做“多因子联合冲击”,比如同时让标的价格跌20%、波动率涨50%、价差扩大10倍。这种组合拳才能暴露真正的风险。
19.2.3 极端市场条件模拟
极端情景,说白了就是“最坏情况有多坏”。这不仅仅是把参数调大,而是要思考市场机制可能失效的场景。
举个例子:2020年4月WTI原油期货跌到负值,这在任何历史数据里都找不到,任何假设情景也很难想到。但如果你做了极端条件模拟,就会考虑“如果交割机制导致价格突破零轴”这种可能性。
构建极端情景时,我常用的方法:
- 压力因子放大法:把历史最大波动再乘以2~3倍
- 尾部风险建模:用极值理论(EVT)拟合收益率的尾部
- 流动性黑洞模拟:假设所有对手方同时撤单,你只能以极差价格成交
19.3 核心框架:压力测试的完整流程
下面这张图是我自己总结的压力测试流程,每次做新项目都会拿出来对照一遍。
19.4 实操中的关键细节
理论讲完了,说点实际的。我在做压力测试时,有几个特别容易踩的坑。
19.4.1 相关性崩溃
正常市场里,股票和债券往往是负相关的。但危机来临时,所有资产可能一起跌,相关性瞬间变成1。如果你用历史相关性做对冲,这时候就惨了。
我的做法:在压力测试里,强制把相关性矩阵改成极端值。比如把所有相关系数设为0.8以上,看看组合会亏多少。
19.4.2 波动率曲面的扭曲
很多做市商只关注平值期权的波动率,但极端行情下,虚值期权的波动率会飙升得更厉害。我记得2020年3月,标普500的虚值看跌期权波动率从20%直接跳到150%。如果你只对冲了平值期权,虚值头寸会给你带来巨大损失。
避坑指南:我曾经因为只测试了平值波动率的变化,忽略了曲面扭曲,结果在2020年3月亏了300多万。从那以后,我每次压力测试都会同时调整整个波动率曲面的斜率、曲率和期限结构。
19.4.3 流动性分层
极端行情下,不同资产的流动性下降速度完全不同。国债可能还能交易,但某些结构化产品的价差可能直接变成“无报价”。
我建议在压力测试里加入流动性分层模型:
- 高流动性资产(国债、主要股指):价差扩大2~5倍
- 中等流动性资产(个股、信用债):价差扩大5~10倍
- 低流动性资产(结构化产品、奇异期权):价差扩大10~50倍,甚至无法成交
19.5 一个完整的压力测试案例
假设你是一个做市商,持有以下结构化产品组合:
- 1000份挂钩沪深300的自动赎回型产品
- 500份挂钩中证500的雪球产品
- 200份挂钩某个股的鲨鱼鳍产品
你定义了一个极端情景:
- 沪深300单日暴跌15%
- 中证500暴跌20%
- 个股暴跌30%
- 所有波动率翻3倍
- 买卖价差扩大10倍
# 压力测试核心代码片段
def extreme_scenario_test(portfolio, scenario_params):
# 1. 施加价格冲击
for product in portfolio:
product.underlying_price *= (1 - scenario_params['price_shock'])
# 2. 调整波动率曲面
vol_surface = adjust_vol_surface(scenario_params['vol_multiplier'])
# 3. 考虑流动性成本
liquidity_cost = calculate_liquidity_cost(scenario_params['spread_multiplier'])
# 4. 重新定价
new_nav = 0
for product in portfolio:
price = reprice_product(product, vol_surface)
new_nav += price * product.quantity
# 5. 计算损益
pnl = new_nav - portfolio.original_nav
pnl -= liquidity_cost # 扣除流动性成本
return pnl
运行结果:组合净值从1.2亿跌到7800万,亏损4200万,超过公司设定的3000万风险限额。这时候就需要立即采取措施——要么减仓,要么买入保护性期权。
19.6 压力测试的频率和报告
我个人建议:
- 每日:运行历史情景和一组标准假设情景
- 每周:更新极端情景参数,加入新的市场风险因子
- 每月:全面复盘,对比压力测试结果与实际市场表现
报告格式要简洁,重点突出:
| 情景名称 | 预期损失 | 是否超限 | 建议措施 |
|---|---|---|---|
| 2008年金融危机 | -2800万 | 否 | 维持 |
| 2020年疫情冲击 | -3500万 | 是 | 减仓20% |
| 假设:加息200bp | -1800万 | 否 | 维持 |
| 极端:多因子联合 | -5200万 | 是 | 买入尾部对冲 |
最后说一句:压力测试不是做给别人看的,而是给自己用的。我见过太多团队,压力测试报告写得漂漂亮亮,但实际风控决策根本不参考。记住,压力测试的价值在于——当市场真的崩了,你知道自己能不能扛住,该怎么做。
嗯,这一章的内容就到这里。核心就一句话:别等到潮水退了,才发现自己在裸泳。