多因子情景 PnL 向量:一阶 Taylor 估值
Multi-Factor Scenario PnL Vector via Linear Taylor
开始编码每个交易台收盘前都要跑一张「情景网格」:一组固定的宏观/微观冲击(「利率平行 +50bp」「股指 -5%」「信用利差 +100bp」……),以及当前组合在每个情景下的预期美元 PnL。定价引擎用的是 delta-only 一阶 Taylor 近似:每个持仓贡献一行逐因子敏感度,交易台把这些向量按持仓维度求和得到组合层面的因子敞口向量,然后某情景下的重估值就是这条向量与情景冲击向量的简单点积。本题就是写这个核心重估值引擎,返回一份直接挂到 dashboard 上的 PnL 向量。
请实现 solution(exposures, factor_shocks)。exposures 形状为 (N, M):exposures[n][m] 是持仓 n 对因子 m 单位冲击的一阶 PnL 敏感度(有符号浮点,单位是「每个因子自然单位的美元 PnL」——例如每 1bp 利率移动多少美元、每 1% 股指移动多少美元)。factor_shocks 形状为 (S, M):factor_shocks[s][m] 是情景 s 下因子 m 的冲击大小,单位与因子自然单位一致。返回长度为 S 的向量
这个双重求和等价于更高效的写法
portfolio_exposure 只算一次,复杂度从 降到 。两种写法在题目约束下都能通过,但先聚合再点积才是生产级风险网格的真正接线方式。
输出是长度为 S 的单个向量,不是 (N, S) 的逐持仓矩阵。PnL 是有符号的:组合在某情景下亏钱时,对应位是负数;不要返回绝对值或对结果取负号。
例
solution([[100.0, 0.0], [0.0, 50.0]], [[50.0, 0.0], [0.0, -1.0], [25.0, 0.5]]) 返回 [5000.0, -50.0, 2525.0]。这里 N=2 个持仓、M=2 个因子(设想是利率因子与股指因子):持仓 0 贡献 (+100, 0)、持仓 1 贡献 (0, +50),组合层面的因子敞口为 [100, 50]。三个情景分别是 [+50, 0]、[0, -1]、[+25, +0.5],对应的 PnL 是 100*50 + 50*0 = 5000、100*0 + 50*(-1) = -50、100*25 + 50*0.5 = 2525。
实现细节由 stubs/stub.py 提供。
实践背景
「情景网格」是收盘风险报告的标配:一份几十条预定义的因子冲击向量(交易台自己的标准压力菜单 + 监管规定的那批),逐一对当前账本重估值,得到一张「一行一情景」的美元 PnL 表。风险官扫一眼最差 PnL、最好 PnL、以及两者之间的跨度,再追究是哪个因子在主导尾端。同样的原语——把持仓求和成组合因子敞口向量、再与每个情景的冲击向量做点积——会被日内实时跑的网格、反向压力测试在情景树上做的搜索、以及和非线性(gamma/cross-gamma)重估值做对比的线性基线反复重用。维度搞反整张 dashboard 报的就是张转置的 PnL 曲面;符号搞反最坏情景会变成最好情景。
约束条件
- 0 <= N <= 100,N = len(exposures) 是持仓数
- 0 <= M <= 20,M 是因子数(`exposures` 与 `factor_shocks` 的第二轴);如果 N == 0 则从 `factor_shocks` 读取 M;如果 S == 0 则从 `exposures` 读取 M;两者都为空时 M 不重要,输出为 `[]`
- 0 <= S <= 200,S = len(factor_shocks) 是情景数
- 所有 exposures[n][m] 与 factor_shocks[s][m] 都是有限浮点数,绝对值不超过 1e6
- 对非空输入,`exposures` 每行长度都是 M,`factor_shocks` 每行长度也都是 M(矩形矩阵)。参考解假设输入形状合法——形状不一致属于调用方前置条件违反,行为未定义
- 输出是长度为 S 的 `list[float]`,按情景顺序;浮点比较容差 rel_tol = 1e-9、abs_tol = 1e-9
样例
Case 1 · statement-example two positions, three scenarios
输入: [[[100,0],[0,50]],[[50,0],[0,-1],[25,0.5]]]
期望: [5000,-50,2525]
组合在 M=2 个因子(利率、股指)上聚合后的因子敞口为 [100, 50]。三种情景下的 PnL 分别为 100·50+50·0=5000、100·0+50·(−1)=−50、100·25+50·0.5=2525。
Case 2 · visible single position single factor positive shock
输入: [[[10]],[[3],[-2]]]
期望: [30,-20]
单一持仓在单因子上敞口为 10,两个情景的因子冲击分别为 +3 和 -2,PnL = [30, -20]。
Case 3 · visible negative exposure with negative shock yields positive pnl
输入: [[[-5,2]],[[-3,1]]]
期望: [17]
位置敞口为 [-5, 2],情景冲击 [-3, 1]:PnL = (-5)(-3) + 2*1 = 15 + 2 = 17(两个负数相乘得正,PnL 是符号化的)。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example two positions, three scenarios
输入: [[[100,0],[0,50]],[[50,0],[0,-1],[25,0.5]]]
期望: [5000,-50,2525]
组合在 M=2 个因子(利率、股指)上聚合后的因子敞口为 [100, 50]。三种情景下的 PnL 分别为 100·50+50·0=5000、100·0+50·(−1)=−50、100·25+50·0.5=2525。
Case 2 · visible single position single factor positive shock
输入: [[[10]],[[3],[-2]]]
期望: [30,-20]
单一持仓在单因子上敞口为 10,两个情景的因子冲击分别为 +3 和 -2,PnL = [30, -20]。
Case 3 · visible negative exposure with negative shock yields positive pnl
输入: [[[-5,2]],[[-3,1]]]
期望: [17]
位置敞口为 [-5, 2],情景冲击 [-3, 1]:PnL = (-5)(-3) + 2*1 = 15 + 2 = 17(两个负数相乘得正,PnL 是符号化的)。