fix: blacklistCoin 计算退出PnL字段, 避免DB写入零值

blacklistCoin 之前只设 Status=close_failed 就跑了, 没给
ExitDiffPct/ExitNetPnl/ExitLongPnl/ExitTotalFees 等字段赋值。
retryClose 写 TradeRecord 时全为零, 导致DB出现 PnL=0 的假记录。

修复: blacklistCoin 现在接收当前价格, 用 checkExit 相同方式
计算 PnL/价差收敛标签并存入 pos 字段。
This commit is contained in:
jackyu66git
2026-05-04 02:00:04 +08:00
parent dd15c6fe43
commit d1e6c965cb
+39 -4
View File
@@ -256,7 +256,7 @@ func (t *Trader) Tick(store *PriceStore, notifier *Notifier) {
// Blacklist: if position still open after 10 minutes without converging,
// the spread is likely stale data. Add coin to blacklist and force close.
if pos.Status == "open" && elapsed > 10*time.Minute {
t.blacklistCoin(pos, notifier)
t.blacklistCoin(pos, bgP, hlP, diffPct, notifier)
}
}
}
@@ -991,17 +991,52 @@ func (t *Trader) restoreOpenPositions() {
}
// blacklistCoin adds a coin to the blacklist and force-closes its position.
func (t *Trader) blacklistCoin(pos *ArbPosition, notifier *Notifier) {
// Calculates exit PnL fields so retryClose writes correct data to DB.
func (t *Trader) blacklistCoin(pos *ArbPosition, bgP, hlP, diffPct float64, notifier *Notifier) {
// Compute exit PnL the same way checkExit does
var longCurrent, shortCurrent float64
if pos.LongLeg.Exchange == ExBitget {
longCurrent, shortCurrent = bgP, hlP
} else {
longCurrent, shortCurrent = hlP, bgP
}
longAvg := weightedAvgPrice(pos.LongEntryPrices, t.cfg.TradeAmountUSD)
shortAvg := weightedAvgPrice(pos.ShortEntryPrices, t.cfg.TradeAmountUSD)
longPnl := (longCurrent - longAvg) / longAvg * 100
shortPnl := (shortAvg - shortCurrent) / shortAvg * 100
totalFees := 2 * (takerFees[ExBitget] + takerFees[ExHyperLiquid])
netPnl := longPnl + shortPnl - totalFees
pos.ExitDiffPct = diffPct
pos.ExitNetPnl = netPnl
pos.ExitLongPnl = longPnl
pos.ExitShortPnl = shortPnl
pos.ExitTotalFees = totalFees
pos.LongLeg.ExitPrice = longCurrent
pos.ShortLeg.ExitPrice = shortCurrent
pos.ExitReasonText = "黑名单强平"
// Convergence label
convergedPct := (pos.EntrySpread - diffPct) / pos.EntrySpread * 100
if convergedPct < -10 {
pos.ExitConvergence = "价差发散"
} else if convergedPct < 10 {
pos.ExitConvergence = "价差持平"
} else {
pos.ExitConvergence = "价差收敛"
}
t.mu.Lock()
t.blacklist[pos.Coin] = time.Now()
t.mu.Unlock()
log.Printf("[Trader] ⛔ %s: Blacklisted — position open %.0fm without convergence", pos.Coin, time.Since(pos.StartedAt).Minutes())
log.Printf("[Trader] ⛔ %s: Blacklisted — position open %.0fm without convergence | spread=%.4f%% netPnl=%.4f%%", pos.Coin, time.Since(pos.StartedAt).Minutes(), diffPct, netPnl)
notifier.Send(fmt.Sprintf(
"<b>[黑名单]</b> %s/USDT\n"+
" 开仓 %.0f 分钟未收敛\n"+
" 价差: %.4f%% 净利: %.4f%%\n"+
" 已加入黑名单观察\n",
pos.Coin, time.Since(pos.StartedAt).Minutes()))
pos.Coin, time.Since(pos.StartedAt).Minutes(), diffPct, netPnl))
// Force-close the position immediately
pos.Status = "close_failed" // triggers retryClose on next tick