diff --git a/trader.go b/trader.go index 47789d0..a9af69e 100644 --- a/trader.go +++ b/trader.go @@ -520,8 +520,7 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier longPnl := (longCurrent - longAvg) / longAvg * 100 shortPnl := (shortAvg - shortCurrent) / shortAvg * 100 - totalFees := float64(2+pos.ScaleLevels) * (takerFees[ExBitget] + takerFees[ExHyperLiquid]) // 开仓(含加仓) + 平仓手续费 - netPnl := longPnl + shortPnl - totalFees + netPnl, totalFees := calcArbPnL(longPnl, shortPnl, pos.ScaleLevels, t.cfg.TradeAmountUSD) // 净利为总资本的百分比 elapsed := time.Since(pos.StartedAt) @@ -602,7 +601,7 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier EntrySpread: pos.EntrySpread, ExitSpread: diffPct, PnlPct: netPnl, - PnlUSD: pos.AmountUSD * netPnl / 100, + PnlUSD: 2 * pos.AmountUSD * netPnl / 100, Convergence: convergenceLabel, Reason: exitReason, Duration: elapsed.Round(time.Second).String(), @@ -854,6 +853,27 @@ func (t *Trader) GetOpenPositions() []*ArbPosition { return r } +// calcArbPnL computes net PnL and total fees in USD, then normalizes to % of total deployed capital. +// This correctly handles scale-ins where the old formula (longPnl+shortPnl - (2+N)*0.105) +// double-counted fees because it didn't divide by (1+N) batches. +func calcArbPnL(longPnl, shortPnl float64, scaleLevels int, tradeAmountUSD float64) (netPnlPct, feePct float64) { + numBatches := 1 + scaleLevels + legCapital := tradeAmountUSD + totalCapital := float64(numBatches) * 2 * legCapital + + // Gross PnL in USD + longPnlUSD := longPnl / 100 * float64(numBatches) * legCapital + shortPnlUSD := shortPnl / 100 * float64(numBatches) * legCapital + grossPnLUSD := longPnlUSD + shortPnlUSD + + // Fee in USD (entry+exit per order-pair) + feeUSD := float64(2+scaleLevels) * legCapital * (takerFees[ExBitget] + takerFees[ExHyperLiquid]) / 100 + + netPnlPct = (grossPnLUSD - feeUSD) / totalCapital * 100 + feePct = feeUSD / totalCapital * 100 + return +} + // weightedAvgPrice computes the weighted average entry price across multiple scale levels. // Each level trades the same USD amount, so the result is the harmonic mean of prices. func weightedAvgPrice(prices []float64, amountPerTrade float64) float64 { @@ -1161,8 +1181,7 @@ func (t *Trader) blacklistCoin(pos *ArbPosition, bgP, hlP, diffPct float64, noti shortAvg := weightedAvgPrice(pos.ShortEntryPrices, t.cfg.TradeAmountUSD) longPnl := (longCurrent - longAvg) / longAvg * 100 shortPnl := (shortAvg - shortCurrent) / shortAvg * 100 - totalFees := float64(2+pos.ScaleLevels) * (takerFees[ExBitget] + takerFees[ExHyperLiquid]) - netPnl := longPnl + shortPnl - totalFees + netPnl, totalFees := calcArbPnL(longPnl, shortPnl, pos.ScaleLevels, t.cfg.TradeAmountUSD) pos.ExitDiffPct = diffPct pos.ExitNetPnl = netPnl