From 90509ae78327271d8daf09dd8b0468cfd528e5ff Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Wed, 14 Jan 2026 12:14:08 +0800 Subject: [PATCH] fix(grid): add leverage setting before order placement CRITICAL BUG FIX: - Call SetLeverage() in GridTraderAdapter.PlaceLimitOrder() - Set leverage during grid initialization - Log leverage setting results --- trader/auto_trader_grid.go | 55 +++++++++++++++++++++++++++++++++++++- trader/interface.go | 13 ++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/trader/auto_trader_grid.go b/trader/auto_trader_grid.go index 7ed74730..5522ecd2 100644 --- a/trader/auto_trader_grid.go +++ b/trader/auto_trader_grid.go @@ -99,6 +99,15 @@ func (at *AutoTrader) InitializeGrid() error { at.initializeGridLevels(price, gridConfig) at.gridState.IsInitialized = true + + // CRITICAL: Set leverage on exchange before trading + if err := at.trader.SetLeverage(gridConfig.Symbol, gridConfig.Leverage); err != nil { + logger.Warnf("[Grid] Failed to set leverage %dx on exchange: %v", gridConfig.Leverage, err) + // Not fatal - continue with default leverage + } else { + logger.Infof("[Grid] Leverage set to %dx for %s", gridConfig.Leverage, gridConfig.Symbol) + } + logger.Infof("📊 [Grid] Initialized: %d levels, $%.2f - $%.2f, spacing $%.2f", gridConfig.GridCount, at.gridState.LowerPrice, at.gridState.UpperPrice, at.gridState.GridSpacing) @@ -332,11 +341,55 @@ func (at *AutoTrader) placeGridLimitOrder(d *kernel.Decision, side string) error gridConfig := at.config.StrategyConfig.GridConfig + // CRITICAL: Validate and cap quantity to prevent excessive position sizes + // This protects against AI miscalculations or leverage misconfigurations + quantity := d.Quantity + if d.Price > 0 && gridConfig.TotalInvestment > 0 { + // Calculate max allowed position value per grid level + // Each level gets proportional share of total investment + maxMarginPerLevel := gridConfig.TotalInvestment / float64(gridConfig.GridCount) + maxPositionValuePerLevel := maxMarginPerLevel * float64(gridConfig.Leverage) + maxQuantityPerLevel := maxPositionValuePerLevel / d.Price + + // Also get the level's allocated USD for additional validation + at.gridState.mu.RLock() + var levelAllocatedUSD float64 + if d.LevelIndex >= 0 && d.LevelIndex < len(at.gridState.Levels) { + levelAllocatedUSD = at.gridState.Levels[d.LevelIndex].AllocatedUSD + } + at.gridState.mu.RUnlock() + + // Use level-specific allocation if available + if levelAllocatedUSD > 0 { + levelMaxPositionValue := levelAllocatedUSD * float64(gridConfig.Leverage) + levelMaxQuantity := levelMaxPositionValue / d.Price + if levelMaxQuantity < maxQuantityPerLevel { + maxQuantityPerLevel = levelMaxQuantity + } + } + + // Cap quantity if it exceeds the maximum allowed + if quantity > maxQuantityPerLevel { + logger.Warnf("[Grid] ⚠️ Quantity %.4f exceeds max allowed %.4f (position_value $%.2f > max $%.2f), capping", + quantity, maxQuantityPerLevel, quantity*d.Price, maxPositionValuePerLevel) + quantity = maxQuantityPerLevel + } + + // Safety check: ensure position value is reasonable (within 2x of intended max as absolute limit) + positionValue := quantity * d.Price + absoluteMaxValue := gridConfig.TotalInvestment * float64(gridConfig.Leverage) * 2 // 2x safety margin + if positionValue > absoluteMaxValue { + logger.Errorf("[Grid] 🚫 CRITICAL: Position value $%.2f exceeds absolute max $%.2f! Rejecting order.", + positionValue, absoluteMaxValue) + return fmt.Errorf("position value $%.2f exceeds safety limit $%.2f", positionValue, absoluteMaxValue) + } + } + req := &LimitOrderRequest{ Symbol: d.Symbol, Side: side, Price: d.Price, - Quantity: d.Quantity, + Quantity: quantity, // Use validated/capped quantity Leverage: gridConfig.Leverage, PostOnly: gridConfig.UseMakerOnly, ReduceOnly: false, diff --git a/trader/interface.go b/trader/interface.go index 74eb6498..cb8f0ddd 100644 --- a/trader/interface.go +++ b/trader/interface.go @@ -1,6 +1,9 @@ package trader -import "time" +import ( + "nofx/logger" + "time" +) // ClosedPnLRecord represents a single closed position record from exchange type ClosedPnLRecord struct { @@ -169,6 +172,14 @@ func NewGridTraderAdapter(t Trader) *GridTraderAdapter { // PlaceLimitOrder implements limit order using available methods // For exchanges without native limit order support, this uses conditional orders func (a *GridTraderAdapter) PlaceLimitOrder(req *LimitOrderRequest) (*LimitOrderResult, error) { + // CRITICAL FIX: Set leverage before placing order + if req.Leverage > 0 { + if err := a.Trader.SetLeverage(req.Symbol, req.Leverage); err != nil { + logger.Warnf("[Grid] Failed to set leverage %dx: %v", req.Leverage, err) + // Continue anyway - some exchanges don't require explicit leverage setting + } + } + // Use SetStopLoss/SetTakeProfit as conditional limit orders // For buy orders below current price, use stop-loss mechanism // For sell orders above current price, use take-profit mechanism