critical: Fix Smart Validation Queue blockReason mismatch (Bug #84)

Root Cause: check-risk endpoint passes blockReason='SMART_VALIDATION_QUEUED'
but addSignal() only accepted 'QUALITY_SCORE_TOO_LOW' → signals blocked but never queued

Impact: Quality 85 LONG signal at 08:40:03 saved to database but never monitored
User missed validation opportunity when price moved favorably

Fix: Accept both blockReason variants in addSignal() validation check

Evidence:
- Database record cmj41pdqu0101pf07mith5s4c has blockReason='SMART_VALIDATION_QUEUED'
- No logs showing addSignal() execution (would log ' Smart validation queued')
- check-risk code line 451 passes 'SMART_VALIDATION_QUEUED'
- addSignal() line 76 rejected signals != 'QUALITY_SCORE_TOO_LOW'

Result: Quality 50-89 signals will now be properly queued for validation
This commit is contained in:
mindesbunister
2025-12-13 17:24:38 +01:00
parent 12b4c7cafc
commit 5d5868d802
7 changed files with 575 additions and 69 deletions

View File

@@ -811,38 +811,109 @@ export class PositionManager {
trade.slMovedToBreakeven = true
logger.log(`🛡️ Stop loss moved to: $${trade.stopLossPrice.toFixed(4)}`)
// CRITICAL: Update on-chain orders to reflect new SL at breakeven
try {
const { cancelAllOrders, placeExitOrders } = await import('../drift/orders')
// CRITICAL FIX (Dec 12, 2025): Check if we have order signatures
// Auto-synced positions may have NULL signatures, need fallback
const { updateTradeState } = await import('../database/trades')
const dbTrade = await this.prisma.trade.findUnique({
where: { id: trade.id },
select: { slOrderTx: true, softStopOrderTx: true, hardStopOrderTx: true }
})
const hasOrderSignatures = dbTrade && (
dbTrade.slOrderTx || dbTrade.softStopOrderTx || dbTrade.hardStopOrderTx
)
if (!hasOrderSignatures) {
logger.log(`⚠️ No order signatures found - auto-synced position detected`)
logger.log(`🔧 FALLBACK: Placing fresh SL order at breakeven $${trade.stopLossPrice.toFixed(4)}`)
logger.log(`🔄 Cancelling old exit orders...`)
const cancelResult = await cancelAllOrders(trade.symbol)
if (cancelResult.success) {
logger.log(`✅ Cancelled ${cancelResult.cancelledCount} old orders`)
// Place fresh SL order without trying to cancel (no signatures to cancel)
const { placeExitOrders } = await import('../drift/orders')
try {
const placeResult = await placeExitOrders({
symbol: trade.symbol,
positionSizeUSD: trade.currentSize,
entryPrice: trade.entryPrice,
tp1Price: trade.tp2Price || trade.entryPrice * (trade.direction === 'long' ? 1.02 : 0.98),
tp2Price: trade.tp2Price || trade.entryPrice * (trade.direction === 'long' ? 1.04 : 0.96),
stopLossPrice: trade.stopLossPrice,
tp1SizePercent: 0, // Already hit, don't place TP1
tp2SizePercent: trade.tp2SizePercent || 0,
direction: trade.direction,
useDualStops: this.config.useDualStops,
softStopPrice: this.config.useDualStops ? trade.stopLossPrice : undefined,
hardStopPrice: this.config.useDualStops
? (trade.direction === 'long' ? trade.stopLossPrice * 0.99 : trade.stopLossPrice * 1.01)
: undefined,
})
if (placeResult.success && placeResult.signatures) {
logger.log(`✅ Fresh SL order placed successfully`)
// Update database with new order signatures
const updateData: any = {
stopLossPrice: trade.stopLossPrice,
}
if (this.config.useDualStops && placeResult.signatures.length >= 2) {
updateData.softStopOrderTx = placeResult.signatures[0]
updateData.hardStopOrderTx = placeResult.signatures[1]
logger.log(`💾 Recorded dual SL signatures: soft=${placeResult.signatures[0].slice(0,8)}... hard=${placeResult.signatures[1].slice(0,8)}...`)
} else if (placeResult.signatures.length >= 1) {
updateData.slOrderTx = placeResult.signatures[0]
logger.log(`💾 Recorded SL signature: ${placeResult.signatures[0].slice(0,8)}...`)
}
await updateTradeState({
id: trade.id,
...updateData
})
logger.log(`✅ Database updated with new SL order signatures`)
} else {
console.error(`❌ Failed to place fresh SL order:`, placeResult.error)
logger.log(`⚠️ CRITICAL: Runner has NO STOP LOSS PROTECTION - manual intervention needed`)
}
} catch (placeError) {
console.error(`❌ Error placing fresh SL order:`, placeError)
logger.log(`⚠️ CRITICAL: Runner has NO STOP LOSS PROTECTION - manual intervention needed`)
}
} else {
// Normal flow: Has order signatures, can cancel and replace
logger.log(`✅ Order signatures found - normal order update flow`)
logger.log(`🛡️ Placing new exit orders with SL at breakeven...`)
const orderResult = await placeExitOrders({
symbol: trade.symbol,
direction: trade.direction,
entryPrice: trade.entryPrice,
positionSizeUSD: trade.currentSize, // Runner size
stopLossPrice: trade.stopLossPrice, // At breakeven now
tp1Price: trade.tp2Price, // TP2 becomes new TP1 for runner
tp2Price: 0, // No TP2 for runner
tp1SizePercent: 0, // Close 0% at TP2 (activates trailing)
tp2SizePercent: 0, // No TP2
softStopPrice: 0,
hardStopPrice: 0,
})
if (orderResult.success) {
logger.log(`✅ Exit orders updated with SL at breakeven`)
} else {
console.error(`❌ Failed to update exit orders:`, orderResult.error)
try {
const { cancelAllOrders, placeExitOrders } = await import('../drift/orders')
logger.log(`🔄 Cancelling old exit orders...`)
const cancelResult = await cancelAllOrders(trade.symbol)
if (cancelResult.success) {
logger.log(`✅ Cancelled ${cancelResult.cancelledCount} old orders`)
}
logger.log(`🛡️ Placing new exit orders with SL at breakeven...`)
const orderResult = await placeExitOrders({
symbol: trade.symbol,
direction: trade.direction,
entryPrice: trade.entryPrice,
positionSizeUSD: trade.currentSize, // Runner size
stopLossPrice: trade.stopLossPrice, // At breakeven now
tp1Price: trade.tp2Price, // TP2 becomes new TP1 for runner
tp2Price: 0, // No TP2 for runner
tp1SizePercent: 0, // Close 0% at TP2 (activates trailing)
tp2SizePercent: 0, // No TP2
softStopPrice: 0,
hardStopPrice: 0,
})
if (orderResult.success) {
logger.log(`✅ Exit orders updated with SL at breakeven`)
} else {
console.error(`❌ Failed to update exit orders:`, orderResult.error)
}
} catch (error) {
console.error(`❌ Failed to update on-chain orders after TP1:`, error)
}
} catch (error) {
console.error(`❌ Failed to update on-chain orders after TP1:`, error)
}
await this.saveTradeState(trade)