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