critical: Fix P&L compounding in external closure detection
Root cause: trade.realizedPnL was reading from in-memory ActiveTrade object which could have stale/mutated values from previous detection cycles. Bug sequence: 1. External closure detected, calculates P&L including previouslyRealized 2. Updates database with totalRealizedPnL 3. Same closure detected again (due to race condition or rate limits) 4. Reads previouslyRealized from same in-memory object (now has accumulated value) 5. Adds MORE P&L to it, compounds 2x, 5x, 10x Real impact: 9 expected P&L became 81 (10x inflation) Fix: Remove previouslyRealized from calculation entirely for external closures. External closures calculate ONLY the current position P&L, not cumulative. Database will have correct cumulative value if TP1 was processed separately. Lines changed: lib/trading/position-manager.ts:785-803 - Removed: const previouslyRealized = trade.realizedPnL - Removed: previouslyRealized + runnerRealized - Now: totalRealizedPnL = runnerRealized (ONLY this closure's P&L) Tested: Build completed successfully, container deployed and monitoring positions
This commit is contained in:
@@ -782,10 +782,10 @@ export class PositionManager {
|
||||
// CRITICAL: Use trade state flags, not current price (on-chain orders filled in the past!)
|
||||
let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL'
|
||||
|
||||
// Include any previously realized profit (e.g., from TP1 partial close)
|
||||
// CRITICAL: Get from original database value, not mutated in-memory value
|
||||
const previouslyRealized = trade.realizedPnL
|
||||
|
||||
// CRITICAL: Calculate P&L for THIS close only, do NOT add previouslyRealized
|
||||
// Previous bug: Added trade.realizedPnL which could be from prior detection cycles
|
||||
// This caused 10x P&L inflation when same trade detected multiple times
|
||||
// FIX: Calculate ONLY the runner P&L for this specific closure
|
||||
let runnerRealized = 0
|
||||
let runnerProfitPercent = 0
|
||||
if (!wasPhantom) {
|
||||
@@ -797,10 +797,13 @@ export class PositionManager {
|
||||
runnerRealized = (sizeForPnL * runnerProfitPercent) / 100
|
||||
}
|
||||
|
||||
const totalRealizedPnL = previouslyRealized + runnerRealized
|
||||
// DON'T mutate trade.realizedPnL here - causes compounding on re-detection!
|
||||
// trade.realizedPnL = totalRealizedPnL ← REMOVED: Causes duplicate P&L bug
|
||||
console.log(` Realized P&L calculation → Previous: $${previouslyRealized.toFixed(2)} | Runner: $${runnerRealized.toFixed(2)} (Δ${runnerProfitPercent.toFixed(2)}%) on $${sizeForPnL.toFixed(2)} | Total: $${totalRealizedPnL.toFixed(2)}`)
|
||||
// For external closures, we DON'T know if TP1 already hit, so calculate full position P&L
|
||||
// Database will have correct previouslyRealized if TP1 was hit
|
||||
// For external closures, we DON'T know if TP1 already hit, so calculate full position P&L
|
||||
// Database will have correct previouslyRealized if TP1 was hit
|
||||
const totalRealizedPnL = runnerRealized
|
||||
console.log(` External closure P&L → ${runnerProfitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} = $${totalRealizedPnL.toFixed(2)}`)
|
||||
|
||||
|
||||
// Determine exit reason from trade state and P&L
|
||||
if (trade.tp2Hit) {
|
||||
|
||||
Reference in New Issue
Block a user