diff --git a/backend/app/crypto_agent/market_signal_analyzer.py b/backend/app/crypto_agent/market_signal_analyzer.py index 978b68f..98301a2 100644 --- a/backend/app/crypto_agent/market_signal_analyzer.py +++ b/backend/app/crypto_agent/market_signal_analyzer.py @@ -418,51 +418,99 @@ class MarketSignalAnalyzer: - **突破交易**:多周期同时突破关键位 + 放量,信号最强 - **回调交易**:30m 趋势向上,15m 回调到 EMA20,5m 反弹确认 -## 八、入场方式(稳健版 - 防止持续止损) +## 八、入场方式(基于形态的智能选择) -### 核心原则:挂单优先,市价慎用 +### 核心原则:根据市场形态选择入场方式 -**🎯 入场方式优先级**: -1. **limit 挂单(首选)**:等待回调/反弹到关键位 -2. **观望(次选)**:价格不合适时耐心等待 -3. **market 市价(慎用)**:仅在极少数情况下使用 +**🎯 形态识别优先**: +1. **突破/跌破形态** → market 市价入场(抓住机会) +2. **箱体震荡形态** → limit 挂单入场(耐心等待) +3. **不明确形态** → limit 挂单或观望 -### limit(挂单等待入场)- 默认首选 -使用场景(90%的情况用 limit): -- ✅ **所有回调/反弹入场**(策略2的主要方式) -- ✅ **信号强度中等**(B/C 级) -- ✅ **市场横盘整理**,价格在区间内波动 -- ✅ **等待回调到支撑位**(EMA20、前期低点) -- ✅ **等待反弹到压力位**(EMA20、前期高点) -- ✅ **希望获得更优成交价格** -- ✅ **当前价格距离关键位 > 0.5%** +### 第一步:识别当前市场形态 -**挂单是稳健交易的基础,可以避免追涨杀跌!** +#### 1. 突破形态(Breakout)- 市价入场 +**识别标准**(必须同时满足): +- ✅ 价格**放量突破**关键阻力位/支撑位(量比 > 1.5) +- ✅ 突破后**没有立即回落**(站稳突破位上方/下方) +- ✅ 15m RSI 在 50-65(多)或 35-50(空)- 有延续空间 +- ✅ 多周期**共振突破**(5m + 15m + 30m 同时突破) -### market(现价立即入场)- 极少使用 -**⚠️ market 入场的严格限制**: +**确认信号**: +- K线实体完全突破关键位(影线不算) +- 突破后至少2根K线站稳 +- 成交量明显放大(量比 > 1.5) +- 无明显的假突破迹象(如快速回落) -仅在以下**极少数**情况使用 market: -- ✅ 强共振信号(A级,confidence ≥ 90) -- ✅ 放量突破**后回踩确认**,不是突破时追 -- ✅ 多周期同时确认,回调明确 -- ✅ 15m RSI 45-60(多)或 40-55(空)- 安全区域 -- ✅ 盈亏比要求:≥ 1:1.5 -- ✅ **止损设置**:正常止损(1-1.5%),正常仓位 +**入场方式**:**market 市价入场** +- **原因**:突破行情通常快速延续,等待回调会错过机会 +- **止损**:突破位下方 1-1.5% +- **目标**:上方 2-3% -**❌ 以下情况绝对不用 market**: -- ❌ 价格正在快速加速移动 -- ❌ 5m 连续 2 根以上大阳/阴线 -- ❌ 价格偏离 EMA5 > 1.5% -- ❌ 15m RSI > 65(多)或 < 35(空) -- ❌ 量比 < 1.0(无放量配合) -- ❌ 盈亏比 < 1:1.5 +#### 2. 跌破形态(Breakdown)- 市价入场 +**识别标准**(必须同时满足): +- ✅ 价格**放量跌破**关键支撑位(量比 > 1.5) +- ✅ 跌破后**没有立即反弹**(继续走弱) +- ✅ 15m RSI 在 35-50(空)- 有延续空间 +- ✅ 多周期**共振跌破**(5m + 15m + 30m 同时跌破) -**重要**: -- 必须同时输出 `entry_price`(建议入场价)和 `entry_type`(入场方式) -- **90% 的情况应该使用 limit 挂单** -- **market 是最后选择,不是首选** -- 宁可错过机会,也不要追涨杀跌 +**入场方式**:**market 市价入场** +- **原因**:跌破行情通常快速延续,等待反弹会错过机会 +- **止损**:跌破位上方 1-1.5% +- **目标**:下方 2-3% + +#### 3. 箱体震荡形态(Range-bound)- 挂单入场 +**识别标准**(满足以下至少3个): +- ✅ 布林带收口(波动率收缩) +- ✅ 15m RSI 在 40-60 震荡(无明确方向) +- ✅ 价格在区间内来回波动(上下边界清晰) +- ✅ EMA5/20/50 走平或纠缠(无趋势) +- ✅ 量能温和(无异常放量) + +**入场方式**:**limit 挂单入场** +- **上沿做空**:价格接近上沿阻力位时挂空单 +- **下沿做多**:价格接近下沿支撑位时挂多单 +- **止损**:箱体边界外 1% +- **目标**:对岸边界 + +#### 4. 不明确形态 - 挂单或观望 +- 如果既不符合突破也不符合震荡,等待更明确的信号 +- 优先使用 limit 挂单,宁可错过也不要做错 + +### 第二步:根据形态决定入场方式 + +| 市场形态 | 入场方式 | 原因 | +|---------|---------|------| +| **放量突破**(多周期共振) | **market 市价** | 抓住突破机会,等待回调会错过 | +| **放量跌破**(多周期共振) | **market 市价** | 抓住跌破机会,等待反弹会错过 | +| **箱体震荡**(区间清晰) | **limit 挂单** | 在边界反向挂单,耐心等待 | +| **趋势回调**(顺势) | **limit 挂单** | 等待回调到支撑位再入场 | +| **不明确** | **观望或 limit** | 等待更明确的信号 | + +### 第三步:入场方式执行规则 + +**market 市价入场**(仅限突破/跌破形态): +- ✅ 必须满足突破/跌破的所有识别标准 +- ✅ 量比 > 1.5(放量确认) +- ✅ 多周期共振(5m + 15m + 30m) +- ✅ 止损设置在突破/跌破位外侧 1-1.5% +- ✅ 盈亏比 ≥ 1:1.5 + +**limit 挂单入场**(震荡和回调形态): +- ✅ 震荡市:在边界反向挂单 +- ✅ 趋势回调:等待回调到 EMA20/支撑位挂单 +- ✅ 挂单价格距离当前价格 ≥ 0.5% +- ✅ 盈亏比 ≥ 1:1.5 + +### ⚠️ 绝对禁止的入场情况(无论哪种形态) + +**❌ 追涨杀跌**(价格正在快速加速移动): +- 5m 连续 2 根以上大阳/阴线 +- 15m RSI > 65(多)或 < 35(空)- 极端超买超卖 +- 价格偏离 EMA5 > 1.5% +- 信号入场价距离当前价格 ≥ 2% + +**以上情况强制 HOLD,禁止任何操作!** ## 输出格式 请严格按照以下 JSON 格式输出: @@ -499,21 +547,26 @@ class MarketSignalAnalyzer: ## 重要说明 - `entry_price`:建议入场价格(单一值) - `entry_type`:入场方式 - `market`(现价立即入场)或 `limit`(挂单等待) +- **基于形态选择入场方式**: + - 突破/跌破形态 + 放量 + 多周期共振 → `market` 市价入场 + - 箱体震荡/趋势回调 → `limit` 挂单入场 + - 不明确形态 → `limit` 或观望 - **所有价格必须是纯数字**,不要加 $ 符号、逗号或其他格式 - `entry_price`、`stop_loss`、`take_profit` 必须是数字类型,不要是字符串 - `key_levels` 中的支撑位和阻力位也必须是数字数组 -## 信号等级与置信度(稳健版) +## 信号等级与置信度(基于形态) ### 按信号质量分类 - **A级**(85-100): - - 强共振:多周期同向 + 多指标共振 + 回调确认 + - **突破/跌破形态**:多周期共振 + 放量 + 站稳 + - **入场方式**:market 市价入场(突破/跌破)或 limit(回调) - 盈亏比 ≥ 1:1.5 - - **建议**:limit 挂单为主(等待回调),light 仓位 + - **建议**:突破/跌破用 market,回调用 limit,light 仓位 - **B级**(70-84): - 量价配合 + 主要指标确认 - - 回调/反弹机会明确 + - 震荡市边界交易或趋势回调 - 盈亏比 ≥ 1:1.5 - **建议**:limit 挂单,light 仓位 @@ -527,35 +580,89 @@ class MarketSignalAnalyzer: - 量价背离或信号矛盾或盈亏比不足 - **不建议交易** -## 注意事项(稳健交易重点) -1. **挂单优先(90%用limit)**: - - 日内交易最重要的是**耐心等待好的入场价格** - - 价格快速移动时,**绝对不要追**,等待回调 - - 只有在极少数确认情况才用 market(<10%) -2. **只在有明确的做多或做空机会时才输出信号**(action 为 buy 或 sell) -3. 如果市场不明朗,没有明确交易机会,**不要输出任何信号**(signals 为空数组 []) -4. 信号强度(confidence)要合理,不要随意给高分: +## 注意事项(基于形态的入场方式) +1. **形态识别优先**: + - 先判断是突破/跌破、震荡、还是回调形态 + - 根据形态选择合适的入场方式 + - 突破/跌破用 market 抓住机会,震荡用 limit 耐心等待 +2. **防止追涨杀跌**(更重要!): + - 价格**加速移动时**(连续大阳/阴线)强制 HOLD + - RSI **极端区间**(>65 或 <35)强制 HOLD + - 价格 **偏离 EMA5 > 1.5%** 强制 HOLD + - 宁可错过,也不要追涨杀跌! +3. **只在有明确的做多或做空机会时才输出信号**(action 为 buy 或 sell) +4. 如果市场不明朗,没有明确交易机会,**不要输出任何信号**(signals 为空数组 []) +5. 信号强度(confidence)要合理,不要随意给高分: - 60-70分:一般信号,可轻仓试探(micro 仓位) - 75-84分:较强信号,可正常仓位(light 仓位) - - 85-100分:强信号,可考虑 medium 仓位 -5. **不要输出 action 为 "wait" 的信号**,如果没有交易机会就不输出 -6. **每次检查盈亏比**:盈亏比 < 1:1.5 的信号不要输出 -7. **避免过度交易**:趋势延续时不重复输出相同方向信号 -8. **关注时效性**:日内信号有效期通常 2-4 小时,超过时间需重新评估 -9. **⚠️ 防止持续止损**: - - 价格加速移动时(连续大阳/阴线)强制 HOLD - - RSI 极端区间(>65 或 <35)强制 HOLD - - 价格偏离 EMA5 > 1.5% 强制 HOLD - - 宁可错过,也不要追涨杀跌! + - 85-100分:强信号(突破/跌破),可考虑 market 入场 +6. **不要输出 action 为 "wait" 的信号**,如果没有交易机会就不输出 +7. **每次检查盈亏比**:盈亏比 < 1:1.5 的信号不要输出 +8. **避免过度交易**:趋势延续时不重复输出相同方向信号 +9. **关注时效性**:日内信号有效期通常 2-4 小时,超过时间需重新评估 ## 🎯 稳健交易成功关键 -1. **盈亏比第一**:宁可错过,不做错 -2. **挂单优先**:等待回调,不要追涨 -3. **耐心等待**:好的入场点需要等待 -4. **严控止损**:触及止损立即离场 -5. **不贪不急**:达到目标就走,达不到就止损 -6. **保持冷静**:不被情绪左右,按规则交易 -7. **防止持续止损**:价格加速时强制观望 +1. **形态识别优先**:先判断形态,再选入场方式 +2. **突破用market**:抓住突破机会,等待会错过 +3. **震荡用limit**:边界反向挂单,耐心等待 +4. **防止追涨杀跌**:价格加速时强制观望 +5. **盈亏比第一**:宁可错过,不做错 +6. **严控止损**:触及止损立即离场 +7. **不贪不急**:达到目标就走,达不到就止损 +8. **保持冷静**:不被情绪左右,按规则交易 + +## 📖 形态识别示例 + +### 示例1:放量突破 → market 市价入场 +**市场状态**: +- BTC 在 $67,500 附近盘整 +- 突然放量突破 $68,000 阻力位(量比 > 2.0) +- 5m、15m、30m 同时突破(多周期共振) +- 15m RSI = 58(有延续空间,不过热) + +**正确做法**: +- ✅ **立即 market 市价做多** +- ✅ 止损:$67,200(突破位下方 1.2%) +- ✅ 目标:$69,500(+2.2%) +- ❌ 不要等待回调,会错过机会 + +### 示例2:箱体震荡 → limit 挂单入场 +**市场状态**: +- BTC 在 $67,000 - $68,000 区间震荡 +- 布林带收口,波动率降低 +- 15m RSI 在 45-55 震荡 +- EMA5/20/50 纠缠(无趋势) + +**正确做法**: +- ✅ **在 $67,900 limit 挂空单**(接近上沿) +- ✅ 或在 $67,100 limit 挂多单**(接近下沿) +- ✅ 止损:区间边界外 1% +- ❌ 不要市价追涨杀跌 + +### 示例3:趋势回调 → limit 挂单等待 +**市场状态**: +- BTC 处于上升趋势,EMA 多头排列 +- 价格从 $68,500 回调到 $68,000 +- 回调到 EMA20 附近获得支撑 +- 15m RSI 从 65 回落到 52 + +**正确做法**: +- ✅ **在 $67,800 limit 挂多单**(EMA20 支撑位) +- ✅ 止损:$67,100(1%) +- ✅ 目标:$69,200(+2%) +- ❌ 不要市价追高,等待回调 + +### 示例4:价格加速 → 强制 HOLD(无论什么形态) +**市场状态**: +- BTC 5m 连续 3 根大阳线 +- 15m RSI = 72(极端超买) +- 价格偏离 EMA5 = 2.3% + +**正确做法**: +- ✅ **HOLD 观望** +- ❌ **禁止 market 入场**(这是追涨!) +- ❌ **禁止 limit 入场**(价格不合适) +- 等待回调或 RSI 回到正常区间 ## 历史信号参考(非常重要!) **如果提供了上一轮的分析信号,必须仔细参考它:** diff --git a/backend/app/services/multi_llm_service.py b/backend/app/services/multi_llm_service.py index 710b562..343ab59 100644 --- a/backend/app/services/multi_llm_service.py +++ b/backend/app/services/multi_llm_service.py @@ -2,6 +2,7 @@ 多模型LLM服务 - 支持智谱AI和DeepSeek """ from typing import Optional, List, Dict, Any +from datetime import datetime, timedelta from app.config import get_settings from app.utils.logger import logger @@ -16,6 +17,7 @@ except ImportError: # DeepSeek (使用OpenAI兼容接口) try: from openai import OpenAI + from openai import APIStatusError, APIError OPENAI_AVAILABLE = True except ImportError: OPENAI_AVAILABLE = False @@ -25,6 +27,9 @@ except ImportError: class MultiLLMService: """多模型LLM服务类""" + # 余额错误通知冷却时间(秒) + BALANCE_ERROR_COOLDOWN = 3600 # 1小时内只通知一次 + def __init__(self): """初始化多模型LLM服务""" settings = get_settings() @@ -33,6 +38,9 @@ class MultiLLMService: self.current_model = None self.model_info = {} + # 余额错误通知时间记录 + self._balance_error_notified = {} # {provider: last_notified_time} + # 初始化智谱AI if ZHIPUAI_AVAILABLE and settings.zhipuai_api_key: try: @@ -109,6 +117,87 @@ class MultiLLMService: logger.error(f"模型不可用: {provider}") return False + def _is_balance_error(self, error: Exception, provider: str) -> bool: + """ + 检查错误是否是余额不足错误 + + Args: + error: 异常对象 + provider: LLM提供商 + + Returns: + 是否是余额不足错误 + """ + error_str = str(error).lower() + error_type = type(error).__name__ + + # DeepSeek 余额错误 + if provider == 'deepseek': + # APIStatusError: Error code: 402 - {'error': {'message': 'Insufficient Balance' + if '402' in error_str and 'insufficient balance' in error_str: + return True + if 'balance' in error_str and 'insufficient' in error_str: + return True + + # 智谱AI 余额错误 + elif provider == 'zhipu': + # 常见错误信息 + if '余额' in error_str or 'balance' in error_str: + if 'insufficient' in error_str or '不足' in error_str: + return True + if error_type == 'APIError' and '130' in error_str: # 智谱错误码130表示余额不足 + return True + + return False + + async def _notify_balance_error(self, provider: str, error: Exception): + """ + 发送余额不足的Telegram通知 + + Args: + provider: LLM提供商 + error: 异常对象 + """ + # 检查冷却时间 + now = datetime.now() + last_notified = self._balance_error_notified.get(provider) + if last_notified: + time_since_last = (now - last_notified).total_seconds() + if time_since_last < self.BALANCE_ERROR_COOLDOWN: + logger.info(f"{provider} 余额错误通知冷却中,剩余 {int(self.BALANCE_ERROR_COOLDOWN - time_since_last)} 秒") + return + + # 发送通知 + try: + from app.services.telegram_service import get_telegram_service + telegram = get_telegram_service() + + provider_name = { + 'zhipu': '智谱AI (GLM-4)', + 'deepseek': 'DeepSeek' + }.get(provider, provider) + + message = f"""🚨 LLM API 余额不足警告 + +━━━━━━━━━━━━━━━━━━━━ + +📊 服务商: {provider_name} +⚠️ 错误类型: 余额不足 (Insufficient Balance) +🔍 错误信息: {str(error)[:200]} + +━━━━━━━━━━━━━━━━━━━━ + +请及时充值,否则智能体将无法正常工作""" + + await telegram.send_message(message, parse_mode="HTML") + logger.warning(f"已发送 {provider} 余额不足Telegram通知") + + # 记录通知时间 + self._balance_error_notified[provider] = now + + except Exception as e: + logger.error(f"发送余额不足通知失败: {e}") + def chat( self, messages: List[Dict[str, str]], @@ -214,6 +303,22 @@ class MultiLLMService: logger.error(f"LLM调用失败: {type(e).__name__}: {e}") import traceback logger.error(f"详细错误: {traceback.format_exc()}") + + # 检查是否是余额错误,发送Telegram通知 + if self._is_balance_error(e, provider): + import asyncio + try: + # 在新的事件循环中运行(避免嵌套事件循环问题) + loop = asyncio.get_event_loop() + if loop.is_running(): + # 如果在异步上下文中,创建任务 + asyncio.create_task(self._notify_balance_error(provider, e)) + else: + # 如果没有运行的循环,直接运行 + asyncio.run(self._notify_balance_error(provider, e)) + except Exception as notify_error: + logger.error(f"发送余额通知异常: {notify_error}") + return None def chat_stream( @@ -287,6 +392,22 @@ class MultiLLMService: logger.error(f"LLM流式调用失败: {type(e).__name__}: {e}") import traceback logger.error(f"详细错误: {traceback.format_exc()}") + + # 检查是否是余额错误,发送Telegram通知 + if self._is_balance_error(e, provider): + import asyncio + try: + # 在新的事件循环中运行(避免嵌套事件循环问题) + loop = asyncio.get_event_loop() + if loop.is_running(): + # 如果在异步上下文中,创建任务 + asyncio.create_task(self._notify_balance_error(provider, e)) + else: + # 如果没有运行的循环,直接运行 + asyncio.run(self._notify_balance_error(provider, e)) + except Exception as notify_error: + logger.error(f"发送余额通知异常: {notify_error}") + return def analyze_intent(self, user_message: str) -> Dict[str, Any]: diff --git a/scripts/analyze_eth_4h.py b/scripts/analyze_eth_4h.py new file mode 100644 index 0000000..5b9cde7 --- /dev/null +++ b/scripts/analyze_eth_4h.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +分析ETH过去4小时的市场数据,检查为什么没有开仓 +""" +import sys +import os +import asyncio +from datetime import datetime, timedelta + +# 添加项目路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend')) + +from app.services.binance_service import binance_service +from app.crypto_agent.market_signal_analyzer import MarketSignalAnalyzer +from app.crypto_agent.trading_decision_maker import TradingDecisionMaker +from app.services.paper_trading_service import get_paper_trading_service +from app.utils.logger import logger + + +async def analyze_eth_4h(): + """分析ETH过去4小时的情况""" + print("=" * 80) + print("📊 ETH 过去4小时市场分析") + print("=" * 80) + print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + # 1. 获取服务 + binance = binance_service + signal_analyzer = MarketSignalAnalyzer() + decision_maker = TradingDecisionMaker() + trading_service = get_paper_trading_service() + + # 2. 获取当前行情 + print("\n📈 当前行情:") + current_price = binance.get_current_price('ETHUSDT') + stats = binance.get_24h_stats('ETHUSDT') + + if current_price: + print(f" 价格: ${current_price:,.2f}") + if stats: + print(f" 涨跌: {stats.get('priceChangePercent', 0):+.2f}%") + print(f" 成交量: {stats.get('volume', 0):,.0f}") + + # 3. 获取K线数据(多周期) + print("\n📊 获取K线数据...") + data = binance.get_multi_timeframe_data('ETHUSDT') + + if not data: + print("❌ 无法获取K线数据") + return + + # 显示各周期数据量 + for tf, df in data.items(): + print(f" {tf}: {len(df)} 条数据") + + # 4. 检查过去4小时的价格波动 + print("\n📊 过去4小时价格波动:") + df_15m = data.get('15m') + if df_15m is not None and len(df_15m) > 0: + # 获取过去4小时的15分钟数据(16根) + recent_4h = df_15m.tail(16) + if len(recent_4h) > 0: + high = recent_4h['high'].max() + low = recent_4h['low'].min() + start_price = recent_4h.iloc[0]['close'] + end_price = recent_4h.iloc[-1]['close'] + volatility = ((high - low) / start_price) * 100 + price_change = ((end_price - start_price) / start_price) * 100 + + print(f" 最高: ${high:,.2f}") + print(f" 最低: ${low:,.2f}") + print(f" 波动幅度: {volatility:.2f}%") + print(f" 价格变化: {price_change:+.2f}%") + + # 5. 运行信号分析 + print("\n🔍 信号分析:") + print("-" * 80) + + analysis = await signal_analyzer.analyze('ETHUSDT', data) + + # 显示分析结果 + print(f"\n市场状态: {analysis.get('analysis_summary', 'N/A')}") + + # 新闻情绪 + if analysis.get('news_sentiment'): + sentiment_map = {'positive': '📈 积极', 'negative': '📉 消极', 'neutral': '➖ 中性'} + print(f"新闻情绪: {sentiment_map.get(analysis['news_sentiment'], analysis['news_sentiment'])}") + + # 信号 + signals = analysis.get('signals', []) + if signals: + print(f"\n🎯 生成 {len(signals)} 个信号:") + for sig in signals: + action = sig.get('action', 'wait') + action_map = {'buy': '🟢 做多', 'sell': '🔴 做空', 'wait': '⏸️ 观望'} + grade = sig.get('grade', 'D') + confidence = sig.get('confidence', 0) + + print(f"\n {action_map.get(action, action)} [{grade}级] {confidence}%") + print(f" 入场: ${sig.get('entry_price', 0):,.2f}") + print(f" 止损: ${sig.get('stop_loss', 0):,.2f}") + print(f" 止盈: ${sig.get('take_profit', 0):,.2f}") + print(f" 入场方式: {sig.get('entry_type', 'N/A')}") + + if sig.get('reason'): + print(f" 理由: {sig['reason']}") + if sig.get('risk_warning'): + print(f" ⚠️ 风险: {sig['risk_warning']}") + else: + print("\n⏸️ 无交易信号") + + # 6. 检查当前持仓 + print("\n💼 当前持仓:") + account = trading_service.get_account_status() + active_orders = trading_service.get_active_orders() + + print(f" 余额: ${account['current_balance']:,.2f}") + print(f" 已用保证金: ${account['used_margin']:,.2f}") + print(f" 持仓数量: {len(active_orders)}") + + if active_orders: + for order in active_orders: + print(f"\n {order.symbol} {order.side.value}") + print(f" 入场价: ${order.filled_price:,.2f}") + print(f" 当前价: ${current_price:,.2f}") + print(f" 保证金: ${order.margin:,.2f}") + print(f" 杠杆: {order.leverage}x") + else: + print(" 无持仓") + + # 7. 模拟交易决策 + print("\n🤖 交易决策分析:") + print("-" * 80) + + if signals: + # 对每个信号做决策 + for sig in signals[:3]: # 只看前3个 + decision = await decision_maker.make_decision( + symbol='ETHUSDT', + signals=[sig], + current_data=data, + current_price=current_price + ) + + print(f"\n信号: {sig.get('action', 'wait')} | {sig.get('grade')}级 | {sig.get('confidence')}%") + print(f"决策: {decision['decision']}") + print(f"理由: {decision.get('reason', 'N/A')}") + + if decision.get('orders_to_open'): + print(f"开仓: {len(decision['orders_to_open'])} 个") + if decision.get('orders_to_close'): + print(f"平仓: {decision['orders_to_close']}") + else: + print("无信号,不生成决策") + + # 8. 检查是否有追涨杀跌的情况 + print("\n⚠️ 追涨杀跌检测:") + print("-" * 80) + + if df_15m is not None and len(df_15m) >= 5: + recent = df_15m.tail(5) + + # 计算RSI + import pandas_ta as ta + rsi = recent['close'].ta.rsi(length=14) + current_rsi = rsi.iloc[-1] if not rsi.empty else 50 + + # 检查连续大阳线/阴线 + big_moves = 0 + for i in range(1, min(4, len(recent))): + change = abs((recent.iloc[i]['close'] - recent.iloc[i-1]['close']) / recent.iloc[i-1]['close']) * 100 + if change >= 1.0: + big_moves += 1 + + # 检查价格偏离EMA + ema5 = recent['close'].ta.ema(length=5) + current_price = recent.iloc[-1]['close'] + deviation = abs((current_price - ema5.iloc[-1]) / ema5.iloc[-1]) * 100 if not ema5.empty else 0 + + print(f" RSI(14): {current_rsi:.1f}") + print(f" 连续大K线: {big_moves} 根") + print(f" 偏离EMA5: {deviation:.2f}%") + + # 判断是否满足追涨杀跌条件 + is_chasing = False + reasons = [] + + if current_rsi > 65 or current_rsi < 35: + is_chasing = True + reasons.append(f"RSI处于极端区间 ({current_rsi:.1f})") + + if big_moves >= 2: + is_chasing = True + reasons.append(f"连续{big_moves}根大K线") + + if deviation > 1.5: + is_chasing = True + reasons.append(f"价格偏离EMA5超过1.5%") + + if is_chasing: + print(f"\n 🚨 检测到追涨杀跌条件!") + for reason in reasons: + print(f" - {reason}") + print(f"\n → 这是防止开仓的原因之一") + else: + print(f"\n ✅ 未检测到追涨杀跌条件") + + print("\n" + "=" * 80) + print("分析完成") + print("=" * 80) + + +if __name__ == "__main__": + try: + asyncio.run(analyze_eth_4h()) + except Exception as e: + print(f"\n❌ 分析失败: {e}") + import traceback + traceback.print_exc()