critical: Bug #90 - Use placeAndTakePerpOrder for immediate MARKET order fills

ROOT CAUSE: placePerpOrder() only places orders on Drift order book, doesn't fill.
SOLUTION: placeAndTakePerpOrder() places AND matches against makers atomically.

Real Incident (Dec 31, 2025):
- Dec 30 18:17: SHORT opened at $124.36
- Dec 31 00:30: LONG signal received - should flip position
- Transaction confirmed but Solscan showed 'Place' not 'Fill'
- Position remained open, eventually hit SL twice
- Total loss: ~$40.21

Files changed:
- lib/drift/orders.ts (line 662): placePerpOrder → placeAndTakePerpOrder
- docs/COMMON_PITFALLS.md: Added Bug #90 documentation

Deployment: Dec 31, 2025 11:38 CET (container trading-bot-v4)
This commit is contained in:
mindesbunister
2025-12-31 11:45:21 +01:00
parent d0323fddd7
commit 465f6bdb82
2 changed files with 126 additions and 7 deletions

View File

@@ -644,7 +644,7 @@ export async function closePosition(
}
}
// Prepare close order (opposite direction) - use simple structure like v3
// Prepare close order (opposite direction)
const orderParams = {
orderType: OrderType.MARKET,
marketIndex: marketConfig.driftMarketIndex,
@@ -655,14 +655,26 @@ export async function closePosition(
reduceOnly: true, // Important: only close existing position
}
// Place market close order using simple placePerpOrder (like v3)
// CRITICAL: Wrap in retry logic for rate limit protection
logger.log('🚀 Placing REAL market close order with retry protection...')
// CRITICAL FIX (Dec 31, 2025 - Bug #89): Use placeAndTakePerpOrder instead of placePerpOrder
//
// ROOT CAUSE: placePerpOrder only PLACES an order on the order book.
// For MARKET orders, the transaction confirms when the order is placed, NOT when it's filled.
// This caused the flip operation to fail - close tx confirmed but position never closed.
//
// SOLUTION: placeAndTakePerpOrder places the order AND fills it against makers atomically.
// This guarantees the position is closed when the transaction confirms.
//
// Evidence: Solscan showed tx 5E713pD6... as "Place long 24.72 SOL perp order at price 0"
// - The order was placed on the book but never filled
// - Position remained open despite "confirmed" transaction
//
// See: https://github.com/drift-labs/drift-vaults/blob/main/tests/testHelpers.ts#L1542
console.log('🚀 Placing market close order with IMMEDIATE FILL (placeAndTakePerpOrder)...')
const txSig = await retryWithBackoff(async () => {
return await driftClient.placePerpOrder(orderParams)
return await driftClient.placeAndTakePerpOrder(orderParams)
}, 3, 8000) // 8s base delay, 3 max retries
logger.log(`✅ Close order placed! Transaction: ${txSig}`)
console.log(`✅ Close order executed! Transaction: ${txSig}`)
// CRITICAL: Confirm transaction on-chain to prevent phantom closes
// BUT: Use timeout to prevent API hangs during network congestion