Files
trading_bot_v4/lib/startup/init-position-manager.ts
2025-11-05 11:42:22 +01:00

126 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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
}
}