""" Telegram 通知服务 - 通过 Bot API 发送交易信号到频道 """ import httpx from typing import Dict, Any, Optional from app.utils.logger import logger from app.config import get_settings class TelegramService: """Telegram 机器人通知服务""" def __init__(self, bot_token: str = "", channel_id: str = "", service_type: str = "default"): """ 初始化 Telegram 服务 Args: bot_token: Telegram Bot Token (从 @BotFather 获取) channel_id: 频道 ID (如 @your_channel 或 -1001234567890) service_type: 服务类型 (crypto/stock/news/default) """ settings = get_settings() self.bot_token = bot_token or getattr(settings, 'telegram_bot_token', '') self.service_type = service_type # 根据服务类型选择频道ID if channel_id: self.channel_id = channel_id else: # 根据service_type选择对应的频道 if service_type == "crypto": self.channel_id = getattr(settings, 'telegram_crypto_channel_id', '') or getattr(settings, 'telegram_channel_id', '') elif service_type == "stock": self.channel_id = getattr(settings, 'telegram_stock_channel_id', '') or getattr(settings, 'telegram_channel_id', '') else: self.channel_id = getattr(settings, 'telegram_channel_id', '') # 检查配置开关和必要参数是否都有效 config_enabled = getattr(settings, 'telegram_enabled', True) self.enabled = config_enabled and bool(self.bot_token and self.channel_id) if not config_enabled: self.api_base = "" logger.info("Telegram 通知已通过配置禁用") elif self.enabled: self.api_base = f"https://api.telegram.org/bot{self.bot_token}" logger.info(f"Telegram 通知服务初始化完成 [{service_type}],频道: {self.channel_id}") else: self.api_base = "" logger.warning(f"Telegram Bot Token 或 Channel ID 未配置 [{service_type}],通知功能已禁用") async def send_message(self, text: str, parse_mode: str = "HTML") -> bool: """ 发送文本消息 Args: text: 消息内容 (支持 HTML 或 Markdown) parse_mode: 解析模式 ("HTML" 或 "Markdown") Returns: 是否发送成功 """ if not self.enabled: logger.warning("Telegram 服务未启用,跳过发送") return False data = { "chat_id": self.channel_id, "text": text, "parse_mode": parse_mode, "disable_web_page_preview": True } return await self._send("sendMessage", data) async def send_trading_signal(self, signal: Dict[str, Any]) -> bool: """ 发送交易信号消息 Args: signal: 交易信号数据 Returns: 是否发送成功 """ if not self.enabled: logger.warning("Telegram 服务未启用,跳过发送") return False action = signal.get('action', 'hold') symbol = signal.get('symbol', 'UNKNOWN') price = signal.get('price', 0) trend = signal.get('trend', 'neutral') confidence = signal.get('confidence', 0) signal_type = signal.get('signal_type', 'swing') signal_grade = signal.get('signal_grade', 'D') indicators = signal.get('indicators', {}) llm_analysis = signal.get('llm_analysis', '') stop_loss = signal.get('stop_loss', 0) take_profit = signal.get('take_profit', 0) reasons = signal.get('reasons', []) # 信号类型 type_emoji = "📈" if signal_type == 'short_term' else "📊" type_text = "短线信号" if signal_type == 'short_term' else "波段信号" type_hint = "快进快出,建议轻仓" if signal_type == 'short_term' else "趋势跟踪,可适当持仓" # 动作 if action == 'buy': action_emoji = "🟢" action_text = "买入 / LONG" elif action == 'sell': action_emoji = "🔴" action_text = "卖出 / SHORT" else: action_emoji = "⚪" action_text = "观望 / WAIT" # 趋势 trend_map = { 'bullish': '📈 看涨', 'bearish': '📉 看跌', 'neutral': '↔️ 震荡' } trend_text = trend_map.get(trend, '未知') # 等级 grade_stars = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(signal_grade, '') # 构建精简消息 - 突出核心交易信息 type_short = "短线" if signal_type == 'short_term' else "波段" lines = [ f"{action_emoji} {symbol} {action_text}", f"", f"📊 {type_short} | {signal_grade}{grade_stars} | {confidence}% | {trend_text}", f"💰 入场: ${price:,.2f}", ] # 止损止盈(核心点位信息) if stop_loss > 0: sl_percent = ((stop_loss - price) / price) * 100 lines.append(f"🛑 止损: ${stop_loss:,.2f} ({sl_percent:+.1f}%)") if take_profit > 0: tp_percent = ((take_profit - price) / price) * 100 lines.append(f"🎯 止盈: ${take_profit:,.2f} ({tp_percent:+.1f}%)") # 触发原因(精简) if reasons: clean_reasons = [] for reason in reasons[:2]: clean = reason.replace("📊 ", "").replace("📈 ", "").replace("📉 ", "").replace("波段信号: ", "").replace("短线", "").replace("超跌反弹", "").replace("超涨回落", "") if clean and len(clean) < 20: clean_reasons.append(clean) if clean_reasons: lines.append(f"📋 {' | '.join(clean_reasons)}") # AI 分析(如果有) if llm_analysis: analysis_text = llm_analysis[:80] + "..." if len(llm_analysis) > 80 else llm_analysis lines.append(f"🤖 {analysis_text}") # 免责声明 lines.append(f"⚠️ 仅供参考") message = "\n".join(lines) return await self.send_message(message, parse_mode="HTML") async def send_trend_change(self, symbol: str, old_trend: str, new_trend: str, price: float) -> bool: """ 发送趋势变化通知 Args: symbol: 交易对 old_trend: 旧趋势 new_trend: 新趋势 price: 当前价格 Returns: 是否发送成功 """ trend_emoji = { 'bullish': '📈', 'bearish': '📉', 'neutral': '↔️' } trend_text = { 'bullish': '看涨', 'bearish': '看跌', 'neutral': '震荡' } old_emoji = trend_emoji.get(old_trend, '❓') new_emoji = trend_emoji.get(new_trend, '❓') old_text = trend_text.get(old_trend, old_trend) new_text = trend_text.get(new_trend, new_trend) message = f"""🔄 {symbol} 趋势变化 {old_text}{old_emoji} → {new_text}{new_emoji} | ${price:,.2f}""" return await self.send_message(message, parse_mode="HTML") async def send_startup_notification(self, symbols: list) -> bool: """ 发送启动通知 Args: symbols: 监控的交易对列表 Returns: 是否发送成功 """ message = f"""🚀 加密货币智能体已启动 ━━━━━━━━━━━━━━━━━━━━ 📊 监控交易对: {', '.join(symbols)} ⏰ 运行模式: 每5分钟整点执行 ━━━━━━━━━━━━━━━━━━━━ 开始监控市场信号...""" return await self.send_message(message, parse_mode="HTML") async def _send(self, method: str, data: Dict[str, Any]) -> bool: """ 发送请求到 Telegram API Args: method: API 方法名 data: 请求数据 Returns: 是否发送成功 """ try: url = f"{self.api_base}/{method}" async with httpx.AsyncClient() as client: response = await client.post( url, json=data, timeout=10.0 ) result = response.json() if result.get('ok'): logger.info(f"Telegram 消息发送成功") return True else: logger.error(f"Telegram 消息发送失败: {result.get('description', 'Unknown error')}") return False except Exception as e: logger.error(f"Telegram 消息发送异常: {e}") return False # 全局实例(延迟初始化) _telegram_service: Optional[TelegramService] = None _telegram_crypto_service: Optional[TelegramService] = None _telegram_stock_service: Optional[TelegramService] = None def get_telegram_service() -> TelegramService: """获取 Telegram 服务实例(默认)""" global _telegram_service if _telegram_service is None: _telegram_service = TelegramService() return _telegram_service def get_telegram_crypto_service() -> TelegramService: """获取加密货币 Telegram 服务实例""" global _telegram_crypto_service if _telegram_crypto_service is None: _telegram_crypto_service = TelegramService(service_type="crypto") return _telegram_crypto_service def get_telegram_stock_service() -> TelegramService: """获取股票 Telegram 服务实例""" global _telegram_stock_service if _telegram_stock_service is None: _telegram_stock_service = TelegramService(service_type="stock") return _telegram_stock_service