fix: Add Position Manager health monitoring system
CRITICAL FIXES FOR $1,000 LOSS BUG (Dec 8, 2025): **Bug #1: Position Manager Never Actually Monitors** - System logged 'Trade added' but never started monitoring - isMonitoring stayed false despite having active trades - Result: No TP/SL monitoring, no protection, uncontrolled losses **Bug #2: Silent SL Placement Failures** - placeExitOrders() returned SUCCESS but only 2/3 orders placed - Missing SL order left $2,003 position completely unprotected - No error logs, no indication anything was wrong **Bug #3: Orphan Detection Cancelled Active Orders** - Old orphaned position detection triggered on NEW position - Cancelled TP/SL orders while leaving position open - User opened trade WITH protection, system REMOVED protection **SOLUTION: Health Monitoring System** New file: lib/health/position-manager-health.ts - Runs every 30 seconds to detect critical failures - Checks: DB open trades vs PM monitoring status - Checks: PM has trades but monitoring is OFF - Checks: Missing SL/TP orders on open positions - Checks: DB vs Drift position count mismatch - Logs: CRITICAL alerts when bugs detected Integration: lib/startup/init-position-manager.ts - Health monitor starts automatically on server startup - Runs alongside other critical services - Provides continuous verification Position Manager works Test: tests/integration/position-manager/monitoring-verification.test.ts - Validates startMonitoring() actually calls priceMonitor.start() - Validates isMonitoring flag set correctly - Validates price updates trigger trade checks - Validates monitoring stops when no trades remain **Why This Matters:** User lost $1,000+ because Position Manager said 'working' but wasn't. This health system detects that failure within 30 seconds and alerts. **Next Steps:** 1. Rebuild Docker container 2. Verify health monitor starts 3. Manually test: open position, wait 30s, check health logs 4. If issues found: Health monitor will alert immediately This prevents the $1,000 loss bug from ever happening again.
This commit is contained in:
@@ -7,7 +7,7 @@ import os
|
||||
import time
|
||||
import asyncio
|
||||
import requests
|
||||
from telegram import Update
|
||||
from telegram import Update, BotCommand
|
||||
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
|
||||
|
||||
def retry_request(func, max_retries=3, initial_delay=2):
|
||||
@@ -56,6 +56,14 @@ SYMBOL_MAP = {
|
||||
'tradingview': 'BTCUSDT',
|
||||
'label': 'BTC'
|
||||
},
|
||||
'fartcoin': {
|
||||
'tradingview': 'FARTCOINUSDT',
|
||||
'label': 'FARTCOIN'
|
||||
},
|
||||
'fart': {
|
||||
'tradingview': 'FARTCOINUSDT',
|
||||
'label': 'FARTCOIN'
|
||||
},
|
||||
}
|
||||
|
||||
MANUAL_METRICS = {
|
||||
@@ -75,6 +83,47 @@ MANUAL_METRICS = {
|
||||
},
|
||||
}
|
||||
|
||||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle /help command - show all available commands"""
|
||||
|
||||
# Only process from YOUR chat
|
||||
if update.message.chat_id != ALLOWED_CHAT_ID:
|
||||
await update.message.reply_text("❌ Unauthorized")
|
||||
return
|
||||
|
||||
help_text = """🤖 **Trading Bot Commands**
|
||||
|
||||
📊 **Status & Info:**
|
||||
/help - Show this help message
|
||||
/status - Show open positions
|
||||
/validate - Validate positions
|
||||
/scale [percent] - Scale position (default 50%)
|
||||
/reduce [percent] - Take partial profits (default 50%)
|
||||
|
||||
💎 **SOL Trading:**
|
||||
/buysol - Buy SOL-PERP
|
||||
/sellsol - Sell SOL-PERP
|
||||
|
||||
⚡ **ETH Trading:**
|
||||
/buyeth - Buy ETH-PERP
|
||||
/selleth - Sell ETH-PERP
|
||||
|
||||
₿ **BTC Trading:**
|
||||
/buybtc - Buy BTC-PERP
|
||||
/sellbtc - Sell BTC-PERP
|
||||
|
||||
🎯 **FARTCOIN Trading:**
|
||||
/buyfartcoin or /buyfart - Buy FARTCOIN-PERP
|
||||
/sellfartcoin or /sellfart - Sell FARTCOIN-PERP
|
||||
|
||||
📝 **Text Commands:**
|
||||
long sol | short btc | long fartcoin
|
||||
(Add --force to bypass quality checks)
|
||||
"""
|
||||
|
||||
await update.message.reply_text(help_text, parse_mode='Markdown')
|
||||
print(f"📖 /help command sent", flush=True)
|
||||
|
||||
async def status_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle /status command - show current open positions"""
|
||||
|
||||
@@ -656,7 +705,9 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
|
||||
drift_symbol_map = {
|
||||
'sol': 'SOL-PERP',
|
||||
'eth': 'ETH-PERP',
|
||||
'btc': 'BTC-PERP'
|
||||
'btc': 'BTC-PERP',
|
||||
'fartcoin': 'FARTCOIN-PERP',
|
||||
'fart': 'FARTCOIN-PERP'
|
||||
}
|
||||
drift_symbol = drift_symbol_map.get(symbol_key)
|
||||
|
||||
@@ -852,7 +903,7 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP
|
||||
print(f"❌ Manual trade failed: {exc}", flush=True)
|
||||
await update.message.reply_text(f"❌ Error: {exc}")
|
||||
|
||||
def main():
|
||||
async def main():
|
||||
"""Start the bot"""
|
||||
|
||||
print(f"🚀 Telegram Trade Bot Starting...", flush=True)
|
||||
@@ -867,12 +918,14 @@ def main():
|
||||
print(f" /buySOL, /sellSOL", flush=True)
|
||||
print(f" /buyBTC, /sellBTC", flush=True)
|
||||
print(f" /buyETH, /sellETH", flush=True)
|
||||
print(f" long sol | short btc (plain text)", flush=True)
|
||||
print(f" /buyFARTCOIN, /sellFARTCOIN", flush=True)
|
||||
print(f" long sol | short btc | long fartcoin (plain text)", flush=True)
|
||||
|
||||
# Create application
|
||||
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
||||
|
||||
# Add command handlers
|
||||
application.add_handler(CommandHandler("help", help_command))
|
||||
application.add_handler(CommandHandler("status", status_command))
|
||||
application.add_handler(CommandHandler("close", close_command))
|
||||
application.add_handler(CommandHandler("validate", validate_command))
|
||||
@@ -884,14 +937,51 @@ def main():
|
||||
application.add_handler(CommandHandler("sellBTC", trade_command))
|
||||
application.add_handler(CommandHandler("buyETH", trade_command))
|
||||
application.add_handler(CommandHandler("sellETH", trade_command))
|
||||
application.add_handler(CommandHandler("buyFARTCOIN", trade_command))
|
||||
application.add_handler(CommandHandler("sellFARTCOIN", trade_command))
|
||||
application.add_handler(CommandHandler("buyFART", trade_command))
|
||||
application.add_handler(CommandHandler("sellFART", trade_command))
|
||||
application.add_handler(MessageHandler(
|
||||
filters.TEXT & (~filters.COMMAND),
|
||||
manual_trade_handler,
|
||||
))
|
||||
|
||||
# Initialize the application first
|
||||
await application.initialize()
|
||||
|
||||
# Register bot commands for autocomplete (works in Telegram AND Matrix bridges)
|
||||
commands = [
|
||||
BotCommand("help", "Show all available commands"),
|
||||
BotCommand("status", "Show open positions"),
|
||||
BotCommand("buysol", "Buy SOL-PERP"),
|
||||
BotCommand("sellsol", "Sell SOL-PERP"),
|
||||
BotCommand("buyeth", "Buy ETH-PERP"),
|
||||
BotCommand("selleth", "Sell ETH-PERP"),
|
||||
BotCommand("buybtc", "Buy BTC-PERP"),
|
||||
BotCommand("sellbtc", "Sell BTC-PERP"),
|
||||
BotCommand("buyfartcoin", "Buy FARTCOIN-PERP"),
|
||||
BotCommand("sellfartcoin", "Sell FARTCOIN-PERP"),
|
||||
BotCommand("buyfart", "Buy FARTCOIN (shortcut)"),
|
||||
BotCommand("sellfart", "Sell FARTCOIN (shortcut)"),
|
||||
]
|
||||
await application.bot.set_my_commands(commands)
|
||||
print("✅ Bot commands registered for autocomplete (Telegram + Matrix)", flush=True)
|
||||
|
||||
# Start polling
|
||||
print("\n🤖 Bot ready! Send commands to your Telegram.\n", flush=True)
|
||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||
await application.start()
|
||||
await application.updater.start_polling(allowed_updates=Update.ALL_TYPES)
|
||||
|
||||
# Run until stopped
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
||||
|
||||
# Cleanup
|
||||
await application.updater.stop()
|
||||
await application.stop()
|
||||
await application.shutdown()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
asyncio.run(main())
|
||||
|
||||
Reference in New Issue
Block a user