diff --git a/backend/app/config.py b/backend/app/config.py index 5e9bf65..91b5467 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -97,6 +97,7 @@ class Settings(BaseSettings): feishu_crypto_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/8a1dcf69-6753-41e2-a393-edc4f7822db0" # 加密货币通知 feishu_stock_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/408ab727-0dcd-4c7a-bde7-4aad38cbf807" # 股票通知 feishu_news_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/c7fd0db7-d295-451c-b943-130278a6cd9d" # 新闻智能体通知 + feishu_paper_trading_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/3f5642e7-420b-45f7-8f88-fff92bb98c69" # 模拟交易通知(交易信号+决策+执行) feishu_error_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/ba6952c9-3b0c-4bc1-8a43-ceaacb27b043" # 系统异常通知 feishu_enabled: bool = True # 是否启用飞书通知 diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 42efb9c..c1bc8ed 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -9,7 +9,7 @@ import pandas as pd from app.utils.logger import logger from app.config import get_settings from app.services.bitget_service import bitget_service -from app.services.feishu_service import get_feishu_service +from app.services.feishu_service import get_feishu_service, get_feishu_paper_trading_service from app.services.telegram_service import get_telegram_service from app.services.dingtalk_service import get_dingtalk_service from app.services.paper_trading_service import get_paper_trading_service @@ -40,7 +40,8 @@ class CryptoAgent: CryptoAgent._initialized = True self.settings = get_settings() self.exchange = bitget_service # 交易所服务 - self.feishu = get_feishu_service() + self.feishu = get_feishu_service() # 通用飞书服务(crypto等) + self.feishu_paper = get_feishu_paper_trading_service() # 模拟交易专用飞书服务 self.telegram = get_telegram_service() self.dingtalk = get_dingtalk_service() # 添加钉钉服务 @@ -138,7 +139,7 @@ class CryptoAgent: content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_card(title, content, "green") + await self.feishu_paper.send_card(title, content, "green") if self.settings.telegram_enabled: message = f"{title}\n\n{content}" await self.telegram.send_message(message) @@ -164,7 +165,7 @@ class CryptoAgent: content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_card(title, content, "orange") + await self.feishu_paper.send_card(title, content, "orange") if self.settings.telegram_enabled: message = f"{title}\n\n{content}" await self.telegram.send_message(message) @@ -193,7 +194,7 @@ class CryptoAgent: content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_card(title, content, "green") + await self.feishu_paper.send_card(title, content, "green") if self.settings.telegram_enabled: message = f"{title}\n\n{content}" await self.telegram.send_message(message) @@ -246,7 +247,7 @@ class CryptoAgent: content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_card(title, content, color) + await self.feishu_paper.send_card(title, content, color) if self.settings.telegram_enabled: message = f"{title}\n\n{content}" await self.telegram.send_message(message) @@ -787,8 +788,7 @@ class CryptoAgent: if decision_type == 'HOLD': reasoning = paper_decision.get('reasoning', '观望') logger.info(f"\n📊 交易决策: {reasoning}") - # 有信号但决策为 HOLD,发送未执行通知 - await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True) + # HOLD决策的理由已在交易决策通知中说明,无需单独通知 else: logger.info(f"\n📊 【执行交易】") @@ -810,14 +810,11 @@ class CryptoAgent: paper_executed = True else: logger.error(f" ❌ 订单对象无效: 缺少order_id属性") - reason = "订单对象无效: 缺少order_id" - await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True, reason=reason) + # 订单创建失败,理由已在日志中记录,无需单独通知 else: - # 有信号但订单创建失败,发送未执行通知 + # 订单创建失败,理由已在日志中记录,无需单独通知 reason = result.get('message', '订单创建失败') if result else '订单创建失败' logger.warning(f" ⚠️ 交易未执行: {reason}") - await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True, reason=reason) - logger.warning(f" ⚠️ 交易未执行: {reason}") elif decision_type == 'CLOSE': await self._execute_close(paper_decision, paper_trading=True) # CLOSE 操作也发送执行通知 @@ -845,8 +842,7 @@ class CryptoAgent: if decision_type == 'HOLD': reasoning = real_decision.get('reasoning', '观望') logger.info(f"\n💰 实盘交易: {reasoning}") - # 有信号但决策为 HOLD,发送未执行通知 - await self._notify_signal_not_executed(market_signal, real_decision, current_price, is_paper=False) + # HOLD决策的理由已在交易决策通知中说明,无需单独通知 else: logger.info(f"\n💰 【执行实盘交易】") @@ -859,10 +855,9 @@ class CryptoAgent: await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) real_executed = True else: - # 有信号但订单创建失败,发送未执行通知 + # 订单创建失败,理由已在日志中记录,无需单独通知 reason = result.get('message', '订单创建失败') if result else '订单创建失败' - await self._notify_signal_not_executed(market_signal, real_decision, current_price, is_paper=False, reason=reason) - logger.warning(f" ⚠️ 实盘交易未执行,已发送通知") + logger.warning(f" ⚠️ 实盘交易未执行: {reason}") elif decision_type == 'CLOSE': await self._execute_close(real_decision, paper_trading=False) # CLOSE 操作也发送执行通知 @@ -1008,12 +1003,12 @@ class CryptoAgent: sl_display = "N/A" tp_display = "N/A" - # 构建卡片标题和颜色 + # 构建卡片标题和颜色 - 添加 [信号] 前缀区分 if sig_action == 'buy': - title = f"🟢 {symbol} {timeframe_text}做多信号" + title = f"[信号] 🟢 {symbol} {timeframe_text}做多信号" color = "green" else: - title = f"🔴 {symbol} {timeframe_text}做空信号" + title = f"[信号] 🔴 {symbol} {timeframe_text}做空信号" color = "red" # 构建卡片内容 @@ -1046,7 +1041,7 @@ class CryptoAgent: content = "\n".join(content_parts) - # 根据配置发送通知 + # 根据配置发送通知 - [信号] 发送到 crypto webhook if self.settings.feishu_enabled: await self.feishu.send_card(title, content, color) if self.settings.telegram_enabled: @@ -1098,8 +1093,8 @@ class CryptoAgent: } color = color_map.get(decision_type, 'blue') - # 构建标题 - title = f"{account_type} {symbol} 交易决策: {decision_text}" + # 构建标题 - 添加 [决策] 前缀区分 + title = f"[决策] {account_type} {symbol} 交易决策: {decision_text}" # 获取最佳信号用于显示 best_signal = self._get_best_signal_from_market(market_signal) @@ -1181,7 +1176,7 @@ class CryptoAgent: content = "\n".join(content_parts) - # 发送通知 + # 发送通知 - [决策] 发送到 crypto webhook if self.settings.feishu_enabled: await self.feishu.send_card(title, content, color) if self.settings.telegram_enabled: @@ -1253,26 +1248,26 @@ class CryptoAgent: position_map = {'heavy': '🔥 重仓', 'medium': '📊 中仓', 'light': '🌱 轻仓'} position_display = position_map.get(position_size, '🌱 轻仓') - # 构建卡片标题和颜色 - 考虑入场方式 + # 构建卡片标题和颜色 - 考虑入场方式,添加 [执行] 前缀区分 # 挂单时标题显示"挂单",现价单时显示"开仓"/"平仓"等 if decision_type == 'OPEN': decision_title = '挂单' if entry_type == 'limit' else '开仓' - title = f"{account_type} {symbol} {decision_title}" + title = f"[执行] {account_type} {symbol} {decision_title}" color = "green" elif decision_type == 'CLOSE': decision_title = '挂单' if entry_type == 'limit' else '平仓' - title = f"{account_type} {symbol} {decision_title}" + title = f"[执行] {account_type} {symbol} {decision_title}" color = "orange" elif decision_type == 'ADD': decision_title = '挂单' if entry_type == 'limit' else '加仓' - title = f"{account_type} {symbol} {decision_title}" + title = f"[执行] {account_type} {symbol} {decision_title}" color = "green" elif decision_type == 'REDUCE': decision_title = '挂单' if entry_type == 'limit' else '减仓' - title = f"{account_type} {symbol} {decision_title}" + title = f"[执行] {account_type} {symbol} {decision_title}" color = "orange" else: - title = f"{account_type} {symbol} 交易执行" + title = f"[执行] {account_type} {symbol} 交易执行" color = "blue" # 构建卡片内容 @@ -1315,9 +1310,9 @@ class CryptoAgent: content = "\n".join(content_parts) - # 根据配置发送通知 + # 根据配置发送通知 - 所有订单执行都发送到 paper trading webhook if self.settings.feishu_enabled: - await self.feishu.send_card(title, content, color) + await self.feishu_paper.send_card(title, content, color) if self.settings.telegram_enabled: # Telegram 使用文本格式 message = f"{title}\n\n{content}" @@ -1806,7 +1801,7 @@ class CryptoAgent: content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_card(title, content, "red") + await self.feishu_paper.send_card(title, content, "red") if self.settings.telegram_enabled: message = f"{title}\n\n{content}" await self.telegram.send_message(message) @@ -1859,7 +1854,7 @@ class CryptoAgent: content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_card(title, content, "blue") + await self.feishu_paper.send_card(title, content, "blue") if self.settings.telegram_enabled: message = f"{title}\n\n{content}" await self.telegram.send_message(message) diff --git a/backend/app/services/feishu_service.py b/backend/app/services/feishu_service.py index ddbe026..efb6970 100644 --- a/backend/app/services/feishu_service.py +++ b/backend/app/services/feishu_service.py @@ -18,7 +18,7 @@ class FeishuService: Args: webhook_url: 飞书机器人 Webhook URL(如果为空,则根据 service_type 从配置读取) - service_type: 服务类型 ("crypto" 或 "stock") + service_type: 服务类型 ("crypto", "stock", "news", "paper_trading", "error") """ settings = get_settings() @@ -33,6 +33,10 @@ class FeishuService: self.webhook_url = getattr(settings, 'feishu_stock_webhook_url', '') elif service_type == "news": self.webhook_url = getattr(settings, 'feishu_news_webhook_url', '') + elif service_type == "paper_trading": + self.webhook_url = getattr(settings, 'feishu_paper_trading_webhook_url', '') + elif service_type == "error": + self.webhook_url = getattr(settings, 'feishu_error_webhook_url', '') else: # 兼容旧配置 self.webhook_url = getattr(settings, 'feishu_webhook_url', '') @@ -285,10 +289,11 @@ class FeishuService: -# 全局实例(延迟初始化)- 分别用于加密货币、股票和新闻 +# 全局实例(延迟初始化)- 分别用于加密货币、股票、新闻和模拟交易 _feishu_crypto_service: Optional[FeishuService] = None _feishu_stock_service: Optional[FeishuService] = None _feishu_news_service: Optional[FeishuService] = None +_feishu_paper_trading_service: Optional[FeishuService] = None def get_feishu_service() -> FeishuService: @@ -318,3 +323,11 @@ def get_feishu_news_service() -> FeishuService: if _feishu_news_service is None: _feishu_news_service = FeishuService(service_type="news") return _feishu_news_service + + +def get_feishu_paper_trading_service() -> FeishuService: + """获取模拟交易飞书服务实例""" + global _feishu_paper_trading_service + if _feishu_paper_trading_service is None: + _feishu_paper_trading_service = FeishuService(service_type="paper_trading") + return _feishu_paper_trading_service