反向压力测试:标记触及亏损阈值的情景
Reverse Stress Test — Flag Scenarios That Hit a Loss Target
开始编码反向压力测试把常规问题倒过来问。常规问题是「给定这个固定冲击向量,PnL 是多少?」;反向问题是「给定一个我们承受不起的目标亏损,哪些因子联动真的能造出这种亏损?」。风险团队会枚举一组合理的候选情景——历史回放、监管规定的冲击、专家手工搭出来的组合——并把每一个线性 Taylor PnL 跨过阈值的情景挑出来。被挑出的子集会上报给董事会评审,或进入下一步的非线性重估值。
请实现 solution(exposures, factor_shocks, target_loss_dollars)。exposures 是长度 M 的组合层面一阶敏感度向量:exposures[m] 是因子 m 单位冲击下的美元 PnL(有符号,单位为因子的自然单位——例如「每 1bp 利率移动多少美元」)。factor_shocks 形状为 (S, M):factor_shocks[s][m] 是候选情景 s 下因子 m 的冲击大小。target_loss_dollars 是非负的亏损量级。
对每个情景 s,线性 Taylor PnL 为
返回满足下式的情景索引列表
按升序排列(即按情景索引顺序)。阈值用的是非严格不等号:PnL 恰好等于 -target_loss_dollars 的情景会被标记。阈值本身是正的亏损量级——比较前要先取负,因为 PnL 是有符号的,亏损就是负的 PnL。如果没有情景触发阈值,输出是 [](空列表,绝不是哨兵值)。
这里的 PnL 模型是线性(delta-only)Taylor;gamma 与 cross-gamma 明确不在本原语范围内。更高阶的重估值是后续步骤,只对被标记的子集做。
例
solution([100.0, 50.0], [[1.0, 1.0], [-2.0, 1.0], [-2.0, -2.0], [0.0, 0.0]], 100.0) 返回 [1, 2]。组合敞口是 [+100, +50](每单位因子 0 +50)。四个情景的 PnL 分别是 100*1 + 50*1 = 150、100*(-2) + 50*1 = -150、100*(-2) + 50*(-2) = -300、100*0 + 50*0 = 0。当 target_loss_dollars = 100 时,阈值是 pnl[s] <= -100。情景 1(PnL -150)与情景 2(PnL -300)满足;情景 0(盈利)与情景 3(盈亏平衡)不满足。输出 [1, 2] 按情景索引升序。
实现细节由 stubs/stub.py 提供。
实践背景
巴塞尔与多数本国银行监管都明确要求做反向压力测试,原因是常规压力测试在回答一个不太对的问题——真正危险的情景未必出现在交易台已有的标准菜单上。标准流程是:(1) 从机构无法承受的目标亏损出发(例如 CET1 资本的四分之一,或某个监管最低缓冲被击穿);(2) 枚举或采样一个较大的候选情景集;(3) 把每一个线性 Taylor PnL 跨阈值的候选挑出来;(4) 用全功能非线性定价器对挑出的子集重估值,按实际亏损排序;(5) 把剩下的清单交给模型风险与治理委员会。本题实现的是第 (3) 步——决定哪些情景值得做昂贵非线性重估值的廉价线性过滤器。不等号方向(<= 配上取负后的阈值)正是审计时会逐行复核的部分:严格 < 会悄无声息地把刚好踩在阈值上的情景从上报清单里漏掉;漏写取负则会把盈利情景而不是危险情景塞进清单。
约束条件
- 0 <= M <= 12,M = len(exposures) 是风险因子数
- 0 <= S <= 200,S = len(factor_shocks) 是候选情景数
- 对非空输入,`factor_shocks` 每行长度都是 M(矩形矩阵)。参考解假设输入形状合法——形状不一致属于调用方前置条件违反,行为未定义
- `exposures` 与 `factor_shocks` 每个分量都是有限浮点数,绝对值不超过 1e6
- 0.0 <= target_loss_dollars <= 1e10(非负的亏损量级)
- 输出是 `list[int]`,每个元素是位于 `[0, S)` 的情景索引,按升序排列,不允许重复
样例
Case 1 · statement-example two factors four scenarios
输入: [[100,50],[[1,1],[-2,1],[-2,-2],[0,0]],100]
期望: [1,2]
组合敞口 [100, 50]。四个情景 PnL = 150, -150, -300, 0。阈值 -100:情景 1 (-150 <= -100) 与情景 2 (-300 <= -100) 满足;情景 0 (+150) 与情景 3 (0) 不满足。按索引升序输出 [1, 2]。
Case 2 · visible single factor one losing scenario
输入: [[10],[[5],[-3],[-1]],20]
期望: [1]
PnL = [50, -30, -10]。阈值 -20:仅情景 1 (-30 <= -20) 满足;情景 2 (-10) 不满足;情景 0 (+50) 是盈利。
Case 3 · visible non-strict inequality at exact boundary
输入: [[2],[[10],[-50],[-100]],100]
期望: [1,2]
PnL = [20, -100, -200]。阈值 -100:情景 1 PnL=-100,因为 -100 <= -100 成立而被标记;情景 2 (-200 <= -100) 也满足;情景 0 (+20) 是盈利。
Case 4 · visible empty scenario list returns empty
输入: [[1,2,3],[],500]
期望: []
S=0,没有情景可评估,直接返回空列表。
Case 5 · visible target zero flags breakeven and losses
输入: [[1,1],[[1,1],[0,0],[-1,0],[1,-1]],0]
期望: [1,2,3]
PnL = [2, 0, -1, 0]。阈值 0:pnl <= 0 标记情景 1 (0)、2 (-1)、3 (0);情景 0 (+2) 不满足。
Case 6 · visible empty factors target zero flags all
输入: [[],[[],[],[]],0]
期望: [0,1,2]
M=0:每个情景在零个因子上求和得 PnL=0。阈值 0:0 <= 0 成立,全部标记。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example two factors four scenarios
输入: [[100,50],[[1,1],[-2,1],[-2,-2],[0,0]],100]
期望: [1,2]
组合敞口 [100, 50]。四个情景 PnL = 150, -150, -300, 0。阈值 -100:情景 1 (-150 <= -100) 与情景 2 (-300 <= -100) 满足;情景 0 (+150) 与情景 3 (0) 不满足。按索引升序输出 [1, 2]。
Case 2 · visible single factor one losing scenario
输入: [[10],[[5],[-3],[-1]],20]
期望: [1]
PnL = [50, -30, -10]。阈值 -20:仅情景 1 (-30 <= -20) 满足;情景 2 (-10) 不满足;情景 0 (+50) 是盈利。
Case 3 · visible non-strict inequality at exact boundary
输入: [[2],[[10],[-50],[-100]],100]
期望: [1,2]
PnL = [20, -100, -200]。阈值 -100:情景 1 PnL=-100,因为 -100 <= -100 成立而被标记;情景 2 (-200 <= -100) 也满足;情景 0 (+20) 是盈利。
Case 4 · visible empty scenario list returns empty
输入: [[1,2,3],[],500]
期望: []
S=0,没有情景可评估,直接返回空列表。
Case 5 · visible target zero flags breakeven and losses
输入: [[1,1],[[1,1],[0,0],[-1,0],[1,-1]],0]
期望: [1,2,3]
PnL = [2, 0, -1, 0]。阈值 0:pnl <= 0 标记情景 1 (0)、2 (-1)、3 (0);情景 0 (+2) 不满足。
Case 6 · visible empty factors target zero flags all
输入: [[],[[],[],[]],0]
期望: [0,1,2]
M=0:每个情景在零个因子上求和得 PnL=0。阈值 0:0 <= 0 成立,全部标记。