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
This commit is contained in:
Lawrence Liu
2025-11-08 10:34:01 +08:00
committed by GitHub
parent f73b4771b2
commit d23628a5a1
+17 -12
View File
@@ -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)
}