mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-06 20:41:14 +08:00
fix: consistent safe type helpers in auto_trader, log emergency exit errors
- Replace all raw pos["key"].(type) assertions with posFloat64/posString/posInt64 helpers across auto_trader_decision, auto_trader_grid, auto_trader_grid_orders, auto_trader_grid_regime, auto_trader_loop, auto_trader_orders, auto_trader_risk - Add posInt64 helper for int64 extraction (createdTime etc) - Fix emergencyExit: log CloseLong/CloseShort errors instead of silently dropping - Fix emergencyExit: log GetPositions error on failure - Upgrade closeAllPositions log level from Infof to Warnf for close failures - Zero raw type assertions remaining in auto_trader_* files
This commit is contained in:
@@ -142,7 +142,7 @@ func (at *AutoTrader) GetAccountInfo() (map[string]interface{}, error) {
|
||||
totalUnrealizedPnLCalculated += unrealizedPnl
|
||||
|
||||
leverage := 10
|
||||
if lev, ok := pos["leverage"].(float64); ok {
|
||||
if lev := posFloat64(pos, "leverage"); lev > 0 {
|
||||
leverage = int(lev)
|
||||
}
|
||||
marginUsed := (quantity * markPrice) / float64(leverage)
|
||||
@@ -211,7 +211,7 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) {
|
||||
liquidationPrice := posFloat64(pos, "liquidationPrice")
|
||||
|
||||
leverage := 10
|
||||
if lev, ok := pos["leverage"].(float64); ok {
|
||||
if lev := posFloat64(pos, "leverage"); lev > 0 {
|
||||
leverage = int(lev)
|
||||
}
|
||||
|
||||
|
||||
@@ -231,16 +231,26 @@ func (at *AutoTrader) emergencyExit(reason string) error {
|
||||
positions, err := at.trader.GetPositions()
|
||||
if err == nil {
|
||||
for _, pos := range positions {
|
||||
if sym, ok := pos["symbol"].(string); ok && sym == gridConfig.Symbol {
|
||||
if size, ok := pos["positionAmt"].(float64); ok && size != 0 {
|
||||
if size > 0 {
|
||||
at.trader.CloseLong(gridConfig.Symbol, size)
|
||||
} else {
|
||||
at.trader.CloseShort(gridConfig.Symbol, -size)
|
||||
}
|
||||
}
|
||||
sym := posString(pos, "symbol")
|
||||
if sym != gridConfig.Symbol {
|
||||
continue
|
||||
}
|
||||
size := posFloat64(pos, "positionAmt")
|
||||
if size == 0 {
|
||||
continue
|
||||
}
|
||||
var closeErr error
|
||||
if size > 0 {
|
||||
_, closeErr = at.trader.CloseLong(gridConfig.Symbol, size)
|
||||
} else {
|
||||
_, closeErr = at.trader.CloseShort(gridConfig.Symbol, -size)
|
||||
}
|
||||
if closeErr != nil {
|
||||
logger.Errorf("[Grid] EMERGENCY: failed to close position (size=%.6f): %v", size, closeErr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Errorf("[Grid] EMERGENCY: failed to get positions: %v", err)
|
||||
}
|
||||
|
||||
// Pause grid
|
||||
@@ -504,10 +514,9 @@ func (at *AutoTrader) buildGridContext() (*kernel.GridContext, error) {
|
||||
positions, err := at.trader.GetPositions()
|
||||
if err == nil {
|
||||
for _, pos := range positions {
|
||||
if sym, ok := pos["symbol"].(string); ok && sym == gridConfig.Symbol {
|
||||
if size, ok := pos["positionAmt"].(float64); ok {
|
||||
ctx.CurrentPosition = size
|
||||
}
|
||||
if posString(pos, "symbol") == gridConfig.Symbol {
|
||||
ctx.CurrentPosition = posFloat64(pos, "positionAmt")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,12 @@ func (at *AutoTrader) checkTotalPositionLimit(symbol string, additionalValue flo
|
||||
positions, err := at.trader.GetPositions()
|
||||
if err == nil {
|
||||
for _, pos := range positions {
|
||||
if sym, ok := pos["symbol"].(string); ok && sym == symbol {
|
||||
if size, ok := pos["positionAmt"].(float64); ok {
|
||||
if price, ok := pos["markPrice"].(float64); ok {
|
||||
currentPositionValue = math.Abs(size) * price
|
||||
} else if entryPrice, ok := pos["entryPrice"].(float64); ok {
|
||||
currentPositionValue = math.Abs(size) * entryPrice
|
||||
}
|
||||
if posString(pos, "symbol") == symbol {
|
||||
size := posFloat64(pos, "positionAmt")
|
||||
if price := posFloat64(pos, "markPrice"); price > 0 {
|
||||
currentPositionValue = math.Abs(size) * price
|
||||
} else if entryPrice := posFloat64(pos, "entryPrice"); entryPrice > 0 {
|
||||
currentPositionValue = math.Abs(size) * entryPrice
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,10 +266,9 @@ func (at *AutoTrader) syncGridState() {
|
||||
logger.Warnf("[Grid] Failed to get positions for state sync: %v", err)
|
||||
} else {
|
||||
for _, pos := range positions {
|
||||
if sym, ok := pos["symbol"].(string); ok && sym == gridConfig.Symbol {
|
||||
if size, ok := pos["positionAmt"].(float64); ok {
|
||||
currentPositionSize = size
|
||||
}
|
||||
if posString(pos, "symbol") == gridConfig.Symbol {
|
||||
currentPositionSize = posFloat64(pos, "positionAmt")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,12 +331,12 @@ func (at *AutoTrader) closeAllPositions() error {
|
||||
}
|
||||
|
||||
for _, pos := range positions {
|
||||
symbol, _ := pos["symbol"].(string)
|
||||
symbol := posString(pos, "symbol")
|
||||
if symbol != gridConfig.Symbol {
|
||||
continue
|
||||
}
|
||||
|
||||
size, _ := pos["positionAmt"].(float64)
|
||||
size := posFloat64(pos, "positionAmt")
|
||||
if size == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -349,7 +347,7 @@ func (at *AutoTrader) closeAllPositions() error {
|
||||
_, err = at.trader.CloseShort(symbol, -size)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Infof("Failed to close position: %v", err)
|
||||
logger.Warnf("[Grid] Failed to close position (size=%.6f): %v", size, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -245,9 +245,9 @@ func (at *AutoTrader) GetGridRiskInfo() *GridRiskInfo {
|
||||
var currentPositionValue float64
|
||||
var currentPositionSize float64
|
||||
for _, pos := range positions {
|
||||
if sym, _ := pos["symbol"].(string); sym == gridConfig.Symbol {
|
||||
size, _ := pos["positionAmt"].(float64)
|
||||
entry, _ := pos["entryPrice"].(float64)
|
||||
if posString(pos, "symbol") == gridConfig.Symbol {
|
||||
size := posFloat64(pos, "positionAmt")
|
||||
entry := posFloat64(pos, "entryPrice")
|
||||
currentPositionValue = math.Abs(size * entry)
|
||||
currentPositionSize = size
|
||||
break
|
||||
|
||||
@@ -350,7 +350,7 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
||||
|
||||
// Calculate margin used (estimated)
|
||||
leverage := 10 // Default value, should actually be fetched from position info
|
||||
if lev, ok := pos["leverage"].(float64); ok {
|
||||
if lev := posFloat64(pos, "leverage"); lev > 0 {
|
||||
leverage = int(lev)
|
||||
}
|
||||
marginUsed := (quantity * markPrice) / float64(leverage)
|
||||
@@ -374,7 +374,7 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
||||
}
|
||||
// Priority 2: Get from exchange API (Bybit: createdTime, OKX: createdTime)
|
||||
if updateTime == 0 {
|
||||
if createdTime, ok := pos["createdTime"].(int64); ok && createdTime > 0 {
|
||||
if createdTime := posInt64(pos, "createdTime"); createdTime > 0 {
|
||||
updateTime = createdTime
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *kernel.Decision, actio
|
||||
|
||||
// Check if there's already a position in the same symbol and direction
|
||||
for _, pos := range positions {
|
||||
if pos["symbol"] == decision.Symbol && pos["side"] == "long" {
|
||||
if posString(pos, "symbol") == decision.Symbol && posString(pos, "side") == "long" {
|
||||
return fmt.Errorf("❌ %s already has long position, close it first", decision.Symbol)
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *kernel.Decision, acti
|
||||
|
||||
// Check if there's already a position in the same symbol and direction
|
||||
for _, pos := range positions {
|
||||
if pos["symbol"] == decision.Symbol && pos["side"] == "short" {
|
||||
if posString(pos, "symbol") == decision.Symbol && posString(pos, "side") == "short" {
|
||||
return fmt.Errorf("❌ %s already has short position, close it first", decision.Symbol)
|
||||
}
|
||||
}
|
||||
@@ -294,11 +294,9 @@ func (at *AutoTrader) executeCloseLongWithRecord(decision *kernel.Decision, acti
|
||||
positions, err := at.trader.GetPositions()
|
||||
if err == nil {
|
||||
for _, pos := range positions {
|
||||
if pos["symbol"] == decision.Symbol && pos["side"] == "long" {
|
||||
if ep, ok := pos["entryPrice"].(float64); ok {
|
||||
entryPrice = ep
|
||||
}
|
||||
if amt, ok := pos["positionAmt"].(float64); ok && amt > 0 {
|
||||
if posString(pos, "symbol") == decision.Symbol && posString(pos, "side") == "long" {
|
||||
entryPrice = posFloat64(pos, "entryPrice")
|
||||
if amt := posFloat64(pos, "positionAmt"); amt > 0 {
|
||||
quantity = amt
|
||||
}
|
||||
break
|
||||
@@ -358,11 +356,9 @@ func (at *AutoTrader) executeCloseShortWithRecord(decision *kernel.Decision, act
|
||||
positions, err := at.trader.GetPositions()
|
||||
if err == nil {
|
||||
for _, pos := range positions {
|
||||
if pos["symbol"] == decision.Symbol && pos["side"] == "short" {
|
||||
if ep, ok := pos["entryPrice"].(float64); ok {
|
||||
entryPrice = ep
|
||||
}
|
||||
if amt, ok := pos["positionAmt"].(float64); ok {
|
||||
if posString(pos, "symbol") == decision.Symbol && posString(pos, "side") == "short" {
|
||||
entryPrice = posFloat64(pos, "entryPrice")
|
||||
if amt := posFloat64(pos, "positionAmt"); amt != 0 {
|
||||
quantity = -amt // positionAmt is negative for short
|
||||
}
|
||||
break
|
||||
|
||||
@@ -41,11 +41,11 @@ func (at *AutoTrader) checkPositionDrawdown() {
|
||||
}
|
||||
|
||||
for _, pos := range positions {
|
||||
symbol, _ := pos["symbol"].(string)
|
||||
side, _ := pos["side"].(string)
|
||||
entryPrice, _ := pos["entryPrice"].(float64)
|
||||
markPrice, _ := pos["markPrice"].(float64)
|
||||
quantity, _ := pos["positionAmt"].(float64)
|
||||
symbol := posString(pos, "symbol")
|
||||
side := posString(pos, "side")
|
||||
entryPrice := posFloat64(pos, "entryPrice")
|
||||
markPrice := posFloat64(pos, "markPrice")
|
||||
quantity := posFloat64(pos, "positionAmt")
|
||||
if symbol == "" || side == "" || entryPrice == 0 {
|
||||
continue // skip malformed position data
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func (at *AutoTrader) checkPositionDrawdown() {
|
||||
|
||||
// Calculate current P&L percentage
|
||||
leverage := 10 // Default value
|
||||
if lev, ok := pos["leverage"].(float64); ok {
|
||||
if lev := posFloat64(pos, "leverage"); lev > 0 {
|
||||
leverage = int(lev)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,30 @@ func posString(pos map[string]interface{}, key string) string {
|
||||
return v
|
||||
}
|
||||
|
||||
// posInt64 extracts an int64 from a position map, returning 0 on failure.
|
||||
func posInt64(pos map[string]interface{}, key string) int64 {
|
||||
value, ok := pos[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case int64:
|
||||
return v
|
||||
case int:
|
||||
return int64(v)
|
||||
case float64:
|
||||
return int64(v)
|
||||
case string:
|
||||
parsed, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// SafeFloat64 Safely extract float64 value from map
|
||||
func SafeFloat64(data map[string]interface{}, key string) (float64, error) {
|
||||
value, ok := data[key]
|
||||
|
||||
Reference in New Issue
Block a user