diff --git a/src/BrighterTrades.py b/src/BrighterTrades.py index 99b4e47..7c23be0 100644 --- a/src/BrighterTrades.py +++ b/src/BrighterTrades.py @@ -1420,7 +1420,8 @@ class BrighterTrades: strategy_id=strategy_id, testnet=testnet, stop_loss=stop_loss, - take_profit=take_profit + take_profit=take_profit, + time_in_force=time_in_force ) if status == 'Error': diff --git a/src/app.py b/src/app.py index 8811e37..e58ce69 100644 --- a/src/app.py +++ b/src/app.py @@ -221,30 +221,43 @@ def strategy_execution_loop(): event_type = event.get('type', 'fill') if event_type == 'sltp_triggered': - # SL/TP triggered - find related trade and notify user + # SL/TP triggered - find and settle related trades symbol = event.get('symbol') + trigger_price = event.get('trigger_price', 0) user_id = event.get('user_id') - # Find trades for this symbol to get the user - related_trade = None - for trade in brighter_trades.trades.active_trades.values(): + # Find ALL matching paper trades for this symbol and settle them + trades_to_settle = [] + for trade in list(brighter_trades.trades.active_trades.values()): if trade.symbol == symbol and (trade.is_paper or trade.broker_kind == 'paper'): - related_trade = trade + trades_to_settle.append(trade) user_id = user_id or trade.creator - break + # Settle each matching trade + for trade in trades_to_settle: + # Settle the trade at the trigger price + trade.settle(qty=trade.stats.get('qty_filled', trade.base_order_qty), price=trigger_price) + # Move from active to settled + if trade.unique_id in brighter_trades.trades.active_trades: + del brighter_trades.trades.active_trades[trade.unique_id] + brighter_trades.trades.settled_trades[trade.unique_id] = trade + brighter_trades.trades._save_trade(trade) + _loop_debug.debug(f"Settled trade {trade.unique_id} via SL/TP at {trigger_price}") + + # Notify user if user_id: user_name = brighter_trades.users.get_username(user_id=user_id) if user_name: + trade_ids = [t.unique_id for t in trades_to_settle] socketio.emit('message', { 'reply': 'sltp_triggered', 'data': sanitize_for_json({ 'trigger': event.get('trigger'), 'symbol': symbol, - 'trigger_price': event.get('trigger_price'), + 'trigger_price': trigger_price, 'size': event.get('size'), 'pnl': event.get('pnl'), - 'trade_id': related_trade.unique_id if related_trade else None + 'trade_ids': trade_ids }) }, room=user_name) _loop_debug.debug(f"Emitted sltp_triggered to room={user_name}") diff --git a/src/static/trade.js b/src/static/trade.js index 1819123..8638b5f 100644 --- a/src/static/trade.js +++ b/src/static/trade.js @@ -20,6 +20,7 @@ class TradeUIManager { this.takeProfitInput = null; this.timeInForceSelect = null; this.exchangeRow = null; + this.sltpRow = null; this.onCloseTrade = null; // Exchanges known to support testnet/sandbox mode @@ -58,7 +59,8 @@ class TradeUIManager { stopLossId = 'stopLoss', takeProfitId = 'takeProfit', timeInForceId = 'timeInForce', - exchangeRowId = 'exchange-row' + exchangeRowId = 'exchange-row', + sltpRowId = 'sltp-row' } = config; this.targetEl = document.getElementById(targetId); @@ -86,6 +88,7 @@ class TradeUIManager { this.takeProfitInput = document.getElementById(takeProfitId); this.timeInForceSelect = document.getElementById(timeInForceId); this.exchangeRow = document.getElementById(exchangeRowId); + this.sltpRow = document.getElementById(sltpRowId); // Set up event listeners this._setupFormListeners(); @@ -162,6 +165,13 @@ class TradeUIManager { this._updateSellAvailability(); }); } + + // Side changes affect SL/TP visibility (not applicable for SELL/close) + if (this.sideSelect) { + this.sideSelect.addEventListener('change', () => { + this._updateSltpVisibility(); + }); + } } /** @@ -321,6 +331,28 @@ class TradeUIManager { } } + /** + * Updates SL/TP row visibility based on order side. + * SL/TP only applies to BUY orders (opening positions). + * SELL orders close existing positions, so SL/TP is not applicable. + */ + _updateSltpVisibility() { + if (!this.sltpRow || !this.sideSelect) return; + + const side = this.sideSelect.value.toLowerCase(); + + if (side === 'sell') { + // Hide SL/TP for SELL (closing positions) + this.sltpRow.style.display = 'none'; + // Clear any values + if (this.stopLossInput) this.stopLossInput.value = ''; + if (this.takeProfitInput) this.takeProfitInput.value = ''; + } else { + // Show SL/TP for BUY (opening positions) + this.sltpRow.style.display = 'contents'; + } + } + /** * Populates the exchange selector with connected exchanges. * @param {string[]} connectedExchanges - List of connected exchange names. @@ -441,6 +473,13 @@ class TradeUIManager { if (this.testnetCheckbox) { this.testnetCheckbox.checked = true; } + // Reset side to BUY and show SL/TP row + if (this.sideSelect) { + this.sideSelect.value = 'buy'; + } + if (this.sltpRow) { + this.sltpRow.style.display = 'contents'; + } this.formElement.style.display = 'grid'; @@ -1154,6 +1193,7 @@ class TradeUIManager { // If SELL is currently selected but no longer valid, reset to BUY if (!hasPosition && this.sideSelect.value === 'SELL') { this.sideSelect.value = 'BUY'; + this._updateSltpVisibility(); } } } @@ -1633,7 +1673,7 @@ class Trade { return; } - // SL/TP validation + // SL/TP validation (only for BUY orders - SELL closes existing positions) if (side.toUpperCase() === 'BUY') { if (stopLoss && stopLoss >= price) { alert('Stop Loss must be below entry price for BUY orders.'); @@ -1643,17 +1683,8 @@ class Trade { alert('Take Profit must be above entry price for BUY orders.'); return; } - } else { - // SELL - if (stopLoss && stopLoss <= price) { - alert('Stop Loss must be above entry price for SELL orders.'); - return; - } - if (takeProfit && takeProfit >= price) { - alert('Take Profit must be below entry price for SELL orders.'); - return; - } } + // Note: SL/TP fields are hidden for SELL orders (inventory-only model) // Show confirmation for production live trades if (!isPaperTrade && !testnet) { diff --git a/src/templates/new_trade_popup.html b/src/templates/new_trade_popup.html index 2dde530..7738b49 100644 --- a/src/templates/new_trade_popup.html +++ b/src/templates/new_trade_popup.html @@ -74,15 +74,16 @@ - - - + +