前向恢复距离:累计增益重新越过阈值所需天数
Forward Recovery Distance: Days Until Cumulative Gain Clears Delta
开始编码实现 solution(returns: list[float], recovery_delta: float) -> list[int]。给定一段长度为 N 的逐期带符号收益流 returns 与一个严格正的恢复阈值 recovery_delta,输出长度为 N 的整数列表 output:output[i] 是从第 i 天之后开始算、累计增益首次达到 recovery_delta 所需的最小前向距离 d >= 1。形式化地,记累计 PnL 路径 cumret[k] = returns[0] + returns[1] + ... + returns[k](k 取 0..N-1);output[i] 是满足 i + d <= N - 1 且 cumret[i + d] - cumret[i] >= recovery_delta 的最小整数 d >= 1;若剩余 bar 用尽仍未恢复,则 output[i] = -1。
示例:solution([0.5, -0.3, 0.4, -0.2, 0.7], 0.5) 返回 [4, 3, 2, 1, -1]。逐步推导:cumret = [0.5, 0.2, 0.6, 0.4, 1.1]。i = 0 时基线为 0.5,需要某个未来 cumret[j] 至少为 1.0;j = 1, 2, 3 处分别为 0.2, 0.6, 0.4(都不到 1.0),j = 4 处为 1.1 >= 1.0,所以 d = 4 - 0 = 4。i = 1 时基线 0.2、目标 0.7,第一个满足 cumret[j] >= 0.7 的是 j = 4(值 1.1),所以 d = 3。i = 2 时基线 0.6、目标 1.1,j = 4 处刚好 1.1 >= 1.1(比较为非严格),所以 d = 2。i = 3 时基线 0.4、目标 0.9,j = 4 处 1.1 >= 0.9,所以 d = 1。i = 4 时未来窗口为空,输出 -1。
边界情况必须严格处理:solution([], 0.5) 返回 []。最后一个下标始终输出 -1,因为不存在更未来的 bar——solution([1.0], 0.1) 返回 [-1],不是 [1],更不是 [0]。一段全部为正的 returns 配上很小的 recovery_delta 会得到一连串的 1(一根未来 bar 就够了)。一段长长的回撤之后突然来一根大正收益 bar,回撤早期那些下标在尖峰太小、补不平累计亏损时会输出 -1,能补平的下标输出有限的 d。比较为非严格:累计增益恰好等于 recovery_delta 也算已恢复。
预期算法是直接的前缀和查询:先 O(N) 建好 cumret,然后对每个 i 向前扫描 j = i+1, i+2, ..., N-1,遇到第一个 cumret[j] - cumret[i] >= recovery_delta 的 j 就停下。每个起点 O(N),整体 O(N^2)——在 N <= 1500 的约束下完全没有时间压力。隐藏用例会专门捕这些常见陷阱:剩余窗口不够时返回 0(而非哨兵 -1)、返回 bar 下标 j 而不是偏移 d = j - i、用严格 > 而非 >=、最后一个起点越界访问 N-1 之后的位置。
实现细节由 stubs/stub.py 提供。
实践背景
策略报告里的"回撤恢复时间"指标会回答:在样本回测里,对每一个交易日来说,当天收盘之后还要再走多少根 bar 累计 PnL 才能补回至少 recovery_delta?把每一天都打上这个"前向恢复距离"标签,单条权益曲线就被翻译成一条 PM 可以直接扫的时间序列——连续若干个起点都背着一个很大的恢复距离,意味着那段是"慢磨"型回撤;恢复距离一短一长交替则提示策略在该区段反复"原地踏步"。生产上有几个合约细节会咬人。哨兵 -1 的意义是"在样本窗口内策略始终没能恢复",这跟"刚好够 1 根 bar 就恢复"是完全不同的事件;如果把 -1 折算成一个大整数(比如 N),下游直方图就会被污染:它假装恢复发生在最后一根 bar 上、实际上是发生在"压根没发生"。非严格比较对齐了 PM 端的一般口径——"累计增益达到或超过恢复阈值就算恢复",用严格 > 会把恰好顶在阈值上的样本点丢掉,而那正是告警应该解除的瞬间。最后一根 bar 强制为 -1 则是分析侧的一个提醒:最后一个样本点是被截断的,不管未来真实世界里那个未观测到的下一根有多大的正收益,从样本里都没法测到它。
约束条件
- 0 <= N <= 1500,其中 N 为 solution(returns, recovery_delta) 输入列表 `returns` 的长度
- 每个 returns[i] 为 [-1e6, 1e6] 区间内的有限带符号 float
- recovery_delta 为 (0, 1e9] 区间内严格正的 float
- 输出为长度 N 的 int 列表;output[i] ∈ {1, 2, ..., N-1-i} 或 -1(哨兵:剩余窗口不足以恢复)
- 空输入返回空列表 [];**最后一个**下标(i = N-1)始终为 -1
样例
Case 1 · statement-example: zigzag returns delta 0.5
输入: [[0.5,-0.3,0.4,-0.2,0.7],0.5]
期望: [4,3,2,1,-1]
cumret=[0.5,0.2,0.6,0.4,1.1];各起点目标依次为 1.0/0.7/1.1/0.9/—,分别在 j=4,4,4,4 命中(最后一个起点未来为空),距离 d=[4,3,2,1,-1]。
Case 2 · visible: empty input returns []
输入: [[],0.5]
期望: []
空输入直接返回空列表。
Case 3 · visible: single bar always -1 (no future)
输入: [[1],0.1]
期望: [-1]
只有一根 bar,未来窗口为空,输出 [-1]。
Case 4 · visible: exact equality on threshold accepted (non-strict >=)
输入: [[0,1],1]
期望: [1,-1]
cumret=[0.0,1.0];i=0 目标 1.0,j=1 处恰好 1.0 ≥ 1.0,d=1;i=1 末尾返回 -1。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: zigzag returns delta 0.5
输入: [[0.5,-0.3,0.4,-0.2,0.7],0.5]
期望: [4,3,2,1,-1]
cumret=[0.5,0.2,0.6,0.4,1.1];各起点目标依次为 1.0/0.7/1.1/0.9/—,分别在 j=4,4,4,4 命中(最后一个起点未来为空),距离 d=[4,3,2,1,-1]。
Case 2 · visible: empty input returns []
输入: [[],0.5]
期望: []
空输入直接返回空列表。
Case 3 · visible: single bar always -1 (no future)
输入: [[1],0.1]
期望: [-1]
只有一根 bar,未来窗口为空,输出 [-1]。
Case 4 · visible: exact equality on threshold accepted (non-strict >=)
输入: [[0,1],1]
期望: [1,-1]
cumret=[0.0,1.0];i=0 目标 1.0,j=1 处恰好 1.0 ≥ 1.0,d=1;i=1 末尾返回 -1。