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

@@ -745,14 +745,14 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
data_age_text = f" ({data_age}s old)" if data_age else ""
message = (
f"🛑 *Analytics suggest NOT entering {direction.upper()} {symbol_info['label']}*\n\n"
f"*Reason:* {analytics.get('reason', 'Unknown')}\n"
f"*Score:* {analytics.get('score', 0)}/100\n"
f"*Data:* {data_icon} {data_source}{data_age_text}\n\n"
f"Use `{text} --force` to override"
f"🛑 Analytics suggest NOT entering {direction.upper()} {symbol_info['label']}\n\n"
f"Reason: {analytics.get('reason', 'Unknown')}\n"
f"Score: {analytics.get('score', 0)}/100\n"
f"Data: {data_icon} {data_source}{data_age_text}\n\n"
f"Use '{text} --force' to override"
)
await update.message.reply_text(message, parse_mode='Markdown')
await update.message.reply_text(message)
print(f"❌ Trade blocked by analytics (score: {analytics.get('score')})", flush=True)
return
@@ -762,12 +762,12 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
data_age_text = f" ({data_age}s old)" if data_age else ""
confirm_message = (
f"*Analytics check passed ({analytics.get('score')}/100)*\n"
f"✅ Analytics check passed ({analytics.get('score')}/100)\n"
f"Data: {data_source}{data_age_text}\n"
f"Proceeding with {direction.upper()} {symbol_info['label']}..."
)
await update.message.reply_text(confirm_message, parse_mode='Markdown')
await update.message.reply_text(confirm_message)
print(f"✅ Analytics passed (score: {analytics.get('score')})", flush=True)
else:
# Analytics endpoint failed - proceed with trade (fail-open)
@@ -783,9 +783,8 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
# Send waiting message to user
await update.message.reply_text(
f"*Waiting for next 1-minute datapoint...*\n"
f"Will execute with fresh ATR (max 90s)",
parse_mode='Markdown'
f"⏳ Waiting for next 1-minute datapoint...\n"
f"Will execute with fresh ATR (max 90s)"
)
# Poll for fresh data (new timestamp = new datapoint arrived)
@@ -813,29 +812,26 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
print(f"✅ Using fresh metrics: ATR={metrics['atr']:.4f}, ADX={metrics['adx']:.1f}, RSI={metrics['rsi']:.1f} ({data_age}s old)", flush=True)
await update.message.reply_text(
f"*Fresh data received*\n"
f"✅ Fresh data received\n"
f"ATR: {metrics['atr']:.4f} | ADX: {metrics['adx']:.1f} | RSI: {metrics['rsi']:.1f}\n"
f"Executing {direction.upper()} {symbol_info['label']}...",
parse_mode='Markdown'
f"Executing {direction.upper()} {symbol_info['label']}..."
)
else:
print(f"⚠️ Fresh data invalid (ATR={fresh_atr}), using preset metrics", flush=True)
await update.message.reply_text(
f"⚠️ *Fresh data invalid*\n"
f"⚠️ Fresh data invalid\n"
f"Using preset ATR: {metrics['atr']}\n"
f"Executing {direction.upper()} {symbol_info['label']}...",
parse_mode='Markdown'
f"Executing {direction.upper()} {symbol_info['label']}..."
)
else:
# Timeout - fallback to preset with warning
print(f"⚠️ Timeout waiting for fresh data - using preset metrics: ATR={metrics['atr']}", flush=True)
await update.message.reply_text(
f"⚠️ *Timeout waiting for fresh data*\n"
f"⚠️ Timeout waiting for fresh data\n"
f"Using preset ATR: {metrics['atr']}\n"
f"Executing {direction.upper()} {symbol_info['label']}...",
parse_mode='Markdown'
f"Executing {direction.upper()} {symbol_info['label']}..."
)
# Execute the trade with fresh or fallback metrics
@@ -865,17 +861,22 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
# Parse JSON even for error responses to get detailed error messages
try:
print(f"🔍 Parsing JSON response...", flush=True)
data = response.json()
except Exception:
print(f"✅ JSON parsed successfully", flush=True)
except Exception as e:
print(f"❌ JSON parse error: {e}", flush=True)
await update.message.reply_text(f"❌ Execution error ({response.status_code})")
return
if not data.get('success'):
# CRITICAL: Show detailed error message (may contain "CLOSE POSITION MANUALLY")
message = data.get('message') or data.get('error') or 'Trade rejected'
print(f"❌ Trade failed: {message}", flush=True)
await update.message.reply_text(f"{message}")
return
print(f"✅ Trade success, extracting data...", flush=True)
entry_price = data.get('entryPrice')
notional = data.get('positionSize')
leverage = data.get('leverage')
@@ -902,12 +903,15 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
f"TP1: {tp1_text}\nTP2: {tp2_text}\nSL: {sl_text}"
)
print(f"📤 Sending success message to user...", flush=True)
await update.message.reply_text(success_message)
print(f"✅ Success message sent!", flush=True)
except Exception as exc:
print(f"❌ Manual trade failed: {exc}", flush=True)
await update.message.reply_text(f"❌ Error: {exc}")
async def main():
"""Start the bot"""
@@ -929,7 +933,7 @@ async def main():
# Create application
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
# Add command handlers
# Add handlers
application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("status", status_command))
application.add_handler(CommandHandler("close", close_command))
@@ -951,10 +955,7 @@ async def main():
manual_trade_handler,
))
# Initialize the application first
await application.initialize()
# Register bot commands for autocomplete (works in Telegram AND Matrix bridges)
# Set bot commands for autocomplete
commands = [
BotCommand("help", "Show all available commands"),
BotCommand("status", "Show open positions"),
@@ -970,23 +971,24 @@ async def main():
BotCommand("sellfart", "Sell FARTCOIN (shortcut)"),
]
await application.bot.set_my_commands(commands)
print("✅ Bot commands registered for autocomplete (Telegram + Matrix)", flush=True)
print("✅ Bot commands registered for autocomplete", flush=True)
# Start polling
print("\n🤖 Bot ready! Send commands to your Telegram.\n", flush=True)
# Start the bot with proper async pattern
await application.initialize()
await application.start()
await application.updater.start_polling(allowed_updates=Update.ALL_TYPES)
# Run until stopped
# Keep running until stopped
try:
await asyncio.Event().wait()
except (KeyboardInterrupt, SystemExit):
pass
# Cleanup
await application.updater.stop()
await application.stop()
await application.shutdown()
finally:
await application.updater.stop()
await application.stop()
await application.shutdown()
if __name__ == '__main__':
asyncio.run(main())