126 lines
4.7 KiB
TypeScript
126 lines
4.7 KiB
TypeScript
/**
|
||
* Position Manager Startup Initialization
|
||
*
|
||
* Ensures Position Manager starts monitoring on bot startup
|
||
* This prevents orphaned trades when the bot restarts
|
||
*/
|
||
|
||
import { getInitializedPositionManager } from '../trading/position-manager'
|
||
import { initializeDriftService } from '../drift/client'
|
||
import { getPrismaClient } from '../database/trades'
|
||
import { getMarketConfig } from '../../config/trading'
|
||
|
||
let initStarted = false
|
||
|
||
export async function initializePositionManagerOnStartup() {
|
||
if (initStarted) {
|
||
return
|
||
}
|
||
|
||
initStarted = true
|
||
|
||
console.log('🚀 Initializing Position Manager on startup...')
|
||
|
||
try {
|
||
// Validate open trades against Drift positions BEFORE starting Position Manager
|
||
await validateOpenTrades()
|
||
|
||
const manager = await getInitializedPositionManager()
|
||
const status = manager.getStatus()
|
||
|
||
console.log(`✅ Position Manager ready - ${status.activeTradesCount} active trades`)
|
||
|
||
if (status.activeTradesCount > 0) {
|
||
console.log(`📊 Monitoring: ${status.symbols.join(', ')}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Failed to initialize Position Manager on startup:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Validate that open trades in database match actual Drift positions
|
||
* Closes phantom trades that don't exist on-chain
|
||
*/
|
||
async function validateOpenTrades() {
|
||
try {
|
||
const prisma = getPrismaClient()
|
||
const openTrades = await prisma.trade.findMany({
|
||
where: { status: 'open' },
|
||
orderBy: { entryTime: 'asc' }
|
||
})
|
||
|
||
if (openTrades.length === 0) {
|
||
console.log('✅ No open trades to validate')
|
||
return
|
||
}
|
||
|
||
console.log(`🔍 Validating ${openTrades.length} open trade(s) against Drift positions...`)
|
||
|
||
const driftService = await initializeDriftService()
|
||
|
||
for (const trade of openTrades) {
|
||
try {
|
||
const marketConfig = getMarketConfig(trade.symbol)
|
||
const position = await driftService.getPosition(marketConfig.driftMarketIndex)
|
||
|
||
// Prefer Position Manager snapshot (captures partial closes) before falling back to original size
|
||
const configSnapshot = trade.configSnapshot as any
|
||
const pmState = configSnapshot?.positionManagerState
|
||
const expectedSizeUSD = typeof pmState?.currentSize === 'number' && pmState.currentSize > 0
|
||
? pmState.currentSize
|
||
: trade.positionSizeUSD
|
||
|
||
// Calculate expected position size in base assets (approximate using entry price for consistency)
|
||
const expectedSizeBase = expectedSizeUSD / trade.entryPrice
|
||
const actualSizeBase = position?.size || 0
|
||
|
||
// Check if position exists and size matches (with 50% tolerance for partial fills)
|
||
const sizeDiff = Math.abs(expectedSizeBase - actualSizeBase)
|
||
const sizeRatio = expectedSizeBase > 0 ? actualSizeBase / expectedSizeBase : 0
|
||
|
||
if (!position || position.side === 'none' || sizeRatio < 0.2) {
|
||
console.log(`⚠️ PHANTOM TRADE DETECTED:`)
|
||
console.log(` Trade ID: ${trade.id.substring(0, 20)}...`)
|
||
console.log(` Symbol: ${trade.symbol} ${trade.direction}`)
|
||
console.log(` Expected size: ${expectedSizeBase.toFixed(4)}`)
|
||
console.log(` Actual size: ${actualSizeBase.toFixed(4)}`)
|
||
console.log(` Entry: $${trade.entryPrice} at ${trade.entryTime.toISOString()}`)
|
||
console.log(` 🗑️ Auto-closing phantom trade...`)
|
||
|
||
// Close phantom trade
|
||
await prisma.trade.update({
|
||
where: { id: trade.id },
|
||
data: {
|
||
status: 'closed',
|
||
exitTime: new Date(),
|
||
exitReason: 'PHANTOM_TRADE_CLEANUP',
|
||
exitPrice: trade.entryPrice,
|
||
realizedPnL: 0,
|
||
realizedPnLPercent: 0,
|
||
}
|
||
})
|
||
|
||
console.log(` ✅ Phantom trade closed`)
|
||
} else if (sizeDiff > expectedSizeBase * 0.1) {
|
||
console.log(`⚠️ SIZE MISMATCH (${(sizeRatio * 100).toFixed(1)}% of expected):`)
|
||
console.log(` Trade ID: ${trade.id.substring(0, 20)}...`)
|
||
console.log(` Symbol: ${trade.symbol} ${trade.direction}`)
|
||
console.log(` Expected: ${expectedSizeBase.toFixed(4)}, Actual: ${actualSizeBase.toFixed(4)}`)
|
||
console.log(` ℹ️ Will monitor with adjusted size`)
|
||
} else {
|
||
console.log(`✅ ${trade.symbol} ${trade.direction}: Size OK (${actualSizeBase.toFixed(4)})`)
|
||
}
|
||
|
||
} catch (posError) {
|
||
console.error(`❌ Error validating trade ${trade.symbol}:`, posError)
|
||
// Don't auto-close on error - might be temporary
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error in validateOpenTrades:', error)
|
||
// Don't throw - allow Position Manager to start anyway
|
||
}
|
||
}
|