diff --git a/.env b/.env index 25abffc..9ee9388 100644 --- a/.env +++ b/.env @@ -406,7 +406,7 @@ MAX_SCALE_MULTIPLIER=2 SCALE_SIZE_PERCENT=50 MIN_ADX_INCREASE=5 MAX_PRICE_POSITION_FOR_SCALE=70 -TRAILING_STOP_ATR_MULTIPLIER=1.8 +TRAILING_STOP_ATR_MULTIPLIER=2.5 TRAILING_STOP_MIN_PERCENT=0.25 TRAILING_STOP_MAX_PERCENT=2.5 USE_PERCENTAGE_SIZE=false diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index c0c40f0..e6c8d20 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -379,6 +379,14 @@ export class PositionManager { private async handleExternalClosure(trade: ActiveTrade, reason: string): Promise { console.log(`๐Ÿงน Handling external closure: ${trade.symbol} (${reason})`) + // CRITICAL: Check if already processed to prevent duplicate notifications + const tradeId = trade.id + if (!this.activeTrades.has(tradeId)) { + console.log(`โš ๏ธ DUPLICATE PREVENTED: Trade ${tradeId} already processed, skipping`) + console.log(` This prevents duplicate Telegram notifications with compounding P&L`) + return + } + // CRITICAL: Calculate P&L using originalPositionSize for accuracy // currentSize may be stale if Drift propagation was interrupted const profitPercent = this.calculateProfitPercent( @@ -391,13 +399,7 @@ export class PositionManager { console.log(`๐Ÿ’ฐ Estimated P&L: ${profitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} โ†’ $${estimatedPnL.toFixed(2)}`) - // Remove from monitoring FIRST to prevent race conditions - const tradeId = trade.id - if (!this.activeTrades.has(tradeId)) { - console.log(`โš ๏ธ Trade ${tradeId} already removed, skipping cleanup`) - return - } - + // Remove from monitoring IMMEDIATELY to prevent race conditions this.activeTrades.delete(tradeId) console.log(`๐Ÿ—‘๏ธ Removed ${trade.symbol} from monitoring`) @@ -968,21 +970,22 @@ export class PositionManager { trade.direction ) - const accountPnL = profitPercent * trade.leverage - trade.unrealizedPnL = (trade.currentSize * profitPercent) / 100 + const currentPnLDollars = (trade.currentSize * profitPercent) / 100 + trade.unrealizedPnL = currentPnLDollars // Track peak P&L (MFE - Maximum Favorable Excursion) if (trade.unrealizedPnL > trade.peakPnL) { trade.peakPnL = trade.unrealizedPnL } - // Track MAE/MFE (account percentage, not USD) - if (accountPnL > trade.maxFavorableExcursion) { - trade.maxFavorableExcursion = accountPnL + // Track MAE/MFE in DOLLAR amounts (not percentages!) + // CRITICAL: Database schema expects DOLLARS for analysis and TP/SL optimization + if (currentPnLDollars > trade.maxFavorableExcursion) { + trade.maxFavorableExcursion = currentPnLDollars trade.maxFavorablePrice = currentPrice } - if (accountPnL < trade.maxAdverseExcursion) { - trade.maxAdverseExcursion = accountPnL + if (currentPnLDollars < trade.maxAdverseExcursion) { + trade.maxAdverseExcursion = currentPnLDollars trade.maxAdversePrice = currentPrice }