条件情景树的概率加权 PnL 聚合
Conditional Scenario-Tree Probability-Weighted PnL Aggregation
开始编码情景树压力引擎把可能的未来沿一棵分支树展开:根表示今天的市场状态,每一层是一个离散决策点("利率上行/不变/下行"、"波动率扩张/持平/收缩"……),每个节点带有给定父节点条件下的条件概率。路径 PnL 沿树自上而下累计,所以每个节点的 node_pnls 已经聚合了从根到该节点这一段的累计情景 PnL。只有叶子代表最终结果;交易台想要的是水平线上的期望 PnL——按每个叶子的无条件概率加权,无条件概率即从根到该叶子唯一路径上所有条件概率的乘积。
实现 solution(node_pnls, parent, conditional_probability, is_leaf)。四个长度均为 N 的并列数组分别给出每个节点的累计路径 PnL、父节点索引(根为 -1)、给定父节点的条件概率以及叶子标志位。调用方保证 parent[i] < i 对 i >= 1 成立,因此一次前向扫描就能让父节点先于子节点被访问。计算
然后返回
无条件概率是路径上条件概率的乘积——绝不是求和。只有叶子参与求和;内部节点的 PnL 已经由其下方的叶子代表,不能重复计入。
Example
solution([0.0, 100.0, -50.0], [-1, 0, 0], [1.0, 0.6, 0.4], [False, True, True]) 返回 40.0。根是内部节点,不贡献 PnL。两个叶子直接挂在根下,无条件概率分别为 1.0 * 0.6 = 0.6 和 1.0 * 0.4 = 0.4,加和为 1.0,构成一个合法的概率分布。期望 PnL 为 0.6 * 100 + 0.4 * (-50) = 60 - 20 = 40。
详见 stubs/stub.py 中的函数骨架。
边界情形
- N == 0:返回
0.0。 - 根即叶子(N == 1, is_leaf = [True], conditional_probability = [1.0]):根本身是唯一的叶子;答案就是它的
node_pnls[0]。 - 零概率分支:
conditional_probability = 0.0的节点其下整棵子树贡献为零,无论叶子的 PnL 是多少;递归式自然处理这种情形,因为运行中的乘积会归零。 - 内部节点带非零 PnL:忽略。只有
is_leaf[i] = True的项进入求和。
实战背景
情景树是持有路径依赖或敲入敲出工具的交易台做多期压力测试的主力工具。一个一字排开的扁平情景网格(所有情景都在同一个时间点)无法表达"波动率先冲后回"这种需要条件结构才能描述的路径;而树可以。叶子上的期望 PnL 是头条数字;同样的原语也用于在子树上算条件期望(下钻"假设利率走强后的期望 PnL")、识别反向压力报告中的最差叶子,以及为 ALM 账本的基于树的资本投影提供输入。两个陷阱——把路径乘积写成求和、把内部节点也算进分子——都会得到一个看起来合理但其实是错的数;只有在小树上做手算对照,才是机械正确性的真正防线。
约束条件
- 0 <= N <= 60,其中 N = len(node_pnls) 是节点总数(根 + 内部 + 叶子)
- `parent` 长度为 N。parent[0] = -1 (根节点约定)。对于 i >= 1,parent[i] 取值范围是 [0, i-1](调用方保证拓扑顺序,父先于子)
- `conditional_probability` 长度为 N。conditional_probability[0] = 1.0(根的无条件概率为 1)。对于 i >= 1,conditional_probability[i] 取值范围是 [0.0, 1.0],含义为 P(节点 i | 节点 i 的父节点)
- `node_pnls` 长度为 N。每项是有限的有符号浮点数,绝对值不超过 1e9(从根到该节点累计路径 PnL,单位为美元)
- `is_leaf` 长度为 N。is_leaf[i] 是调用方提供的权威终端标志——只有 is_leaf[i] = True 的节点才会进入期望 PnL 的求和。实现必须**信任**该标志,而不是从 parent 数组反推。一般情形下调用方把拓扑叶子标记为 True,但退化输入(例如 N == 1 且 is_leaf = [False],含义是 '没有终端节点,返回 0.0')也合法且必须按原样接受
- 输出是 `float`——叶子集合上的期望 PnL;浮点比较容差 rel_tol = 1e-9 且 abs_tol = 1e-9。哨兵:N == 0 时返回 0.0
样例
Case 1 · visible_two_level_simple
输入: [[0,100,-50],[-1,0,0],[1,0.6,0.4],[false,true,true]]
期望: 40
根节点为内部节点;两个叶子的无条件概率为 0.6 与 0.4,期望 PnL = 0.6*100 + 0.4*(-50) = 40。
Case 2 · visible_three_level_branching
输入: [[0,0,0,80,20,30,70],[-1,0,0,1,1,2,2],[1,0.5,0.5,0.7,0.3,0.4,0.6],[false,false,false,true,true,true,true]]
期望: 58
四个叶子的无条件概率为 0.5*0.7=0.35, 0.5*0.3=0.15, 0.5*0.4=0.20, 0.5*0.6=0.30;期望 PnL = 0.35*80 + 0.15*20 + 0.20*30 + 0.30*70 = 58。
Case 3 · empty_zero_nodes
输入: [[],[],[],[]]
期望: 0
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · visible_two_level_simple
输入: [[0,100,-50],[-1,0,0],[1,0.6,0.4],[false,true,true]]
期望: 40
根节点为内部节点;两个叶子的无条件概率为 0.6 与 0.4,期望 PnL = 0.6*100 + 0.4*(-50) = 40。
Case 2 · visible_three_level_branching
输入: [[0,0,0,80,20,30,70],[-1,0,0,1,1,2,2],[1,0.5,0.5,0.7,0.3,0.4,0.6],[false,false,false,true,true,true,true]]
期望: 58
四个叶子的无条件概率为 0.5*0.7=0.35, 0.5*0.3=0.15, 0.5*0.4=0.20, 0.5*0.6=0.30;期望 PnL = 0.35*80 + 0.15*20 + 0.20*30 + 0.30*70 = 58。
Case 3 · empty_zero_nodes
输入: [[],[],[],[]]
期望: 0