From c161632e2b4818ff4ba42e68b13eefd92691ac23 Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Wed, 14 Jan 2026 12:25:59 +0800 Subject: [PATCH] feat(grid): implement stop loss execution CRITICAL: Add code-level stop loss protection - New checkAndExecuteStopLoss() function - Checks each filled level against StopLossPct - Automatically closes positions exceeding stop loss - Called during every grid state sync --- trader/auto_trader_grid.go | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/trader/auto_trader_grid.go b/trader/auto_trader_grid.go index d20f445f..8499d5d6 100644 --- a/trader/auto_trader_grid.go +++ b/trader/auto_trader_grid.go @@ -622,6 +622,9 @@ func (at *AutoTrader) syncGridState() { } logger.Debugf("[Grid] Synced state: position=%.4f, orders=%d", totalPosition, len(openOrders)) + + // CRITICAL: Check stop loss for filled levels + at.checkAndExecuteStopLoss() } // saveGridDecisionRecord saves the grid decision to database @@ -681,3 +684,61 @@ func (at *AutoTrader) IsGridStrategy() bool { } return at.config.StrategyConfig.StrategyType == "grid_trading" && at.config.StrategyConfig.GridConfig != nil } + +// checkAndExecuteStopLoss checks if any filled level has exceeded stop loss and closes it +func (at *AutoTrader) checkAndExecuteStopLoss() { + gridConfig := at.config.StrategyConfig.GridConfig + if gridConfig.StopLossPct <= 0 { + return // Stop loss not configured + } + + currentPrice, err := at.trader.GetMarketPrice(gridConfig.Symbol) + if err != nil { + logger.Warnf("[Grid] Failed to get market price for stop loss check: %v", err) + return + } + + at.gridState.mu.Lock() + defer at.gridState.mu.Unlock() + + for i := range at.gridState.Levels { + level := &at.gridState.Levels[i] + if level.State != "filled" || level.PositionEntry <= 0 { + continue + } + + // Calculate loss percentage + var lossPct float64 + if level.Side == "buy" { + // Long position: loss when price drops + lossPct = (level.PositionEntry - currentPrice) / level.PositionEntry * 100 + } else { + // Short position: loss when price rises + lossPct = (currentPrice - level.PositionEntry) / level.PositionEntry * 100 + } + + // Check if stop loss triggered + if lossPct >= gridConfig.StopLossPct { + logger.Warnf("[Grid] STOP LOSS TRIGGERED: Level %d, entry=$%.2f, current=$%.2f, loss=%.2f%%", + i, level.PositionEntry, currentPrice, lossPct) + + // Close the position + var closeErr error + if level.Side == "buy" { + _, closeErr = at.trader.CloseLong(gridConfig.Symbol, level.PositionSize) + } else { + _, closeErr = at.trader.CloseShort(gridConfig.Symbol, level.PositionSize) + } + + if closeErr != nil { + logger.Errorf("[Grid] Failed to execute stop loss for level %d: %v", i, closeErr) + } else { + level.State = "stopped" + level.UnrealizedPnL = -lossPct * level.AllocatedUSD / 100 + at.gridState.TotalTrades++ + logger.Infof("[Grid] Stop loss executed: Level %d closed at $%.2f (loss %.2f%%)", + i, currentPrice, lossPct) + } + } + } +}