From eccecf7aaaab346e2565980aba4be40d065e6df2 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Wed, 19 Nov 2025 15:03:15 +0100 Subject: [PATCH] critical: Fix container restart killing positions + phantom detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two critical bugs caused by container restart: 1. **Startup order restore failure:** - Wrong field names: takeProfit1OrderTx → tp1OrderTx - Caused: Prisma error, orders not restored, position unprotected - Impact: Container restart left position with NO TP/SL backup 2. **Phantom detection killing runners:** - Bug: Flagged runners after TP1 as phantom trades - Logic: (currentSize / positionSize) < 0.5 - Example: $3,317 runner / $8,325 original = 40% = PHANTOM! - Result: Set P&L to $0.00 on profitable runner exit Fixes: - Use correct DB field names (tp1OrderTx, tp2OrderTx, slOrderTx) - Phantom detection only checks BEFORE TP1 hit - Runner P&L calculated on currentSize, not originalPositionSize - If TP1 hit, we're closing the RUNNER (currentSize) - If TP1 not hit, we're closing FULL position (originalPositionSize) Real Impact (Nov 19, 2025): - SHORT $138.355 → Runner trailing at $136.72 (peak) - Container restart → Orders failed to restore Closed with $0.00 P&L - Actual profit from Drift: ~$54.41 (TP1 + runner combined) Prevention: - Next restart will restore orders correctly - Runners will calculate P&L properly - No more premature closures from schema errors --- lib/startup/init-position-manager.ts | 6 +++--- lib/trading/position-manager.ts | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/startup/init-position-manager.ts b/lib/startup/init-position-manager.ts index 0bd3f64..5d0efce 100644 --- a/lib/startup/init-position-manager.ts +++ b/lib/startup/init-position-manager.ts @@ -261,9 +261,9 @@ async function restoreOrdersIfMissing( await prisma.trade.update({ where: { id: trade.id }, data: { - takeProfit1OrderTx: result.signatures?.[0], - takeProfit2OrderTx: result.signatures?.[1], - stopLossOrderTx: result.signatures?.[2], + tp1OrderTx: result.signatures?.[0], + tp2OrderTx: result.signatures?.[1], + slOrderTx: result.signatures?.[2], } }) } else { diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index 8d4e763..9797ae6 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -782,13 +782,15 @@ export class PositionManager { // Position Manager detected it, causing zero P&L bug // HOWEVER: If this was a phantom trade (extreme size mismatch), set P&L to 0 - // Use full position size for P&L calculation since we don't know if TP1 filled - // The exit reason logic will determine TP1 vs SL based on profit amount - const sizeForPnL = trade.originalPositionSize + // CRITICAL: Determine size for P&L calculation based on TP1 status + // If TP1 already hit, we're closing the RUNNER only (currentSize) + // If TP1 not hit, we're closing the FULL position (originalPositionSize) + const sizeForPnL = trade.tp1Hit ? trade.currentSize : trade.originalPositionSize - // Check if this was a phantom trade by looking at the last known on-chain size - // If last on-chain size was <50% of expected, this is a phantom - const wasPhantom = trade.currentSize > 0 && (trade.currentSize / trade.positionSize) < 0.5 + // Check if this was a phantom trade by looking at ORIGINAL size mismatch + // Phantom = position opened but size was <50% of expected FROM THE START + // DO NOT flag runners after TP1 as phantom! + const wasPhantom = !trade.tp1Hit && trade.currentSize > 0 && (trade.currentSize / trade.positionSize) < 0.5 console.log(`📊 External closure detected - Position size tracking:`) console.log(` Original size: $${trade.positionSize.toFixed(2)}`)