← Назад"""
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",
)