diff --git a/src/main.py b/src/main.py index 58fb62d..ba14d64 100644 --- a/src/main.py +++ b/src/main.py @@ -29,7 +29,7 @@ from src.db import init_db, log_trade from src.logging.decision_logger import DecisionLogger from src.logging_config import setup_logging from src.markets.schedule import MarketInfo, get_next_market_open, get_open_markets -from src.notifications.telegram_client import TelegramClient +from src.notifications.telegram_client import TelegramClient, TelegramCommandHandler logger = logging.getLogger(__name__) @@ -575,6 +575,40 @@ async def run(settings: Settings) -> None: enabled=settings.TELEGRAM_ENABLED, ) + # Initialize Telegram command handler + command_handler = TelegramCommandHandler(telegram) + + # Register basic commands + async def handle_start() -> None: + """Handle /start command.""" + message = ( + "🤖 The Ouroboros Trading Bot\n\n" + "AI-powered global stock trading agent with real-time notifications.\n\n" + "Available commands:\n" + "/help - Show this help message\n" + "/status - Current trading status\n" + "/positions - View holdings\n" + "/stop - Pause trading\n" + "/resume - Resume trading" + ) + await telegram.send_message(message) + + async def handle_help() -> None: + """Handle /help command.""" + message = ( + "📖 Available Commands\n\n" + "/start - Welcome message\n" + "/help - Show available commands\n" + "/status - Trading status (mode, markets, P&L)\n" + "/positions - Current holdings\n" + "/stop - Pause trading\n" + "/resume - Resume trading" + ) + await telegram.send_message(message) + + command_handler.register_command("start", handle_start) + command_handler.register_command("help", handle_help) + # Initialize volatility hunter volatility_analyzer = VolatilityAnalyzer(min_volume_surge=2.0, min_price_change=1.0) market_scanner = MarketScanner( @@ -621,6 +655,12 @@ async def run(settings: Settings) -> None: except Exception as exc: logger.warning("System startup notification failed: %s", exc) + # Start command handler + try: + await command_handler.start_polling() + except Exception as exc: + logger.warning("Failed to start command handler: %s", exc) + try: # Branch based on trading mode if settings.TRADE_MODE == "daily": @@ -831,6 +871,7 @@ async def run(settings: Settings) -> None: pass # Normal — timeout means it's time for next cycle finally: # Clean up resources + await command_handler.stop_polling() await broker.close() await telegram.close() db_conn.close() diff --git a/tests/test_telegram_commands.py b/tests/test_telegram_commands.py index c271b49..91332dd 100644 --- a/tests/test_telegram_commands.py +++ b/tests/test_telegram_commands.py @@ -253,6 +253,103 @@ class TestUpdateHandling: await handler._handle_update(update) +class TestBasicCommands: + """Test basic command implementations.""" + + @pytest.mark.asyncio + async def test_start_command_content(self) -> None: + """Start command contains welcome message and command list.""" + client = TelegramClient(bot_token="123:abc", chat_id="456", enabled=True) + handler = TelegramCommandHandler(client) + + mock_resp = AsyncMock() + mock_resp.status = 200 + mock_resp.__aenter__ = AsyncMock(return_value=mock_resp) + mock_resp.__aexit__ = AsyncMock(return_value=False) + + async def mock_start() -> None: + """Mock /start handler.""" + message = ( + "🤖 The Ouroboros Trading Bot\n\n" + "AI-powered global stock trading agent with real-time notifications.\n\n" + "Available commands:\n" + "/help - Show this help message\n" + "/status - Current trading status\n" + "/positions - View holdings\n" + "/stop - Pause trading\n" + "/resume - Resume trading" + ) + await client.send_message(message) + + handler.register_command("start", mock_start) + + with patch("aiohttp.ClientSession.post", return_value=mock_resp) as mock_post: + update = { + "update_id": 1, + "message": { + "chat": {"id": 456}, + "text": "/start", + }, + } + + await handler._handle_update(update) + + # Verify message was sent + assert mock_post.call_count == 1 + payload = mock_post.call_args.kwargs["json"] + assert "Ouroboros Trading Bot" in payload["text"] + assert "/help" in payload["text"] + assert "/status" in payload["text"] + + @pytest.mark.asyncio + async def test_help_command_content(self) -> None: + """Help command lists all available commands.""" + client = TelegramClient(bot_token="123:abc", chat_id="456", enabled=True) + handler = TelegramCommandHandler(client) + + mock_resp = AsyncMock() + mock_resp.status = 200 + mock_resp.__aenter__ = AsyncMock(return_value=mock_resp) + mock_resp.__aexit__ = AsyncMock(return_value=False) + + async def mock_help() -> None: + """Mock /help handler.""" + message = ( + "📖 Available Commands\n\n" + "/start - Welcome message\n" + "/help - Show available commands\n" + "/status - Trading status (mode, markets, P&L)\n" + "/positions - Current holdings\n" + "/stop - Pause trading\n" + "/resume - Resume trading" + ) + await client.send_message(message) + + handler.register_command("help", mock_help) + + with patch("aiohttp.ClientSession.post", return_value=mock_resp) as mock_post: + update = { + "update_id": 1, + "message": { + "chat": {"id": 456}, + "text": "/help", + }, + } + + await handler._handle_update(update) + + # Verify message was sent + assert mock_post.call_count == 1 + payload = mock_post.call_args.kwargs["json"] + assert "Available Commands" in payload["text"] + assert "/start" in payload["text"] + assert "/help" in payload["text"] + assert "/status" in payload["text"] + assert "/positions" in payload["text"] + assert "/stop" in payload["text"] + assert "/resume" in payload["text"] + + class TestGetUpdates: """Test getUpdates API interaction."""