critical: Fix container restart killing positions + phantom detection
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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)}`)
|
||||
|
||||
Reference in New Issue
Block a user