Files
nofx/trader/bybit/trader_positions.go
tinkle-community cb31782be4 refactor: split large files and clean up project structure
- Rename experience/ to telemetry/ for clarity
- Split 15+ large Go files (800-2200 lines) into focused modules:
  kernel/engine.go, backtest/runner.go, market/data.go, store/position.go,
  api/handler_trader.go, trader/auto_trader_grid.go, and 9 exchange traders
- Split frontend monoliths: types.ts, api.ts, AITradersPage.tsx, BacktestPage.tsx
  into domain-specific modules with barrel re-exports
- Remove stale files: screenshots, .yml.old, pyproject.toml
- Remove unused scripts/ and cmd/ directories
- Remove broken/outdated test files (network-dependent, stale expectations)
2026-03-12 12:53:57 +08:00

126 lines
3.4 KiB
Go

package bybit
import (
"context"
"fmt"
"nofx/logger"
"strconv"
"strings"
"time"
)
// GetPositions retrieves all positions
func (t *BybitTrader) GetPositions() ([]map[string]interface{}, error) {
// Check cache
t.positionsCacheMutex.RLock()
if t.cachedPositions != nil && time.Since(t.positionsCacheTime) < t.cacheDuration {
positions := t.cachedPositions
t.positionsCacheMutex.RUnlock()
return positions, nil
}
t.positionsCacheMutex.RUnlock()
// Call API
params := map[string]interface{}{
"category": "linear",
"settleCoin": "USDT",
}
result, err := t.client.NewUtaBybitServiceWithParams(params).GetPositionList(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to get Bybit positions: %w", err)
}
if result.RetCode != 0 {
return nil, fmt.Errorf("Bybit API error: %s", result.RetMsg)
}
resultData, ok := result.Result.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Bybit positions return format error")
}
list, _ := resultData["list"].([]interface{})
var positions []map[string]interface{}
for _, item := range list {
pos, ok := item.(map[string]interface{})
if !ok {
continue
}
sizeStr, _ := pos["size"].(string)
size, _ := strconv.ParseFloat(sizeStr, 64)
// Skip empty positions
if size == 0 {
continue
}
entryPriceStr, _ := pos["avgPrice"].(string)
entryPrice, _ := strconv.ParseFloat(entryPriceStr, 64)
unrealisedPnlStr, _ := pos["unrealisedPnl"].(string)
unrealisedPnl, _ := strconv.ParseFloat(unrealisedPnlStr, 64)
leverageStr, _ := pos["leverage"].(string)
leverage, _ := strconv.ParseFloat(leverageStr, 64)
// Mark price
markPriceStr, _ := pos["markPrice"].(string)
markPrice, _ := strconv.ParseFloat(markPriceStr, 64)
// Liquidation price
liqPriceStr, _ := pos["liqPrice"].(string)
liqPrice, _ := strconv.ParseFloat(liqPriceStr, 64)
// Position created/updated time (milliseconds timestamp)
createdTimeStr, _ := pos["createdTime"].(string)
createdTime, _ := strconv.ParseInt(createdTimeStr, 10, 64)
updatedTimeStr, _ := pos["updatedTime"].(string)
updatedTime, _ := strconv.ParseInt(updatedTimeStr, 10, 64)
positionSide, _ := pos["side"].(string) // Buy = long, Sell = short
// Log raw position data for debugging
logger.Infof("[Bybit] GetPositions raw: symbol=%v, side=%s, size=%v", pos["symbol"], positionSide, sizeStr)
// Convert to unified format (use lowercase for consistency with other exchanges)
// Bybit returns "Buy" for long, "Sell" for short
side := "long"
positionAmt := size
positionSideLower := strings.ToLower(positionSide)
if positionSideLower == "sell" {
side = "short"
positionAmt = -size
}
logger.Infof("[Bybit] GetPositions converted: symbol=%v, rawSide=%s -> side=%s", pos["symbol"], positionSide, side)
position := map[string]interface{}{
"symbol": pos["symbol"],
"side": side,
"positionAmt": positionAmt,
"entryPrice": entryPrice,
"markPrice": markPrice,
"unRealizedProfit": unrealisedPnl,
"unrealizedPnL": unrealisedPnl,
"liquidationPrice": liqPrice,
"leverage": leverage,
"createdTime": createdTime, // Position open time (ms)
"updatedTime": updatedTime, // Position last update time (ms)
}
positions = append(positions, position)
}
// Update cache
t.positionsCacheMutex.Lock()
t.cachedPositions = positions
t.positionsCacheTime = time.Now()
t.positionsCacheMutex.Unlock()
return positions, nil
}