diff --git a/store/position.go b/store/position.go index f833792995..0ac5200cee 100644 --- a/store/position.go +++ b/store/position.go @@ -280,7 +280,12 @@ func (s *PositionStore) GetOpenPositions(traderID string) ([]*TraderPosition, er // GetOpenPositionBySymbol gets open position for specified symbol and direction func (s *PositionStore) GetOpenPositionBySymbol(traderID, symbol, side string) (*TraderPosition, error) { var pos TraderPosition - err := s.db.Where("trader_id = ? AND symbol = ? AND side = ? AND status = ?", traderID, symbol, side, "OPEN"). + + // Build side condition: match exact side OR "BOTH" (one-way mode positions) + // "BOTH" is used in one-way position mode where positions don't have a specific side + sideCondition := "(side = ? OR side = 'BOTH')" + + err := s.db.Where("trader_id = ? AND symbol = ? AND "+sideCondition+" AND status = ?", traderID, symbol, side, "OPEN"). Order("entry_time DESC"). First(&pos).Error @@ -295,7 +300,7 @@ func (s *PositionStore) GetOpenPositionBySymbol(traderID, symbol, side string) ( // Try without USDT suffix for backward compatibility if strings.HasSuffix(symbol, "USDT") { baseSymbol := strings.TrimSuffix(symbol, "USDT") - err = s.db.Where("trader_id = ? AND symbol = ? AND side = ? AND status = ?", traderID, baseSymbol, side, "OPEN"). + err = s.db.Where("trader_id = ? AND symbol = ? AND "+sideCondition+" AND status = ?", traderID, baseSymbol, side, "OPEN"). Order("entry_time DESC"). First(&pos).Error if err == nil { diff --git a/trader/bitget/order_sync.go b/trader/bitget/order_sync.go index e8f1078962..4607ae9bfc 100644 --- a/trader/bitget/order_sync.go +++ b/trader/bitget/order_sync.go @@ -103,18 +103,32 @@ func (t *BitgetTrader) GetTrades(startTime time.Time, limit int) ([]BitgetTrade, feeAsset = fill.FeeDetail[0].FeeCoin } - // Determine order action based on side and tradeSide - // Bitget one-way mode: buy_single (open long), sell_single (close long) - // Bitget hedge mode: open + buy = open_long, close + sell = close_long + // Determine order action based on side, tradeSide, and profit + // Key insight: profit != 0 means it's a closing trade (realized PnL) + // profit == 0 means it's an opening trade (no PnL yet) + // + // Bitget one-way mode: buy_single/sell_single + // Bitget hedge mode: open/close + buy/sell orderAction := "open_long" side := strings.ToLower(fill.Side) tradeSide := strings.ToLower(fill.TradeSide) + hasProfit := profit != 0 // Non-zero profit indicates closing trade // One-way position mode (buy_single/sell_single) if tradeSide == "buy_single" { - orderAction = "open_long" + // buy_single: could be open_long (no profit) or close_short (has profit) + if hasProfit { + orderAction = "close_short" + } else { + orderAction = "open_long" + } } else if tradeSide == "sell_single" { - orderAction = "close_long" + // sell_single: could be open_short (no profit) or close_long (has profit) + if hasProfit { + orderAction = "close_long" + } else { + orderAction = "open_short" + } } else if tradeSide == "open" { // Hedge mode: open if side == "buy" {