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)
200 lines
5.0 KiB
Go
200 lines
5.0 KiB
Go
package bitget
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"nofx/logger"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// GetBalance gets account balance
|
|
func (t *BitgetTrader) GetBalance() (map[string]interface{}, error) {
|
|
// Check cache
|
|
t.balanceCacheMutex.RLock()
|
|
if t.cachedBalance != nil && time.Since(t.balanceCacheTime) < t.cacheDuration {
|
|
t.balanceCacheMutex.RUnlock()
|
|
return t.cachedBalance, nil
|
|
}
|
|
t.balanceCacheMutex.RUnlock()
|
|
|
|
params := map[string]interface{}{
|
|
"productType": "USDT-FUTURES",
|
|
}
|
|
|
|
data, err := t.doRequest("GET", bitgetAccountPath, params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get account balance: %w", err)
|
|
}
|
|
|
|
var accounts []struct {
|
|
MarginCoin string `json:"marginCoin"`
|
|
Available string `json:"available"` // Available balance
|
|
AccountEquity string `json:"accountEquity"` // Total equity
|
|
UsdtEquity string `json:"usdtEquity"` // USDT equity
|
|
UnrealizedPL string `json:"unrealizedPL"` // Unrealized P&L
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &accounts); err != nil {
|
|
return nil, fmt.Errorf("failed to parse balance data: %w, raw: %s", err, string(data))
|
|
}
|
|
|
|
var totalEquity, availableBalance, unrealizedPnL float64
|
|
for _, acc := range accounts {
|
|
if acc.MarginCoin == "USDT" {
|
|
totalEquity, _ = strconv.ParseFloat(acc.AccountEquity, 64)
|
|
availableBalance, _ = strconv.ParseFloat(acc.Available, 64)
|
|
unrealizedPnL, _ = strconv.ParseFloat(acc.UnrealizedPL, 64)
|
|
logger.Infof("✓ [Bitget] Balance: equity=%.2f, available=%.2f", totalEquity, availableBalance)
|
|
break
|
|
}
|
|
}
|
|
|
|
result := map[string]interface{}{
|
|
"totalWalletBalance": totalEquity - unrealizedPnL,
|
|
"availableBalance": availableBalance,
|
|
"totalUnrealizedProfit": unrealizedPnL,
|
|
"total_equity": totalEquity,
|
|
}
|
|
|
|
// Update cache
|
|
t.balanceCacheMutex.Lock()
|
|
t.cachedBalance = result
|
|
t.balanceCacheTime = time.Now()
|
|
t.balanceCacheMutex.Unlock()
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// SetMarginMode sets margin mode
|
|
func (t *BitgetTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
|
symbol = t.convertSymbol(symbol)
|
|
|
|
marginMode := "isolated"
|
|
if isCrossMargin {
|
|
marginMode = "crossed"
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"symbol": symbol,
|
|
"productType": "USDT-FUTURES",
|
|
"marginCoin": "USDT",
|
|
"marginMode": marginMode,
|
|
}
|
|
|
|
_, err := t.doRequest("POST", bitgetMarginModePath, body)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "same") || strings.Contains(err.Error(), "already") {
|
|
return nil
|
|
}
|
|
if strings.Contains(err.Error(), "position") {
|
|
logger.Infof(" ⚠️ %s has positions, cannot change margin mode", symbol)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
logger.Infof(" ✓ %s margin mode set to %s", symbol, marginMode)
|
|
return nil
|
|
}
|
|
|
|
// SetLeverage sets leverage
|
|
func (t *BitgetTrader) SetLeverage(symbol string, leverage int) error {
|
|
symbol = t.convertSymbol(symbol)
|
|
|
|
body := map[string]interface{}{
|
|
"symbol": symbol,
|
|
"productType": "USDT-FUTURES",
|
|
"marginCoin": "USDT",
|
|
"leverage": fmt.Sprintf("%d", leverage),
|
|
}
|
|
|
|
_, err := t.doRequest("POST", bitgetLeveragePath, body)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "same") {
|
|
return nil
|
|
}
|
|
logger.Infof(" ⚠️ Failed to set %s leverage: %v", symbol, err)
|
|
return err
|
|
}
|
|
|
|
logger.Infof(" ✓ %s leverage set to %dx", symbol, leverage)
|
|
return nil
|
|
}
|
|
|
|
// GetMarketPrice gets market price
|
|
func (t *BitgetTrader) GetMarketPrice(symbol string) (float64, error) {
|
|
symbol = t.convertSymbol(symbol)
|
|
|
|
params := map[string]interface{}{
|
|
"symbol": symbol,
|
|
"productType": "USDT-FUTURES",
|
|
}
|
|
|
|
data, err := t.doRequest("GET", bitgetTickerPath, params)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get price: %w", err)
|
|
}
|
|
|
|
var tickers []struct {
|
|
LastPr string `json:"lastPr"`
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &tickers); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if len(tickers) == 0 {
|
|
return 0, fmt.Errorf("no price data received")
|
|
}
|
|
|
|
price, err := strconv.ParseFloat(tickers[0].LastPr, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return price, nil
|
|
}
|
|
|
|
// GetOrderBook gets the order book for a symbol
|
|
// Implements GridTrader interface
|
|
func (t *BitgetTrader) GetOrderBook(symbol string, depth int) (bids, asks [][]float64, err error) {
|
|
symbol = t.convertSymbol(symbol)
|
|
path := fmt.Sprintf("/api/v2/mix/market/depth?symbol=%s&productType=USDT-FUTURES&limit=%d", symbol, depth)
|
|
|
|
data, err := t.doRequest("GET", path, nil)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to get order book: %w", err)
|
|
}
|
|
|
|
var result struct {
|
|
Bids [][]string `json:"bids"`
|
|
Asks [][]string `json:"asks"`
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &result); err != nil {
|
|
return nil, nil, fmt.Errorf("failed to parse order book: %w", err)
|
|
}
|
|
|
|
// Parse bids
|
|
for _, b := range result.Bids {
|
|
if len(b) >= 2 {
|
|
price, _ := strconv.ParseFloat(b[0], 64)
|
|
qty, _ := strconv.ParseFloat(b[1], 64)
|
|
bids = append(bids, []float64{price, qty})
|
|
}
|
|
}
|
|
|
|
// Parse asks
|
|
for _, a := range result.Asks {
|
|
if len(a) >= 2 {
|
|
price, _ := strconv.ParseFloat(a[0], 64)
|
|
qty, _ := strconv.ParseFloat(a[1], 64)
|
|
asks = append(asks, []float64{price, qty})
|
|
}
|
|
}
|
|
|
|
return bids, asks, nil
|
|
}
|