mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-12 07:16:56 +08:00
fix: prevent panics from unsafe type assertions in trading code + add request body limit
Security & Reliability: - Add requestBodyLimitMiddleware (1MB) to prevent OOM from oversized API payloads - Fix defer resp.Body.Close() inside loop in getPublicIPFromAPI (connection leak) - Add posFloat64/posString safe helpers for position map access Panic Prevention (critical for trading): - Convert 30+ unsafe type assertions (pos["key"].(type)) to safe comma-ok pattern across all exchange traders: OKX, Hyperliquid, Aster, Bybit, KuCoin, Gate, Bitget, Binance - Fix auto_trader_risk.go: drawdown monitor could panic and silently stop monitoring, leaving positions unprotected - Fix auto_trader_decision.go & auto_trader_loop.go: core trading loop position parsing now crash-proof - All trader/ code now has zero unsafe type assertions Frontend: - Fix config.ts: rejected promise cached forever on network error (never retries)
This commit is contained in:
@@ -36,6 +36,9 @@ func NewServer(traderManager *manager.TraderManager, st *store.Store, cryptoServ
|
|||||||
|
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Limit request body size to prevent OOM from oversized payloads (1MB default)
|
||||||
|
router.Use(requestBodyLimitMiddleware(1 << 20))
|
||||||
|
|
||||||
// Enable CORS
|
// Enable CORS
|
||||||
router.Use(corsMiddleware())
|
router.Use(corsMiddleware())
|
||||||
|
|
||||||
@@ -57,6 +60,17 @@ func NewServer(traderManager *manager.TraderManager, st *store.Store, cryptoServ
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requestBodyLimitMiddleware limits the size of incoming request bodies.
|
||||||
|
// Returns 413 Payload Too Large if the body exceeds maxBytes.
|
||||||
|
func requestBodyLimitMiddleware(maxBytes int64) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if c.Request.Body != nil {
|
||||||
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxBytes)
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// corsMiddleware CORS middleware with configurable allowed origins.
|
// corsMiddleware CORS middleware with configurable allowed origins.
|
||||||
// Set CORS_ALLOWED_ORIGINS env var to a comma-separated list of origins
|
// Set CORS_ALLOWED_ORIGINS env var to a comma-separated list of origins
|
||||||
// (e.g. "http://localhost:5173,https://nofx.example.com").
|
// (e.g. "http://localhost:5173,https://nofx.example.com").
|
||||||
@@ -445,25 +459,33 @@ func getPublicIPFromAPI() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
resp, err := client.Get(service)
|
ip := func() string {
|
||||||
if err != nil {
|
resp, err := client.Get(service)
|
||||||
continue
|
if err != nil {
|
||||||
}
|
return ""
|
||||||
defer resp.Body.Close()
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
body := make([]byte, 128)
|
body := make([]byte, 128)
|
||||||
n, err := resp.Body.Read(body)
|
n, err := resp.Body.Read(body)
|
||||||
if err != nil && err.Error() != "EOF" {
|
if err != nil && err.Error() != "EOF" {
|
||||||
continue
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := strings.TrimSpace(string(body[:n]))
|
candidate := strings.TrimSpace(string(body[:n]))
|
||||||
parsedIP := net.ParseIP(ip)
|
parsedIP := net.ParseIP(candidate)
|
||||||
// Verify if it's a valid IPv4 address (not containing ":")
|
// Verify if it's a valid IPv4 address (not containing ":")
|
||||||
if parsedIP != nil && parsedIP.To4() != nil {
|
if parsedIP != nil && parsedIP.To4() != nil {
|
||||||
return ip
|
return candidate
|
||||||
}
|
}
|
||||||
|
return ""
|
||||||
|
}()
|
||||||
|
if ip != "" {
|
||||||
|
return ip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,12 +70,12 @@ func (t *AsterTrader) GetBalance() (map[string]interface{}, error) {
|
|||||||
totalMarginUsed := 0.0
|
totalMarginUsed := 0.0
|
||||||
realUnrealizedPnl := 0.0
|
realUnrealizedPnl := 0.0
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
markPrice := pos["markPrice"].(float64)
|
markPrice, _ := pos["markPrice"].(float64)
|
||||||
quantity := pos["positionAmt"].(float64)
|
quantity, _ := pos["positionAmt"].(float64)
|
||||||
if quantity < 0 {
|
if quantity < 0 {
|
||||||
quantity = -quantity
|
quantity = -quantity
|
||||||
}
|
}
|
||||||
unrealizedPnl := pos["unRealizedProfit"].(float64)
|
unrealizedPnl, _ := pos["unRealizedProfit"].(float64)
|
||||||
realUnrealizedPnl += unrealizedPnl
|
realUnrealizedPnl += unrealizedPnl
|
||||||
|
|
||||||
leverage := 10
|
leverage := 10
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ func (t *AsterTrader) CloseLong(symbol string, quantity float64) (map[string]int
|
|||||||
|
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
if pos["symbol"] == symbol && pos["side"] == "long" {
|
if pos["symbol"] == symbol && pos["side"] == "long" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +249,7 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in
|
|||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
if pos["symbol"] == symbol && pos["side"] == "short" {
|
if pos["symbol"] == symbol && pos["side"] == "short" {
|
||||||
// Aster's GetPositions has already converted short position quantity to positive, use directly
|
// Aster's GetPositions has already converted short position quantity to positive, use directly
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,12 +133,12 @@ func (at *AutoTrader) GetAccountInfo() (map[string]interface{}, error) {
|
|||||||
totalMarginUsed := 0.0
|
totalMarginUsed := 0.0
|
||||||
totalUnrealizedPnLCalculated := 0.0
|
totalUnrealizedPnLCalculated := 0.0
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
markPrice := pos["markPrice"].(float64)
|
markPrice := posFloat64(pos, "markPrice")
|
||||||
quantity := pos["positionAmt"].(float64)
|
quantity := posFloat64(pos, "positionAmt")
|
||||||
if quantity < 0 {
|
if quantity < 0 {
|
||||||
quantity = -quantity
|
quantity = -quantity
|
||||||
}
|
}
|
||||||
unrealizedPnl := pos["unRealizedProfit"].(float64)
|
unrealizedPnl := posFloat64(pos, "unRealizedProfit")
|
||||||
totalUnrealizedPnLCalculated += unrealizedPnl
|
totalUnrealizedPnLCalculated += unrealizedPnl
|
||||||
|
|
||||||
leverage := 10
|
leverage := 10
|
||||||
@@ -199,16 +199,16 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) {
|
|||||||
|
|
||||||
var result []map[string]interface{}
|
var result []map[string]interface{}
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
symbol := pos["symbol"].(string)
|
symbol := posString(pos, "symbol")
|
||||||
side := pos["side"].(string)
|
side := posString(pos, "side")
|
||||||
entryPrice := pos["entryPrice"].(float64)
|
entryPrice := posFloat64(pos, "entryPrice")
|
||||||
markPrice := pos["markPrice"].(float64)
|
markPrice := posFloat64(pos, "markPrice")
|
||||||
quantity := pos["positionAmt"].(float64)
|
quantity := posFloat64(pos, "positionAmt")
|
||||||
if quantity < 0 {
|
if quantity < 0 {
|
||||||
quantity = -quantity
|
quantity = -quantity
|
||||||
}
|
}
|
||||||
unrealizedPnl := pos["unRealizedProfit"].(float64)
|
unrealizedPnl := posFloat64(pos, "unRealizedProfit")
|
||||||
liquidationPrice := pos["liquidationPrice"].(float64)
|
liquidationPrice := posFloat64(pos, "liquidationPrice")
|
||||||
|
|
||||||
leverage := 10
|
leverage := 10
|
||||||
if lev, ok := pos["leverage"].(float64); ok {
|
if lev, ok := pos["leverage"].(float64); ok {
|
||||||
|
|||||||
@@ -331,11 +331,11 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
|||||||
currentPositionKeys := make(map[string]bool)
|
currentPositionKeys := make(map[string]bool)
|
||||||
|
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
symbol := pos["symbol"].(string)
|
symbol := posString(pos, "symbol")
|
||||||
side := pos["side"].(string)
|
side := posString(pos, "side")
|
||||||
entryPrice := pos["entryPrice"].(float64)
|
entryPrice := posFloat64(pos, "entryPrice")
|
||||||
markPrice := pos["markPrice"].(float64)
|
markPrice := posFloat64(pos, "markPrice")
|
||||||
quantity := pos["positionAmt"].(float64)
|
quantity := posFloat64(pos, "positionAmt")
|
||||||
if quantity < 0 {
|
if quantity < 0 {
|
||||||
quantity = -quantity // Short position quantity is negative, convert to positive
|
quantity = -quantity // Short position quantity is negative, convert to positive
|
||||||
}
|
}
|
||||||
@@ -345,8 +345,8 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
unrealizedPnl := pos["unRealizedProfit"].(float64)
|
unrealizedPnl := posFloat64(pos, "unRealizedProfit")
|
||||||
liquidationPrice := pos["liquidationPrice"].(float64)
|
liquidationPrice := posFloat64(pos, "liquidationPrice")
|
||||||
|
|
||||||
// Calculate margin used (estimated)
|
// Calculate margin used (estimated)
|
||||||
leverage := 10 // Default value, should actually be fetched from position info
|
leverage := 10 // Default value, should actually be fetched from position info
|
||||||
|
|||||||
@@ -41,11 +41,14 @@ func (at *AutoTrader) checkPositionDrawdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
symbol := pos["symbol"].(string)
|
symbol, _ := pos["symbol"].(string)
|
||||||
side := pos["side"].(string)
|
side, _ := pos["side"].(string)
|
||||||
entryPrice := pos["entryPrice"].(float64)
|
entryPrice, _ := pos["entryPrice"].(float64)
|
||||||
markPrice := pos["markPrice"].(float64)
|
markPrice, _ := pos["markPrice"].(float64)
|
||||||
quantity := pos["positionAmt"].(float64)
|
quantity, _ := pos["positionAmt"].(float64)
|
||||||
|
if symbol == "" || side == "" || entryPrice == 0 {
|
||||||
|
continue // skip malformed position data
|
||||||
|
}
|
||||||
if quantity < 0 {
|
if quantity < 0 {
|
||||||
quantity = -quantity // Short position quantity is negative, convert to positive
|
quantity = -quantity // Short position quantity is negative, convert to positive
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func (t *FuturesTrader) CloseLong(symbol string, quantity float64) (map[string]i
|
|||||||
|
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
if pos["symbol"] == symbol && pos["side"] == "long" {
|
if pos["symbol"] == symbol && pos["side"] == "long" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ func (t *FuturesTrader) GetSymbolPrecision(symbol string) (int, error) {
|
|||||||
// Get precision from LOT_SIZE filter
|
// Get precision from LOT_SIZE filter
|
||||||
for _, filter := range s.Filters {
|
for _, filter := range s.Filters {
|
||||||
if filter["filterType"] == "LOT_SIZE" {
|
if filter["filterType"] == "LOT_SIZE" {
|
||||||
stepSize := filter["stepSize"].(string)
|
stepSize, _ := filter["stepSize"].(string)
|
||||||
precision := calculatePrecision(stepSize)
|
precision := calculatePrecision(stepSize)
|
||||||
logger.Infof(" %s quantity precision: %d (stepSize: %s)", symbol, precision, stepSize)
|
logger.Infof(" %s quantity precision: %d (stepSize: %s)", symbol, precision, stepSize)
|
||||||
return precision, nil
|
return precision, nil
|
||||||
@@ -264,7 +264,7 @@ func (t *FuturesTrader) GetSymbolPricePrecision(symbol string) (int, error) {
|
|||||||
// Get precision from PRICE_FILTER filter
|
// Get precision from PRICE_FILTER filter
|
||||||
for _, filter := range s.Filters {
|
for _, filter := range s.Filters {
|
||||||
if filter["filterType"] == "PRICE_FILTER" {
|
if filter["filterType"] == "PRICE_FILTER" {
|
||||||
tickSize := filter["tickSize"].(string)
|
tickSize, _ := filter["tickSize"].(string)
|
||||||
precision := calculatePrecision(tickSize)
|
precision := calculatePrecision(tickSize)
|
||||||
return precision, nil
|
return precision, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func (t *BitgetTrader) CloseLong(symbol string, quantity float64) (map[string]in
|
|||||||
}
|
}
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
if pos["symbol"] == symbol && pos["side"] == "long" {
|
if pos["symbol"] == symbol && pos["side"] == "long" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ func (t *BitgetTrader) CloseShort(symbol string, quantity float64) (map[string]i
|
|||||||
}
|
}
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
if pos["symbol"] == symbol && pos["side"] == "short" {
|
if pos["symbol"] == symbol && pos["side"] == "short" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func (t *BybitTrader) CloseLong(symbol string, quantity float64) (map[string]int
|
|||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
side, _ := pos["side"].(string)
|
side, _ := pos["side"].(string)
|
||||||
if pos["symbol"] == symbol && strings.ToLower(side) == "long" {
|
if pos["symbol"] == symbol && strings.ToLower(side) == "long" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,9 +165,10 @@ func (t *GateTrader) CloseLong(symbol string, quantity float64) (map[string]inte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
posSymbol := t.convertSymbol(pos["symbol"].(string))
|
rawSymbol, _ := pos["symbol"].(string)
|
||||||
|
posSymbol := t.convertSymbol(rawSymbol)
|
||||||
if posSymbol == symbol && pos["side"] == "long" {
|
if posSymbol == symbol && pos["side"] == "long" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,9 +234,10 @@ func (t *GateTrader) CloseShort(symbol string, quantity float64) (map[string]int
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
posSymbol := t.convertSymbol(pos["symbol"].(string))
|
rawSymbol, _ := pos["symbol"].(string)
|
||||||
|
posSymbol := t.convertSymbol(rawSymbol)
|
||||||
if posSymbol == symbol && pos["side"] == "short" {
|
if posSymbol == symbol && pos["side"] == "short" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,19 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// posFloat64 extracts a float64 from a position map, returning 0 on failure.
|
||||||
|
// Use in loops where a malformed position should be skipped, not crash.
|
||||||
|
func posFloat64(pos map[string]interface{}, key string) float64 {
|
||||||
|
v, _ := SafeFloat64(pos, key)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// posString extracts a string from a position map, returning "" on failure.
|
||||||
|
func posString(pos map[string]interface{}, key string) string {
|
||||||
|
v, _ := SafeString(pos, key)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// SafeFloat64 Safely extract float64 value from map
|
// SafeFloat64 Safely extract float64 value from map
|
||||||
func SafeFloat64(data map[string]interface{}, key string) (float64, error) {
|
func SafeFloat64(data map[string]interface{}, key string) (float64, error) {
|
||||||
value, ok := data[key]
|
value, ok := data[key]
|
||||||
|
|||||||
@@ -179,9 +179,9 @@ func (t *HyperliquidTrader) CloseLong(symbol string, quantity float64) (map[stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
posSymbol := pos["symbol"].(string)
|
posSymbol, _ := pos["symbol"].(string)
|
||||||
if (posSymbol == symbol || posSymbol == searchSymbol) && pos["side"] == "long" {
|
if (posSymbol == symbol || posSymbol == searchSymbol) && pos["side"] == "long" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,9 +266,9 @@ func (t *HyperliquidTrader) CloseShort(symbol string, quantity float64) (map[str
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
posSymbol := pos["symbol"].(string)
|
posSymbol, _ := pos["symbol"].(string)
|
||||||
if (posSymbol == symbol || posSymbol == searchSymbol) && pos["side"] == "short" {
|
if (posSymbol == symbol || posSymbol == searchSymbol) && pos["side"] == "short" {
|
||||||
quantity = pos["positionAmt"].(float64)
|
quantity, _ = pos["positionAmt"].(float64)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ func (t *KuCoinTrader) CloseLong(symbol string, quantity float64) (map[string]in
|
|||||||
var marginMode string = "CROSS" // Default to CROSS
|
var marginMode string = "CROSS" // Default to CROSS
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
if pos["symbol"] == symbol && pos["side"] == "long" {
|
if pos["symbol"] == symbol && pos["side"] == "long" {
|
||||||
actualQty = pos["positionAmt"].(float64)
|
actualQty, _ = pos["positionAmt"].(float64)
|
||||||
posFound = true
|
posFound = true
|
||||||
// Get margin mode from position
|
// Get margin mode from position
|
||||||
if mgnMode, ok := pos["mgnMode"].(string); ok {
|
if mgnMode, ok := pos["mgnMode"].(string); ok {
|
||||||
@@ -242,7 +242,7 @@ func (t *KuCoinTrader) CloseShort(symbol string, quantity float64) (map[string]i
|
|||||||
var marginMode string = "CROSS" // Default to CROSS
|
var marginMode string = "CROSS" // Default to CROSS
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
if pos["symbol"] == symbol && pos["side"] == "short" {
|
if pos["symbol"] == symbol && pos["side"] == "short" {
|
||||||
actualQty = pos["positionAmt"].(float64)
|
actualQty, _ = pos["positionAmt"].(float64)
|
||||||
posFound = true
|
posFound = true
|
||||||
// Get margin mode from position
|
// Get margin mode from position
|
||||||
if mgnMode, ok := pos["mgnMode"].(string); ok {
|
if mgnMode, ok := pos["mgnMode"].(string); ok {
|
||||||
|
|||||||
@@ -188,11 +188,11 @@ func (t *OKXTrader) CloseLong(symbol string, quantity float64) (map[string]inter
|
|||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
logger.Infof("🔍 OKX position: symbol=%v, side=%v, positionAmt=%v, mgnMode=%v", pos["symbol"], pos["side"], pos["positionAmt"], pos["mgnMode"])
|
logger.Infof("🔍 OKX position: symbol=%v, side=%v, positionAmt=%v, mgnMode=%v", pos["symbol"], pos["side"], pos["positionAmt"], pos["mgnMode"])
|
||||||
if pos["symbol"] == symbol {
|
if pos["symbol"] == symbol {
|
||||||
side := pos["side"].(string)
|
side, _ := pos["side"].(string)
|
||||||
// In net_mode, "long" means positive position
|
// In net_mode, "long" means positive position
|
||||||
// In dual mode, check explicit "long" side
|
// In dual mode, check explicit "long" side
|
||||||
if side == "long" || (t.positionMode == "net_mode" && side == "long") {
|
if side == "long" || (t.positionMode == "net_mode" && side == "long") {
|
||||||
actualQty = pos["positionAmt"].(float64)
|
actualQty, _ = pos["positionAmt"].(float64)
|
||||||
posFound = true
|
posFound = true
|
||||||
if mgnMode, ok := pos["mgnMode"].(string); ok && mgnMode != "" {
|
if mgnMode, ok := pos["mgnMode"].(string); ok && mgnMode != "" {
|
||||||
posMgnMode = mgnMode
|
posMgnMode = mgnMode
|
||||||
@@ -300,7 +300,7 @@ func (t *OKXTrader) CloseShort(symbol string, quantity float64) (map[string]inte
|
|||||||
logger.Infof("🔍 OKX position: symbol=%v, side=%v, positionAmt=%v, mgnMode=%v",
|
logger.Infof("🔍 OKX position: symbol=%v, side=%v, positionAmt=%v, mgnMode=%v",
|
||||||
pos["symbol"], pos["side"], pos["positionAmt"], pos["mgnMode"])
|
pos["symbol"], pos["side"], pos["positionAmt"], pos["mgnMode"])
|
||||||
if pos["symbol"] == symbol && pos["side"] == "short" {
|
if pos["symbol"] == symbol && pos["side"] == "short" {
|
||||||
actualQty = pos["positionAmt"].(float64)
|
actualQty, _ = pos["positionAmt"].(float64)
|
||||||
posFound = true
|
posFound = true
|
||||||
if mgnMode, ok := pos["mgnMode"].(string); ok && mgnMode != "" {
|
if mgnMode, ok := pos["mgnMode"].(string); ok && mgnMode != "" {
|
||||||
posMgnMode = mgnMode
|
posMgnMode = mgnMode
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export function getSystemConfig(): Promise<SystemConfig> {
|
|||||||
cachedConfig = data
|
cachedConfig = data
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// Reset so next call retries instead of returning the rejected promise forever
|
||||||
|
configPromise = null
|
||||||
|
throw err
|
||||||
|
})
|
||||||
return configPromise
|
return configPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user