fix: Implement critical risk management fixes for bugs #76, #77, #78, #80

Co-authored-by: mindesbunister <32161838+mindesbunister@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-09 22:23:43 +00:00
parent 2b0673636f
commit 63b94016fe
4 changed files with 326 additions and 131 deletions

View File

@@ -267,31 +267,89 @@ export class PositionManager {
// Start monitoring if not already running
if (!this.isMonitoring && this.activeTrades.size > 0) {
await this.startMonitoring()
// BUG #77 FIX: Verify monitoring actually started
if (this.activeTrades.size > 0 && !this.isMonitoring) {
const errorMsg = `CRITICAL: Failed to start monitoring! activeTrades=${this.activeTrades.size}, isMonitoring=${this.isMonitoring}`
console.error(`${errorMsg}`)
// Log to persistent file
const { logCriticalError } = await import('../utils/persistent-logger')
await logCriticalError('MONITORING_START_FAILED', {
activeTradesCount: this.activeTrades.size,
isMonitoring: this.isMonitoring,
symbols: Array.from(this.activeTrades.values()).map(t => t.symbol),
tradeIds: Array.from(this.activeTrades.keys())
})
throw new Error(errorMsg)
}
logger.log(`✅ Monitoring verification passed: isMonitoring=${this.isMonitoring}`)
}
}
/**
* Remove a trade from monitoring
* BUG #78 FIX: Safely handle order cancellation to avoid removing active position orders
*/
async removeTrade(tradeId: string): Promise<void> {
const trade = this.activeTrades.get(tradeId)
if (trade) {
logger.log(`🗑️ Removing trade: ${trade.symbol}`)
// Cancel all orders for this symbol (cleanup orphaned orders)
// BUG #78 FIX: Check Drift position size before canceling orders
// If Drift shows an open position, DON'T cancel orders (may belong to active position)
try {
const { cancelAllOrders } = await import('../drift/orders')
const cancelResult = await cancelAllOrders(trade.symbol)
if (cancelResult.success && cancelResult.cancelledCount! > 0) {
logger.log(`✅ Cancelled ${cancelResult.cancelledCount} orphaned orders`)
const driftService = getDriftService()
const marketConfig = getMarketConfig(trade.symbol)
// Query Drift for current position
const driftPosition = await driftService.getPosition(marketConfig.driftMarketIndex)
if (driftPosition && Math.abs(driftPosition.size) >= 0.01) {
// Position still open on Drift - DO NOT cancel orders
console.warn(`⚠️ SAFETY CHECK: ${trade.symbol} position still open on Drift (size: ${driftPosition.size})`)
console.warn(` Skipping order cancellation to avoid removing active position protection`)
console.warn(` Removing from tracking only`)
// Just remove from map, don't cancel orders
this.activeTrades.delete(tradeId)
// Log for monitoring
const { logCriticalError } = await import('../utils/persistent-logger')
await logCriticalError('ORPHAN_REMOVAL_SKIPPED_ACTIVE_POSITION', {
tradeId,
symbol: trade.symbol,
driftSize: driftPosition.size,
reason: 'Drift position still open - preserved orders for safety'
})
} else {
// Position confirmed closed on Drift - safe to cancel orders
logger.log(`✅ Drift position confirmed closed (size: ${driftPosition?.size || 0})`)
logger.log(` Safe to cancel remaining orders`)
const { cancelAllOrders } = await import('../drift/orders')
const cancelResult = await cancelAllOrders(trade.symbol)
if (cancelResult.success && cancelResult.cancelledCount! > 0) {
logger.log(`✅ Cancelled ${cancelResult.cancelledCount} orphaned orders`)
} else if (!cancelResult.success) {
console.error(`❌ Failed to cancel orders: ${cancelResult.error}`)
} else {
logger.log(` No orders to cancel`)
}
this.activeTrades.delete(tradeId)
}
} catch (error) {
console.error('❌ Failed to cancel orders during trade removal:', error)
// Continue with removal even if cancel fails
console.error('❌ Error checking Drift position during trade removal:', error)
console.warn('⚠️ Removing from tracking without canceling orders (safety first)')
// On error, err on side of caution - don't cancel orders
this.activeTrades.delete(tradeId)
}
this.activeTrades.delete(tradeId)
// Stop monitoring if no more trades
if (this.activeTrades.size === 0 && this.isMonitoring) {
this.stopMonitoring()
@@ -481,6 +539,7 @@ export class PositionManager {
*/
private async startMonitoring(): Promise<void> {
if (this.isMonitoring) {
logger.log('⚠️ Monitoring already active, skipping duplicate start')
return
}
@@ -490,28 +549,49 @@ export class PositionManager {
)]
if (symbols.length === 0) {
logger.log('⚠️ No symbols to monitor, skipping start')
return
}
logger.log('🚀 Starting price monitoring for:', symbols)
logger.log('🚀 Starting price monitoring...')
logger.log(` Active trades: ${this.activeTrades.size}`)
logger.log(` Symbols: ${symbols.join(', ')}`)
logger.log(` Current isMonitoring: ${this.isMonitoring}`)
const priceMonitor = getPythPriceMonitor()
await priceMonitor.start({
symbols,
onPriceUpdate: async (update: PriceUpdate) => {
await this.handlePriceUpdate(update)
},
onError: (error: Error) => {
console.error('❌ Price monitor error:', error)
},
})
try {
logger.log('📡 Calling priceMonitor.start()...')
await priceMonitor.start({
symbols,
onPriceUpdate: async (update: PriceUpdate) => {
await this.handlePriceUpdate(update)
},
onError: (error: Error) => {
console.error('❌ Price monitor error:', error)
},
})
this.isMonitoring = true
logger.log('✅ Position monitoring active')
// Schedule periodic validation to detect and cleanup ghost positions
this.scheduleValidation()
this.isMonitoring = true
logger.log('✅ Position monitoring active')
logger.log(` isMonitoring flag set to: ${this.isMonitoring}`)
// Schedule periodic validation to detect and cleanup ghost positions
this.scheduleValidation()
} catch (error) {
console.error('❌ CRITICAL: Failed to start price monitoring:', error)
// Log error to persistent file
const { logCriticalError } = await import('../utils/persistent-logger')
await logCriticalError('PRICE_MONITOR_START_FAILED', {
symbols,
activeTradesCount: this.activeTrades.size,
error: error instanceof Error ? error.message : String(error)
})
throw error // Re-throw so caller knows monitoring failed
}
}
/**