分制度条件历史期望损失
Regime-Conditional Historical Expected Shortfall
开始编码当一个交易台已经把自己看成是在两种明显不同的制度下运行(例如低波动的 calm 和高波动的 stressed,由上游制度分类器打标),它会按制度分别报期望损失(Expected Shortfall),让今天对照的尾部损失基准正好对应今天的制度。请实现 solution(pnl_samples: list[float], regime_indicator: list[int], alpha: float) -> list[float]。输入是按时间排序的 PnL 序列(带符号:正值=盈利)、等长的制度标签序列(每位严格是 0=calm 或 1=stressed)和置信度 alpha。返回 [es_calm, es_stressed]:每个制度的历史 ES,等于该制度最差 k 笔 PnL 均值的相反数(通常是正的损失数;如果该制度最差 k 笔本身平均下来仍盈利,结果可能为负数——不要钳到零);如果某个制度没有样本,对应位返回 float('nan')。
参考算法分三步。(1) *按制度分桶*:同步遍历 pnl_samples 和 regime_indicator,regime_indicator[t] == 0 时把 pnl_samples[t] 追加到 pnl_calm,否则追加到 pnl_stressed。(2) *逐制度计算 ES*:每条桶若为空返回 float('nan');否则升序排序、按 k = max(1, ceil(N_r * (1 - alpha) - 1e-12)) 取尾部大小(N_r 是该制度样本数)、再取 -mean(sorted_r[0:k])。(3) 按 [es_calm, es_stressed] 的固定顺序返回。整体复杂度是分桶的 O(T) 加上每个制度排序的 O(N_r log N_r) 再加上尾均值的 O(k)。
例
solution([-0.05, 0.02, -0.03, 0.01, -0.04, -0.02, 0.005, -0.06], [0, 0, 0, 0, 1, 1, 1, 1], 0.5) 应返回 [0.04, 0.05]。calm 桶(制度 0)收集 [-0.05, 0.02, -0.03, 0.01],升序排序为 [-0.05, -0.03, 0.01, 0.02];N_0 = 4、alpha = 0.5 时 k = max(1, ceil(4 * 0.5 - 1e-12)) = 2,所以 es_calm = -mean([-0.05, -0.03]) = -(-0.04) = 0.04。stressed 桶(制度 1)收集 [-0.04, -0.02, 0.005, -0.06],升序排序为 [-0.06, -0.04, -0.02, 0.005];N_1 = 4、alpha = 0.5 时 k = 2,所以 es_stressed = -mean([-0.06, -0.04]) = -(-0.05) = 0.05。本例两个制度样本数相同,但 stressed 这边的最深回撤更大,因此 stressed 的 ES 也更大——这正是分制度条件化想要凸显的差异。
实践背景
六个正确性陷阱。第一,*排序前先按制度分桶*:排序必须在每个制度自己的桶内独立做。先对联合序列排序再按制度切片,等于已经把制度标签丢了,得到的数没有统计意义。第二,*k 公式里要用每制度的 N_r,不能用全局 T*:alpha = 0.95 时一个 20 样本的制度 k = max(1, ceil(20 * 0.05 - 1e-12)) = 1,而 100 样本的联合分布会用 k = 5;混用这两个尺度是最常见的暗坑。第三,*ES 是最差 k 笔的均值,不是阈值那一笔*:返回 -sorted_r[k - 1] 只是一笔观测,那是分制度 VaR 不是 ES——ES 的核心就是把最差 (1 - alpha) 那一片每一笔 PnL 都加权进来,桶内更深处的爆仓才会真的把数字往上拉。第四,*符号约定*:ES 是尾均值的*相反数*,典型亏损尾部得到正的 ES;如果不取负号,面板上会出现反向符号。第五,*空制度返回 NaN*,既不是 0、也不能跳过:下游归因面板按固定下标读取,少一位或写成 0 都会让本来「没样本」的制度被误读成「没风险」。第六,*输出严格是长度 2 的列表*,顺序是 [es_calm, es_stressed];返回 dict、单一值、或者把两制度反过来都会破坏契约。
本题是历史 ES 家族里的「分制度条件化」兄弟题,与全样本 ES(无制度过滤)、分制度 VaR(取阈值不取尾均值)、滚动 K 窗 ES(无制度滑窗)、EWMA 加权 ES(按时间衰减不按制度)、Cornish-Fisher 参数尾等兄弟题分属不同切面。共同骨架是「单桶排序、最差 k 取均值、取负号」;区分点在于「桶」如何定义。
约束条件
- 0 ≤ len(pnl_samples) == len(regime_indicator) ≤ 1500。两个列表长度必须一致(指示器给每笔 PnL 打标签)。
- regime_indicator[t] 严格只能是 0(calm)或 1(stressed),不接受其他值。
- |pnl_samples[t]| ≤ 1e6,均为有限浮点数。
- 0.5 ≤ alpha < 1.0(常见 VaR/ES 置信度:0.5、0.9、0.95、0.99、0.999)。
- 每个制度的尾部大小:`k = max(1, ceil(N_r * (1 - alpha) - 1e-12))`,N_r 是该制度的样本数——不是全局 T。
- 输出长度严格为 2:`[es_calm, es_stressed]`。每个条目是 `-mean(sorted_r[0:k])`(通常是正的损失数,但当该制度最差 k 笔本身平均下来仍盈利时可能为负数——不要在 0 处截断),或 `float('nan')`(该制度无样本时)。
- 浮点容忍:rel_tol=1e-9,abs_tol=1e-9。
样例
Case 1 · statement-example, T=8 alpha=0.5 split 4/4
输入: [[-0.05,0.02,-0.03,0.01,-0.04,-0.02,0.005,-0.06],[0,0,0,0,1,1,1,1],0.5]
期望: [0.04,0.05]
calm=[-0.05,0.02,-0.03,0.01] 升序 [-0.05,-0.03,0.01,0.02],N=4、k=2,ES_calm=-mean([-0.05,-0.03])=0.04;stressed=[-0.04,-0.02,0.005,-0.06] 升序 [-0.06,-0.04,-0.02,0.005],k=2,ES_stressed=0.05。
Case 2 · visible, T=8 alpha=0.95 single-tail per regime
输入: [[-0.05,0.02,-0.03,0.01,-0.04,-0.02,0.005,-0.06],[0,0,0,0,1,1,1,1],0.95]
期望: [0.05,0.06]
alpha=0.95 时每制度 N=4 -> k=1。calm 最差 -0.05 -> ES_calm=0.05;stressed 最差 -0.06 -> ES_stressed=0.06。
Case 3 · visible, all-calm regime returns NaN for stressed
输入: [[-0.1,0.05,-0.02],[0,0,0],0.5]
期望: [0.060000000000000005,"NaN"]
stressed 无样本 -> NaN。calm N=3、alpha=0.5、k=2,升序 [-0.10,-0.02,0.05],ES_calm=-mean([-0.10,-0.02])=0.06。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example, T=8 alpha=0.5 split 4/4
输入: [[-0.05,0.02,-0.03,0.01,-0.04,-0.02,0.005,-0.06],[0,0,0,0,1,1,1,1],0.5]
期望: [0.04,0.05]
calm=[-0.05,0.02,-0.03,0.01] 升序 [-0.05,-0.03,0.01,0.02],N=4、k=2,ES_calm=-mean([-0.05,-0.03])=0.04;stressed=[-0.04,-0.02,0.005,-0.06] 升序 [-0.06,-0.04,-0.02,0.005],k=2,ES_stressed=0.05。
Case 2 · visible, T=8 alpha=0.95 single-tail per regime
输入: [[-0.05,0.02,-0.03,0.01,-0.04,-0.02,0.005,-0.06],[0,0,0,0,1,1,1,1],0.95]
期望: [0.05,0.06]
alpha=0.95 时每制度 N=4 -> k=1。calm 最差 -0.05 -> ES_calm=0.05;stressed 最差 -0.06 -> ES_stressed=0.06。
Case 3 · visible, all-calm regime returns NaN for stressed
输入: [[-0.1,0.05,-0.02],[0,0,0],0.5]
期望: [0.060000000000000005,"NaN"]
stressed 无样本 -> NaN。calm N=3、alpha=0.5、k=2,升序 [-0.10,-0.02,0.05],ES_calm=-mean([-0.10,-0.02])=0.06。