critical: Fix startup validator reopening duplicate trades

- Group trades by symbol before validation
- Keep only most recent trade per symbol
- Close older duplicates with DUPLICATE_CLEANUP reason
- Prevents reopening old closed trades when checking recent trades

Bug: Startup validator was reopening ALL closed trades for a symbol
if Drift showed one position, causing 3 trades to be tracked when
only 1 actual position existed on Drift.

Impact: Position Manager was tracking ghost positions, causing
confusion and potential missed risk management.
This commit is contained in:
mindesbunister
2025-11-16 21:49:26 +01:00
parent b813a38ae9
commit cdd3a5dcb0

View File

@@ -22,7 +22,16 @@ export async function initializePositionManagerOnStartup() {
console.log('🚀 Initializing Position Manager on startup...')
try {
// Validate open trades against Drift positions BEFORE starting Position Manager
// CRITICAL: Run database sync validator FIRST to clean up duplicates
const { validateAllOpenTrades } = await import('../database/sync-validator')
console.log('🔍 Running database sync validation before Position Manager init...')
const validationResult = await validateAllOpenTrades()
if (validationResult.ghosts > 0) {
console.log(`✅ Cleaned up ${validationResult.ghosts} ghost/duplicate trades`)
}
// Then validate open trades against Drift positions
await validateOpenTrades()
const manager = await getInitializedPositionManager()
@@ -77,10 +86,45 @@ async function validateOpenTrades() {
console.log(`🔍 Validating ${openTrades.length} open + ${recentlyClosedTrades.length} recently closed trades against Drift...`)
// CRITICAL: Group trades by symbol to handle multiple DB entries for same Drift position
// This prevents reopening old closed trades when only the most recent should be restored
const tradesBySymbol = new Map<string, any[]>()
for (const trade of allTradesToCheck) {
const existing = tradesBySymbol.get(trade.symbol) || []
existing.push(trade)
tradesBySymbol.set(trade.symbol, existing)
}
const driftService = await initializeDriftService()
const driftPositions = await driftService.getAllPositions() // Get all positions once
for (const trade of allTradesToCheck) {
// Process each symbol's trades (keep only most recent if multiple exist)
for (const [symbol, trades] of tradesBySymbol) {
// Sort by creation time, newest first
trades.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
const mostRecentTrade = trades[0]
const olderTrades = trades.slice(1)
// Close any older trades BEFORE validating the most recent
for (const oldTrade of olderTrades) {
if (oldTrade.exitReason === null) {
console.log(`🗑️ Closing duplicate old trade: ${oldTrade.id} (${symbol}, created ${oldTrade.createdAt.toISOString()})`)
await prisma.trade.update({
where: { id: oldTrade.id },
data: {
status: 'closed',
exitTime: new Date(),
exitReason: 'DUPLICATE_CLEANUP',
exitPrice: oldTrade.entryPrice,
realizedPnL: 0,
}
})
}
}
// Now validate only the most recent trade for this symbol
const trade = mostRecentTrade
try {
const marketConfig = getMarketConfig(trade.symbol)