123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703 |
- #!/usr/bin/env python3
- """
- Telegram Bot for Hyperliquid Trading
- This module provides a Telegram interface for manual Hyperliquid trading
- with comprehensive statistics tracking and phone-friendly controls.
- """
- import logging
- import asyncio
- import re
- from datetime import datetime
- from typing import Optional, Dict, Any
- from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
- from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes, MessageHandler, filters
- from hyperliquid_client import HyperliquidClient
- from trading_stats import TradingStats
- from config import Config
- # Set up logging
- logging.basicConfig(
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
- level=getattr(logging, Config.LOG_LEVEL)
- )
- logger = logging.getLogger(__name__)
- class TelegramTradingBot:
- """Telegram bot for manual trading with comprehensive statistics."""
-
- def __init__(self):
- """Initialize the Telegram trading bot."""
- self.client = HyperliquidClient(use_testnet=Config.HYPERLIQUID_TESTNET)
- self.stats = TradingStats()
- self.authorized_chat_id = Config.TELEGRAM_CHAT_ID
- self.application = None
-
- # Initialize stats with current balance
- self._initialize_stats()
-
- def _initialize_stats(self):
- """Initialize stats with current balance."""
- try:
- balance = self.client.get_balance()
- if balance and balance.get('total'):
- # Get USDC balance as the main balance
- usdc_balance = float(balance['total'].get('USDC', 0))
- self.stats.set_initial_balance(usdc_balance)
- except Exception as e:
- logger.error(f"Could not initialize stats: {e}")
-
- def is_authorized(self, chat_id: str) -> bool:
- """Check if the chat ID is authorized to use the bot."""
- return str(chat_id) == str(self.authorized_chat_id)
-
- async def send_message(self, text: str, parse_mode: str = 'HTML') -> None:
- """Send a message to the authorized chat."""
- if self.application and self.authorized_chat_id:
- try:
- await self.application.bot.send_message(
- chat_id=self.authorized_chat_id,
- text=text,
- parse_mode=parse_mode
- )
- except Exception as e:
- logger.error(f"Failed to send message: {e}")
-
- async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /start command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- welcome_text = """
- 🤖 <b>Hyperliquid Manual Trading Bot</b>
- Welcome to your personal trading assistant! Control your Hyperliquid account directly from your phone.
- <b>📱 Quick Actions:</b>
- Tap the buttons below for instant access to key functions.
- <b>💼 Account Commands:</b>
- /balance - Account balance
- /positions - Open positions
- /orders - Open orders
- /stats - Trading statistics
- <b>📊 Market Commands:</b>
- /market - Market data
- /price - Current price
- <b>🔄 Trading Commands:</b>
- /buy [amount] [price] - Buy order
- /sell [amount] [price] - Sell order
- /trades - Recent trades
- /cancel [order_id] - Cancel order
- <b>📈 Statistics:</b>
- /stats - Full trading statistics
- /performance - Performance metrics
- /risk - Risk analysis
- Type /help for detailed command information.
- """
-
- keyboard = [
- [
- InlineKeyboardButton("💰 Balance", callback_data="balance"),
- InlineKeyboardButton("📊 Stats", callback_data="stats")
- ],
- [
- InlineKeyboardButton("📈 Positions", callback_data="positions"),
- InlineKeyboardButton("📋 Orders", callback_data="orders")
- ],
- [
- InlineKeyboardButton("💵 Price", callback_data="price"),
- InlineKeyboardButton("📊 Market", callback_data="market")
- ],
- [
- InlineKeyboardButton("🔄 Recent Trades", callback_data="trades"),
- InlineKeyboardButton("⚙️ Help", callback_data="help")
- ]
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- await update.message.reply_text(welcome_text, parse_mode='HTML', reply_markup=reply_markup)
-
- async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /help command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- help_text = """
- 🔧 <b>Hyperliquid Trading Bot - Complete Guide</b>
- <b>💼 Account Management:</b>
- • /balance - Show account balance
- • /positions - Show open positions
- • /orders - Show open orders
- <b>📊 Market Data:</b>
- • /market - Detailed market data
- • /price - Quick price check
- <b>🔄 Manual Trading:</b>
- • /buy 0.001 50000 - Buy 0.001 BTC at $50,000
- • /sell 0.001 55000 - Sell 0.001 BTC at $55,000
- • /cancel ABC123 - Cancel order with ID ABC123
- <b>📈 Statistics & Analytics:</b>
- • /stats - Complete trading statistics
- • /performance - Win rate, profit factor, etc.
- • /risk - Sharpe ratio, drawdown, VaR
- • /trades - Recent trade history
- <b>⚙️ Configuration:</b>
- • Symbol: {symbol}
- • Default Amount: {amount}
- • Network: {network}
- <b>🛡️ Safety Features:</b>
- • All trades logged automatically
- • Comprehensive performance tracking
- • Real-time balance monitoring
- • Risk metrics calculation
- <b>📱 Mobile Optimized:</b>
- • Quick action buttons
- • Instant notifications
- • Clean, readable layout
- • One-tap commands
- For support, contact your bot administrator.
- """.format(
- symbol=Config.DEFAULT_TRADING_SYMBOL,
- amount=Config.DEFAULT_TRADE_AMOUNT,
- network="Testnet" if Config.HYPERLIQUID_TESTNET else "Mainnet"
- )
-
- await update.message.reply_text(help_text, parse_mode='HTML')
-
- async def stats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /stats command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- # Get current balance for stats
- balance = self.client.get_balance()
- current_balance = 0
- if balance and balance.get('total'):
- current_balance = float(balance['total'].get('USDC', 0))
-
- stats_message = self.stats.format_stats_message(current_balance)
- await update.message.reply_text(stats_message, parse_mode='HTML')
-
- async def buy_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /buy command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- try:
- if len(context.args) < 2:
- await update.message.reply_text(
- "❌ Usage: /buy [amount] [price]\n"
- f"Example: /buy {Config.DEFAULT_TRADE_AMOUNT} 50000"
- )
- return
-
- amount = float(context.args[0])
- price = float(context.args[1])
- symbol = Config.DEFAULT_TRADING_SYMBOL
-
- # Confirmation message
- confirmation_text = f"""
- 🟢 <b>Buy Order Confirmation</b>
- 📊 <b>Order Details:</b>
- • Symbol: {symbol}
- • Side: BUY
- • Amount: {amount}
- • Price: ${price:,.2f}
- • Total Value: ${amount * price:,.2f}
- ⚠️ <b>Are you sure you want to place this order?</b>
- This will attempt to buy {amount} {symbol} at ${price:,.2f} per unit.
- """
-
- keyboard = [
- [
- InlineKeyboardButton("✅ Confirm Buy", callback_data=f"confirm_buy_{amount}_{price}"),
- InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
- ]
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
-
- except ValueError:
- await update.message.reply_text("❌ Invalid amount or price. Please use numbers only.")
- except Exception as e:
- await update.message.reply_text(f"❌ Error processing buy command: {e}")
-
- async def sell_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /sell command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- try:
- if len(context.args) < 2:
- await update.message.reply_text(
- "❌ Usage: /sell [amount] [price]\n"
- f"Example: /sell {Config.DEFAULT_TRADE_AMOUNT} 55000"
- )
- return
-
- amount = float(context.args[0])
- price = float(context.args[1])
- symbol = Config.DEFAULT_TRADING_SYMBOL
-
- # Confirmation message
- confirmation_text = f"""
- 🔴 <b>Sell Order Confirmation</b>
- 📊 <b>Order Details:</b>
- • Symbol: {symbol}
- • Side: SELL
- • Amount: {amount}
- • Price: ${price:,.2f}
- • Total Value: ${amount * price:,.2f}
- ⚠️ <b>Are you sure you want to place this order?</b>
- This will attempt to sell {amount} {symbol} at ${price:,.2f} per unit.
- """
-
- keyboard = [
- [
- InlineKeyboardButton("✅ Confirm Sell", callback_data=f"confirm_sell_{amount}_{price}"),
- InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
- ]
- ]
- reply_markup = InlineKeyboardMarkup(keyboard)
-
- await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
-
- except ValueError:
- await update.message.reply_text("❌ Invalid amount or price. Please use numbers only.")
- except Exception as e:
- await update.message.reply_text(f"❌ Error processing sell command: {e}")
-
- async def trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /trades command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- recent_trades = self.stats.get_recent_trades(10)
-
- if not recent_trades:
- await update.message.reply_text("📝 No trades recorded yet.")
- return
-
- trades_text = "🔄 <b>Recent Trades</b>\n\n"
-
- for trade in reversed(recent_trades[-5:]): # Show last 5 trades
- timestamp = datetime.fromisoformat(trade['timestamp']).strftime('%m/%d %H:%M')
- side_emoji = "🟢" if trade['side'] == 'buy' else "🔴"
-
- trades_text += f"{side_emoji} <b>{trade['side'].upper()}</b> {trade['amount']} {trade['symbol']}\n"
- trades_text += f" 💰 ${trade['price']:,.2f} | 💵 ${trade['value']:,.2f}\n"
- trades_text += f" 📅 {timestamp}\n\n"
-
- await update.message.reply_text(trades_text, parse_mode='HTML')
-
- async def balance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /balance command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- balance = self.client.get_balance()
- if balance:
- balance_text = "💰 <b>Account Balance</b>\n\n"
- total_balance = balance.get('total', {})
-
- if total_balance:
- total_value = 0
- for asset, amount in total_balance.items():
- if float(amount) > 0:
- balance_text += f"💵 <b>{asset}:</b> {amount}\n"
- if asset == 'USDC':
- total_value += float(amount)
-
- balance_text += f"\n💼 <b>Total Value:</b> ${total_value:,.2f}"
-
- # Add stats summary
- basic_stats = self.stats.get_basic_stats()
- if basic_stats['initial_balance'] > 0:
- pnl = total_value - basic_stats['initial_balance']
- pnl_percent = (pnl / basic_stats['initial_balance']) * 100
-
- balance_text += f"\n📊 <b>P&L:</b> ${pnl:,.2f} ({pnl_percent:+.2f}%)"
- else:
- balance_text += "No balance data available"
- else:
- balance_text = "❌ Could not fetch balance data"
-
- await update.message.reply_text(balance_text, parse_mode='HTML')
-
- async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /positions command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- positions = self.client.get_positions()
- if positions:
- positions_text = "📈 <b>Open Positions</b>\n\n"
-
- open_positions = [p for p in positions if float(p.get('contracts', 0)) != 0]
-
- if open_positions:
- total_unrealized = 0
- for position in open_positions:
- symbol = position.get('symbol', 'Unknown')
- contracts = float(position.get('contracts', 0))
- unrealized_pnl = float(position.get('unrealizedPnl', 0))
- entry_price = float(position.get('entryPx', 0))
-
- pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
-
- positions_text += f"📊 <b>{symbol}</b>\n"
- positions_text += f" 📏 Size: {contracts} contracts\n"
- positions_text += f" 💰 Entry: ${entry_price:,.2f}\n"
- positions_text += f" {pnl_emoji} PnL: ${unrealized_pnl:,.2f}\n\n"
-
- total_unrealized += unrealized_pnl
-
- positions_text += f"💼 <b>Total Unrealized P&L:</b> ${total_unrealized:,.2f}"
- else:
- positions_text += "No open positions"
- else:
- positions_text = "❌ Could not fetch positions data"
-
- await update.message.reply_text(positions_text, parse_mode='HTML')
-
- async def orders_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /orders command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- orders = self.client.get_open_orders()
- if orders:
- orders_text = "📋 <b>Open Orders</b>\n\n"
-
- if orders and len(orders) > 0:
- for order in orders:
- symbol = order.get('symbol', 'Unknown')
- side = order.get('side', 'Unknown')
- amount = order.get('amount', 0)
- price = order.get('price', 0)
- order_id = order.get('id', 'Unknown')
-
- side_emoji = "🟢" if side.lower() == 'buy' else "🔴"
-
- orders_text += f"{side_emoji} <b>{symbol}</b>\n"
- orders_text += f" 📊 {side.upper()} {amount} @ ${price:,.2f}\n"
- orders_text += f" 💵 Value: ${float(amount) * float(price):,.2f}\n"
- orders_text += f" 🔑 ID: <code>{order_id}</code>\n\n"
- else:
- orders_text += "No open orders"
- else:
- orders_text = "❌ Could not fetch orders data"
-
- await update.message.reply_text(orders_text, parse_mode='HTML')
-
- async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /market command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- symbol = Config.DEFAULT_TRADING_SYMBOL
- market_data = self.client.get_market_data(symbol)
-
- if market_data:
- ticker = market_data['ticker']
- orderbook = market_data['orderbook']
-
- # Calculate 24h change
- current_price = float(ticker.get('last', 0))
- high_24h = float(ticker.get('high', 0))
- low_24h = float(ticker.get('low', 0))
-
- market_text = f"📊 <b>Market Data - {symbol}</b>\n\n"
- market_text += f"💵 <b>Current Price:</b> ${current_price:,.2f}\n"
- market_text += f"📈 <b>24h High:</b> ${high_24h:,.2f}\n"
- market_text += f"📉 <b>24h Low:</b> ${low_24h:,.2f}\n"
- market_text += f"📊 <b>24h Volume:</b> {ticker.get('baseVolume', 'N/A')}\n\n"
-
- if orderbook.get('bids') and orderbook.get('asks'):
- best_bid = float(orderbook['bids'][0][0]) if orderbook['bids'] else 0
- best_ask = float(orderbook['asks'][0][0]) if orderbook['asks'] else 0
- spread = best_ask - best_bid
- spread_percent = (spread / best_ask * 100) if best_ask > 0 else 0
-
- market_text += f"🟢 <b>Best Bid:</b> ${best_bid:,.2f}\n"
- market_text += f"🔴 <b>Best Ask:</b> ${best_ask:,.2f}\n"
- market_text += f"📏 <b>Spread:</b> ${spread:.2f} ({spread_percent:.3f}%)\n"
- else:
- market_text = "❌ Could not fetch market data"
-
- await update.message.reply_text(market_text, parse_mode='HTML')
-
- async def price_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /price command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- symbol = Config.DEFAULT_TRADING_SYMBOL
- market_data = self.client.get_market_data(symbol)
-
- if market_data:
- price = float(market_data['ticker'].get('last', 0))
- price_text = f"💵 <b>{symbol}</b>: ${price:,.2f}"
-
- # Add timestamp
- timestamp = datetime.now().strftime('%H:%M:%S')
- price_text += f"\n⏰ <i>Updated: {timestamp}</i>"
- else:
- price_text = f"❌ Could not fetch price for {symbol}"
-
- await update.message.reply_text(price_text, parse_mode='HTML')
-
- async def button_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle inline keyboard button presses."""
- query = update.callback_query
- await query.answer()
-
- if not self.is_authorized(query.message.chat_id):
- await query.edit_message_text("❌ Unauthorized access.")
- return
-
- callback_data = query.data
-
- # Handle trading confirmations
- if callback_data.startswith('confirm_buy_'):
- parts = callback_data.split('_')
- amount = float(parts[2])
- price = float(parts[3])
- await self._execute_buy_order(query, amount, price)
- return
-
- elif callback_data.startswith('confirm_sell_'):
- parts = callback_data.split('_')
- amount = float(parts[2])
- price = float(parts[3])
- await self._execute_sell_order(query, amount, price)
- return
-
- elif callback_data == 'cancel_order':
- await query.edit_message_text("❌ Order cancelled.")
- return
-
- # Create a fake update object for reusing command handlers
- fake_update = Update(
- update_id=update.update_id,
- message=query.message,
- callback_query=query
- )
-
- # Handle regular button callbacks
- if callback_data == "balance":
- await self.balance_command(fake_update, context)
- elif callback_data == "stats":
- await self.stats_command(fake_update, context)
- elif callback_data == "positions":
- await self.positions_command(fake_update, context)
- elif callback_data == "orders":
- await self.orders_command(fake_update, context)
- elif callback_data == "market":
- await self.market_command(fake_update, context)
- elif callback_data == "price":
- await self.price_command(fake_update, context)
- elif callback_data == "trades":
- await self.trades_command(fake_update, context)
- elif callback_data == "help":
- await self.help_command(fake_update, context)
-
- async def _execute_buy_order(self, query, amount: float, price: float):
- """Execute a buy order."""
- symbol = Config.DEFAULT_TRADING_SYMBOL
-
- try:
- await query.edit_message_text("⏳ Placing buy order...")
-
- # Place the order
- order = self.client.place_limit_order(symbol, 'buy', amount, price)
-
- if order:
- # Record the trade in stats
- order_id = order.get('id', 'N/A')
- self.stats.record_trade(symbol, 'buy', amount, price, order_id)
-
- success_message = f"""
- ✅ <b>Buy Order Placed Successfully!</b>
- 📊 <b>Order Details:</b>
- • Symbol: {symbol}
- • Side: BUY
- • Amount: {amount}
- • Price: ${price:,.2f}
- • Order ID: <code>{order_id}</code>
- • Total Value: ${amount * price:,.2f}
- The order has been submitted to Hyperliquid.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Buy order placed: {amount} {symbol} @ ${price}")
- else:
- await query.edit_message_text("❌ Failed to place buy order. Please try again.")
-
- except Exception as e:
- error_message = f"❌ Error placing buy order: {str(e)}"
- await query.edit_message_text(error_message)
- logger.error(f"Error placing buy order: {e}")
-
- async def _execute_sell_order(self, query, amount: float, price: float):
- """Execute a sell order."""
- symbol = Config.DEFAULT_TRADING_SYMBOL
-
- try:
- await query.edit_message_text("⏳ Placing sell order...")
-
- # Place the order
- order = self.client.place_limit_order(symbol, 'sell', amount, price)
-
- if order:
- # Record the trade in stats
- order_id = order.get('id', 'N/A')
- self.stats.record_trade(symbol, 'sell', amount, price, order_id)
-
- success_message = f"""
- ✅ <b>Sell Order Placed Successfully!</b>
- 📊 <b>Order Details:</b>
- • Symbol: {symbol}
- • Side: SELL
- • Amount: {amount}
- • Price: ${price:,.2f}
- • Order ID: <code>{order_id}</code>
- • Total Value: ${amount * price:,.2f}
- The order has been submitted to Hyperliquid.
- """
-
- await query.edit_message_text(success_message, parse_mode='HTML')
- logger.info(f"Sell order placed: {amount} {symbol} @ ${price}")
- else:
- await query.edit_message_text("❌ Failed to place sell order. Please try again.")
-
- except Exception as e:
- error_message = f"❌ Error placing sell order: {str(e)}"
- await query.edit_message_text(error_message)
- logger.error(f"Error placing sell order: {e}")
-
- async def unknown_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle unknown commands."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- await update.message.reply_text(
- "❓ Unknown command. Use /help to see available commands or tap the buttons in /start."
- )
-
- def setup_handlers(self):
- """Set up command handlers for the bot."""
- if not self.application:
- return
-
- # Command handlers
- self.application.add_handler(CommandHandler("start", self.start_command))
- self.application.add_handler(CommandHandler("help", self.help_command))
- self.application.add_handler(CommandHandler("balance", self.balance_command))
- self.application.add_handler(CommandHandler("positions", self.positions_command))
- self.application.add_handler(CommandHandler("orders", self.orders_command))
- self.application.add_handler(CommandHandler("market", self.market_command))
- self.application.add_handler(CommandHandler("price", self.price_command))
- self.application.add_handler(CommandHandler("stats", self.stats_command))
- self.application.add_handler(CommandHandler("trades", self.trades_command))
- self.application.add_handler(CommandHandler("buy", self.buy_command))
- self.application.add_handler(CommandHandler("sell", self.sell_command))
-
- # Callback query handler for inline keyboards
- self.application.add_handler(CallbackQueryHandler(self.button_callback))
-
- # Handle unknown commands
- self.application.add_handler(MessageHandler(filters.COMMAND, self.unknown_command))
-
- async def run(self):
- """Run the Telegram bot."""
- if not Config.TELEGRAM_BOT_TOKEN:
- logger.error("❌ TELEGRAM_BOT_TOKEN not configured")
- return
-
- if not Config.TELEGRAM_CHAT_ID:
- logger.error("❌ TELEGRAM_CHAT_ID not configured")
- return
-
- # Create application
- self.application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
-
- # Set up handlers
- self.setup_handlers()
-
- logger.info("🚀 Starting Telegram trading bot...")
-
- # Send startup notification
- await self.send_message(
- "🤖 <b>Manual Trading Bot Started</b>\n\n"
- f"✅ Connected to Hyperliquid {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}\n"
- f"📊 Default Symbol: {Config.DEFAULT_TRADING_SYMBOL}\n"
- f"📱 Manual trading ready!\n"
- f"⏰ Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
- "Use /start for quick actions or /help for all commands."
- )
-
- # Start the bot
- await self.application.run_polling()
- def main():
- """Main entry point for the Telegram bot."""
- try:
- # Validate configuration
- if not Config.validate():
- logger.error("❌ Configuration validation failed!")
- return
-
- if not Config.TELEGRAM_ENABLED:
- logger.error("❌ Telegram is not enabled in configuration")
- return
-
- # Create and run the bot
- bot = TelegramTradingBot()
- asyncio.run(bot.run())
-
- except KeyboardInterrupt:
- logger.info("👋 Bot stopped by user")
- except Exception as e:
- logger.error(f"❌ Unexpected error: {e}")
- if __name__ == "__main__":
- main()
|