# AUTO 最小价差:100%→50% 动态系数方案 ## 现状 - **BinanceKlineAutoSpreadService**:拉取历史 K 线 → IQR 剔除异常值 → 求平均得到「基础价差」→ **固定 ×0.7** 后缓存。 - 预加载(周期开始时):`computeAndCache()` 计算并缓存的是 **已乘 0.7** 的值。 - 触发时:`getAutoMinSpread()` 直接返回缓存值,等价于始终用 **70%** 的系数。 问题:70% 固定,无法随周期内时间变化放宽或收紧。 --- ## 目标 1. **预加载提供 100% 数值**:缓存里存「基础价差」(IQR 平均),不再乘 0.7,即预加载 = 100% 基准。 2. **系数随区间时间点动态递减**:从 **100%** 线性递减到 **50%**,根据「当前时间在区间内的进度」计算。 --- ## 方案一:按「触发窗口」进度(推荐) **区间**:策略的触发窗口 `[periodStartUnix + windowStartSeconds, periodStartUnix + windowEndSeconds]`。 - 窗口起始:系数 = **100%**(最严,价差要求最高)。 - 窗口内时间越靠后,系数越小;窗口结束:系数 = **50%**(最松,更容易触发)。 公式(**progress 按毫秒计算**,保证精度): ``` windowStartMs = (periodStartUnix + windowStartSeconds) * 1000 windowEndMs = (periodStartUnix + windowEndSeconds) * 1000 windowLenMs = windowEndMs - windowStartMs nowMs = System.currentTimeMillis() progress = (nowMs - windowStartMs) / windowLenMs progress = clamp(progress, 0, 1) // 比例系数 = progress × (100% - 50%),即已「消耗」的系数降幅 // 真正系数 = 100% - 比例系数 coefficient = 1.0 - progress × (1.0 - 0.5) = 1.0 - 0.5 × progress effectiveMinSpread = baseSpread × coefficient ``` **计算示例**(时间区间 14分0秒~15分0秒,窗口 60 秒 = 60000 ms): | 时刻 | 进入窗口的毫秒数 | progress(按毫秒) | 比例系数 | 真正系数 | |------------|------------------|--------------------|--------------------|------------| | 14:00 | 0 | 0/60000 = 0% | 0% × 50% = 0% | 100% | | 14:15 | 15000 | 15000/60000 = 25% | 25% × 50% = 12.5% | **87.5%** | | 14:30 | 30000 | 30000/60000 = 50% | 50% × 50% = 25% | 75% | | 15:00 | 60000 | 60000/60000 = 100% | 100% × 50% = 50% | 50% | 即:在 14分15秒 时,progress = 15000ms / 60000ms = 25%,比例系数 = 12.5%,真正系数 = **87.5%**。实现时统一用毫秒计算 progress,避免秒级舍入误差。 - 需要策略的 `windowStartSeconds`、`windowEndSeconds` 传入计算处;若窗口长度为 0,可退化为系数 = 1.0 或 0.5(需约定)。 **优点**:与「尾盘只在窗口内触发」一致,时间语义清晰;毫秒级 progress 更精确。 **缺点**:`getAutoMinSpread` 需要增加当前时间(毫秒)和窗口参数(或传整个 strategy)。 --- ## 方案二:按「整周期」进度 **区间**:整个周期 `[periodStartUnix, periodStartUnix + intervalSeconds]`。**progress 按毫秒计算**。 ``` periodStartMs = periodStartUnix * 1000 periodEndMs = (periodStartUnix + intervalSeconds) * 1000 periodLenMs = intervalSeconds * 1000L nowMs = System.currentTimeMillis() progress = (nowMs - periodStartMs) / periodLenMs progress = clamp(progress, 0, 1) coefficient = 1.0 - 0.5 * progress effectiveMinSpread = baseSpread × coefficient ``` **优点**:只依赖 `intervalSeconds`、`periodStartUnix`、`nowSeconds`,不依赖窗口配置。 **缺点**:若窗口只占周期后半段,周期前半段也会在算系数,语义上不如按窗口精确。 --- ## 实现要点 ### 1. 缓存 100% 基准值 - **BinanceKlineAutoSpreadService**: - `computeAndCache()`:缓存 **不乘 0.7** 的 (avgUp, avgDown),即 IQR 平均后的原始值(100% 基准)。 - 可保留方法名与入参不变,仅去掉 `autoSpreadCoefficient` 的乘法;或新增 `getBaseSpread()` 语义,内部仍用同一缓存。 ### 2. 动态系数计算位置 - 系数依赖「当前时间」和「区间定义」,适合在 **触发校验处** 算,而不是在 AutoSpread 服务里写死。 - **CryptoTailStrategyExecutionService.passMinSpreadCheck()**: - 当前:`getAutoMinSpread(intervalSeconds, periodStartUnix, outcomeIndex)` 得到已乘系数的值。 - 改为: - 取「基础价差」:`getAutoMinSpreadBase(intervalSeconds, periodStartUnix, outcomeIndex)` 或由现有缓存返回 100% 值。 - 在 `passMinSpreadCheck` 内根据 `strategy.windowStartSeconds/windowEndSeconds` 和 `System.currentTimeMillis()`(毫秒)算 `progress`(按毫秒)→ `coefficient` → `effectiveMinSpread = baseSpread × coefficient`。 ### 3. 接口形态建议 - **BinanceKlineAutoSpreadService**: - `computeAndCache(interval, periodStartUnix)`:只缓存 100% 基准 (baseUp, baseDown),不再乘 0.7。 - `getAutoMinSpreadBase(interval, periodStartUnix, outcomeIndex): BigDecimal?`:仅返回缓存的基础价差;若需兼容旧名,可保留 `getAutoMinSpread` 但增加可选参数 `coefficient`,默认 1.0。 - **CryptoTailStrategyExecutionService**: - 在 `passMinSpreadCheck(strategy, periodStartUnix, outcomeIndex)` 内: - 取 `baseSpread = getAutoMinSpreadBase(...)`。 - 计算 `progress`(按方案一用 windowStart/End,或方案二用 interval)。 - `coefficient = 1.0 - 0.5 * progress`,再 `effectiveMinSpread = baseSpread * coefficient` 做比较。 ### 4. 边界与兼容 - 窗口长度为 0:可约定 `coefficient = 0.5` 或 1.0,避免除零。 - 已有策略未配置窗口(全 0):若用方案一,可退化为「整周期」或固定 0.5/1.0」。 - 预加载逻辑(如 CryptoTailOrderbookWsService 的 `precomputeAutoMinSpreadForCurrentPeriods`)无需改,仍调用 `computeAndCache`,只是缓存内容变为 100% 基准。 --- ## 小结 | 项目 | 内容 | |------------|------| | 预加载 | 缓存 100% 基础价差(去掉固定 0.7) | | 系数范围 | 100% → 50% 线性递减 | | 推荐区间 | 按触发窗口 `windowStartSeconds`~`windowEndSeconds` 计算进度(方案一) | | progress | **按毫秒计算**:`(nowMs - windowStartMs) / windowLenMs`,避免秒级舍入误差 | | 计算位置 | 触发时在 `passMinSpreadCheck` 中算 progress → coefficient → effectiveMinSpread | 按上述实现后,AUTO 模式即为「预加载提供 100% 数值 + 随区间时间点从 100% 递减到 50%」的动态方案。