← 返回编程题库
coding-multifactor-scenario-pnl-vector中等免费版2000ms未尝试

多因子情景 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 的向量

pnl[s]  =  n=0N1m=0M1exposures[n][m]factor_shocks[s][m] \text{pnl}[s] \;=\; \sum_{n=0}^{N-1} \sum_{m=0}^{M-1} \text{exposures}[n][m] \cdot \text{factor\_shocks}[s][m]\text{。}

这个双重求和等价于更高效的写法

pnl[s]  =  m=0M1portfolio_exposure[m]factor_shocks[s][m],portfolio_exposure[m]  =  n=0N1exposures[n][m] \text{pnl}[s] \;=\; \sum_{m=0}^{M-1} \text{portfolio\_exposure}[m] \cdot \text{factor\_shocks}[s][m], \qquad \text{portfolio\_exposure}[m] \;=\; \sum_{n=0}^{N-1} \text{exposures}[n][m]\text{。}

portfolio_exposure 只算一次,复杂度从 O(NMS)O(N \cdot M \cdot S) 降到 O(NM+SM)O(N \cdot M + S \cdot M)。两种写法在题目约束下都能通过,但先聚合再点积才是生产级风险网格的真正接线方式。

输出是长度为 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 = 5000100*0 + 50*(-1) = -50100*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 可见样例;服务端提交会运行可见样例和隐藏测试。

加载编辑器...
计时0:00

默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。

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 是符号化的)。