diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 13369fe..5dea24e 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -115,40 +115,54 @@ class CryptoAgent: async def _notify_order_filled(self, result: Dict[str, Any]): """发送挂单成交通知""" side_text = "做多" if result.get('side') == 'long' else "做空" + side_icon = "🟢" if result.get('side') == 'long' else "🔴" grade = result.get('signal_grade', 'N/A') - message = f"""✅ 挂单成交 + title = f"✅ 挂单成交 - {result.get('symbol')}" -交易对: {result.get('symbol')} -方向: {side_text} -等级: {grade} -挂单价: ${result.get('entry_price', 0):,.2f} -成交价: ${result.get('filled_price', 0):,.2f} -仓位: ${result.get('quantity', 0):,.0f} -止损: ${result.get('stop_loss', 0):,.2f} -止盈: ${result.get('take_profit', 0):,.2f}""" + content_parts = [ + f"{side_icon} **方向**: {side_text}", + f"⭐ **信号等级**: {grade}", + f"💰 **挂单价**: ${result.get('entry_price', 0):,.2f}", + f"🎯 **成交价**: ${result.get('filled_price', 0):,.2f}", + f"💵 **仓位**: ${result.get('quantity', 0):,.0f}", + ] + + if result.get('stop_loss'): + content_parts.append(f"🛑 **止损**: ${result.get('stop_loss', 0):,.2f}") + if result.get('take_profit'): + content_parts.append(f"🎯 **止盈**: ${result.get('take_profit', 0):,.2f}") + + content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_text(message) + await self.feishu.send_card(title, content, "green") if self.settings.telegram_enabled: + message = f"{title}\n\n{content}" await self.telegram.send_message(message) logger.info(f"已发送挂单成交通知: {result.get('order_id')}") async def _notify_pending_cancelled(self, result: Dict[str, Any]): """发送挂单撤销通知""" side_text = "做多" if result.get('side') == 'long' else "做空" + side_icon = "🟢" if result.get('side') == 'long' else "🔴" new_side_text = "做多" if result.get('new_side') == 'long' else "做空" - message = f"""⚠️ 挂单撤销 + title = f"⚠️ 挂单撤销 - {result.get('symbol')}" -交易对: {result.get('symbol')} -原方向: {side_text} -挂单价: ${result.get('entry_price', 0):,.2f} -原因: 收到反向{new_side_text}信号,自动撤销""" + content_parts = [ + f"{side_icon} **原方向**: {side_text}", + f"💰 **挂单价**: ${result.get('entry_price', 0):,.2f}", + f"", + f"📝 **原因**: 收到反向{new_side_text}信号,自动撤销", + ] + + content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_text(message) + await self.feishu.send_card(title, content, "orange") if self.settings.telegram_enabled: + message = f"{title}\n\n{content}" await self.telegram.send_message(message) logger.info(f"已发送挂单撤销通知: {result.get('order_id')}") @@ -187,31 +201,42 @@ class CryptoAgent: if status == 'closed_tp': emoji = "🎯" status_text = "止盈平仓" + color = "green" elif status == 'closed_sl': emoji = "🛑" status_text = "止损平仓" + color = "red" elif status == 'closed_be': emoji = "📈" status_text = "移动止损" + color = "orange" else: emoji = "📤" status_text = "手动平仓" + color = "blue" win_text = "盈利" if is_win else "亏损" + win_emoji = "✅" if is_win else "❌" side_text = "做多" if result.get('side') == 'long' else "做空" + side_icon = "🟢" if result.get('side') == 'long' else "🔴" - message = f"""{emoji} 订单{status_text} + title = f"{emoji} 订单{status_text}" -交易对: {result.get('symbol')} -方向: {side_text} -入场: ${result.get('entry_price', 0):,.2f} -出场: ${result.get('exit_price', 0):,.2f} -{win_text}: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f}) -持仓时间: {result.get('hold_duration', 'N/A')}""" + content_parts = [ + f"{side_icon} **方向**: {side_text}", + f"💰 **交易对**: {result.get('symbol')}", + f"📊 **入场**: ${result.get('entry_price', 0):,.2f}", + f"🎯 **出场**: ${result.get('exit_price', 0):,.2f}", + f"{win_emoji} **{win_text}**: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f})", + f"⏱️ **持仓时间**: {result.get('hold_duration', 'N/A')}", + ] + + content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_text(message) + await self.feishu.send_card(title, content, color) if self.settings.telegram_enabled: + message = f"{title}\n\n{content}" await self.telegram.send_message(message) logger.info(f"已发送订单平仓通知: {result.get('order_id')}") @@ -1371,25 +1396,30 @@ class CryptoAgent: """发送实盘订单创建通知""" side = signal.get('action', 'buy') side_text = "做多" if side == 'buy' else "做空" + side_icon = "🟢" if side == 'buy' else "🔴" grade = signal.get('grade', 'N/A') position_size = result.get('position_size', 'light') quantity = result.get('quantity', 0) # 这是保证金金额 position_value = quantity * 20 # 持仓价值 = 保证金 × 杠杆 - message = f"""💰 实盘订单已创建 + title = f"💰 实盘订单已创建 - {symbol}" -交易对: {symbol} -方向: {side_text} -等级: {grade} -仓位: {position_size} -持仓价值: ${position_value:,.2f} -订单 ID: {result.get('order_id', '')[:12]}... + content_parts = [ + f"{side_icon} **方向**: {side_text}", + f"⭐ **信号等级**: {grade}", + f"📊 **仓位**: {position_size}", + f"💰 **持仓价值**: ${position_value:,.2f}", + f"🆔 **订单ID**: {result.get('order_id', '')[:12]}...", + f"", + f"⚠️ **真实资金交易中**", + ] -⚠️ 真实资金交易中""" + content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_text(message) + await self.feishu.send_card(title, content, "red") if self.settings.telegram_enabled: + message = f"{title}\n\n{content}" await self.telegram.send_message(message) logger.info(f"已发送实盘订单创建通知: {result.get('order_id')}") @@ -1411,32 +1441,36 @@ class CryptoAgent: } action_text = action_map.get(action, action) - message = f"""{action_text} + title = f"{action_text} - {symbol}" -交易对: {symbol} -订单: {order_id[:8]} -原因: {reason}""" + content_parts = [ + f"📊 **订单**: {order_id[:8]}", + f"📝 **原因**: {reason}", + ] if action == 'ADJUST_SL_TP': changes = result.get('changes', []) - message += f"\n调整内容: {', '.join(changes)}" + content_parts.append(f"🔄 **调整内容**: {', '.join(changes)}") elif action in ['PARTIAL_CLOSE', 'FULL_CLOSE']: pnl_info = result.get('pnl', {}) if pnl_info: pnl = pnl_info.get('pnl', 0) pnl_percent = pnl_info.get('pnl_percent', 0) - message += f"\n实现盈亏: ${pnl:+.2f} ({pnl_percent:+.1f}%)" + content_parts.append(f"💰 **实现盈亏**: ${pnl:+.2f} ({pnl_percent:+.1f}%)") if action == 'PARTIAL_CLOSE': close_percent = decision.get('close_percent', 0) remaining = result.get('remaining_quantity', 0) - message += f"\n平仓比例: {close_percent:.0f}%" - message += f"\n剩余仓位: ${remaining:,.0f}" + content_parts.append(f"📊 **平仓比例**: {close_percent:.0f}%") + content_parts.append(f"💵 **剩余仓位**: ${remaining:,.0f}") + + content = "\n".join(content_parts) if self.settings.feishu_enabled: - await self.feishu.send_text(message) + await self.feishu.send_card(title, content, "blue") if self.settings.telegram_enabled: + message = f"{title}\n\n{content}" await self.telegram.send_message(message) async def _notify_signal_not_executed( diff --git a/backend/app/main.py b/backend/app/main.py index 2c5b703..a5d7d2d 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -50,22 +50,27 @@ async def price_monitor_loop(): # 处理挂单成交事件 if event_type == 'order_filled': side_text = "做多" if result.get('side') == 'long' else "做空" + side_icon = "🟢" if result.get('side') == 'long' else "🔴" grade = result.get('signal_grade', 'N/A') - message = f"""✅ 挂单成交 + title = f"✅ 挂单成交 - {result.get('symbol')}" + content_parts = [ + f"{side_icon} **方向**: {side_text}", + f"⭐ **信号等级**: {grade}", + f"💰 **挂单价**: ${result.get('entry_price', 0):,.2f}", + f"🎯 **成交价**: ${result.get('filled_price', 0):,.2f}", + f"💵 **仓位**: ${result.get('quantity', 0):,.0f}", + ] + if result.get('stop_loss'): + content_parts.append(f"🛑 **止损**: ${result.get('stop_loss', 0):,.2f}") + if result.get('take_profit'): + content_parts.append(f"🎯 **止盈**: ${result.get('take_profit', 0):,.2f}") -交易对: {result.get('symbol')} -方向: {side_text} -等级: {grade} -挂单价: ${result.get('entry_price', 0):,.2f} -成交价: ${result.get('filled_price', 0):,.2f} -仓位: ${result.get('quantity', 0):,.0f} -止损: ${result.get('stop_loss', 0):,.2f} -止盈: ${result.get('take_profit', 0):,.2f}""" + content = "\n".join(content_parts) # 发送通知 - await feishu.send_text(message) - await telegram.send_message(message) + await feishu.send_card(title, content, "green") + await telegram.send_message(f"{title}\n\n{content}") logger.info(f"后台监控触发挂单成交: {result.get('order_id')} | {symbol}") continue @@ -75,31 +80,38 @@ async def price_monitor_loop(): if status == 'closed_tp': emoji = "🎯" status_text = "止盈平仓" + color = "green" elif status == 'closed_sl': emoji = "🛑" status_text = "止损平仓" + color = "red" elif status == 'closed_be': emoji = "🔒" status_text = "保本止损" + color = "orange" else: emoji = "📤" status_text = "平仓" + color = "blue" win_text = "盈利" if is_win else "亏损" + win_emoji = "✅" if is_win else "❌" side_text = "做多" if result.get('side') == 'long' else "做空" + side_icon = "🟢" if result.get('side') == 'long' else "🔴" - message = f"""{emoji} 订单{status_text} - -交易对: {result.get('symbol')} -方向: {side_text} -入场: ${result.get('entry_price', 0):,.2f} -出场: ${result.get('exit_price', 0):,.2f} -{win_text}: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f}) -持仓时间: {result.get('hold_duration', 'N/A')}""" + title = f"{emoji} 订单{status_text} - {result.get('symbol')}" + content_parts = [ + f"{side_icon} **方向**: {side_text}", + f"📊 **入场**: ${result.get('entry_price', 0):,.2f}", + f"🎯 **出场**: ${result.get('exit_price', 0):,.2f}", + f"{win_emoji} **{win_text}**: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f})", + f"⏱️ **持仓时间**: {result.get('hold_duration', 'N/A')}", + ] + content = "\n".join(content_parts) # 发送通知 - await feishu.send_text(message) - await telegram.send_message(message) + await feishu.send_card(title, content, color) + await telegram.send_message(f"{title}\n\n{content}") logger.info(f"后台监控触发平仓: {result.get('order_id')} | {symbol}") except Exception as e: @@ -223,31 +235,38 @@ async def price_monitor_loop(): if status == 'closed_tp': emoji = "🎯" status_text = "止盈平仓" + color = "green" elif status == 'closed_sl': emoji = "🛑" status_text = "止损平仓" + color = "red" elif status == 'closed_be': emoji = "📈" status_text = "移动止损" + color = "orange" else: emoji = "📤" status_text = "平仓" + color = "blue" win_text = "盈利" if is_win else "亏损" + win_emoji = "✅" if is_win else "❌" side_text = "做多" if result.get('side') == 'long' else "做空" + side_icon = "🟢" if result.get('side') == 'long' else "🔴" - message = f"""{emoji} 订单{status_text} - -交易对: {result.get('symbol')} -方向: {side_text} -入场: ${result.get('entry_price', 0):,.2f} -出场: ${result.get('exit_price', 0):,.2f} -{win_text}: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f}) -持仓时间: {result.get('hold_duration', 'N/A')}""" + title = f"{emoji} 订单{status_text} - {result.get('symbol')}" + content_parts = [ + f"{side_icon} **方向**: {side_text}", + f"📊 **入场**: ${result.get('entry_price', 0):,.2f}", + f"🎯 **出场**: ${result.get('exit_price', 0):,.2f}", + f"{win_emoji} **{win_text}**: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f})", + f"⏱️ **持仓时间**: {result.get('hold_duration', 'N/A')}", + ] + content = "\n".join(content_parts) # 发送通知 - await feishu.send_text(message) - await telegram.send_message(message) + await feishu.send_card(title, content, color) + await telegram.send_message(f"{title}\n\n{content}") logger.info(f"后台监控触发平仓: {result.get('order_id')} | {symbol}") except Exception as e: diff --git a/backend/app/stock_agent/stock_agent.py b/backend/app/stock_agent/stock_agent.py index 5b50614..c32c3da 100644 --- a/backend/app/stock_agent/stock_agent.py +++ b/backend/app/stock_agent/stock_agent.py @@ -519,6 +519,8 @@ class StockAgent: logger.info(f"\n⏸️ 信号质量不高,不发送通知") return result + logger.info(f"\n📢 【最佳信号】{best_signal.get('action')} {best_signal.get('grade')}级 {best_signal.get('confidence')}%") + # 检查置信度阈值 threshold = self.settings.stock_llm_threshold * 100 if best_signal.get('confidence', 0) < threshold: @@ -530,14 +532,23 @@ class StockAgent: logger.info(f"\n⏸️ 信号冷却中,不发送通知") return result - # 发送通知 - await self._send_signal_notification(symbol, best_signal, current_price) - result['notified'] = True - result['best_signal'] = best_signal + logger.info(f"\n✅ 满足所有条件,准备发送通知...") - # 更新状态 - self.last_signals[symbol] = best_signal - self.signal_cooldown[symbol] = datetime.now() + # 发送通知 + try: + await self._send_signal_notification(symbol, best_signal, current_price) + result['notified'] = True + result['best_signal'] = best_signal + + # 更新状态 + self.last_signals[symbol] = best_signal + self.signal_cooldown[symbol] = datetime.now() + except Exception as notify_error: + logger.error(f"❌ 发送 {symbol} 通知失败: {notify_error}") + import traceback + logger.error(traceback.format_exc()) + result['notified'] = False + result['notify_error'] = str(notify_error) return result @@ -589,6 +600,8 @@ class StockAgent: ): """发送信号通知""" try: + logger.info(f"📤 准备发送 {symbol} 信号通知...") + from app.utils.signal_formatter import get_signal_formatter formatter = get_signal_formatter() @@ -597,11 +610,20 @@ class StockAgent: title = card['title'] content = card['content'] + logger.info(f" 标题: {title}") + logger.info(f" 内容长度: {len(content)} 字符") + # 根据信号方向选择颜色 color = "green" if signal.get('action') == 'buy' else "red" + logger.info(f" 颜色: {color}") + + # 检查飞书服务 + logger.info(f" 飞书服务: {type(self.feishu).__name__}") + logger.info(f" Webhook URL: {self.feishu.webhook_url[:50]}...") # 发送到飞书 await self.feishu.send_card(title, content, color) + logger.info(f" ✅ 飞书通知发送成功") # 发送到 Telegram await self.telegram.send_message(formatter.format_signal_message(signal, symbol, agent_type='stock')) @@ -616,7 +638,11 @@ class StockAgent: self.signal_db.add_signal(signal_to_save) except Exception as e: - logger.error(f"发送通知失败: {e}") + logger.error(f"❌ 发送通知失败: {e}") + import traceback + logger.error(traceback.format_exc()) + # 重新抛出异常,让上层能够捕获 + raise def _validate_data(self, data: Dict[str, pd.DataFrame]) -> bool: """验证数据完整性"""