二阶情景 PnL 向量:delta 加 gamma 的 Taylor 估值
Quadratic-Taylor Scenario PnL Vector via Delta and Gamma
开始编码交易台日内风险报告里已经有一份 delta-only 的情景网格(一阶 Taylor 基线)。下一版 dashboard 升级要把 gamma 修正项接上来,让网格能反映「凸性」:多波动率账本在大冲击下显得更好、空波动率账本显得更差,cross-gamma(例如股权-波动率交叉敏感度)在原本线性网格上看起来「擦肩而过」的情景里也能现身。本题就是把这个二阶重估值引擎实现出来:吃同一份情景网格,吐出 delta-plus-gamma 的 PnL 向量。
请实现 solution(delta, gamma, factor_shocks)。delta 是长度 M 的组合一阶敏感度向量(有符号美元 PnL,每单位因子冲击);gamma 是 M x M 交叉 gamma 矩阵(有符号美元 PnL,每单位平方冲击;按构造对称);factor_shocks 形状为 (S, M),每行是一个情景的逐因子冲击向量。返回长度 S 的 PnL 向量
等价地,pnl[s] = delta . shock[s] + 0.5 * shock[s]^T . gamma . shock[s]。i, j 的双重求和遍历完整方阵——同时包含 (i, j) 和 (j, i),因此每对非对角项被访问两次;前面的 把每对非对角的净贡献修正为 gamma[i][j] * shock[i] * shock[j](净算一次)、把对角项修正为 0.5 * gamma[i][i] * shock[i]^2。如果你选择只遍历上三角 j >= i,则非对角必须乘 2、对角保持系数 1(再统一乘以 0.5),两种写法在数值上必须一致。
PnL 是有符号的:负 gamma(凹账本——卖期权)的二次项在 shock=0 时为 0,任何非零冲击下严格为负,因此任意方向的大冲击都让 PnL 下降;正 gamma(凸账本——买期权)则是任意非零冲击都让 PnL 上升。叠加 delta 后,总 PnL 可正可负,不要返回绝对值。
例
solution([100.0, 50.0], [[2.0, 1.0], [1.0, 4.0]], [[1.0, 2.0]]) 返回 [211.0]。算术过程:M=2(设想一个利率因子、一个股指因子),单情景冲击为 [1, 2]。线性部分 delta . shock = 100*1 + 50*2 = 200。完整二次型双重求和为 gamma[0][0]*1*1 + gamma[0][1]*1*2 + gamma[1][0]*2*1 + gamma[1][1]*2*2 = 2*1 + 1*2 + 1*2 + 4*4 = 2 + 2 + 2 + 16 = 22。前面的 1/2 给出 0.5 * 22 = 11。总 PnL = 200 + 11 = 211。注意非对角项 gamma[0][1]=1 被访问了两次(一次作为 gamma[0][1]、一次作为 gamma[1][0]),每次贡献 1*1*2 = 2;前面的 0.5 把净 cross-gamma 贡献修正为 1 * 1 * 2 = 2,正是「cross-gamma 净算一次」的正确答案。
实现细节由 stubs/stub.py 提供。
实践背景
线性的 delta-only 情景网格是交易台第一版风险视图;冲击较小时这种近似没问题,但只要账本里有像样的期权头寸,在压力情景(利率 +200bp、股指 -20% 之类——也就是监管包真正关心的那批情景)下定价就会走偏。把 gamma 二次项(二阶导矩阵,包括 vega-vega、equity-vol、rate-vol 之类的非对角 cross-gamma)加上是最便宜的升级:十来个因子、上百个情景下每次刷新只是 O(S * M^2) 的小活,结果直接做成「delta-plus-gamma」一栏挂在 dashboard 上、与线性栏并排展示。风险官两栏对照看:线性 PnL 很小但 gamma 修正很大的情景属于凸性主导(多半是好事——长期权或自然对冲);gamma 让损失比线性更糟的情景就是空凸性预警。这道题的两个常见坑是:(1) 漏掉二次型前面的 1/2,把 gamma 效应翻了一倍;(2) 把 factor_shocks 的第二轴误当成持仓维度,dashboard 上 PnL 曲面就转置了。两种错误都立刻能被发现:长 gamma 情景会报错符号或者错幅度,与交易台预期的凸性曲线对不上。
约束条件
- 0 <= M <= 10,M 为因子数;`len(delta) == M`、`len(gamma) == M`、每个 `gamma[i]` 长度都为 M(方阵)
- 0 <= S <= 100,S = len(factor_shocks);每个 `factor_shocks[s]` 长度为 M
- `delta`、`gamma`、`factor_shocks` 中每个元素都是有限浮点数,绝对值不超过 1e6
- `gamma` 是组合价值对因子的二阶导矩阵,按构造**应该**对称(gamma[i][j] == gamma[j][i]);参考解不会强制对称化——传入非对称矩阵就是按字面公式直接算,属于调用方契约违反
- 输出是长度为 S 的 `list[float]`,按情景顺序;浮点比较容差 rel_tol = 1e-9、abs_tol = 1e-9
样例
Case 1 · statement-example two factors single scenario
输入: [[100,50],[[2,1],[1,4]],[[1,2]]]
期望: [211]
线性部分 delta·shock = 100·1 + 50·2 = 200。完整二次型 sum_{i,j} gamma[i][j]·s[i]·s[j] = 2·1 + 1·2 + 1·2 + 4·4 = 22;乘 0.5 得 11。PnL = 200 + 11 = 211。
Case 2 · visible single factor positive gamma symmetric in shock sign
输入: [[10],[[2]],[[3],[-3]]]
期望: [39,-21]
M=1:每个情景 PnL = delta[0]·s + 0.5·gamma[0][0]·s²。s=3:30 + 0.5·2·9 = 30 + 9 = 39;s=-3:-30 + 0.5·2·9 = -30 + 9 = -21。注意正 gamma 二次项总是非负。
Case 3 · visible single factor negative gamma both shocks lose
输入: [[0],[[-4]],[[2],[-2],[0]]]
期望: [-8,-8,0]
delta=0,gamma 为负的纯凹账本。任意非零冲击都减分:s=±2 时 PnL = 0.5·(-4)·4 = -8;s=0 时 PnL = 0。
Case 4 · visible cross-gamma off-diagonal symmetric two-by-two
输入: [[0,0],[[0,3],[3,0]],[[1,1],[1,-1]]]
期望: [3,-3]
纯 cross-gamma:完整双重和 = 3·1·1 + 3·1·1 = 6(s=[1,1])或 3·1·(-1) + 3·(-1)·1 = -6(s=[1,-1]);乘 0.5 得 3 与 -3。
Case 5 · boundary empty scenarios returns empty list
输入: [[1,2],[[1,0],[0,1]],[]]
期望: []
S=0 时返回 `[]`。
Case 6 · boundary single factor single scenario nonzero
输入: [[3],[[6]],[[2]]]
期望: [18]
M=1:3·2 + 0.5·6·4 = 6 + 12 = 18。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example two factors single scenario
输入: [[100,50],[[2,1],[1,4]],[[1,2]]]
期望: [211]
线性部分 delta·shock = 100·1 + 50·2 = 200。完整二次型 sum_{i,j} gamma[i][j]·s[i]·s[j] = 2·1 + 1·2 + 1·2 + 4·4 = 22;乘 0.5 得 11。PnL = 200 + 11 = 211。
Case 2 · visible single factor positive gamma symmetric in shock sign
输入: [[10],[[2]],[[3],[-3]]]
期望: [39,-21]
M=1:每个情景 PnL = delta[0]·s + 0.5·gamma[0][0]·s²。s=3:30 + 0.5·2·9 = 30 + 9 = 39;s=-3:-30 + 0.5·2·9 = -30 + 9 = -21。注意正 gamma 二次项总是非负。
Case 3 · visible single factor negative gamma both shocks lose
输入: [[0],[[-4]],[[2],[-2],[0]]]
期望: [-8,-8,0]
delta=0,gamma 为负的纯凹账本。任意非零冲击都减分:s=±2 时 PnL = 0.5·(-4)·4 = -8;s=0 时 PnL = 0。
Case 4 · visible cross-gamma off-diagonal symmetric two-by-two
输入: [[0,0],[[0,3],[3,0]],[[1,1],[1,-1]]]
期望: [3,-3]
纯 cross-gamma:完整双重和 = 3·1·1 + 3·1·1 = 6(s=[1,1])或 3·1·(-1) + 3·(-1)·1 = -6(s=[1,-1]);乘 0.5 得 3 与 -3。
Case 5 · boundary empty scenarios returns empty list
输入: [[1,2],[[1,0],[0,1]],[]]
期望: []
S=0 时返回 `[]`。
Case 6 · boundary single factor single scenario nonzero
输入: [[3],[[6]],[[2]]]
期望: [18]
M=1:3·2 + 0.5·6·4 = 6 + 12 = 18。