diff --git a/main.go b/main.go index 610e99ed..e94ecfaf 100644 --- a/main.go +++ b/main.go @@ -192,6 +192,10 @@ func main() { } logger.Info("✅ HTTP server stopped") + // Stop NOFXi agent (sentinel, brain, scheduler, cleanup goroutines) + nofxiAgent.Stop() + logger.Info("✅ NOFXi agent stopped") + // Stop all traders traderManager.StopAll() logger.Info("✅ System shut down safely") diff --git a/trader/auto_trader_orders.go b/trader/auto_trader_orders.go index de1e6ae7..4328d8d4 100644 --- a/trader/auto_trader_orders.go +++ b/trader/auto_trader_orders.go @@ -134,12 +134,16 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *kernel.Decision, actio posKey := decision.Symbol + "_long" at.positionFirstSeenTime[posKey] = time.Now().UnixMilli() - // Set stop loss and take profit - if err := at.trader.SetStopLoss(decision.Symbol, "LONG", quantity, decision.StopLoss); err != nil { - logger.Infof(" ⚠ Failed to set stop loss: %v", err) + // Set stop loss and take profit (skip if AI didn't specify a price) + if decision.StopLoss > 0 { + if err := at.trader.SetStopLoss(decision.Symbol, "LONG", quantity, decision.StopLoss); err != nil { + logger.Infof(" ⚠ Failed to set stop loss: %v", err) + } } - if err := at.trader.SetTakeProfit(decision.Symbol, "LONG", quantity, decision.TakeProfit); err != nil { - logger.Infof(" ⚠ Failed to set take profit: %v", err) + if decision.TakeProfit > 0 { + if err := at.trader.SetTakeProfit(decision.Symbol, "LONG", quantity, decision.TakeProfit); err != nil { + logger.Infof(" ⚠ Failed to set take profit: %v", err) + } } return nil @@ -252,11 +256,15 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *kernel.Decision, acti at.positionFirstSeenTime[posKey] = time.Now().UnixMilli() // Set stop loss and take profit - if err := at.trader.SetStopLoss(decision.Symbol, "SHORT", quantity, decision.StopLoss); err != nil { - logger.Infof(" ⚠ Failed to set stop loss: %v", err) + if decision.StopLoss > 0 { + if err := at.trader.SetStopLoss(decision.Symbol, "SHORT", quantity, decision.StopLoss); err != nil { + logger.Infof(" ⚠ Failed to set stop loss: %v", err) + } } - if err := at.trader.SetTakeProfit(decision.Symbol, "SHORT", quantity, decision.TakeProfit); err != nil { - logger.Infof(" ⚠ Failed to set take profit: %v", err) + if decision.TakeProfit > 0 { + if err := at.trader.SetTakeProfit(decision.Symbol, "SHORT", quantity, decision.TakeProfit); err != nil { + logger.Infof(" ⚠ Failed to set take profit: %v", err) + } } return nil @@ -306,6 +314,14 @@ func (at *AutoTrader) executeCloseLongWithRecord(decision *kernel.Decision, acti logger.Infof(" 📊 Using exchange position data: qty=%.8f, entry=%.2f", quantity, entryPrice) } + // Cancel existing stop-loss and take-profit orders BEFORE closing position + // Critical: orphaned SL/TP orders could trigger after position is closed and create unintended positions + if err := at.trader.CancelStopOrders(decision.Symbol); err != nil { + logger.Warnf(" ⚠️ Failed to cancel stop orders for %s: %v (proceeding with close)", decision.Symbol, err) + } else { + logger.Infof(" 🗑️ Cancelled stop/TP orders for %s", decision.Symbol) + } + // Close position order, err := at.trader.CloseLong(decision.Symbol, 0) // 0 = close all if err != nil { @@ -368,6 +384,14 @@ func (at *AutoTrader) executeCloseShortWithRecord(decision *kernel.Decision, act logger.Infof(" 📊 Using exchange position data: qty=%.8f, entry=%.2f", quantity, entryPrice) } + // Cancel existing stop-loss and take-profit orders BEFORE closing position + // Critical: orphaned SL/TP orders could trigger after position is closed and create unintended positions + if err := at.trader.CancelStopOrders(decision.Symbol); err != nil { + logger.Warnf(" ⚠️ Failed to cancel stop orders for %s: %v (proceeding with close)", decision.Symbol, err) + } else { + logger.Infof(" 🗑️ Cancelled stop/TP orders for %s", decision.Symbol) + } + // Close position order, err := at.trader.CloseShort(decision.Symbol, 0) // 0 = close all if err != nil { diff --git a/trader/auto_trader_risk.go b/trader/auto_trader_risk.go index 890efebb..8ec65e6c 100644 --- a/trader/auto_trader_risk.go +++ b/trader/auto_trader_risk.go @@ -112,6 +112,12 @@ func (at *AutoTrader) checkPositionDrawdown() { // emergencyClosePosition emergency close position function func (at *AutoTrader) emergencyClosePosition(symbol, side string) error { + // Cancel existing stop-loss and take-profit orders BEFORE closing position + // Critical: orphaned SL/TP orders could trigger after position is closed and create unintended positions + if err := at.trader.CancelStopOrders(symbol); err != nil { + logger.Warnf(" ⚠️ Emergency close: failed to cancel stop orders for %s: %v", symbol, err) + } + switch side { case "long": order, err := at.trader.CloseLong(symbol, 0) // 0 = close all diff --git a/trader/hyperliquid/trader_account.go b/trader/hyperliquid/trader_account.go index 8b63b60a..7dee04d6 100644 --- a/trader/hyperliquid/trader_account.go +++ b/trader/hyperliquid/trader_account.go @@ -132,9 +132,6 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) { spotUSDCBalance, availableBalance) } - // Suppress unused variable warning - _ = totalUnrealizedPnlAll - result["totalWalletBalance"] = totalWalletBalance // Total assets (Perp + Spot + xyz) - unrealized result["totalEquity"] = totalEquityCalculated // Total equity = Perp AV + Spot + xyz AV result["availableBalance"] = availableBalance // Available balance (Perp + Spot if unified)