按父单 ID 归集成交:子单回填到父单
Fill Attribution by Parent Order ID
开始编码真实的算法执行系统几乎不会把一个大单直接打到交易所。OMS 会把一个父单(例如"在 16:00 之前买 50,000 股 AAPL")切成许多子单,路由到不同场所,被分批成交后以一连串 fill 的形式回流。要看清父单的真实进度——平均成交价、已经做了多少、什么时候开始的、最后一笔在何时——就需要遍历这条 fill 流,把子单的成交按父单聚合回去。
实现 solution(fills) -> dict[str, dict]。fills 是一个 dict 列表,每条包含 parent_id、qty、price、ts 四个 key。按 parent_id 分组,返回一个 dict,把每个 parent_id 映射到一个汇总字典,必须包含下列 key:
total_filled_qty(float):该父单下所有 fill 的qty之和。vwap(float):sum(qty * price) / sum(qty)。当total_filled_qty == 0时返回0.0。n_fills(int):该父单下的 fill 数量(qty == 0也计数)。first_fill_ts(int):该父单下所有 fill 的ts最小值。last_fill_ts(int):该父单下所有 fill 的ts最大值。
返回字典的 key 顺序必须与每个 parent_id 在输入中首次出现的顺序一致。
例子
fills = [
{"parent_id": "P1", "qty": 100, "price": 50.0, "ts": 1000},
{"parent_id": "P1", "qty": 200, "price": 51.0, "ts": 1100},
{"parent_id": "P2", "qty": 50, "price": 99.0, "ts": 1050},
]
solution(fills)
# -> {
# "P1": {"total_filled_qty": 300.0, "vwap": 50.666666666666664, "n_fills": 2,
# "first_fill_ts": 1000, "last_fill_ts": 1100},
# "P2": {"total_filled_qty": 50.0, "vwap": 99.0, "n_fills": 1,
# "first_fill_ts": 1050, "last_fill_ts": 1050},
# }solution([])
# -> {}fills = [
{"parent_id": "P9", "qty": 0, "price": 10.0, "ts": 5},
{"parent_id": "P9", "qty": 0, "price": 12.0, "ts": 7},
]
solution(fills)
# -> {"P9": {"total_filled_qty": 0.0, "vwap": 0.0, "n_fills": 2,
# "first_fill_ts": 5, "last_fill_ts": 7}}注意第三个例子:两条 qty == 0 的 fill 仍然算作 fill(n_fills = 2),仍然会刷新 first_fill_ts / last_fill_ts——只是 VWAP 因为没有可加权的数量而退化为 0.0。
实践背景
父单/子单的拆单路由是算法执行里的常态:交易员面前的一个父单("按 VWAP 算法,在 16:00 之前买 50,000 股 AAPL")会被算法切成成百个子单,分散打到不同交易所、暗池和时点上。交易所磁带只看得到子单成交,但所有 TCA 报告、盘后对账、实时进度监控都需要把这些 fill 折回父单视角:进度多少、平均价多少、何时开始成交、最后一笔何时打出。这正是 OMS 内部以及每个执行研究 notebook 在做 slippage 和 impact 分析之前必经的一步簿记。
约束条件
- 0 ≤ len(fills) ≤ 10000。
- 每条 fill 是一个 dict,包含 `parent_id`(非空字符串)、`qty`(int 或 float,≥ 0)、`price`(float,> 0)、`ts`(int)。
- 允许 `qty == 0` 的成交——它们会让 `n_fills` 加 1、刷新 `first_fill_ts` / `last_fill_ts`,但对 `total_filled_qty` 和 VWAP 没有贡献。
- VWAP 定义为 `sum(qty * price) / sum(qty)`;当父单总量为 0 时,返回 `vwap = 0.0`,不要除以零。
- 返回字典的 key 顺序必须等于每个 `parent_id` 在 `fills` 中首次出现的顺序——不要排序。
样例
Case 1 · statement-example: two parents, one with two fills
输入: [[{"parent_id":"P1","qty":100,"price":50,"ts":1000},{"parent_id":"P1","qty":200,"price":51,"ts":1100},{"parent_id":"P2","qty":50,"price":99,"ts":1050}]]
期望: {"P1":{"total_filled_qty":300,"vwap":50.666666666666664,"n_fills":2,"first_fill_ts":1000,"last_fill_ts":1100},"P2":{"total_filled_qty":50,"vwap":99,"n_fills":1,"first_fill_ts":1050,"last_fill_ts":1050}}
按 parent_id 把 fill 累加;P1 的 VWAP = (100*50 + 200*51)/300 ≈ 50.6667,first_fill_ts=1000,last_fill_ts=1100。
Case 2 · empty: no fills returns empty dict
输入: [[]]
期望: {}
空输入:直接返回空字典 {}。
Case 3 · single fill: trivial single-parent single-fill
输入: [[{"parent_id":"PX","qty":10,"price":100,"ts":42}]]
期望: {"PX":{"total_filled_qty":10,"vwap":100,"n_fills":1,"first_fill_ts":42,"last_fill_ts":42}}
只有一笔 fill,VWAP 等于该 fill 的价格,first_fill_ts == last_fill_ts。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: two parents, one with two fills
输入: [[{"parent_id":"P1","qty":100,"price":50,"ts":1000},{"parent_id":"P1","qty":200,"price":51,"ts":1100},{"parent_id":"P2","qty":50,"price":99,"ts":1050}]]
期望: {"P1":{"total_filled_qty":300,"vwap":50.666666666666664,"n_fills":2,"first_fill_ts":1000,"last_fill_ts":1100},"P2":{"total_filled_qty":50,"vwap":99,"n_fills":1,"first_fill_ts":1050,"last_fill_ts":1050}}
按 parent_id 把 fill 累加;P1 的 VWAP = (100*50 + 200*51)/300 ≈ 50.6667,first_fill_ts=1000,last_fill_ts=1100。
Case 2 · empty: no fills returns empty dict
输入: [[]]
期望: {}
空输入:直接返回空字典 {}。
Case 3 · single fill: trivial single-parent single-fill
输入: [[{"parent_id":"PX","qty":10,"price":100,"ts":42}]]
期望: {"PX":{"total_filled_qty":10,"vwap":100,"n_fills":1,"first_fill_ts":42,"last_fill_ts":42}}
只有一笔 fill,VWAP 等于该 fill 的价格,first_fill_ts == last_fill_ts。