From d23628a5a16aa651e7533103d99a5beb0a2e4ff8 Mon Sep 17 00:00:00 2001 From: Lawrence Liu Date: Sat, 8 Nov 2025 10:34:01 +0800 Subject: [PATCH] fix: use symbol_side as peakPnLCache key to support dual-side positions (#657) Fixes #652 Previously, peakPnLCache used only 'symbol' as the key, causing LONG and SHORT positions of the same symbol to share the same peak P&L value. This led to incorrect drawdown calculations and emergency close triggers. Changes: - checkPositionDrawdown: use posKey (symbol_side) for cache access - UpdatePeakPnL: add side parameter and use posKey internally - ClearPeakPnLCache: add side parameter and use posKey internally Example fix: - Before: peakPnLCache["BTCUSDT"] shared by both LONG and SHORT - After: peakPnLCache["BTCUSDT_long"] and peakPnLCache["BTCUSDT_short"] Impact: - Fixes incorrect drawdown monitoring for dual positions - Prevents false emergency close triggers on profitable positions --- trader/auto_trader.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 059313b5..76de2051 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -1553,18 +1553,21 @@ func (at *AutoTrader) checkPositionDrawdown() { currentPnLPct = ((entryPrice - markPrice) / entryPrice) * float64(leverage) * 100 } + // 构造持仓唯一标识(区分多空) + posKey := symbol + "_" + side + // 获取该持仓的历史最高收益 at.peakPnLCacheMutex.RLock() - peakPnLPct, exists := at.peakPnLCache[symbol] + peakPnLPct, exists := at.peakPnLCache[posKey] at.peakPnLCacheMutex.RUnlock() if !exists { // 如果没有历史最高记录,使用当前盈亏作为初始值 peakPnLPct = currentPnLPct - at.UpdatePeakPnL(symbol, currentPnLPct) + at.UpdatePeakPnL(symbol, side, currentPnLPct) } else { // 更新峰值缓存 - at.UpdatePeakPnL(symbol, currentPnLPct) + at.UpdatePeakPnL(symbol, side, currentPnLPct) } // 计算回撤(从最高点下跌的幅度) @@ -1583,8 +1586,8 @@ func (at *AutoTrader) checkPositionDrawdown() { log.Printf("❌ 回撤平仓失败 (%s %s): %v", symbol, side, err) } else { log.Printf("✅ 回撤平仓成功: %s %s", symbol, side) - // 平仓后清理该symbol的缓存 - at.ClearPeakPnLCache(symbol) + // 平仓后清理该持仓的缓存 + at.ClearPeakPnLCache(symbol, side) } } else if currentPnLPct > 5.0 { // 记录接近平仓条件的情况(用于调试) @@ -1630,25 +1633,27 @@ func (at *AutoTrader) GetPeakPnLCache() map[string]float64 { } // UpdatePeakPnL 更新最高收益缓存 -func (at *AutoTrader) UpdatePeakPnL(symbol string, currentPnLPct float64) { +func (at *AutoTrader) UpdatePeakPnL(symbol, side string, currentPnLPct float64) { at.peakPnLCacheMutex.Lock() defer at.peakPnLCacheMutex.Unlock() - if peak, exists := at.peakPnLCache[symbol]; exists { + posKey := symbol + "_" + side + if peak, exists := at.peakPnLCache[posKey]; exists { // 更新峰值(如果是多头,取较大值;如果是空头,currentPnLPct为负,也要比较) if currentPnLPct > peak { - at.peakPnLCache[symbol] = currentPnLPct + at.peakPnLCache[posKey] = currentPnLPct } } else { // 首次记录 - at.peakPnLCache[symbol] = currentPnLPct + at.peakPnLCache[posKey] = currentPnLPct } } -// ClearPeakPnLCache 清除指定symbol的峰值缓存 -func (at *AutoTrader) ClearPeakPnLCache(symbol string) { +// ClearPeakPnLCache 清除指定持仓的峰值缓存 +func (at *AutoTrader) ClearPeakPnLCache(symbol, side string) { at.peakPnLCacheMutex.Lock() defer at.peakPnLCacheMutex.Unlock() - delete(at.peakPnLCache, symbol) + posKey := symbol + "_" + side + delete(at.peakPnLCache, posKey) }