mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-16 01:06:59 +08:00
fix: cancel orphaned stop/TP orders on position close + guard zero SL/TP prices
Critical bugs fixed: - executeCloseLong/CloseShort: cancel stop-loss and take-profit orders BEFORE closing position to prevent orphaned orders from triggering and creating unintended positions - emergencyClosePosition: same fix for drawdown monitor's emergency close path - Skip SetStopLoss/SetTakeProfit when AI returns price=0 (prevents exchange API errors) Also includes: - Add agent.Stop() to graceful shutdown sequence in main.go - Remove unused variable suppression in Hyperliquid trader
This commit is contained in:
4
main.go
4
main.go
@@ -192,6 +192,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
logger.Info("✅ HTTP server stopped")
|
logger.Info("✅ HTTP server stopped")
|
||||||
|
|
||||||
|
// Stop NOFXi agent (sentinel, brain, scheduler, cleanup goroutines)
|
||||||
|
nofxiAgent.Stop()
|
||||||
|
logger.Info("✅ NOFXi agent stopped")
|
||||||
|
|
||||||
// Stop all traders
|
// Stop all traders
|
||||||
traderManager.StopAll()
|
traderManager.StopAll()
|
||||||
logger.Info("✅ System shut down safely")
|
logger.Info("✅ System shut down safely")
|
||||||
|
|||||||
@@ -134,12 +134,16 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *kernel.Decision, actio
|
|||||||
posKey := decision.Symbol + "_long"
|
posKey := decision.Symbol + "_long"
|
||||||
at.positionFirstSeenTime[posKey] = time.Now().UnixMilli()
|
at.positionFirstSeenTime[posKey] = time.Now().UnixMilli()
|
||||||
|
|
||||||
// Set stop loss and take profit
|
// Set stop loss and take profit (skip if AI didn't specify a price)
|
||||||
if err := at.trader.SetStopLoss(decision.Symbol, "LONG", quantity, decision.StopLoss); err != nil {
|
if decision.StopLoss > 0 {
|
||||||
logger.Infof(" ⚠ Failed to set stop loss: %v", err)
|
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 {
|
if decision.TakeProfit > 0 {
|
||||||
logger.Infof(" ⚠ Failed to set take profit: %v", err)
|
if err := at.trader.SetTakeProfit(decision.Symbol, "LONG", quantity, decision.TakeProfit); err != nil {
|
||||||
|
logger.Infof(" ⚠ Failed to set take profit: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -252,11 +256,15 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *kernel.Decision, acti
|
|||||||
at.positionFirstSeenTime[posKey] = time.Now().UnixMilli()
|
at.positionFirstSeenTime[posKey] = time.Now().UnixMilli()
|
||||||
|
|
||||||
// Set stop loss and take profit
|
// Set stop loss and take profit
|
||||||
if err := at.trader.SetStopLoss(decision.Symbol, "SHORT", quantity, decision.StopLoss); err != nil {
|
if decision.StopLoss > 0 {
|
||||||
logger.Infof(" ⚠ Failed to set stop loss: %v", err)
|
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 {
|
if decision.TakeProfit > 0 {
|
||||||
logger.Infof(" ⚠ Failed to set take profit: %v", err)
|
if err := at.trader.SetTakeProfit(decision.Symbol, "SHORT", quantity, decision.TakeProfit); err != nil {
|
||||||
|
logger.Infof(" ⚠ Failed to set take profit: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
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
|
// Close position
|
||||||
order, err := at.trader.CloseLong(decision.Symbol, 0) // 0 = close all
|
order, err := at.trader.CloseLong(decision.Symbol, 0) // 0 = close all
|
||||||
if err != nil {
|
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)
|
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
|
// Close position
|
||||||
order, err := at.trader.CloseShort(decision.Symbol, 0) // 0 = close all
|
order, err := at.trader.CloseShort(decision.Symbol, 0) // 0 = close all
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ func (at *AutoTrader) checkPositionDrawdown() {
|
|||||||
|
|
||||||
// emergencyClosePosition emergency close position function
|
// emergencyClosePosition emergency close position function
|
||||||
func (at *AutoTrader) emergencyClosePosition(symbol, side string) error {
|
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 {
|
switch side {
|
||||||
case "long":
|
case "long":
|
||||||
order, err := at.trader.CloseLong(symbol, 0) // 0 = close all
|
order, err := at.trader.CloseLong(symbol, 0) // 0 = close all
|
||||||
|
|||||||
@@ -132,9 +132,6 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) {
|
|||||||
spotUSDCBalance, availableBalance)
|
spotUSDCBalance, availableBalance)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suppress unused variable warning
|
|
||||||
_ = totalUnrealizedPnlAll
|
|
||||||
|
|
||||||
result["totalWalletBalance"] = totalWalletBalance // Total assets (Perp + Spot + xyz) - unrealized
|
result["totalWalletBalance"] = totalWalletBalance // Total assets (Perp + Spot + xyz) - unrealized
|
||||||
result["totalEquity"] = totalEquityCalculated // Total equity = Perp AV + Spot + xyz AV
|
result["totalEquity"] = totalEquityCalculated // Total equity = Perp AV + Spot + xyz AV
|
||||||
result["availableBalance"] = availableBalance // Available balance (Perp + Spot if unified)
|
result["availableBalance"] = availableBalance // Available balance (Perp + Spot if unified)
|
||||||
|
|||||||
Reference in New Issue
Block a user