Skip to content

[Bug] / Backtest 资金曲线异常:图表使用现金余额且 SELL 未减仓,导致资金先跌到 0 再在结算时突然拉升 #39

@Jaqen00

Description

@Jaqen00

📝 Bug Description / Bug 描述

问题描述

回测详情页的资金曲线存在明显异常:

  • 资金曲线经常跌到 0
  • 随后在回测结束时突然大幅拉升
  • 最大回撤等统计值也明显不合理

我排查了一个具体样本:

  • task_name = 111
  • backtest_task.id = 12
  • initial_balance = 1000
  • final_balance = 4961.58585636
  • max_drawdown = 1015.97518000

数据库中这条任务的交易记录表现为:

  • backtest_trade 最小 balance_after = 0.00000000
  • 最后一个时间点 2026-03-22 12:04:29.120224SETTLEMENT
  • 曲线尾部的“突然拉升”基本就是这批结算同时入账造成的

复现现象

task_name = 111 为例:

  1. 中途出现现金余额被买到 0 的记录

    • 例如某笔 BUY 后 balance_after = 0
  2. 回测结束时,系统将所有剩余持仓一次性做 SETTLEMENT

    • 同一毫秒写入大量 SETTLEMENT
    • 导致图表尾部突然抬升

已确认的代码问题

1. 前端图表画的是 balanceAfter,即“现金余额”,不是“总权益”

前端图表直接使用每笔交易的 balanceAfter 作为曲线值:

  • frontend/src/pages/BacktestChart.tsx
  • 关键位置:
    • trades.map(...) -> value: parseFloat(trade.balanceAfter)

这会导致:

  • 只要现金被买完,图表就会掉到 0
  • 即使实际上还有大量未平仓仓位,图上也看不出来

2. BUY 逻辑允许把现金全部买光

后端 BUY 分支在余额不足时,直接用当前全部余额继续买:

  • backend/src/main/kotlin/com/wrbug/polymarketbot/service/backtest/BacktestExecutionService.kt
  • actualBuyAmount = currentBalance

这会直接制造出 balance_after = 0 的现金曲线低点。

3. SELL 逻辑没有减少持仓数量,这是核心 bug

在 SELL 分支里:

  • 代码计算了 actualSellQuantity
  • 也把卖出金额加回了 currentBalance
  • 但没有把 position.quantity 减掉

也就是说,仓位卖出后仍然留在 positions 里。

结果:

  • 中途已经卖过的仓位,回测结束时还会再次被 SETTLEMENT
  • 最终资金被重复抬高
  • 资金曲线尾部异常拉升
  • 最终收益失真

4. 回测结束时 CLOSED 结算的 profitLoss 计算不合理

对未到期持仓的 settleRemainingPositions() 中:

  • settlementPrice = avgPrice
  • profitLoss = settlementValue.negate()

如果是按均价平仓,profitLoss 应该接近 0
当前写法会把这类结算记成负值,污染统计结果。

5. 最大回撤计算公式有问题

calculateStatistics() 中:

  • 使用的是 peakBalance - runningBalance
  • runningBalance 在该轮循环末尾才更新
  • 实际应使用当前 balance

这会导致 max_drawdown 计算失真。
在样本任务里,max_drawdown = 1015.97518000,甚至高于初始资金 1000,明显不合理。

预期行为

  1. 资金曲线应展示“总权益(现金 + 未平仓仓位估值)”

    • 如果当前只想展示现金,至少应明确标注为“现金余额”,不能命名为资金曲线/净值曲线
  2. SELL 后必须正确减少仓位

    • position.quantity -= actualSellQuantity
    • 若剩余数量为 0,则移除该持仓
    • 若存在 leaderBuyQuantity,也需要按比例同步递减
  3. 回测结束时只应结算真正剩余的未平仓仓位

    • 已经卖掉的仓位不能再次 SETTLEMENT
  4. CLOSED 结算的 profitLoss 应正确计算

    • 若按均价平仓,应为 0
    • 若按市场价结算,应为 settlementValue - cost
  5. 最大回撤应基于当前时点的真实权益计算

    • 而不是上一轮的现金值

建议修复方案

后端

  • 修复 SELL 分支的减仓逻辑
  • 结算前确保 positions 里只保留真实剩余仓位
  • 修复 settleRemainingPositions()profitLoss
  • 修复 calculateStatistics() 的回撤公式
  • 最好增加一个真正的 equity_after 字段或单独生成权益曲线数据

前端

  • 图表不要直接用 balanceAfter 作为“资金曲线”
  • 应改为展示真实权益曲线
  • 如果暂时做不到,至少把当前图表名称改成“现金余额曲线”

影响

这不是单纯的 UI 展示问题:

  • 中途曲线会错误地显示“资金归零”
  • 尾部会因为统一结算而突然抬升
  • 卖出后未减仓会导致收益和最终资金失真
  • 最大回撤等统计指标也不可信

我建议你额外附上的关键证据

  • task_name = 111
  • backtest_task.id = 12
  • initial_balance = 1000
  • final_balance = 4961.58585636
  • max_drawdown = 1015.97518000
  • backtest_trade 里最小 balance_after = 0
  • 最后同一时间点有 224 笔 SETTLEMENT

对应代码位置

  • /tmp/polyhermes-src-7y03vK/frontend/src/pages/BacktestChart.tsx
  • /tmp/polyhermes-src-7y03vK/backend/src/main/kotlin/com/wrbug/polymarketbot/service/backtest/BacktestExecutionService.kt
Image

🎯 Bug Type / 问题类型

Frontend bug (UI/UX/interaction) / 前端 bug (UI/UX/交互问题)

📍 Affected Scope / 影响范围

Specific page/function only / 仅影响特定页面/功能

🔍 Steps to Reproduce / 复现步骤

Reproduction Frequency / 复现频率

Always reproducible (100%) / 总是能复现 (100%)

💻 Expected Behavior / 预期行为

❌ Actual Behavior / 实际行为

📸 Screenshots / Recordings / 截图/录屏

🌐 Environment / 环境

Browser (for frontend issues) / 浏览器(前端问题):

  • Browser: ______ / 浏览器:______
  • Browser version: ______ / 浏览器版本:______
  • Operating System: ______ / 操作系统:______

Backend Environment (for backend issues) / 后端环境(后端问题):

  • Node.js version: ______ / Node.js 版本:______
  • Database version: ______ / 数据库版本:______
  • Docker version (if used): ______ / Docker 版本(如果使用):______
  • Other relevant dependencies: ______ / 其他相关依赖版本:______

📁 Related Files / Code / 相关文件/代码

No response

🎯 Fix Suggestions (Optional) / 修复建议(可选)

No response

🚨 Priority / 优先级

🟢 Low - Minor issue, doesn't affect usage / 低 - 小问题,不影响使用

📝 Additional Information / 补充说明

No response

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions