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:
mindesbunister
2025-11-16 22:18:56 +01:00
parent 40d69b13ef
commit be2410c639

View File

@@ -191,6 +191,13 @@ async function validateOpenTrades() {
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) {
console.error(`❌ Error validating trade ${trade.symbol}:`, posError)
}
@@ -200,3 +207,72 @@ async function validateOpenTrades() {
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`)
}
}