From cd001c0c0058e2d0b58f8ea52b7e2bfdec7d71b9 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sun, 22 Mar 2026 12:34:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crypto_agent/market_signal_analyzer.py | 832 ++++++++++++++++++ .../crypto_agent/trading_decision_maker.py | 59 ++ 2 files changed, 891 insertions(+) diff --git a/backend/app/crypto_agent/market_signal_analyzer.py b/backend/app/crypto_agent/market_signal_analyzer.py index b593727..a0bfec9 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 numpy as np import pandas as pd from typing import Dict, Any, Optional, List from datetime import datetime @@ -128,6 +129,100 @@ class MarketSignalAnalyzer: - **级别背离**:小级别反转 + 大级别强劲 → 谨慎,可能是假突破 - **强反转信号**:多个小级别同时反转 + 大级别走弱 → **重点考虑反手** +## 🚨 趋势反转信号处理(新增 - 极其重要!) + +### 系统会自动检测以下反转信号: +1. **RSI 背离**:价格创新高/低但 RSI 不创新高/低(权重2) +2. **MACD 柱状图缩短**:动能衰竭信号(权重1) +3. **MACD 金叉/死叉**:趋势反转信号(权重1) +4. **量价背离**:价格上涨但成交量下降(权重1) +5. **关键K线形态**:吞没、锤子线、十字星(权重1-2) +6. **多周期趋势不一致**:小周期反转但大周期未反应(权重1) + +### 反转信号出现时的处理规则(必须遵守!) + +**🔴 当检测到反转信号(置信度 ≥ 60%)时:** + +1. **立即停止原方向新开仓** + - 如果之前做多,现在检测到看跌反转 → **严禁开新多单** + - 如果之前做空,现在检测到看涨反转 → **严禁开新空单** + +2. **评估现有持仓** + - 如果有同向持仓 → **建议立即平仓或收紧止损** + - 不要等待反弹/回调,反转可能很快发生 + +3. **考虑反手操作(仅当反转置信度 ≥ 70%时)** + - 可以考虑平掉旧仓位,同时开新方向仓位 + - 或者先平仓观望,等待反转确认后再入场 + +4. **观望等待确认** + - 如果不确定,先平仓观望 + - 等待价格明确突破关键位再入场 + +**⚠️ 常见错误(必须避免):** +- ❌ 检测到反转信号后继续原方向加仓("摊平成本") +- ❌ 认为是"假突破"而忽略反转信号 +- ❌ 等待反弹/回调到更好价格(可能等不到) + +## 🌅 趋势阶段判断(新增 - 避免趋势晚期被套) + +### 系统会自动判断趋势处于哪个阶段: +- **早期**:刚突破关键位,均线刚开始排列,动能开始释放 +- **中期**:均线排列稳定,价格沿趋势移动,量能健康 +- **晚期**:价格过度延伸,RSI极端区,量价背离,多次假突破 + +### 不同阶段的交易规则: + +**✅ 早期阶段(可积极入场):** +- 可以顺势轻仓入场 +- 设置止损后可持有更长时间 +- 目标可看更大空间(3-5%) + +**✅ 中期阶段(稳健持仓):** +- 等待回调/反弹入场 +- 顺势持仓,让利润奔跑 +- 不要被小波动洗出 + +**🔴 晚期阶段(强制谨慎):** +- **严禁追涨/追空开新仓** +- **现有盈利持仓建议逐步止盈** +- **等待明确反转信号后再决策** +- 宁可错过最后一段利润,也不要被套在高位/低位 + +### 晚期阶段的识别信号(系统自动检测): +1. EMA 间距过大(> 3%) +2. RSI 进入极端区(> 70 或 < 30) +3. 价格偏离 EMA20 > 5% +4. 量价背离(价格上涨但成交量下降) +5. 连续 4 根以上同向K线 +6. ATR 收缩(动能衰竭) + +## 📊 震荡区间交易规则(新增 - 基于明确区间交易) + +### 系统会自动计算震荡区间: +- **支撑位**:价格通道下沿 + 成交量密集区 + 布林带下轨 +- **压力位**:价格通道上沿 + 成交量密集区 + 布林带上轨 +- **区间宽度**:用于判断震荡有效性(< 5% 为有效震荡) + +### 震荡市交易规则(当系统判断为震荡市时): + +**✅ 推荐操作:** +1. **下沿挂多单**:价格接近支撑位时,挂限价多单 +2. **上沿挂空单**:价格接近压力位时,挂限价空单 +3. **目标明确**:盈利目标就是对岸边界 +4. **快进快出**:持仓时间 30分钟-2小时 + +**🔴 严禁操作:** +1. **追涨杀跌**:价格突破边界后不要追,通常会回落 +2. **期待大行情**:震荡市无大趋势,不要贪婪 +3. **持仓过久**:区间内快速进出,不要拿着不动 +4. **逆势加仓**:亏损后不要加仓"摊平成本" + +### 震荡区间突破的处理: +- 放量突破 + 多周期确认 → 可能转为趋势市,切换策略 +- 无量突破 + 快速回落 → 假突破,继续震荡策略 +- 区间收口 + 波动率下降 → 即将变盘,观望等待 + ## 🚨 铁律(违反即失败) 1. **盈亏比第一**:所有交易必须满足盈亏比 ≥ 1:1.2 - 盈亏比 = (目标盈利 - 入场价) / (入场价 - 止损价) @@ -953,6 +1048,64 @@ class MarketSignalAnalyzer: context_parts.append(f"\n## 趋势位置分析") context_parts.append(trend_position_analysis) + # ========== 新增:震荡区间检测 ========== + range_zone = self._detect_range_zone(data) + if range_zone['is_ranging']: + context_parts.append(f"\n## 🔔 震荡区间检测(重要!)") + context_parts.append(f"**状态**: 震荡市(置信度: {range_zone['confidence']}%)") + if range_zone['support_level'] and range_zone['resistance_level']: + context_parts.append(f"**支撑位**: ${range_zone['support_level']:,.2f}") + context_parts.append(f"**压力位**: ${range_zone['resistance_level']:,.2f}") + context_parts.append(f"**区间宽度**: {range_zone['range_width_pct']:.2f}%") + if range_zone['volume_profile_support']: + context_parts.append(f"**成交量密集区支撑**: ${range_zone['volume_profile_support']:,.2f}") + if range_zone['volume_profile_resistance']: + context_parts.append(f"**成交量密集区压力**: ${range_zone['volume_profile_resistance']:,.2f}") + context_parts.append(f"**分析**: {range_zone['analysis']}") + context_parts.append(f"\n**震荡市交易策略**:") + context_parts.append(f" → 下沿附近挂多单,上沿附近挂空单") + context_parts.append(f" → 目标: 对岸边界,快进快出") + context_parts.append(f" → 严禁追涨杀跌!") + + # ========== 新增:趋势反转检测 ========== + reversal_detection = self._detect_trend_reversal(data) + if reversal_detection['is_reversing']: + context_parts.append(f"\n## ⚠️ 趋势反转信号(非常重要!)") + context_parts.append(f"**检测到反转信号**!置信度: {reversal_detection['confidence']}%") + if reversal_detection['reversal_type'] == 'bullish_reversal': + context_parts.append(f"**反转类型**: 看涨反转 📈") + else: + context_parts.append(f"**反转类型**: 看跌反转 📉") + context_parts.append(f"\n**反转信号详情**:") + for sig in reversal_detection['signals'][:5]: # 最多显示5个信号 + context_parts.append(f" - [{sig['type']}] {sig['desc']} (权重: {sig['weight']})") + context_parts.append(f"\n**🚨 反转信号处理规则**:") + context_parts.append(f" → 现有同向持仓建议平仓") + context_parts.append(f" → 考虑反方向开仓(需等待确认)") + context_parts.append(f" → 或者暂时观望,等待反转确认") + context_parts.append(f" → 严禁继续原方向开新仓!") + + # ========== 新增:趋势阶段检测 ========== + trend_stage = self._detect_trend_stage(data) + if trend_stage['stage'] != 'unknown': + stage_emoji = {'early': '🌱', 'middle': '🔄', 'late': '🌅'}.get(trend_stage['stage'], '❓') + stage_name = {'early': '早期', 'middle': '中期', 'late': '晚期'}.get(trend_stage['stage'], '未知') + context_parts.append(f"\n## 趋势阶段分析") + context_parts.append(f"**当前阶段**: {stage_emoji} {stage_name}(置信度: {trend_stage['confidence']}%)") + context_parts.append(f"**分析**: {trend_stage['analysis']}") + + if trend_stage['stage'] == 'late': + context_parts.append(f"\n**⚠️ 晚期阶段警告**:") + context_parts.append(f" → 趋势可能即将反转或进入震荡") + context_parts.append(f" → 严禁追涨/追空开新仓") + context_parts.append(f" → 现有盈利持仓建议逐步止盈") + context_parts.append(f" → 等待明确反转信号后再决策") + elif trend_stage['stage'] == 'early': + context_parts.append(f"\n**✅ 早期阶段机会**:") + context_parts.append(f" → 趋势刚启动,可顺势轻仓入场") + context_parts.append(f" → 设置止损后可持有更长时间") + context_parts.append(f" → 目标可看更大空间") + return "\n".join(context_parts) async def _get_news_context(self, symbol: str) -> str: @@ -1740,3 +1893,682 @@ class MarketSignalAnalyzer: lines.append(f"**布林带开口**: 宽度 {bb_width:.1f}%,趋势延续") return "\n".join(lines) + + def _detect_range_zone(self, data: Dict[str, pd.DataFrame]) -> Dict[str, Any]: + """ + 检测震荡区间 - 计算明确的支撑位和压力位 + + 使用多种方法综合判断: + 1. 价格通道(最近N根K线的最高/最低价) + 2. 成交量密集区(Volume Profile) + 3. 布林带 + 4. EMA支撑/压力 + """ + result = { + 'is_ranging': False, + 'support_level': None, + 'resistance_level': None, + 'range_width_pct': None, + 'confidence': 0, + 'volume_profile_support': None, + 'volume_profile_resistance': None, + 'analysis': '' + } + + try: + df_30m = data.get('30m') + df_1h = data.get('1h') + df_15m = data.get('15m') + + if df_30m is None or len(df_30m) < 48: # 需要至少48根K线(24小时) + return result + + current_price = float(df_30m['close'].iloc[-1]) + + # ========== 1. 价格通道分析 ========== + # 使用最近24-48根K线(12-24小时)计算价格通道 + lookback_periods = [24, 36, 48] + price_channels = [] + + for period in lookback_periods: + if len(df_30m) >= period: + period_data = df_30m.iloc[-period:] + high = period_data['high'].max() + low = period_data['low'].min() + price_channels.append({'high': high, 'low': low, 'width': high - low}) + + # 选择波动最稳定的通道(宽度变化最小的) + if price_channels: + avg_width = sum(pc['width'] for pc in price_channels) / len(price_channels) + selected_channel = min(price_channels, + key=lambda pc: abs(pc['width'] - avg_width)) + support = selected_channel['low'] + resistance = selected_channel['high'] + range_width = resistance - support + range_width_pct = (range_width / current_price) * 100 + + # 震荡区间判断标准 + # 1. 区间宽度 < 5%(震荡市) + # 2. 价格在区间中位数附近 + # 3. EMA 纠缠 + is_narrow_range = range_width_pct < 5.0 + price_in_middle = (current_price - support) / range_width > 0.3 and \ + (current_price - support) / range_width < 0.7 + + # EMA 纠缠检查 + ema5 = df_30m['ma5'].iloc[-1] if 'ma5' in df_30m.columns else None + ema10 = df_30m['ma10'].iloc[-1] if 'ma10' in df_30m.columns else None + ema20 = df_30m['ma20'].iloc[-1] if 'ma20' in df_30m.columns else None + ema_entangled = False + if all([ema5, ema10, ema20]): + ema_spread = (max(ema5, ema10, ema20) - min(ema5, ema10, ema20)) / current_price * 100 + ema_entangled = ema_spread < 1.0 # EMA 排列差距 < 1% + + # ========== 2. 成交量密集区分析 ========== + volume_profile_support = None + volume_profile_resistance = None + + if len(df_30m) >= 48: + # 找出成交量最大的价格区间 + df_30m_copy = df_30m.iloc[-48:].copy() + df_30m_copy['avg_price'] = (df_30m_copy['high'] + df_30m_copy['low'] + df_30m_copy['close']) / 3 + df_30m_copy['volume_weight'] = df_30m_copy['volume'] * df_30m_copy['avg_price'] + + # 按价格分层,找出高成交量区域 + price_bins = pd.cut(df_30m_copy['avg_price'], bins=10) + volume_by_price = df_30m_copy.groupby(price_bins, observed=True)['volume'].sum() + + if len(volume_by_price) > 0: + # 高成交量区作为支撑/压力 + max_vol_bin = volume_by_price.idxmax() + if max_vol_bin is not None: + vp_level = (max_vol_bin.left + max_vol_bin.right) / 2 + if vp_level < current_price * 0.98: + volume_profile_support = float(vp_level) + elif vp_level > current_price * 1.02: + volume_profile_resistance = float(vp_level) + + # ========== 3. 布林带支撑/压力 ========== + bb_support = None + bb_resistance = None + if 'bb_lower' in df_30m.columns and 'bb_upper' in df_30m.columns: + bb_support = float(df_30m['bb_lower'].iloc[-1]) + bb_resistance = float(df_30m['bb_upper'].iloc[-1]) + + # ========== 4. 关键价格点综合 ========== + # 综合多个指标得出最可靠的支撑/压力位 + support_candidates = [] + resistance_candidates = [] + + if support: + support_candidates.append(support) + if volume_profile_support: + support_candidates.append(volume_profile_support) + if bb_support: + support_candidates.append(bb_support) + + if resistance: + resistance_candidates.append(resistance) + if volume_profile_resistance: + resistance_candidates.append(volume_profile_resistance) + if bb_resistance: + resistance_candidates.append(bb_resistance) + + # 取中位数作为最终的支撑/压力位 + final_support = np.median(support_candidates) if support_candidates else None + final_resistance = np.median(resistance_candidates) if resistance_candidates else None + + # ========== 5. 计算置信度 ========== + confidence = 0 + reasons = [] + + if is_narrow_range: + confidence += 30 + reasons.append(f"区间窄({range_width_pct:.1f}%)") + + if price_in_middle: + confidence += 20 + reasons.append("价格在中部") + + if ema_entangled: + confidence += 25 + reasons.append("EMA纠缠") + + # 成交量分布检查 - 如果成交量在区间两端较小,说明是有效震荡 + if len(df_30m) >= 24: + recent_vol = df_30m['volume'].iloc[-12:].mean() + older_vol = df_30m['volume'].iloc[-24:-12].mean() + if abs(recent_vol - older_vol) / older_vol < 0.3: + confidence += 15 + reasons.append("成交量平稳") + + # 价格反弹次数 - 检查在支撑/压力位附近是否有多次反弹 + if final_support and final_resistance: + bounce_count = 0 + for i in range(-24, 0): + if i >= -len(df_30m): + row = df_30m.iloc[i] + # 检查是否在支撑位附近反弹 + if abs(row['low'] - final_support) / final_support < 0.005 and row['close'] > row['open']: + bounce_count += 1 + # 检查是否在压力位附近回落 + if abs(row['high'] - final_resistance) / final_resistance < 0.005 and row['close'] < row['open']: + bounce_count += 1 + + if bounce_count >= 2: + confidence += 10 + reasons.append(f"边界反弹{bounce_count}次") + + result.update({ + 'is_ranging': confidence >= 60, + 'support_level': float(final_support) if final_support else None, + 'resistance_level': float(final_resistance) if final_resistance else None, + 'range_width_pct': range_width_pct, + 'confidence': confidence, + 'volume_profile_support': volume_profile_support, + 'volume_profile_resistance': volume_profile_resistance, + 'analysis': f"震荡判断: {confidence}% ({', '.join(reasons) if reasons else '无'})" + }) + + except Exception as e: + logger.warning(f"震荡区间检测失败: {e}") + import traceback + logger.debug(traceback.format_exc()) + + return result + + def _detect_trend_reversal(self, data: Dict[str, pd.DataFrame]) -> Dict[str, Any]: + """ + 检测趋势反转信号 + + 综合多个指标判断趋势是否可能反转: + 1. RSI 背离(价格创新高但RSI不创新高 / 价格创新低但RSI不创新低) + 2. MACD 柱状图缩短/背离 + 3. 量价背离(价格上涨但成交量下降) + 4. 关键K线形态(吞没、锤子线、十字星等) + 5. 多周期趋势不一致 + """ + result = { + 'is_reversing': False, + 'reversal_type': None, # 'bullish_reversal' or 'bearish_reversal' + 'confidence': 0, + 'signals': [], + 'analysis': '' + } + + try: + df_15m = data.get('15m') + df_30m = data.get('30m') + df_1h = data.get('1h') + + if df_15m is None or len(df_15m) < 30: + return result + + reversal_signals = [] + bullish_signals = 0 + bearish_signals = 0 + + # ========== 1. RSI 背离检测 ========== + if 'rsi' in df_15m.columns and len(df_15m) >= 20: + recent_5 = df_15m.iloc[-5:] + prev_5 = df_15m.iloc[-15:-10] + + # 顶背离(看跌反转) + recent_high = recent_5['high'].max() + recent_rsi_at_high = recent_5.loc[recent_5['high'] == recent_high, 'rsi'].values[0] + prev_high = prev_5['high'].max() + prev_rsi_at_high = prev_5.loc[prev_5['high'] == prev_high, 'rsi'].values[0] + + if recent_high > prev_high and recent_rsi_at_high < prev_rsi_at_high: + bearish_signals += 2 + reversal_signals.append({ + 'type': 'rsi_divergence', + 'direction': 'bearish', + 'weight': 2, + 'desc': 'RSI顶背离:价格创新高但RSI不创新高' + }) + + # 底背离(看涨反转) + recent_low = recent_5['low'].min() + recent_rsi_at_low = recent_5.loc[recent_5['low'] == recent_low, 'rsi'].values[0] + prev_low = prev_5['low'].min() + prev_rsi_at_low = prev_5.loc[prev_5['low'] == prev_low, 'rsi'].values[0] + + if recent_low < prev_low and recent_rsi_at_low > prev_rsi_at_low: + bullish_signals += 2 + reversal_signals.append({ + 'type': 'rsi_divergence', + 'direction': 'bullish', + 'weight': 2, + 'desc': 'RSI底背离:价格创新低但RSI不创新低' + }) + + # ========== 2. MACD 柱状图分析 ========== + if 'macd_hist' in df_15m.columns and len(df_15m) >= 10: + hist_recent = df_15m['macd_hist'].iloc[-3:].values + hist_prev = df_15m['macd_hist'].iloc[-6:-3].values + + # MACD 柱状图缩短 = 动能衰竭 + if all(h > 0 for h in hist_prev): # 之前是正向 + if hist_recent[2] < hist_recent[1] < hist_recent[0]: # 持续缩短 + bearish_signals += 1 + reversal_signals.append({ + 'type': 'macd_histogram', + 'direction': 'bearish', + 'weight': 1, + 'desc': 'MACD柱状图持续缩短:上涨动能衰竭' + }) + + if all(h < 0 for h in hist_prev): # 之前是负向 + if hist_recent[2] > hist_recent[1] > hist_recent[0]: # 持续收窄 + bullish_signals += 1 + reversal_signals.append({ + 'type': 'macd_histogram', + 'direction': 'bullish', + 'weight': 1, + 'desc': 'MACD柱状图持续收窄:下跌动能衰竭' + }) + + # MACD 金叉/死叉 + if len(df_15m) >= 2: + macd_current = df_15m['macd'].iloc[-1] + signal_current = df_15m['macd_signal'].iloc[-1] + macd_prev = df_15m['macd'].iloc[-2] + signal_prev = df_15m['macd_signal'].iloc[-2] + + # 金叉 + if macd_prev <= signal_prev and macd_current > signal_current: + bullish_signals += 1 + reversal_signals.append({ + 'type': 'macd_cross', + 'direction': 'bullish', + 'weight': 1, + 'desc': 'MACD金叉' + }) + + # 死叉 + if macd_prev >= signal_prev and macd_current < signal_current: + bearish_signals += 1 + reversal_signals.append({ + 'type': 'macd_cross', + 'direction': 'bearish', + 'weight': 1, + 'desc': 'MACD死叉' + }) + + # ========== 3. 量价背离检测 ========== + if 'volume' in df_15m.columns and len(df_15m) >= 10: + recent_price_change = (df_15m['close'].iloc[-1] - df_15m['close'].iloc[-5]) / df_15m['close'].iloc[-5] + recent_volume = df_15m['volume'].iloc[-5:].mean() + older_volume = df_15m['volume'].iloc[-10:-5].mean() + + # 价格上涨但成交量下降(量价背离) + if recent_price_change > 0.01 and recent_volume < older_volume * 0.8: + bearish_signals += 1 + reversal_signals.append({ + 'type': 'volume_divergence', + 'direction': 'bearish', + 'weight': 1, + 'desc': '量价背离:价格上涨但成交量萎缩' + }) + + # 价格下跌但成交量下降(可能见底) + if recent_price_change < -0.01 and recent_volume < older_volume * 0.7: + bullish_signals += 1 + reversal_signals.append({ + 'type': 'volume_divergence', + 'direction': 'bullish', + 'weight': 1, + 'desc': '下跌缩量:抛压枯竭,可能见底' + }) + + # ========== 4. 关键K线形态检测 ========== + if len(df_15m) >= 3: + latest = df_15m.iloc[-1] + prev = df_15m.iloc[-2] + + # 吞没形态 + open_latest, close_latest = latest['open'], latest['close'] + open_prev, close_prev = prev['open'], prev['close'] + + # 阳包阴(看涨) + if (close_latest > open_latest and # 当前是阳线 + close_prev < open_prev and # 前一个是阴线 + open_latest <= close_prev and # 开盘价低于前一个收盘价 + close_latest >= open_prev): # 收盘价高于前一个开盘价 + bullish_signals += 2 + reversal_signals.append({ + 'type': 'candlestick', + 'direction': 'bullish', + 'weight': 2, + 'desc': '阳包阴吞没形态(强反转信号)' + }) + + # 阴包阳(看跌) + if (close_latest < open_latest and # 当前是阴线 + close_prev > open_prev and # 前一个是阳线 + open_latest >= close_prev and # 开盘价高于前一个收盘价 + close_latest <= open_prev): # 收盘价低于前一个开盘价 + bearish_signals += 2 + reversal_signals.append({ + 'type': 'candlestick', + 'direction': 'bearish', + 'weight': 2, + 'desc': '阴包阳吞没形态(强反转信号)' + }) + + # 锤子线/倒锤子 + body_size = abs(close_latest - open_latest) + upper_shadow = df_15m['high'].iloc[-1] - max(open_latest, close_latest) + lower_shadow = min(open_latest, close_latest) - df_15m['low'].iloc[-1] + + # 锤子线(看涨) + if lower_shadow >= body_size * 2 and upper_shadow < body_size * 0.5: + bullish_signals += 1 + reversal_signals.append({ + 'type': 'candlestick', + 'direction': 'bullish', + 'weight': 1, + 'desc': '锤子线(底部反转信号)' + }) + + # 倒锤子(看跌) + if upper_shadow >= body_size * 2 and lower_shadow < body_size * 0.5: + bearish_signals += 1 + reversal_signals.append({ + 'type': 'candlestick', + 'direction': 'bearish', + 'weight': 1, + 'desc': '倒锤子线(顶部反转信号)' + }) + + # ========== 5. 多周期趋势不一致 ========== + trend_15m = self._get_trend_direction(df_15m) + trend_30m = self._get_trend_direction(df_30m) + trend_1h = self._get_trend_direction(df_1h) + + # 小周期反转但大周期未反应 + if trend_15m and trend_1h and trend_15m != trend_1h: + if trend_15m == 'bull' and trend_1h == 'bear': + bullish_signals += 1 + reversal_signals.append({ + 'type': 'timeframe_divergence', + 'direction': 'bullish', + 'weight': 1, + 'desc': '15分钟转多但1小时仍看空(潜在反转)' + }) + elif trend_15m == 'bear' and trend_1h == 'bull': + bearish_signals += 1 + reversal_signals.append({ + 'type': 'timeframe_divergence', + 'direction': 'bearish', + 'weight': 1, + 'desc': '15分钟转空但1小时仍看多(潜在反转)' + }) + + # ========== 计算反转信号强度 ========== + total_signals = len(reversal_signals) + if total_signals >= 3: + # 至少3个反转信号才认为可能反转 + if bullish_signals >= bearish_signals + 2: + result['is_reversing'] = True + result['reversal_type'] = 'bullish_reversal' + result['confidence'] = min(90, bullish_signals * 15) + result['signals'] = [s for s in reversal_signals if s['direction'] == 'bullish'] + elif bearish_signals >= bullish_signals + 2: + result['is_reversing'] = True + result['reversal_type'] = 'bearish_reversal' + result['confidence'] = min(90, bearish_signals * 15) + result['signals'] = [s for s in reversal_signals if s['direction'] == 'bearish'] + + if reversal_signals: + result['analysis'] = f"检测到 {len(reversal_signals)} 个反转信号" + else: + result['analysis'] = "无反转信号" + + except Exception as e: + logger.warning(f"趋势反转检测失败: {e}") + import traceback + logger.debug(traceback.format_exc()) + + return result + + def _get_trend_direction(self, df: pd.DataFrame) -> str: + """获取趋势方向:bull/bear/neutral""" + if df is None or len(df) < 10: + return 'neutral' + + try: + # 使用EMA判断 + ma5 = df['ma5'].iloc[-1] if 'ma5' in df.columns else None + ma10 = df['ma10'].iloc[-1] if 'ma10' in df.columns else None + ma20 = df['ma20'].iloc[-1] if 'ma20' in df.columns else None + + if ma5 and ma10 and ma20: + if ma5 > ma10 > ma20: + return 'bull' + elif ma5 < ma10 < ma20: + return 'bear' + + # 使用MACD判断 + if 'macd' in df.columns and 'macd_signal' in df.columns: + macd = df['macd'].iloc[-1] + signal = df['macd_signal'].iloc[-1] + + if macd > signal and macd > 0: + return 'bull' + elif macd < signal and macd < 0: + return 'bear' + + except Exception as e: + logger.debug(f"趋势方向判断失败: {e}") + + return 'neutral' + + def _detect_trend_stage(self, data: Dict[str, pd.DataFrame]) -> Dict[str, Any]: + """ + 检测趋势阶段:早期/中期/晚期 + + 判断标准: + 1. 早期:刚突破关键位,均线刚开始排列,动能开始释放 + 2. 中期:均线排列稳定,价格沿趋势移动,量能健康 + 3. 晚期:价格过度延伸,RSI极端区,量价背离,多次假突破 + """ + result = { + 'stage': 'unknown', # 'early', 'middle', 'late' + 'confidence': 0, + 'signals': [], + 'analysis': '' + } + + try: + df_30m = data.get('30m') + df_1h = data.get('1h') + + if df_30m is None or len(df_30m) < 30: + return result + + current_price = float(df_30m['close'].iloc[-1]) + + stage_signals = [] + early_score = 0 + middle_score = 0 + late_score = 0 + + # ========== 1. EMA 排列状态 ========== + ema5 = df_30m['ma5'].iloc[-1] if 'ma5' in df_30m.columns else None + ema10 = df_30m['ma10'].iloc[-1] if 'ma10' in df_30m.columns else None + ema20 = df_30m['ma20'].iloc[-1] if 'ma20' in df_30m.columns else None + ema50 = df_30m['ma50'].iloc[-1] if 'ma50' in df_30m.columns else None + + if all([ema5, ema10, ema20, ema50]): + # 检查EMA排列是否形成 + if ema5 > ema10 > ema20 > ema50: + # 多头排列 + # 检查排列刚刚形成(早期)还是已经稳定(中期/晚期) + ema5_cross_ma20 = False + if len(df_30m) >= 10: + # 检查最近10根内是否发生过金叉 + for i in range(-10, 0): + if df_30m['ma5'].iloc[i] > df_30m['ma20'].iloc[i]: + if i > -10 and df_30m['ma5'].iloc[i-1] <= df_30m['ma20'].iloc[i-1]: + ema5_cross_ma20 = True + break + + if ema5_cross_ma20: + early_score += 30 + stage_signals.append("EMA排列刚形成(早期)") + else: + # 检查EMA间距 + ema_spread = (ema5 - ema20) / ema20 * 100 + if ema_spread > 3: + late_score += 20 + stage_signals.append(f"EMA间距过大({ema_spread:.1f}%) - 可能过度延伸") + else: + middle_score += 20 + stage_signals.append("EMA排列稳定(中期)") + + elif ema5 < ema10 < ema20 < ema50: + # 空头排列 + ema5_cross_ma20 = False + if len(df_30m) >= 10: + for i in range(-10, 0): + if df_30m['ma5'].iloc[i] < df_30m['ma20'].iloc[i]: + if i > -10 and df_30m['ma5'].iloc[i-1] >= df_30m['ma20'].iloc[i-1]: + ema5_cross_ma20 = True + break + + if ema5_cross_ma20: + early_score += 30 + stage_signals.append("EMA排列刚形成(早期)") + else: + ema_spread = (ema20 - ema5) / ema20 * 100 + if ema_spread > 3: + late_score += 20 + stage_signals.append(f"EMA间距过大({ema_spread:.1f}%) - 可能过度延伸") + else: + middle_score += 20 + stage_signals.append("EMA排列稳定(中期)") + + # ========== 2. RSI 状态 ========== + if 'rsi' in df_30m.columns: + rsi_current = df_30m['rsi'].iloc[-1] + rsi_prev = df_30m['rsi'].iloc[-5:-1].values + + # RSI极端区 - 晚期信号 + if rsi_current > 70: + late_score += 25 + stage_signals.append(f"RSI超买({rsi_current:.0f}) - 趋势晚期") + elif rsi_current < 30: + late_score += 25 + stage_signals.append(f"RSI超卖({rsi_current:.0f}) - 趋势晚期") + elif 50 <= rsi_current <= 65: + middle_score += 15 + stage_signals.append(f"RSI健康({rsi_current:.0f}) - 趋势中期") + elif 40 <= rsi_current <= 60: + early_score += 10 + stage_signals.append(f"RSI中性({rsi_current:.0f}) - 可能早期") + + # RSI趋势检查 + if len(rsi_prev) >= 3: + rsi_trend = "up" if rsi_current > rsi_prev[-1] else "down" if rsi_current < rsi_prev[-1] else "flat" + if rsi_trend == "flat": + late_score += 10 + stage_signals.append("RSI走平 - 动能衰竭") + + # ========== 3. 价格偏离度 ========== + if ema20: + deviation = abs(current_price - ema20) / ema20 * 100 + + if deviation > 5: + late_score += 30 + stage_signals.append(f"价格偏离EMA20 {deviation:.1f}% - 过度延伸") + elif deviation > 3: + late_score += 15 + stage_signals.append(f"价格偏离EMA20 {deviation:.1f}% - 警戒区域") + elif deviation < 1: + if early_score < middle_score: # 只在不是明显早期时加分 + middle_score += 10 + stage_signals.append("价格贴近EMA20 - 趋势稳固") + + # ========== 4. 量价关系 ========== + if 'volume' in df_30m.columns and len(df_30m) >= 10: + recent_vol = df_30m['volume'].iloc[-5:].mean() + older_vol = df_30m['volume'].iloc[-10:-5].mean() + vol_change = (recent_vol - older_vol) / older_vol * 100 + + price_change_5 = (df_30m['close'].iloc[-1] - df_30m['close'].iloc[-5]) / df_30m['close'].iloc[-5] * 100 + + # 价格上涨但成交量下降(量价背离)- 晚期信号 + if price_change_5 > 1 and vol_change < -20: + late_score += 20 + stage_signals.append(f"量价背离(涨{price_change_5:.1f}%量减{vol_change:.0f}%)- 可能见顶") + elif price_change_5 < -1 and vol_change < -20: + late_score += 20 + stage_signals.append(f"量价背离(跌{price_change_5:.1f}%量减{vol_change:.0f}%)- 可能见底") + elif price_change_5 > 1 and vol_change > 30: + early_score += 15 + stage_signals.append(f"放量上涨(涨{price_change_5:.1f}%量增{vol_change:.0f}%)- 可能早期") + elif price_change_5 < -1 and vol_change > 30: + early_score += 15 + stage_signals.append(f"放量下跌(跌{price_change_5:.1f}%量增{vol_change:.0f}%)- 可能早期") + + # ========== 5. 波动率状态 ========== + if 'atr' in df_30m.columns and len(df_30m) >= 20: + recent_atr = df_30m['atr'].iloc[-5:].mean() + older_atr = df_30m['atr'].iloc[-15:-5].mean() + atr_change = (recent_atr - older_atr) / older_atr * 100 if older_atr > 0 else 0 + + if atr_change > 30: + early_score += 10 + stage_signals.append(f"ATR扩张({atr_change:.0f}%) - 趋势启动") + elif atr_change < -30: + late_score += 10 + stage_signals.append(f"ATR收缩({atr_change:.0f}%) - 动能衰竭") + + # ========== 6. 连续同向K线数量 ========== + if len(df_30m) >= 5: + recent_closes = df_30m['close'].iloc[-5:].values + consecutive_up = sum(1 for i in range(1, len(recent_closes)) if recent_closes[i] > recent_closes[i-1]) + consecutive_down = sum(1 for i in range(1, len(recent_closes)) if recent_closes[i] < recent_closes[i-1]) + + if consecutive_up >= 4: + late_score += 15 + stage_signals.append(f"连续{consecutive_up}根阳线 - 可能过度") + elif consecutive_down >= 4: + late_score += 15 + stage_signals.append(f"连续{consecutive_down}根阴线 - 可能过度") + + # ========== 综合判断趋势阶段 ========== + scores = { + 'early': early_score, + 'middle': middle_score, + 'late': late_score + } + max_score = max(scores.values()) + + if max_score < 20: + result['stage'] = 'unknown' + result['analysis'] = "趋势阶段不明确" + elif max_score == late_score and late_score >= 40: + result['stage'] = 'late' + result['confidence'] = min(95, late_score) + result['signals'] = stage_signals + result['analysis'] = f"⚠️ 趋势晚期({late_score}分)- " + "; ".join(stage_signals[:3]) + elif max_score == early_score and early_score >= 30: + result['stage'] = 'early' + result['confidence'] = min(90, early_score) + result['signals'] = stage_signals + result['analysis'] = f"趋势早期({early_score}分)- " + "; ".join(stage_signals[:3]) + else: + result['stage'] = 'middle' + result['confidence'] = min(85, middle_score) + result['signals'] = stage_signals + result['analysis'] = f"趋势中期({middle_score}分)- " + "; ".join(stage_signals[:3]) + + except Exception as e: + logger.warning(f"趋势阶段检测失败: {e}") + import traceback + logger.debug(traceback.format_exc()) + + return result diff --git a/backend/app/crypto_agent/trading_decision_maker.py b/backend/app/crypto_agent/trading_decision_maker.py index 950876a..25ebab0 100644 --- a/backend/app/crypto_agent/trading_decision_maker.py +++ b/backend/app/crypto_agent/trading_decision_maker.py @@ -23,6 +23,65 @@ class TradingDecisionMaker: ## 🎯 核心理念 **日内交易:快进快出 + 盈亏比第一 + 严控风险** +## 🚨 反转信号处理(最高优先级!) + +### 系统会检测以下反转信号: +1. **RSI 背离**:价格创新高/低但 RSI 不创新高/低(权重2) +2. **MACD 柱状图缩短**:动能衰竭信号(权重1) +3. **MACD 金叉/死叉**:趋势反转信号(权重1) +4. **量价背离**:价格上涨但成交量下降(权重1) +5. **关键K线形态**:吞没、锤子线、十字星(权重1-2) +6. **多周期趋势不一致**:小周期反转但大周期未反应(权重1) + +### 🔴 当检测到反转信号时(必须遵守!): + +**1. 如果有同方向持仓 → 强制平仓** + - 检测到看跌反转 + 有做多持仓 → **CLOSE(立即平仓)** + - 检测到看涨反转 + 有做空持仓 → **CLOSE(立即平仓)** + - 不要等待反弹/回调,反转可能很快发生 + +**2. 严禁继续原方向开新仓** + - 检测到反转信号后 → **停止原方向任何新操作** + - 之前做多,现在检测到看跌反转 → **严禁开新多单** + - 之前做空,现在检测到看涨反转 → **严禁开新空单** + +**3. 可以考虑反手操作(仅当反转置信度 ≥ 70%)** + - FLIP_POSITION:平掉旧仓位 + 开立新方向仓位 + - 或者先平仓观望,等待反转确认后再入场 + +**4. 如果不确定 → 先平仓观望** + - 宁可错过机会,也不要被套在反向位置 + +## 🌅 趋势阶段处理(避免晚期被套) + +### 系统会判断趋势处于哪个阶段: +- **早期**:刚突破关键位,均线刚开始排列,动能开始释放 +- **中期**:均线排列稳定,价格沿趋势移动,量能健康 +- **晚期**:价格过度延伸,RSI极端区,量价背离 + +### 不同阶段的处理规则: + +**✅ 早期阶段(可积极入场):** +- 可以顺势轻仓入场 +- 设置止损后可持有更长时间 +- 目标可看更大空间(3-5%) + +**✅ 中期阶段(稳健持仓):** +- 等待回调/反弹入场 +- 顺势持仓,让利润奔跑 +- 不要被小波动洗出 + +**🔴 晚期阶段(强制谨慎!):** +- **严禁追涨/追空开新仓** +- **现有盈利持仓建议逐步止盈** +- **等待明确反转信号后再决策** +- 宁可错过最后一段利润,也不要被套 + +### ⚠️ 铁律:趋势晚期 + 检测到反转信号 = 立即平仓 +- 晚期阶段本身风险就大 +- 如果再检测到反转信号 → 必须立即平仓 +- 不要幻想"最后一段利润" + ### 🚨 盈亏比铁律(违反即拒绝) **所有交易必须满足盈亏比 ≥ 1:1.5,回调入场≥ 1:1.8**