critical: Auto-restore missing on-chain orders on startup
PROBLEM (Nov 16, 22:03): - Ghost position closed with -$6.77 loss - Validator cleanup removed orders - Position existed on Drift with NO on-chain TP/SL - Only Position Manager software protection active - If bot crashes, position completely unprotected FIX: - Added restoreOrdersIfMissing() to startup validator - Checks every verified position for orders - Automatically places TP/SL if missing - Updates database with order transaction IDs BEHAVIOR: - Runs on every container startup - Validates all open positions - Logs: '✅ {symbol} has X on-chain orders' - Or: '⚠️ {symbol} has NO orders - restoring...' - Provides dual-layer protection always Impact: Eliminates unprotected position risk after validator cleanups, container restarts, or order issues.
This commit is contained in:
@@ -191,6 +191,13 @@ async function validateOpenTrades() {
|
|||||||
console.log(`✅ ${trade.symbol} ${trade.direction}: Position verified on Drift`)
|
console.log(`✅ ${trade.symbol} ${trade.direction}: Position verified on Drift`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL FIX (Nov 16, 2025): Restore missing on-chain orders
|
||||||
|
// Ghost position closed at 22:03 because orders were missing after validator cleanup
|
||||||
|
// This ensures EVERY verified position has on-chain TP/SL protection
|
||||||
|
if (position && Math.abs(position.size) >= 0.01) {
|
||||||
|
await restoreOrdersIfMissing(trade, position, driftService, prisma)
|
||||||
|
}
|
||||||
|
|
||||||
} catch (posError) {
|
} catch (posError) {
|
||||||
console.error(`❌ Error validating trade ${trade.symbol}:`, posError)
|
console.error(`❌ Error validating trade ${trade.symbol}:`, posError)
|
||||||
}
|
}
|
||||||
@@ -200,3 +207,72 @@ async function validateOpenTrades() {
|
|||||||
console.error('❌ Error in validateOpenTrades:', error)
|
console.error('❌ Error in validateOpenTrades:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore on-chain exit orders if missing (Nov 16, 2025)
|
||||||
|
*
|
||||||
|
* CRITICAL: After validator cleanups or container restarts, positions may exist
|
||||||
|
* on Drift without any on-chain TP/SL orders. This leaves only Position Manager
|
||||||
|
* software protection - if bot crashes, position is completely unprotected.
|
||||||
|
*
|
||||||
|
* This function checks if orders exist and places them if missing.
|
||||||
|
*/
|
||||||
|
async function restoreOrdersIfMissing(
|
||||||
|
trade: any,
|
||||||
|
position: any,
|
||||||
|
driftService: any,
|
||||||
|
prisma: any
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Check if position has any reduce-only orders
|
||||||
|
const hasOrders = position.orders && position.orders.length > 0
|
||||||
|
|
||||||
|
if (hasOrders) {
|
||||||
|
console.log(`✅ ${trade.symbol} has ${position.orders.length} on-chain orders - protection active`)
|
||||||
|
return // Orders exist, nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`⚠️ ${trade.symbol} has NO on-chain orders - restoring TP/SL protection...`)
|
||||||
|
|
||||||
|
// Import order placement function
|
||||||
|
const { placeExitOrders } = await import('../drift/orders')
|
||||||
|
|
||||||
|
// Place exit orders using trade's TP/SL prices
|
||||||
|
const result = await placeExitOrders({
|
||||||
|
symbol: trade.symbol,
|
||||||
|
direction: trade.direction,
|
||||||
|
entryPrice: trade.entryPrice,
|
||||||
|
tp1Price: trade.takeProfit1Price,
|
||||||
|
tp2Price: trade.takeProfit2Price,
|
||||||
|
stopLossPrice: trade.stopLossPrice,
|
||||||
|
positionSizeUSD: trade.positionSizeUSD,
|
||||||
|
tp1SizePercent: 75,
|
||||||
|
tp2SizePercent: 0, // TP2-as-runner
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(`✅ Orders restored for ${trade.symbol}:`)
|
||||||
|
console.log(` TP1: $${trade.takeProfit1Price.toFixed(4)} (75%)`)
|
||||||
|
console.log(` TP2: $${trade.takeProfit2Price.toFixed(4)} (runner trigger)`)
|
||||||
|
console.log(` SL: $${trade.stopLossPrice.toFixed(4)}`)
|
||||||
|
console.log(` TX: ${result.signatures?.[0]?.slice(0, 8)}...`)
|
||||||
|
|
||||||
|
// Update database with order transaction signatures
|
||||||
|
await prisma.trade.update({
|
||||||
|
where: { id: trade.id },
|
||||||
|
data: {
|
||||||
|
takeProfit1OrderTx: result.signatures?.[0],
|
||||||
|
takeProfit2OrderTx: result.signatures?.[1],
|
||||||
|
stopLossOrderTx: result.signatures?.[2],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error(`❌ Failed to restore orders for ${trade.symbol}:`, result.error)
|
||||||
|
console.error(` 🚨 CRITICAL: Position is unprotected - only Position Manager monitoring active`)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error restoring orders for ${trade.symbol}:`, error)
|
||||||
|
console.error(` 🚨 CRITICAL: Position may be unprotected`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user