From efc0abf5cbac6e999b0a26b581efbb0b8ed6afb1 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Tue, 24 Feb 2026 01:50:56 +0800 Subject: [PATCH] 1 --- backend/app/crypto_agent/crypto_agent.py | 87 ++++++++++++++++++- .../crypto_agent/market_signal_analyzer.py | 49 +++++++++++ 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 7a01222..5e0f5c4 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -675,7 +675,10 @@ class CryptoAgent: decision_type = paper_decision.get('decision', 'HOLD') if decision_type == 'HOLD': - logger.info(f"\n📊 模拟交易: {paper_decision.get('reasoning', '观望')}") + 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) else: logger.info(f"\n📊 【执行模拟交易】") @@ -689,7 +692,10 @@ class CryptoAgent: await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) paper_executed = True else: - logger.warning(f" ⚠️ 模拟交易未执行,跳过通知") + # 有信号但订单创建失败,发送未执行通知 + reason = result.get('message', '订单创建失败') if result else '订单创建失败' + await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True, reason=reason) + logger.warning(f" ⚠️ 模拟交易未执行,已发送通知") elif decision_type == 'CLOSE': await self._execute_close(paper_decision, paper_trading=True) paper_executed = True @@ -703,7 +709,10 @@ class CryptoAgent: decision_type = real_decision.get('decision', 'HOLD') if decision_type == 'HOLD': - logger.info(f"\n💰 实盘交易: {real_decision.get('reasoning', '观望')}") + 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) else: logger.info(f"\n💰 【执行实盘交易】") @@ -716,7 +725,10 @@ class CryptoAgent: await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) real_executed = True else: - logger.warning(f" ⚠️ 实盘交易未执行,跳过通知") + # 有信号但订单创建失败,发送未执行通知 + 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" ⚠️ 实盘交易未执行,已发送通知") elif decision_type == 'CLOSE': await self._execute_close(real_decision, paper_trading=False) real_executed = True @@ -1402,6 +1414,73 @@ class CryptoAgent: if self.settings.telegram_enabled: await self.telegram.send_message(message) + async def _notify_signal_not_executed( + self, + market_signal: Dict[str, Any], + decision: Dict[str, Any], + current_price: float, + is_paper: bool = True, + reason: str = "" + ): + """发送有信号但未执行交易的通知""" + try: + symbol = market_signal.get('symbol') + account_type = "📊 模拟" if is_paper else "💰 实盘" + + # 获取最佳信号 + best_signal = self._get_best_signal_from_market(market_signal) + if not best_signal: + return + + confidence = best_signal.get('confidence', 0) + entry_type = best_signal.get('entry_type', 'market') + entry_zone = best_signal.get('entry_zone', current_price) + + # 决策信息 + decision_type = decision.get('decision', 'HOLD') + decision_reasoning = decision.get('reasoning', reason) + + # 方向图标 + action = best_signal.get('action', 'wait') + if action == 'buy': + action_icon = '🟢' + action_text = '做多' + elif action == 'sell': + action_icon = '🔴' + action_text = '做空' + else: + action_icon = '➖' + action_text = '观望' + + # 构建标题 + title = f"{account_type} {symbol} 信号未执行" + + # 构建内容 + content_parts = [ + f"{action_icon} **信号**: {action_text} | 📈 信心度: **{confidence}%**", + f"", + f"**入场方式**: {entry_type}", + f"**建议入场价**: ${entry_zone:,.2f}" if isinstance(entry_zone, (int, float)) else f"**建议入场价**: {entry_zone}", + f"**当前价格**: ${current_price:,.2f}", + f"", + f"⚠️ **未执行原因**:", + f"{decision_reasoning}", + ] + + content = "\n".join(content_parts) + + # 发送通知 + if self.settings.feishu_enabled: + 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" 📤 已发送信号未执行通知: {decision_type} - {decision_reasoning[:50]}") + + except Exception as e: + logger.warning(f"发送信号未执行通知失败: {e}") + async def analyze_once(self, symbol: str) -> Dict[str, Any]: """单次分析(用于测试或手动触发)""" data = self.binance.get_multi_timeframe_data(symbol) diff --git a/backend/app/crypto_agent/market_signal_analyzer.py b/backend/app/crypto_agent/market_signal_analyzer.py index ca5232a..7df447f 100644 --- a/backend/app/crypto_agent/market_signal_analyzer.py +++ b/backend/app/crypto_agent/market_signal_analyzer.py @@ -13,6 +13,7 @@ """ import json import re +import pandas as pd from typing import Dict, Any, Optional, List from datetime import datetime from app.utils.logger import logger @@ -329,6 +330,12 @@ class MarketSignalAnalyzer: else: context_parts.append("量价状态: 平量 ➖") + # 波动率分析 + volatility_analysis = self._analyze_volatility(data) + if volatility_analysis: + context_parts.append(f"\n## 波动率分析") + context_parts.append(volatility_analysis) + return "\n".join(context_parts) async def _get_news_context(self, symbol: str) -> str: @@ -544,3 +551,45 @@ class MarketSignalAnalyzer: 'timestamp': datetime.now().isoformat(), 'error': '信号分析失败' } + + def _analyze_volatility(self, data: Dict[str, pd.DataFrame]) -> str: + """分析波动率变化""" + df = data.get('1h') + if df is None or len(df) < 24 or 'atr' not in df.columns: + return "" + + lines = [] + + # ATR 变化趋势 + recent_atr = df['atr'].iloc[-6:].mean() # 最近 6 根 + older_atr = df['atr'].iloc[-12:-6].mean() # 之前 6 根 + + if pd.isna(recent_atr) or pd.isna(older_atr) or older_atr == 0: + return "" + + atr_change = (recent_atr - older_atr) / older_atr * 100 + + current_atr = float(df['atr'].iloc[-1]) + current_price = float(df['close'].iloc[-1]) + atr_percent = current_atr / current_price * 100 + + lines.append(f"当前 ATR: ${current_atr:.2f} ({atr_percent:.2f}%)") + + if atr_change > 20: + lines.append(f"**波动率扩张**: ATR 上升 {atr_change:.0f}%,趋势可能启动") + elif atr_change < -20: + lines.append(f"**波动率收缩**: ATR 下降 {abs(atr_change):.0f}%,可能即将突破") + else: + lines.append(f"波动率稳定: ATR 变化 {atr_change:+.0f}%") + + # 布林带宽度 + if 'bb_upper' in df.columns and 'bb_lower' in df.columns: + bb_width = (float(df['bb_upper'].iloc[-1]) - float(df['bb_lower'].iloc[-1])) / current_price * 100 + bb_width_prev = (float(df['bb_upper'].iloc[-6]) - float(df['bb_lower'].iloc[-6])) / float(df['close'].iloc[-6]) * 100 + + if bb_width < bb_width_prev * 0.8: + lines.append(f"**布林带收口**: 宽度 {bb_width:.1f}%,变盘信号") + elif bb_width > bb_width_prev * 1.2: + lines.append(f"**布林带开口**: 宽度 {bb_width:.1f}%,趋势延续") + + return "\n".join(lines)