This commit is contained in:
aaron 2026-03-04 12:14:07 +08:00
parent 7e58b5203b
commit c080962373
3 changed files with 513 additions and 66 deletions

View File

@ -418,51 +418,99 @@ class MarketSignalAnalyzer:
- **突破交易**多周期同时突破关键位 + 放量信号最强
- **回调交易**30m 趋势向上15m 回调到 EMA205m 反弹确认
## 八、入场方式(稳健版 - 防止持续止损
## 八、入场方式(基于形态的智能选择
### 核心原则:挂单优先,市价慎用
### 核心原则:根据市场形态选择入场方式
**🎯 入场方式优先级**
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回调用 limitlight 仓位
- **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
- 5m15m30m 同时突破多周期共振
- 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,1001%
- 目标$69,200+2%
- 不要市价追高等待回调
### 示例4价格加速 → 强制 HOLD无论什么形态
**市场状态**
- BTC 5m 连续 3 根大阳线
- 15m RSI = 72极端超买
- 价格偏离 EMA5 = 2.3%
**正确做法**
- **HOLD 观望**
- **禁止 market 入场**这是追涨
- **禁止 limit 入场**价格不合适
- 等待回调或 RSI 回到正常区间
## 历史信号参考(非常重要!)
**如果提供了上一轮的分析信号必须仔细参考它**

View File

@ -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"""🚨 <b>LLM API 余额不足警告</b>
📊 <b>服务商:</b> {provider_name}
<b>错误类型:</b> 余额不足 (Insufficient Balance)
🔍 <b>错误信息:</b> {str(error)[:200]}
<i>请及时充值否则智能体将无法正常工作</i>"""
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]:

219
scripts/analyze_eth_4h.py Normal file
View File

@ -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()