← 返回编程题库
coding-portfolio-tree-aggregate-pnl简单免费版2000ms未尝试

沿组合层级树向上聚合 P&L

Aggregate P&L Up a Portfolio Hierarchy Tree

开始编码

某多策略基金在策略叶子层级记账,但按 *book-of-books* 层级向上汇报:单个策略汇总到 pod,pod 汇总到 desk,desk 汇总到全公司。该层级是任意 N 叉树(并非二叉——一个 desk 可包含任意数量的 pod,一个 pod 可包含任意数量的策略),任何一天全公司层级 P&L 就是所有叶子策略 P&L 之和。你的任务是接受这棵树并一次性返回根节点的聚合 P&L。

实现 solution(tree: dict) -> float,其中每个节点形如以下之一:

  • 叶子: {"name": str, "pnl": float} —— 该策略在本期实现的 P&L。
  • 内部节点: {"name": str, "children": list[node]} —— 子账本,其聚合 P&L 等于其子节点聚合之和。

返回根节点的聚合 P&L,类型为 float

children 为空列表的非叶节点贡献为 0.0(尚未建仓的子账本——退化但合法)。同时携带 pnlchildren、或两者均不携带的节点为非法:原始规范要求 raise ValueError,但为了与批量打分基础设施兼容(后者把抛异常视作 harness 失败而非预期结果),本参考解改为返回 math.nan。NaN 会沿求和向上传播至根节点,再由比较器的 nan_equals_nan = true 标志断言。

例如,solution({"name": "alpha", "pnl": 1500.0}) 返回 1500.0(单个叶子即根节点)。solution({"name": "pod", "children": [{"name": "a", "pnl": 100.0}, {"name": "b", "pnl": -30.0}]}) 返回 70.0。一棵三层树(firm → 2 个 desk → 各叶子策略)无论分叉形态如何,都会把所有叶子求和。

实践背景

层级 P&L 上卷是量化运营里最经典的递归树练习。同一形态出现在风险归因(子账本暴露上卷为公司级 VaR 贡献)、合规报送(交易名义本金上卷到 LEI)、业绩归因(策略级 Sharpe 贡献级联到基金级)。之所以更偏好递归形式而不是手写栈,是因为真实层级常常 *不规则*——某个 desk 下面可能一个 pod 有二十个策略,另一个 pod 只有两个策略,还有第三个"壳"pod 暂无建仓——而递推对三种情形的处理完全一致。两条非法节点路径(同时含两键、两键都不含)是标准的防御契约:若上游配置管理交付的子账本不合法,在上卷一步就大声失败,远比静悄悄把某个叶子重复计入或把已建仓节点清零要好。在真实代码库里这个"大声失败"是 ValueError;在本批量打分环境里则编码为 math.nan 哨兵,使其能沿递归传播并被比较器断言。

约束条件

  • 节点总数在 1 到 1000(含)之间
  • 树深度在 1 到 50(含)之间
  • 每个节点是 `dict`,带 `name: str` 键,并恰好含 `pnl: float`(叶子)或 `children: list[node]`(内部)中的一个
  • 叶子的 `pnl` 是有限浮点数(正、负、零均可)
  • 内部节点的 `children` 可为空列表(贡献为 `0.0`),但永远不为 `None`
  • 同时含 `pnl` 与 `children`、或两者都不含的节点为非法:规范要求 `raise ValueError`,本批量打分环境改为返回 `math.nan` 哨兵(可经求和向上传播)
  • 浮点比较容差:`rel_tol = 1e-9`、`abs_tol = 1e-12`

样例

Case 1 · statement-example: single-leaf root returns its own pnl

输入: [{"name":"alpha","pnl":1500}]

期望: 1500

单叶子节点即根节点;聚合 P&L 就是该叶子的 pnl。

Case 2 · statement-example: internal with two leaves

输入: [{"name":"pod","children":[{"name":"a","pnl":100},{"name":"b","pnl":-30}]}]

期望: 70

聚合 = 子节点之和 = 100 + (-30) = 70。

Case 3 · typical: three-level firm to desks to strategies

输入: [{"name":"firm","children":[{"name":"desk-equities","children":[{"name":"stat-arb","pnl":250},{"name":"long-short","pnl":-120}]},{"name":"desk-rates","children":[{"name":"macro","pnl":80},{"name":"carry","pnl":45.5}]}]}]

期望: 255.5

firm = (250 + (-120)) + (80 + 45.5) = 130 + 125.5 = 255.5。三层树按层求和无差别。

Case 4 · boundary: empty children list at non-leaf contributes zero

输入: [{"name":"fund","children":[{"name":"active-pod","children":[{"name":"s","pnl":50}]},{"name":"shell-pod-no-strats-yet","children":[]}]}]

期望: 50

shell-pod 的 children 为空,贡献 0.0;总和 = 50 + 0 = 50。空 children 是退化但合法的输入。

Case 5 · adversarial: malformed root with both pnl and children returns NaN sentinel

输入: [{"name":"malformed","pnl":100,"children":[{"name":"x","pnl":5}]}]

期望: "NaN"

节点同时含 pnl 与 children:歧义,规范上抛 ValueError;本环境返回 math.nan 哨兵以兼容批量打分。

Case 6 · adversarial: neither pnl nor children returns NaN sentinel

输入: [{"name":"neither"}]

期望: "NaN"

节点既无 pnl 也无 children:无可聚合内容,规范上抛 ValueError;本环境返回 math.nan 哨兵。

最近提交

还没有提交记录。

编码区

实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。

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

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

Case 1 · statement-example: single-leaf root returns its own pnl

输入: [{"name":"alpha","pnl":1500}]

期望: 1500

单叶子节点即根节点;聚合 P&L 就是该叶子的 pnl。

Case 2 · statement-example: internal with two leaves

输入: [{"name":"pod","children":[{"name":"a","pnl":100},{"name":"b","pnl":-30}]}]

期望: 70

聚合 = 子节点之和 = 100 + (-30) = 70。

Case 3 · typical: three-level firm to desks to strategies

输入: [{"name":"firm","children":[{"name":"desk-equities","children":[{"name":"stat-arb","pnl":250},{"name":"long-short","pnl":-120}]},{"name":"desk-rates","children":[{"name":"macro","pnl":80},{"name":"carry","pnl":45.5}]}]}]

期望: 255.5

firm = (250 + (-120)) + (80 + 45.5) = 130 + 125.5 = 255.5。三层树按层求和无差别。

Case 4 · boundary: empty children list at non-leaf contributes zero

输入: [{"name":"fund","children":[{"name":"active-pod","children":[{"name":"s","pnl":50}]},{"name":"shell-pod-no-strats-yet","children":[]}]}]

期望: 50

shell-pod 的 children 为空,贡献 0.0;总和 = 50 + 0 = 50。空 children 是退化但合法的输入。

Case 5 · adversarial: malformed root with both pnl and children returns NaN sentinel

输入: [{"name":"malformed","pnl":100,"children":[{"name":"x","pnl":5}]}]

期望: "NaN"

节点同时含 pnl 与 children:歧义,规范上抛 ValueError;本环境返回 math.nan 哨兵以兼容批量打分。

Case 6 · adversarial: neither pnl nor children returns NaN sentinel

输入: [{"name":"neither"}]

期望: "NaN"

节点既无 pnl 也无 children:无可聚合内容,规范上抛 ValueError;本环境返回 math.nan 哨兵。