mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
cb31782be4
- 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)
161 lines
4.8 KiB
Go
161 lines
4.8 KiB
Go
package bitget
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"nofx/trader/types"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// GetPositions gets all positions
|
|
func (t *BitgetTrader) GetPositions() ([]map[string]interface{}, error) {
|
|
// Check cache
|
|
t.positionsCacheMutex.RLock()
|
|
if t.cachedPositions != nil && time.Since(t.positionsCacheTime) < t.cacheDuration {
|
|
t.positionsCacheMutex.RUnlock()
|
|
return t.cachedPositions, nil
|
|
}
|
|
t.positionsCacheMutex.RUnlock()
|
|
|
|
params := map[string]interface{}{
|
|
"productType": "USDT-FUTURES",
|
|
"marginCoin": "USDT",
|
|
}
|
|
|
|
data, err := t.doRequest("GET", bitgetPositionPath, params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get positions: %w", err)
|
|
}
|
|
|
|
var positions []struct {
|
|
Symbol string `json:"symbol"`
|
|
HoldSide string `json:"holdSide"` // long, short
|
|
OpenPriceAvg string `json:"openPriceAvg"` // Average entry price
|
|
MarkPrice string `json:"markPrice"` // Mark price
|
|
Total string `json:"total"` // Total position size
|
|
Available string `json:"available"` // Available to close
|
|
UnrealizedPL string `json:"unrealizedPL"` // Unrealized P&L
|
|
Leverage string `json:"leverage"` // Leverage
|
|
LiquidationPrice string `json:"liquidationPrice"` // Liquidation price
|
|
MarginSize string `json:"marginSize"` // Position margin
|
|
CTime string `json:"cTime"` // Create time
|
|
UTime string `json:"uTime"` // Update time
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &positions); err != nil {
|
|
return nil, fmt.Errorf("failed to parse position data: %w", err)
|
|
}
|
|
|
|
var result []map[string]interface{}
|
|
for _, pos := range positions {
|
|
total, _ := strconv.ParseFloat(pos.Total, 64)
|
|
if total == 0 {
|
|
continue
|
|
}
|
|
|
|
entryPrice, _ := strconv.ParseFloat(pos.OpenPriceAvg, 64)
|
|
markPrice, _ := strconv.ParseFloat(pos.MarkPrice, 64)
|
|
unrealizedPnL, _ := strconv.ParseFloat(pos.UnrealizedPL, 64)
|
|
leverage, _ := strconv.ParseFloat(pos.Leverage, 64)
|
|
liqPrice, _ := strconv.ParseFloat(pos.LiquidationPrice, 64)
|
|
cTime, _ := strconv.ParseInt(pos.CTime, 10, 64)
|
|
uTime, _ := strconv.ParseInt(pos.UTime, 10, 64)
|
|
|
|
// Normalize side
|
|
side := "long"
|
|
if pos.HoldSide == "short" {
|
|
side = "short"
|
|
}
|
|
|
|
posMap := map[string]interface{}{
|
|
"symbol": pos.Symbol,
|
|
"positionAmt": total,
|
|
"entryPrice": entryPrice,
|
|
"markPrice": markPrice,
|
|
"unRealizedProfit": unrealizedPnL,
|
|
"leverage": leverage,
|
|
"liquidationPrice": liqPrice,
|
|
"side": side,
|
|
"createdTime": cTime,
|
|
"updatedTime": uTime,
|
|
}
|
|
result = append(result, posMap)
|
|
}
|
|
|
|
// Update cache
|
|
t.positionsCacheMutex.Lock()
|
|
t.cachedPositions = result
|
|
t.positionsCacheTime = time.Now()
|
|
t.positionsCacheMutex.Unlock()
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetClosedPnL retrieves closed position PnL records
|
|
func (t *BitgetTrader) GetClosedPnL(startTime time.Time, limit int) ([]types.ClosedPnLRecord, error) {
|
|
if limit <= 0 {
|
|
limit = 100
|
|
}
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"productType": "USDT-FUTURES",
|
|
"startTime": fmt.Sprintf("%d", startTime.UnixMilli()),
|
|
"limit": fmt.Sprintf("%d", limit),
|
|
}
|
|
|
|
data, err := t.doRequest("GET", "/api/v2/mix/position/history-position", params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get positions history: %w", err)
|
|
}
|
|
|
|
var resp struct {
|
|
List []struct {
|
|
Symbol string `json:"symbol"`
|
|
HoldSide string `json:"holdSide"`
|
|
OpenPriceAvg string `json:"openPriceAvg"`
|
|
ClosePriceAvg string `json:"closePriceAvg"`
|
|
CloseVol string `json:"closeVol"`
|
|
AchievedProfits string `json:"achievedProfits"`
|
|
TotalFee string `json:"totalFee"`
|
|
Leverage string `json:"leverage"`
|
|
CTime string `json:"cTime"`
|
|
UTime string `json:"uTime"`
|
|
} `json:"list"`
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
records := make([]types.ClosedPnLRecord, 0, len(resp.List))
|
|
for _, pos := range resp.List {
|
|
record := types.ClosedPnLRecord{
|
|
Symbol: pos.Symbol,
|
|
Side: pos.HoldSide,
|
|
}
|
|
|
|
record.EntryPrice, _ = strconv.ParseFloat(pos.OpenPriceAvg, 64)
|
|
record.ExitPrice, _ = strconv.ParseFloat(pos.ClosePriceAvg, 64)
|
|
record.Quantity, _ = strconv.ParseFloat(pos.CloseVol, 64)
|
|
record.RealizedPnL, _ = strconv.ParseFloat(pos.AchievedProfits, 64)
|
|
fee, _ := strconv.ParseFloat(pos.TotalFee, 64)
|
|
record.Fee = -fee
|
|
lev, _ := strconv.ParseFloat(pos.Leverage, 64)
|
|
record.Leverage = int(lev)
|
|
|
|
cTime, _ := strconv.ParseInt(pos.CTime, 10, 64)
|
|
uTime, _ := strconv.ParseInt(pos.UTime, 10, 64)
|
|
record.EntryTime = time.UnixMilli(cTime).UTC()
|
|
record.ExitTime = time.UnixMilli(uTime).UTC()
|
|
|
|
record.CloseType = "unknown"
|
|
records = append(records, record)
|
|
}
|
|
|
|
return records, nil
|
|
}
|