From e408d1c9e487067b89b35aadaa50a80a68c698d8 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Fri, 6 Feb 2026 09:30:15 +0800 Subject: [PATCH] update --- backend/app/crypto_agent/crypto_agent.py | 4 +- backend/app/crypto_agent/signal_analyzer.py | 176 +++++++++++++++++++- backend/app/services/feishu_service.py | 36 +++- 3 files changed, 202 insertions(+), 14 deletions(-) diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 9138ed2..d8ec15e 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -170,8 +170,10 @@ class CryptoAgent: # 输出信号详情 action_icon = {'buy': '🟢 买入', 'sell': '🔴 卖出', 'hold': '⏸️ 观望'}.get(signal['action'], '❓') grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(signal.get('signal_grade', 'D'), '') + signal_type = signal.get('signal_type', 'swing') + type_text = '📈短线' if signal_type == 'short_term' else '📊波段' - logger.info(f" 信号: {action_icon} | 置信度: {signal['confidence']}% | 等级: {signal.get('signal_grade', 'D')} {grade_icon}") + logger.info(f" 信号: {action_icon} | 类型: {type_text} | 置信度: {signal['confidence']}% | 等级: {signal.get('signal_grade', 'D')} {grade_icon}") # 输出触发原因 if signal.get('reasons'): diff --git a/backend/app/crypto_agent/signal_analyzer.py b/backend/app/crypto_agent/signal_analyzer.py index 30e648c..19183b0 100644 --- a/backend/app/crypto_agent/signal_analyzer.py +++ b/backend/app/crypto_agent/signal_analyzer.py @@ -370,6 +370,149 @@ class SignalAnalyzer: 'volume_weight': weight } + # ==================== 短线信号分析 ==================== + + def _analyze_short_term_signal(self, m5_data: pd.DataFrame, m15_data: pd.DataFrame, + trend_direction: str) -> Dict[str, Any]: + """ + 分析短线交易信号(超跌反弹/超涨回落) + + 短线信号特点: + - 不依赖大趋势方向,主要看短周期超买超卖 + - 快进快出,持仓时间短 + - 置信度上限较低,建议轻仓 + + Args: + m5_data: 5分钟K线数据 + m15_data: 15分钟K线数据 + trend_direction: 大趋势方向(用于顺势加分) + + Returns: + { + 'action': 'buy' | 'sell' | 'hold', + 'confidence': 0-100, + 'reasons': [...], + 'signal_type': 'short_term' + } + """ + if len(m5_data) < 5 or len(m15_data) < 5: + return {'action': 'hold', 'confidence': 0, 'reasons': [], 'signal_type': 'short_term'} + + m5_latest = m5_data.iloc[-1] + m5_prev = m5_data.iloc[-2] + m15_latest = m15_data.iloc[-1] + m15_prev = m15_data.iloc[-2] + + buy_score = 0 + sell_score = 0 + buy_reasons = [] + sell_reasons = [] + + # === 15M RSI 超卖/超买 === + m15_rsi = m15_latest.get('rsi', 50) + if pd.notna(m15_rsi): + if m15_rsi < 25: + buy_reasons.append(f"15M RSI极度超卖({m15_rsi:.1f})") + buy_score += 3 + elif m15_rsi < 35: + buy_reasons.append(f"15M RSI超卖({m15_rsi:.1f})") + buy_score += 2 + elif m15_rsi > 75: + sell_reasons.append(f"15M RSI极度超买({m15_rsi:.1f})") + sell_score += 3 + elif m15_rsi > 65: + sell_reasons.append(f"15M RSI超买({m15_rsi:.1f})") + sell_score += 2 + + # === 5M RSI 反转确认 === + m5_rsi = m5_latest.get('rsi', 50) + m5_prev_rsi = m5_prev.get('rsi', 50) + if pd.notna(m5_rsi) and pd.notna(m5_prev_rsi): + # 超卖后回升 + if m5_rsi < 40 and m5_rsi > m5_prev_rsi and m5_prev_rsi < 35: + buy_reasons.append(f"5M RSI反转回升({m5_prev_rsi:.1f}→{m5_rsi:.1f})") + buy_score += 2 + # 超买后回落 + if m5_rsi > 60 and m5_rsi < m5_prev_rsi and m5_prev_rsi > 65: + sell_reasons.append(f"5M RSI反转回落({m5_prev_rsi:.1f}→{m5_rsi:.1f})") + sell_score += 2 + + # === 布林带触轨 === + if 'bb_lower' in m15_latest and pd.notna(m15_latest['bb_lower']): + if m15_latest['close'] <= m15_latest['bb_lower']: + buy_reasons.append("15M触及布林下轨") + buy_score += 2 + elif m15_latest['close'] >= m15_latest['bb_upper']: + sell_reasons.append("15M触及布林上轨") + sell_score += 2 + + # === KDJ 超卖/超买 === + m15_k = m15_latest.get('k', 50) + m15_d = m15_latest.get('d', 50) + if pd.notna(m15_k) and pd.notna(m15_d): + if m15_k < 20 and m15_d < 20: + buy_reasons.append(f"15M KDJ超卖区(K={m15_k:.1f})") + buy_score += 1.5 + elif m15_k > 80 and m15_d > 80: + sell_reasons.append(f"15M KDJ超买区(K={m15_k:.1f})") + sell_score += 1.5 + + # === 5M K线反转形态 === + if m5_latest['close'] > m5_latest['open'] and m5_prev['close'] < m5_prev['open']: + # 阴转阳 + if m5_rsi < 40: + buy_reasons.append("5M阴转阳反转") + buy_score += 1.5 + elif m5_latest['close'] < m5_latest['open'] and m5_prev['close'] > m5_prev['open']: + # 阳转阴 + if m5_rsi > 60: + sell_reasons.append("5M阳转阴反转") + sell_score += 1.5 + + # === 5M MACD 金叉/死叉 === + m5_macd = m5_latest.get('macd', 0) + m5_macd_signal = m5_latest.get('macd_signal', 0) + m5_prev_macd = m5_prev.get('macd', 0) + m5_prev_macd_signal = m5_prev.get('macd_signal', 0) + if pd.notna(m5_macd) and pd.notna(m5_prev_macd): + if m5_prev_macd <= m5_prev_macd_signal and m5_macd > m5_macd_signal: + buy_reasons.append("5M MACD金叉") + buy_score += 1.5 + elif m5_prev_macd >= m5_prev_macd_signal and m5_macd < m5_macd_signal: + sell_reasons.append("5M MACD死叉") + sell_score += 1.5 + + # === 顺势加分 === + if trend_direction == 'bullish' and buy_score > 0: + buy_score += 1 + buy_reasons.append("顺大势做多") + elif trend_direction == 'bearish' and sell_score > 0: + sell_score += 1 + sell_reasons.append("顺大势做空") + + # === 决策 === + action = 'hold' + confidence = 0 + reasons = [] + + # 短线信号阈值较低,但置信度上限也低 + if buy_score >= 4 and buy_score > sell_score: + action = 'buy' + confidence = min(35 + buy_score * 6, 65) # 短线最高65% + reasons = buy_reasons + ["📈 短线超跌反弹"] + elif sell_score >= 4 and sell_score > buy_score: + action = 'sell' + confidence = min(35 + sell_score * 6, 65) + reasons = sell_reasons + ["📉 短线超涨回落"] + + return { + 'action': action, + 'confidence': confidence, + 'reasons': reasons, + 'signal_type': 'short_term', + 'scores': {'buy': buy_score, 'sell': sell_score} + } + # ==================== 5M 精确入场 ==================== def _analyze_5m_entry(self, m5_data: pd.DataFrame, action: str) -> Dict[str, Any]: @@ -819,29 +962,30 @@ class SignalAnalyzer: sell_signals.append(f"触及阻力位({levels['nearest_resistance']:.2f})") signal_weights['sell'] += 1.5 - # ==================== 5. 根据趋势和阶段决定动作 ==================== + # ==================== 5. 根据趋势和阶段决定动作(中长线信号)==================== action = 'hold' confidence = 0 reasons = [] signal_grade = 'D' + signal_type = 'swing' # 默认波段信号 # 波段交易核心逻辑:在回调中寻找入场机会 if trend_direction == 'bullish': if trend_phase == 'correction' and signal_weights['buy'] >= 3: action = 'buy' confidence = min(40 + signal_weights['buy'] * 10, 95) - reasons = buy_signals + [f"上涨趋势回调({trend_strength})"] + reasons = buy_signals + [f"📊 波段信号: 上涨趋势回调({trend_strength})"] signal_grade = 'A' if confidence >= 80 else ('B' if confidence >= 60 else 'C') elif trend_phase == 'impulse' and signal_weights['buy'] >= 4: action = 'buy' confidence = min(30 + signal_weights['buy'] * 8, 80) - reasons = buy_signals + ["主升浪追多"] + reasons = buy_signals + ["📊 波段信号: 主升浪追多"] signal_grade = 'B' if confidence >= 60 else 'C' elif trend_phase == 'oversold' and signal_weights['buy'] >= 3: # 极度超卖时允许抄底,但降低置信度并提示风险 action = 'buy' confidence = min(30 + signal_weights['buy'] * 8, 70) # 最高70% - reasons = buy_signals + ["⚠️ 极度超卖抄底(高风险)", "建议轻仓试探"] + reasons = buy_signals + ["📊 波段信号: 极度超卖抄底(高风险)", "建议轻仓试探"] signal_grade = 'C' # 最高C级 elif trend_phase == 'overbought': reasons = ['极度超买,不宜追多'] @@ -850,23 +994,35 @@ class SignalAnalyzer: if trend_phase == 'correction' and signal_weights['sell'] >= 3: action = 'sell' confidence = min(40 + signal_weights['sell'] * 10, 95) - reasons = sell_signals + [f"下跌趋势反弹({trend_strength})"] + reasons = sell_signals + [f"📊 波段信号: 下跌趋势反弹({trend_strength})"] signal_grade = 'A' if confidence >= 80 else ('B' if confidence >= 60 else 'C') elif trend_phase == 'impulse' and signal_weights['sell'] >= 4: action = 'sell' confidence = min(30 + signal_weights['sell'] * 8, 80) - reasons = sell_signals + ["主跌浪追空"] + reasons = sell_signals + ["📊 波段信号: 主跌浪追空"] signal_grade = 'B' if confidence >= 60 else 'C' elif trend_phase == 'overbought' and signal_weights['sell'] >= 3: # 极度超买时允许做空,但降低置信度并提示风险 action = 'sell' confidence = min(30 + signal_weights['sell'] * 8, 70) # 最高70% - reasons = sell_signals + ["⚠️ 极度超买摸顶(高风险)", "建议轻仓试探"] + reasons = sell_signals + ["📊 波段信号: 极度超买摸顶(高风险)", "建议轻仓试探"] signal_grade = 'C' # 最高C级 elif trend_phase == 'oversold': reasons = ['极度超卖,不宜追空'] - else: # neutral + # ==================== 5.5 短线信号检测(中长线无信号时)==================== + # 如果中长线没有触发信号,检查短线超跌反弹/超涨回落机会 + if action == 'hold': + short_term = self._analyze_short_term_signal(m5_data, m15_data, trend_direction) + if short_term['action'] != 'hold' and short_term['confidence'] >= 50: + action = short_term['action'] + confidence = short_term['confidence'] + reasons = short_term['reasons'] + signal_type = 'short_term' + signal_grade = 'C' # 短线信号最高C级 + + # 如果还是没有信号 + if action == 'hold' and trend_direction == 'neutral': reasons = ['趋势不明确,观望'] # ==================== 6. 5M 精确入场确认 ==================== @@ -895,12 +1051,14 @@ class SignalAnalyzer: # 记录详细日志(简化版,详细日志在 crypto_agent 中输出) if action != 'hold': - logger.debug(f"信号详情: {action} {confidence}% {signal_grade} | 买权重={signal_weights['buy']:.1f} 卖权重={signal_weights['sell']:.1f}") + type_text = "短线" if signal_type == 'short_term' else "波段" + logger.debug(f"信号详情: [{type_text}] {action} {confidence}% {signal_grade} | 买权重={signal_weights['buy']:.1f} 卖权重={signal_weights['sell']:.1f}") return { 'action': action, 'confidence': confidence, 'signal_grade': signal_grade, + 'signal_type': signal_type, # 'swing' 波段 | 'short_term' 短线 'reasons': reasons, 'indicators': indicators, 'trend_info': { diff --git a/backend/app/services/feishu_service.py b/backend/app/services/feishu_service.py index e4a578b..2ba8aab 100644 --- a/backend/app/services/feishu_service.py +++ b/backend/app/services/feishu_service.py @@ -107,6 +107,8 @@ class FeishuService: - price: 当前价格 - trend: 趋势方向 - confidence: 信号强度 (0-100) + - signal_type: 'swing' | 'short_term' + - signal_grade: 'A' | 'B' | 'C' | 'D' - indicators: 技术指标数据 - llm_analysis: LLM 分析结果(可选) - stop_loss: 建议止损价 @@ -124,18 +126,25 @@ class FeishuService: 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_text = "📈 短线信号" if signal_type == 'short_term' else "📊 波段信号" + type_hint = "(快进快出,建议轻仓)" if signal_type == 'short_term' else "(趋势跟踪,可适当持仓)" # 确定标题和颜色 if action == 'buy': - title = f"🟢 买入信号 - {symbol}" + title = f"🟢 买入信号 - {symbol} [{type_text}]" color = "green" action_text = "做多" elif action == 'sell': - title = f"🔴 卖出信号 - {symbol}" + title = f"🔴 卖出信号 - {symbol} [{type_text}]" color = "red" action_text = "做空" else: @@ -150,16 +159,35 @@ class FeishuService: 'neutral': '震荡 ↔️' }.get(trend, '未知') + # 等级图标 + grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(signal_grade, '') + # 构建内容 content_parts = [ + f"**信号类型**: {type_text} {type_hint}", + f"**信号等级**: {signal_grade} {grade_icon}", f"**当前价格**: ${price:,.2f}", f"**趋势方向**: {trend_text}", - f"**信号强度**: {confidence}%", + f"**置信度**: {confidence}%", + ] + + # 添加触发原因 + if reasons: + content_parts.extend([ + "", + "---", + "", + "**触发原因**:", + ]) + for reason in reasons[:5]: # 最多显示5个原因 + content_parts.append(f"• {reason}") + + content_parts.extend([ "", "---", "", "**技术指标**:" - ] + ]) # 添加技术指标 if indicators: