← Назад
""" WT Bot v3 — Telegram Bot =========================== Команды для управления и мониторинга. Использует python-telegram-bot v20+ (async). """ import logging from telegram import Update from telegram.ext import Application, CommandHandler, ContextTypes from src.config import TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, MAX_POSITIONS logger = logging.getLogger("bot") class TelegramBot: def __init__(self, manager=None, screener=None, exchange=None, tmm=None): self.manager = manager self.screener = screener self.exchange = exchange self.tmm = tmm self.app = None self._chat_id = TELEGRAM_CHAT_ID async def start(self): """Инициализация и запуск бота.""" self.app = Application.builder().token(TELEGRAM_BOT_TOKEN).build() # Регистрация команд self.app.add_handler(CommandHandler("start", self._cmd_start)) self.app.add_handler(CommandHandler("help", self._cmd_help)) self.app.add_handler(CommandHandler("positions", self._cmd_positions)) self.app.add_handler(CommandHandler("pos", self._cmd_positions)) self.app.add_handler(CommandHandler("pnl", self._cmd_pnl)) self.app.add_handler(CommandHandler("whitelist", self._cmd_whitelist)) self.app.add_handler(CommandHandler("wl", self._cmd_whitelist)) self.app.add_handler(CommandHandler("balance", self._cmd_balance)) self.app.add_handler(CommandHandler("bal", self._cmd_balance)) self.app.add_handler(CommandHandler("status", self._cmd_status)) self.app.add_handler(CommandHandler("tmm", self._cmd_tmm)) self.app.add_handler(CommandHandler("stats", self._cmd_tmm)) await self.app.initialize() await self.app.start() await self.app.updater.start_polling(drop_pending_updates=True) logger.info("Telegram bot started") async def stop(self): """Остановка бота.""" if self.app: await self.app.updater.stop() await self.app.stop() await self.app.shutdown() # ============================================================ # NOTIFY (called from manager) # ============================================================ async def send_message(self, text): """Отправить сообщение в чат.""" if self.app and self._chat_id: try: await self.app.bot.send_message( chat_id=self._chat_id, text=text, parse_mode="Markdown", ) except Exception as e: logger.error(f"Send message error: {e}") # Fallback без Markdown try: await self.app.bot.send_message( chat_id=self._chat_id, text=text, ) except Exception: pass # ============================================================ # COMMANDS # ============================================================ async def _cmd_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text( "🤖 WT Bot v3 — Active\n" "Strategy: S3_EMA_Filter 5m\n" "SL 2% / TP 3%\n\n" "Commands: /help" ) async def _cmd_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text( "📋 *Commands:*\n\n" "/positions (/pos) — open positions\n" "/pnl — local PnL summary\n" "/tmm (/stats) — TMM stats (today/week/total)\n" "/whitelist (/wl) — current whitelist\n" "/balance (/bal) — USDT balance\n" "/status — bot status\n", parse_mode="Markdown", ) async def _cmd_positions(self, update: Update, context: ContextTypes.DEFAULT_TYPE): if not self.manager or not self.manager.positions: await update.message.reply_text("📭 No open positions") return lines = ["📊 *Open Positions:*\n"] for symbol, pos in self.manager.positions.items(): try: mark = self.exchange.get_mark_price(symbol) if pos.side == "LONG": pnl_pct = (mark - pos.entry_price) / pos.entry_price * 100 else: pnl_pct = (pos.entry_price - mark) / pos.entry_price * 100 emoji = "🟢" if pos.side == "LONG" else "🔴" pnl_emoji = "📈" if pnl_pct > 0 else "📉" lines.append( f"{emoji} *{symbol}* {pos.side}\n" f" Entry: {pos.entry_price}\n" f" Mark: {mark} {pnl_emoji} {pnl_pct:+.2f}%\n" f" SL: {pos.sl_price:.4f} | TP: {pos.tp_price:.4f}\n" ) except Exception: lines.append(f"⚠️ {symbol} — error getting price\n") await update.message.reply_text("\n".join(lines), parse_mode="Markdown") async def _cmd_pnl(self, update: Update, context: ContextTypes.DEFAULT_TYPE): if not self.manager: await update.message.reply_text("No data") return summary = self.manager.get_pnl_summary() await update.message.reply_text(summary, parse_mode="Markdown") async def _cmd_whitelist(self, update: Update, context: ContextTypes.DEFAULT_TYPE): if not self.screener: await update.message.reply_text("No screener") return wl = self.screener.get_whitelist() if not wl: await update.message.reply_text("📭 Whitelist empty") return lines = [f"📋 *Whitelist ({len(wl)}):*\n"] for e in wl: zone_str = "🟢 OVERSOLD" if e["zone"] == 1 else "🔴 OVERBOUGHT" lines.append( f"• *{e['symbol']}* {zone_str}\n" f" WT1={e['wt1']:.1f} ATR={e.get('atr_pct', '?')}%" ) await update.message.reply_text("\n".join(lines), parse_mode="Markdown") async def _cmd_balance(self, update: Update, context: ContextTypes.DEFAULT_TYPE): if not self.exchange: await update.message.reply_text("No exchange") return try: balance = self.exchange.get_balance() positions = len(self.manager.positions) if self.manager else 0 await update.message.reply_text( f"💰 *Balance:* ${balance:.2f} USDT\n" f"📊 Positions: {positions}/{MAX_POSITIONS}", parse_mode="Markdown", ) except Exception as e: await update.message.reply_text(f"Error: {e}") async def _cmd_tmm(self, update: Update, context: ContextTypes.DEFAULT_TYPE): if not self.tmm or not self.tmm.enabled: await update.message.reply_text("TMM not connected") return try: today = self.tmm.get_today_summary() week = self.tmm.get_weekly_summary() total = self.tmm.get_total_summary() await update.message.reply_text(f"{today}\n\n{week}\n\n{total}") except Exception as e: await update.message.reply_text(f"TMM error: {e}") async def _cmd_status(self, update: Update, context: ContextTypes.DEFAULT_TYPE): wl_count = len(self.screener.get_whitelist()) if self.screener else 0 pos_count = len(self.manager.positions) if self.manager else 0 await update.message.reply_text( f"🤖 *WT Bot v3 Status*\n\n" f"Strategy: S3\\_EMA\\_Filter 5m\n" f"SL: 2% | TP: 3%\n" f"Leverage: 10x | Size: $5\n\n" f"Whitelist: {wl_count}\n" f"Positions: {pos_count}/{MAX_POSITIONS}\n" f"Status: ✅ Running", parse_mode="Markdown", )