This commit is contained in:
aaron 2026-03-13 21:24:31 +08:00
parent d242a5978f
commit 9166e4a987
3 changed files with 258 additions and 9 deletions

View File

@ -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:

View File

@ -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:
"""
根据分析结果计算优先级

View File

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