From 68c0a342a64b5aef715c70affd2e3175c2676138 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Sun, 3 May 2026 23:19:15 +0800 Subject: [PATCH] fix: retryClose saves trade record and persists to DB on success --- trader.go | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/trader.go b/trader.go index 2e302e7..fa8dc9c 100644 --- a/trader.go +++ b/trader.go @@ -51,6 +51,15 @@ type ArbPosition struct { RealizedPnl float64 ErrorLog string + // Exit metadata — saved when close is first attempted; reused by retryClose + ExitDiffPct float64 // spread % at exit trigger + ExitNetPnl float64 // net PnL % at exit trigger + ExitLongPnl float64 // long leg PnL % + ExitShortPnl float64 // short leg PnL % + ExitTotalFees float64 // total fee % + ExitConvergence string // convergence label + ExitReasonText string // reason for exit + // Track all entry prices for weighted-average PnL across scale-ins (Issue #2) LongEntryPrices []float64 // all long entry prices (initial + scale-ins) ShortEntryPrices []float64 // all short entry prices (initial + scale-ins) @@ -534,6 +543,15 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier pos.LongLeg.ExitPrice = longCurrent pos.ShortLeg.ExitPrice = shortCurrent + // Save exit metadata for retryClose in case closeBothLegs fails + pos.ExitDiffPct = diffPct + pos.ExitNetPnl = netPnl + pos.ExitLongPnl = longPnl + pos.ExitShortPnl = shortPnl + pos.ExitTotalFees = totalFees + pos.ExitConvergence = convergenceLabel + pos.ExitReasonText = exitReason + closeErr := t.closeBothLegs(pos) if closeErr != "" { @@ -691,17 +709,41 @@ func (t *Trader) retryClose(pos *ArbPosition, bgP, hlP float64, notifier *Notifi closeErr := t.closeBothLegs(pos) if closeErr == "" { - // All legs finally closed — mark as done + // All legs finally closed — record + persist pos.Status = "closed" pos.ExitedAt = time.Now() + + elapsed := time.Since(pos.StartedAt) + record := TradeRecord{ + Coin: pos.Coin, + Direction: pos.Direction, + EntrySpread: pos.EntrySpread, + ExitSpread: pos.ExitDiffPct, + PnlPct: pos.ExitNetPnl, + Convergence: pos.ExitConvergence, + Reason: pos.ExitReasonText, + Duration: elapsed.Round(time.Second).String(), + OpenedAt: pos.StartedAt, + ClosedAt: pos.ExitedAt, + ScaleLevels: pos.ScaleLevels, + AmountUSD: pos.AmountUSD, + } + t.mu.Lock() delete(t.positions, pos.Coin) t.lastTradeTime[pos.Coin] = time.Now() + t.closedTrades = append(t.closedTrades, record) t.mu.Unlock() + + if t.db != nil { + t.persistTrade(pos, pos.ExitDiffPct, pos.ExitConvergence, pos.ExitReasonText, + pos.ExitNetPnl, pos.ExitLongPnl, pos.ExitShortPnl, pos.ExitTotalFees) + } + notifier.Send(fmt.Sprintf( "[平仓重试成功] %s/USDT %s\n"+ " 之前失败: %s\n"+ - " 已成功关掉所有腿\n", pos.Coin, pos.Direction, pos.ErrorLog)) + " 已成功关掉所有腿 | 盈亏: %+.4f%%\n", pos.Coin, pos.Direction, pos.ErrorLog, pos.ExitNetPnl)) return }