-
Notifications
You must be signed in to change notification settings - Fork 32
[Bug] / Backtest 资金曲线异常:图表使用现金余额且 SELL 未减仓,导致资金先跌到 0 再在结算时突然拉升 #39
Description
📝 Bug Description / Bug 描述
问题描述
回测详情页的资金曲线存在明显异常:
- 资金曲线经常跌到
0 - 随后在回测结束时突然大幅拉升
- 最大回撤等统计值也明显不合理
我排查了一个具体样本:
task_name = 111backtest_task.id = 12initial_balance = 1000final_balance = 4961.58585636max_drawdown = 1015.97518000
数据库中这条任务的交易记录表现为:
backtest_trade最小balance_after = 0.00000000- 最后一个时间点
2026-03-22 12:04:29.120有224笔SETTLEMENT - 曲线尾部的“突然拉升”基本就是这批结算同时入账造成的
复现现象
以 task_name = 111 为例:
-
中途出现现金余额被买到
0的记录- 例如某笔 BUY 后
balance_after = 0
- 例如某笔 BUY 后
-
回测结束时,系统将所有剩余持仓一次性做
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.ktactualBuyAmount = 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,明显不合理。
预期行为
-
资金曲线应展示“总权益(现金 + 未平仓仓位估值)”
- 如果当前只想展示现金,至少应明确标注为“现金余额”,不能命名为资金曲线/净值曲线
-
SELL 后必须正确减少仓位
position.quantity -= actualSellQuantity- 若剩余数量为 0,则移除该持仓
- 若存在
leaderBuyQuantity,也需要按比例同步递减
-
回测结束时只应结算真正剩余的未平仓仓位
- 已经卖掉的仓位不能再次
SETTLEMENT
- 已经卖掉的仓位不能再次
-
CLOSED结算的profitLoss应正确计算- 若按均价平仓,应为
0 - 若按市场价结算,应为
settlementValue - cost
- 若按均价平仓,应为
-
最大回撤应基于当前时点的真实权益计算
- 而不是上一轮的现金值
建议修复方案
后端
- 修复 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
🎯 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