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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user