update
This commit is contained in:
parent
d242a5978f
commit
9166e4a987
@ -24,10 +24,78 @@ from app.services.news_service import get_news_service
|
||||
class MarketSignalAnalyzer:
|
||||
"""市场信号分析器 - 只关注市场,输出客观信号"""
|
||||
|
||||
# 纯市场分析系统提示词(日内交易优化版 + 多级别反转检测)
|
||||
MARKET_ANALYSIS_PROMPT = """你是一位专业的加密货币**日内交易员**和技术分析师。你的任务是综合分析**趋势方向、K线数据、量价关系、技术指标和新闻舆情**,给出**适合日内快进快出**的交易信号。
|
||||
# 纯市场分析系统提示词(日内交易优化版 + 多级别反转检测 + 市场状态动态调整)
|
||||
MARKET_ANALYSIS_PROMPT = """你是一位专业的加密货币**智能交易员**和技术分析师。你的任务是综合分析**趋势方向、市场状态、K线数据、量价关系、技术指标和新闻舆情**,给出**适合当前市场状态**的交易信号。
|
||||
|
||||
## 🎯 日内交易核心定位
|
||||
## 🎯 核心策略:根据市场状态动态调整(最重要!)
|
||||
|
||||
### 📊 第一步:判断市场状态
|
||||
|
||||
**市场状态分类**:
|
||||
- **震荡市(Range-bound)**:价格在一定区间内波动,无明确方向
|
||||
- **趋势市(Trending)**:价格有明确方向(上涨/下跌),持续突破/跌破
|
||||
|
||||
**判断标准**:
|
||||
1. **1h EMA 趋势判断**:
|
||||
- EMA5/10/20 多头/空头排列 → 趋势市
|
||||
- EMA 纠缠,无明确方向 → 震荡市
|
||||
|
||||
2. **30m 波动率判断(ATR)**:
|
||||
- ATR 收缩(较前期下降 > 20%) → 震荡市
|
||||
- ATR 扩张(较前期上升 > 20%) → 趋势市启动
|
||||
|
||||
3. **15m 价格动量判断**:
|
||||
- 价格在区间内波动(±2%) → 震荡市
|
||||
- 价格持续创新高/新低 → 趋势市
|
||||
|
||||
### 📈 第二步:根据市场状态选择策略
|
||||
|
||||
#### 策略A:震荡市策略
|
||||
**特点**:无明确方向,价格在区间内波动
|
||||
**核心操作**:**5分钟级别高抛低吸,快进快出**
|
||||
|
||||
**震荡市入场时机**:
|
||||
- ✅ 回调到下沿支撑 → 5m 反弹信号 → 做多(目标 1-2%)
|
||||
- ✅ 反弹到上沿压力 → 5m 下跌信号 → 做空(目标 1-2%)
|
||||
- ✅ RSI 40-60 震荡,超卖区做多,超买区做空
|
||||
- ✅ 盈亏比 ≥ 1:1.2(震荡市目标小,盈亏比可适当放宽)
|
||||
- ✅ 持仓时间:30分钟-2小时(快速进出)
|
||||
|
||||
**震荡市严禁**:
|
||||
- ❌ 追涨杀跌(价格突破后不要追,通常会回落)
|
||||
- ❌ 期待大行情(震荡市无大趋势,不要贪婪)
|
||||
- ❌ 持仓过久(区间内快速进出)
|
||||
|
||||
#### 策略B:趋势市策略
|
||||
**特点**:价格有明确方向,持续突破/跌破
|
||||
**核心操作**:**跟随趋势,等待回调/反弹入场,持仓更长**
|
||||
|
||||
**趋势市入场时机**:
|
||||
- ✅ 趋势回调/反弹到 EMA20/支撑压力位 → 顺势入场
|
||||
- ✅ 放量突破/跌破关键位 → 等待回踩确认后入场
|
||||
- ✅ 盈亏比 ≥ 1:1.5(趋势市目标更大,要求更严格)
|
||||
- ✅ 持仓时间:2-6小时(跟随趋势)
|
||||
|
||||
**趋势市严禁**:
|
||||
- ❌ **逆势做超短线**(趋势明确时不要反向操作!)
|
||||
- ❌ 频繁进出(趋势要持仓,不要被小波动洗出)
|
||||
- ❌ 小波动就止盈(让利润奔跑)
|
||||
|
||||
### 🔄 策略切换规则(关键!)
|
||||
|
||||
**震荡 → 趋势**:
|
||||
- 1h EMA 多头/空头排列形成
|
||||
- 放量突破/跌破震荡区间
|
||||
- ATR 显著扩张(> 20%)
|
||||
- **立即切换到趋势市策略,停止超短线反向操作**
|
||||
|
||||
**趋势 → 震荡**:
|
||||
- 趋势减弱,EMA 开始纠缠
|
||||
- 波动率收缩(ATR 下降 > 20%)
|
||||
- 价格不再创新高/新低
|
||||
- **切换到震荡市策略,准备高抛低吸**
|
||||
|
||||
## 🎯 日内交易核心定位
|
||||
**日内交易 = 快进快出 + 盈亏比第一 + 严控风险**
|
||||
- 目标:2-3% 快速获利,不是波段行情
|
||||
- 时限:单笔持仓不超过 4 小时
|
||||
@ -544,6 +612,7 @@ class MarketSignalAnalyzer:
|
||||
|
||||
```json
|
||||
{
|
||||
"market_state": "ranging/trending",
|
||||
"trend_direction": "uptrend/downtrend/neutral",
|
||||
"trend_strength": "strong/medium/weak",
|
||||
"analysis_summary": "简要描述当前市场状态(50字以内)",
|
||||
@ -571,6 +640,31 @@ class MarketSignalAnalyzer:
|
||||
}
|
||||
```
|
||||
|
||||
## 重要说明 - market_state 字段(新增)
|
||||
- **market_state**:**必须明确判断当前是震荡市还是趋势市**
|
||||
- `ranging`(震荡市):价格在区间内波动,无明确方向,使用5分钟高抛低吸策略
|
||||
- `trending`(趋势市):价格有明确方向,跟随趋势,等待回调/反弹入场
|
||||
|
||||
**判断标准**:
|
||||
1. **1h EMA 纠缠** → ranging
|
||||
2. **30m ATR 收缩 > 20%** → ranging
|
||||
3. **1h EMA 多头/空头排列** → trending
|
||||
4. **价格持续创新高/新低** → trending
|
||||
|
||||
**策略差异**:
|
||||
- **震荡市(ranging)**:
|
||||
- 5分钟级别操作
|
||||
- 支撑位做多,压力位做空
|
||||
- 目标 1-2%,快速进出
|
||||
- 盈亏比 ≥ 1:1.2
|
||||
|
||||
- **趋势市(trending)**:
|
||||
- 30分钟/1小时级别操作
|
||||
- 等待回调/反弹到 EMA20 顺势入场
|
||||
- 目标 3-5%,跟随趋势
|
||||
- 盈亏比 ≥ 1:1.5
|
||||
- **严禁逆势做超短线**
|
||||
|
||||
## 重要说明
|
||||
- `entry_price`:建议入场价格(单一值)
|
||||
- `entry_type`:入场方式 - `market`(现价立即入场)或 `limit`(挂单等待)
|
||||
@ -887,10 +981,11 @@ class MarketSignalAnalyzer:
|
||||
return "新闻获取失败"
|
||||
|
||||
def _analyze_trend_position(self, data: Dict[str, pd.DataFrame]) -> str:
|
||||
"""分析趋势位置和日内交易机会(使用 EMA)"""
|
||||
"""分析趋势位置和日内交易机会(使用 EMA)+ 市场状态判断(震荡/趋势)"""
|
||||
try:
|
||||
df_30m = data.get('30m')
|
||||
df_15m = data.get('15m')
|
||||
df_1h = data.get('1h')
|
||||
|
||||
if df_30m is None or len(df_30m) < 50:
|
||||
return ""
|
||||
@ -906,6 +1001,61 @@ class MarketSignalAnalyzer:
|
||||
if not all([ema5_30m, ema10_30m, ema20_30m]):
|
||||
return ""
|
||||
|
||||
# ========== 新增:市场状态判断(震荡 vs 趋势) ==========
|
||||
market_state = "unknown"
|
||||
market_state_reason = []
|
||||
|
||||
# 1h EMA 趋势判断
|
||||
if df_1h is not None and len(df_1h) >= 20:
|
||||
latest_1h = df_1h.iloc[-1]
|
||||
ema5_1h = latest_1h.get('ma5')
|
||||
ema10_1h = latest_1h.get('ma10')
|
||||
ema20_1h = latest_1h.get('ma20')
|
||||
|
||||
if ema5_1h and ema10_1h and ema20_1h:
|
||||
# 1h EMA 多头/空头排列 → 趋势市
|
||||
if ema5_1h > ema10_1h > ema20_1h:
|
||||
market_state = "trending"
|
||||
market_state_reason.append("1h EMA 多头排列")
|
||||
elif ema5_1h < ema10_1h < ema20_1h:
|
||||
market_state = "trending"
|
||||
market_state_reason.append("1h EMA 空头排列")
|
||||
else:
|
||||
market_state = "ranging"
|
||||
market_state_reason.append("1h EMA 纠缠")
|
||||
|
||||
# 波动率判断(ATR 变化)
|
||||
if df_30m is not None and len(df_30m) >= 24 and 'atr' in df_30m.columns:
|
||||
recent_atr = df_30m['atr'].iloc[-6:].mean() # 最近3小时
|
||||
older_atr = df_30m['atr'].iloc[-12:-6].mean() # 之前3小时
|
||||
|
||||
if pd.notna(recent_atr) and pd.notna(older_atr) and older_atr > 0:
|
||||
atr_change = (recent_atr - older_atr) / older_atr * 100
|
||||
|
||||
if atr_change > 20:
|
||||
if market_state != "trending":
|
||||
market_state = "trending"
|
||||
market_state_reason.append(f"ATR 扩张 {atr_change:.0f}%")
|
||||
elif atr_change < -20:
|
||||
if market_state != "ranging":
|
||||
market_state = "ranging"
|
||||
market_state_reason.append(f"ATR 收缩 {abs(atr_change):.0f}%")
|
||||
|
||||
# 价格动量判断(15m)
|
||||
if df_15m is not None and len(df_15m) >= 20:
|
||||
recent_high = df_15m['high'].iloc[-20:].max()
|
||||
recent_low = df_15m['low'].iloc[-20:].min()
|
||||
price_range = (recent_high - recent_low) / current_price * 100
|
||||
|
||||
if price_range < 2.5: # 15分钟内波动小于2.5% → 震荡
|
||||
if market_state != "trending":
|
||||
market_state = "ranging"
|
||||
market_state_reason.append(f"15m 波动 {price_range:.1f}% 较小")
|
||||
elif price_range > 4: # 15分钟内波动大于4% → 趋势
|
||||
if market_state != "ranging":
|
||||
market_state = "trending"
|
||||
market_state_reason.append(f"15m 波动 {price_range:.1f}% 较大")
|
||||
|
||||
# 判断日内趋势(30m EMA 为主)
|
||||
if ema5_30m > ema10_30m > ema20_30m:
|
||||
intraday_trend = "上升"
|
||||
@ -917,7 +1067,33 @@ class MarketSignalAnalyzer:
|
||||
intraday_trend = "震荡"
|
||||
intraday_emoji = "➖"
|
||||
|
||||
analysis = [f"日内趋势(30m EMA): {intraday_emoji} {intraday_trend}"]
|
||||
# 构建市场状态分析
|
||||
analysis_parts = []
|
||||
|
||||
# 市场状态显示(新增)
|
||||
if market_state == "trending":
|
||||
state_emoji = "📊"
|
||||
state_text = f"{state_emoji} **市场状态: 趋势市**"
|
||||
analysis_parts.append(state_text)
|
||||
analysis_parts.append(f" 判断依据: {', '.join(market_state_reason)}")
|
||||
analysis_parts.append(f" 策略: 跟随趋势,等待回调/反弹到 EMA20 顺势入场")
|
||||
analysis_parts.append(f" 目标: 3-5%,盈亏比 ≥ 1:1.5")
|
||||
analysis_parts.append(f" 严禁: 逆势做超短线")
|
||||
elif market_state == "ranging":
|
||||
state_emoji = "🔄"
|
||||
state_text = f"{state_emoji} **市场状态: 震荡市**"
|
||||
analysis_parts.append(state_text)
|
||||
analysis_parts.append(f" 判断依据: {', '.join(market_state_reason)}")
|
||||
analysis_parts.append(f" 策略: 5分钟级别高抛低吸,支撑位多、压力位空")
|
||||
analysis_parts.append(f" 目标: 1-2%,盈亏比 ≥ 1:1.2")
|
||||
analysis_parts.append(f" 严禁: 追涨杀跌")
|
||||
else:
|
||||
analysis_parts.append(f"⚠️ 市场状态: 不明确,观望为主")
|
||||
|
||||
analysis_parts.append(f"")
|
||||
analysis_parts.append(f"日内趋势(30m EMA): {intraday_emoji} {intraday_trend}")
|
||||
|
||||
analysis = analysis_parts
|
||||
|
||||
# 检查15分钟级别入场时机
|
||||
if df_15m is not None and len(df_15m) >= 20:
|
||||
|
||||
@ -32,6 +32,10 @@ class NewsAnalyzer:
|
||||
self.batch_size = 10 # 每次最多分析 10 条新闻(只传标题,可以增加数量)
|
||||
self.max_retries = 2
|
||||
|
||||
# 余额错误通知冷却时间(秒)
|
||||
self._balance_error_cooldown = 3600 # 1小时内只通知一次
|
||||
self._balance_error_last_notified = None
|
||||
|
||||
def _build_analysis_prompt(self, news_item: NewsItem) -> str:
|
||||
"""构建单条新闻的分析提示词"""
|
||||
|
||||
@ -315,6 +319,13 @@ class NewsAnalyzer:
|
||||
except Exception as e:
|
||||
logger.warning(f"分析失败 (尝试 {attempt + 1}/{self.max_retries}): {e}")
|
||||
|
||||
# 检查是否是余额不足错误 (402)
|
||||
error_str = str(e)
|
||||
error_code = str(e).split('Error code: ')[1].split(' -')[0] if 'Error code:' in error_str else ''
|
||||
if error_code == '402' or ('402' in error_str and 'insufficient balance' in error_str.lower()):
|
||||
await self._notify_balance_error(e)
|
||||
break # 余额不足不再重试
|
||||
|
||||
logger.error(f"新闻分析失败,已达最大重试次数: {news_item.title[:50]}")
|
||||
return None
|
||||
|
||||
@ -369,11 +380,57 @@ class NewsAnalyzer:
|
||||
results.extend([None] * len(batch))
|
||||
|
||||
except Exception as e:
|
||||
error_str = str(e)
|
||||
error_code = str(e).split('Error code: ')[1].split(' -')[0] if 'Error code:' in error_str else ''
|
||||
|
||||
logger.error(f"批量分析失败: {e}")
|
||||
|
||||
# 检查是否是余额不足错误 (402)
|
||||
if error_code == '402' or ('402' in error_str and 'insufficient balance' in error_str.lower()):
|
||||
await self._notify_balance_error(e)
|
||||
|
||||
results.extend([None] * len(batch))
|
||||
|
||||
return results
|
||||
|
||||
async def _notify_balance_error(self, error: Exception):
|
||||
"""
|
||||
发送余额不足的飞书通知
|
||||
|
||||
Args:
|
||||
error: 异常对象
|
||||
"""
|
||||
# 检查冷却时间
|
||||
now = datetime.now()
|
||||
if self._balance_error_last_notified:
|
||||
time_since_last = (now - self._balance_error_last_notified).total_seconds()
|
||||
if time_since_last < self._balance_error_cooldown:
|
||||
logger.info(f"余额错误通知冷却中,剩余 {int(self._balance_error_cooldown - time_since_last)} 秒")
|
||||
return
|
||||
|
||||
# 发送通知
|
||||
try:
|
||||
from app.services.feishu_service import get_feishu_service
|
||||
feishu = get_feishu_service()
|
||||
|
||||
message = f"""🚨 **新闻分析 LLM API 余额不足警告**
|
||||
|
||||
**服务商**: DeepSeek
|
||||
**错误类型**: 余额不足 (Insufficient Balance)
|
||||
**错误信息**: {str(error)[:200]}
|
||||
**时间**: {now.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
|
||||
⚠️ 请及时充值,否则新闻智能体将无法正常工作"""
|
||||
|
||||
await feishu.send_text(message)
|
||||
logger.warning("已发送 DeepSeek 余额不足飞书通知")
|
||||
|
||||
# 记录通知时间
|
||||
self._balance_error_last_notified = now
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送余额不足通知失败: {e}")
|
||||
|
||||
def calculate_priority(self, analysis: Dict[str, Any], quality_score: float = 0.5) -> float:
|
||||
"""
|
||||
根据分析结果计算优先级
|
||||
|
||||
@ -152,7 +152,7 @@ class MultiLLMService:
|
||||
|
||||
async def _notify_balance_error(self, provider: str, error: Exception):
|
||||
"""
|
||||
发送余额不足的Telegram通知
|
||||
发送余额不足的通知(Telegram + 飞书)
|
||||
|
||||
Args:
|
||||
provider: LLM提供商
|
||||
@ -170,14 +170,17 @@ class MultiLLMService:
|
||||
# 发送通知
|
||||
try:
|
||||
from app.services.telegram_service import get_telegram_service
|
||||
from app.services.feishu_service import get_feishu_service
|
||||
telegram = get_telegram_service()
|
||||
feishu = get_feishu_service()
|
||||
|
||||
provider_name = {
|
||||
'zhipu': '智谱AI (GLM-4)',
|
||||
'deepseek': 'DeepSeek'
|
||||
}.get(provider, provider)
|
||||
|
||||
message = f"""🚨 <b>LLM API 余额不足警告</b>
|
||||
# Telegram 通知
|
||||
telegram_message = f"""🚨 <b>LLM API 余额不足警告</b>
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
@ -189,8 +192,21 @@ class MultiLLMService:
|
||||
|
||||
<i>请及时充值,否则智能体将无法正常工作</i>"""
|
||||
|
||||
await telegram.send_message(message, parse_mode="HTML")
|
||||
logger.warning(f"已发送 {provider} 余额不足Telegram通知")
|
||||
await telegram.send_message(telegram_message, parse_mode="HTML")
|
||||
logger.info(f"已发送 {provider} 余额不足 Telegram 通知")
|
||||
|
||||
# 飞书通知
|
||||
feishu_message = f"""🚨 **LLM API 余额不足警告**
|
||||
|
||||
**服务商**: {provider_name}
|
||||
**错误类型**: 余额不足 (Insufficient Balance)
|
||||
**错误信息**: {str(error)[:200]}
|
||||
**时间**: {now.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
|
||||
⚠️ 请及时充值,否则智能体将无法正常工作"""
|
||||
|
||||
await feishu.send_text(feishu_message)
|
||||
logger.info(f"已发送 {provider} 余额不足飞书通知")
|
||||
|
||||
# 记录通知时间
|
||||
self._balance_error_notified[provider] = now
|
||||
|
||||
Loading…
Reference in New Issue
Block a user