← Back
"""
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",
        )

📜 Git History

c6f6bd5chore: initial commit — version control setup5 weeks ago
Show last diff
Loading...