This commit is contained in:
aaron 2026-02-06 09:30:15 +08:00
parent 56c0eb1357
commit e408d1c9e4
3 changed files with 202 additions and 14 deletions

View File

@ -170,8 +170,10 @@ class CryptoAgent:
# 输出信号详情 # 输出信号详情
action_icon = {'buy': '🟢 买入', 'sell': '🔴 卖出', 'hold': '⏸️ 观望'}.get(signal['action'], '') action_icon = {'buy': '🟢 买入', 'sell': '🔴 卖出', 'hold': '⏸️ 观望'}.get(signal['action'], '')
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '', 'D': ''}.get(signal.get('signal_grade', 'D'), '') grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '', 'D': ''}.get(signal.get('signal_grade', 'D'), '')
signal_type = signal.get('signal_type', 'swing')
type_text = '📈短线' if signal_type == 'short_term' else '📊波段'
logger.info(f" 信号: {action_icon} | 置信度: {signal['confidence']}% | 等级: {signal.get('signal_grade', 'D')} {grade_icon}") logger.info(f" 信号: {action_icon} | 类型: {type_text} | 置信度: {signal['confidence']}% | 等级: {signal.get('signal_grade', 'D')} {grade_icon}")
# 输出触发原因 # 输出触发原因
if signal.get('reasons'): if signal.get('reasons'):

View File

@ -370,6 +370,149 @@ class SignalAnalyzer:
'volume_weight': weight 'volume_weight': weight
} }
# ==================== 短线信号分析 ====================
def _analyze_short_term_signal(self, m5_data: pd.DataFrame, m15_data: pd.DataFrame,
trend_direction: str) -> Dict[str, Any]:
"""
分析短线交易信号超跌反弹/超涨回落
短线信号特点
- 不依赖大趋势方向主要看短周期超买超卖
- 快进快出持仓时间短
- 置信度上限较低建议轻仓
Args:
m5_data: 5分钟K线数据
m15_data: 15分钟K线数据
trend_direction: 大趋势方向用于顺势加分
Returns:
{
'action': 'buy' | 'sell' | 'hold',
'confidence': 0-100,
'reasons': [...],
'signal_type': 'short_term'
}
"""
if len(m5_data) < 5 or len(m15_data) < 5:
return {'action': 'hold', 'confidence': 0, 'reasons': [], 'signal_type': 'short_term'}
m5_latest = m5_data.iloc[-1]
m5_prev = m5_data.iloc[-2]
m15_latest = m15_data.iloc[-1]
m15_prev = m15_data.iloc[-2]
buy_score = 0
sell_score = 0
buy_reasons = []
sell_reasons = []
# === 15M RSI 超卖/超买 ===
m15_rsi = m15_latest.get('rsi', 50)
if pd.notna(m15_rsi):
if m15_rsi < 25:
buy_reasons.append(f"15M RSI极度超卖({m15_rsi:.1f})")
buy_score += 3
elif m15_rsi < 35:
buy_reasons.append(f"15M RSI超卖({m15_rsi:.1f})")
buy_score += 2
elif m15_rsi > 75:
sell_reasons.append(f"15M RSI极度超买({m15_rsi:.1f})")
sell_score += 3
elif m15_rsi > 65:
sell_reasons.append(f"15M RSI超买({m15_rsi:.1f})")
sell_score += 2
# === 5M RSI 反转确认 ===
m5_rsi = m5_latest.get('rsi', 50)
m5_prev_rsi = m5_prev.get('rsi', 50)
if pd.notna(m5_rsi) and pd.notna(m5_prev_rsi):
# 超卖后回升
if m5_rsi < 40 and m5_rsi > m5_prev_rsi and m5_prev_rsi < 35:
buy_reasons.append(f"5M RSI反转回升({m5_prev_rsi:.1f}{m5_rsi:.1f})")
buy_score += 2
# 超买后回落
if m5_rsi > 60 and m5_rsi < m5_prev_rsi and m5_prev_rsi > 65:
sell_reasons.append(f"5M RSI反转回落({m5_prev_rsi:.1f}{m5_rsi:.1f})")
sell_score += 2
# === 布林带触轨 ===
if 'bb_lower' in m15_latest and pd.notna(m15_latest['bb_lower']):
if m15_latest['close'] <= m15_latest['bb_lower']:
buy_reasons.append("15M触及布林下轨")
buy_score += 2
elif m15_latest['close'] >= m15_latest['bb_upper']:
sell_reasons.append("15M触及布林上轨")
sell_score += 2
# === KDJ 超卖/超买 ===
m15_k = m15_latest.get('k', 50)
m15_d = m15_latest.get('d', 50)
if pd.notna(m15_k) and pd.notna(m15_d):
if m15_k < 20 and m15_d < 20:
buy_reasons.append(f"15M KDJ超卖区(K={m15_k:.1f})")
buy_score += 1.5
elif m15_k > 80 and m15_d > 80:
sell_reasons.append(f"15M KDJ超买区(K={m15_k:.1f})")
sell_score += 1.5
# === 5M K线反转形态 ===
if m5_latest['close'] > m5_latest['open'] and m5_prev['close'] < m5_prev['open']:
# 阴转阳
if m5_rsi < 40:
buy_reasons.append("5M阴转阳反转")
buy_score += 1.5
elif m5_latest['close'] < m5_latest['open'] and m5_prev['close'] > m5_prev['open']:
# 阳转阴
if m5_rsi > 60:
sell_reasons.append("5M阳转阴反转")
sell_score += 1.5
# === 5M MACD 金叉/死叉 ===
m5_macd = m5_latest.get('macd', 0)
m5_macd_signal = m5_latest.get('macd_signal', 0)
m5_prev_macd = m5_prev.get('macd', 0)
m5_prev_macd_signal = m5_prev.get('macd_signal', 0)
if pd.notna(m5_macd) and pd.notna(m5_prev_macd):
if m5_prev_macd <= m5_prev_macd_signal and m5_macd > m5_macd_signal:
buy_reasons.append("5M MACD金叉")
buy_score += 1.5
elif m5_prev_macd >= m5_prev_macd_signal and m5_macd < m5_macd_signal:
sell_reasons.append("5M MACD死叉")
sell_score += 1.5
# === 顺势加分 ===
if trend_direction == 'bullish' and buy_score > 0:
buy_score += 1
buy_reasons.append("顺大势做多")
elif trend_direction == 'bearish' and sell_score > 0:
sell_score += 1
sell_reasons.append("顺大势做空")
# === 决策 ===
action = 'hold'
confidence = 0
reasons = []
# 短线信号阈值较低,但置信度上限也低
if buy_score >= 4 and buy_score > sell_score:
action = 'buy'
confidence = min(35 + buy_score * 6, 65) # 短线最高65%
reasons = buy_reasons + ["📈 短线超跌反弹"]
elif sell_score >= 4 and sell_score > buy_score:
action = 'sell'
confidence = min(35 + sell_score * 6, 65)
reasons = sell_reasons + ["📉 短线超涨回落"]
return {
'action': action,
'confidence': confidence,
'reasons': reasons,
'signal_type': 'short_term',
'scores': {'buy': buy_score, 'sell': sell_score}
}
# ==================== 5M 精确入场 ==================== # ==================== 5M 精确入场 ====================
def _analyze_5m_entry(self, m5_data: pd.DataFrame, action: str) -> Dict[str, Any]: def _analyze_5m_entry(self, m5_data: pd.DataFrame, action: str) -> Dict[str, Any]:
@ -819,29 +962,30 @@ class SignalAnalyzer:
sell_signals.append(f"触及阻力位({levels['nearest_resistance']:.2f})") sell_signals.append(f"触及阻力位({levels['nearest_resistance']:.2f})")
signal_weights['sell'] += 1.5 signal_weights['sell'] += 1.5
# ==================== 5. 根据趋势和阶段决定动作 ==================== # ==================== 5. 根据趋势和阶段决定动作(中长线信号)====================
action = 'hold' action = 'hold'
confidence = 0 confidence = 0
reasons = [] reasons = []
signal_grade = 'D' signal_grade = 'D'
signal_type = 'swing' # 默认波段信号
# 波段交易核心逻辑:在回调中寻找入场机会 # 波段交易核心逻辑:在回调中寻找入场机会
if trend_direction == 'bullish': if trend_direction == 'bullish':
if trend_phase == 'correction' and signal_weights['buy'] >= 3: if trend_phase == 'correction' and signal_weights['buy'] >= 3:
action = 'buy' action = 'buy'
confidence = min(40 + signal_weights['buy'] * 10, 95) confidence = min(40 + signal_weights['buy'] * 10, 95)
reasons = buy_signals + [f"上涨趋势回调({trend_strength})"] reasons = buy_signals + [f"📊 波段信号: 上涨趋势回调({trend_strength})"]
signal_grade = 'A' if confidence >= 80 else ('B' if confidence >= 60 else 'C') signal_grade = 'A' if confidence >= 80 else ('B' if confidence >= 60 else 'C')
elif trend_phase == 'impulse' and signal_weights['buy'] >= 4: elif trend_phase == 'impulse' and signal_weights['buy'] >= 4:
action = 'buy' action = 'buy'
confidence = min(30 + signal_weights['buy'] * 8, 80) confidence = min(30 + signal_weights['buy'] * 8, 80)
reasons = buy_signals + ["主升浪追多"] reasons = buy_signals + ["📊 波段信号: 主升浪追多"]
signal_grade = 'B' if confidence >= 60 else 'C' signal_grade = 'B' if confidence >= 60 else 'C'
elif trend_phase == 'oversold' and signal_weights['buy'] >= 3: elif trend_phase == 'oversold' and signal_weights['buy'] >= 3:
# 极度超卖时允许抄底,但降低置信度并提示风险 # 极度超卖时允许抄底,但降低置信度并提示风险
action = 'buy' action = 'buy'
confidence = min(30 + signal_weights['buy'] * 8, 70) # 最高70% confidence = min(30 + signal_weights['buy'] * 8, 70) # 最高70%
reasons = buy_signals + ["⚠️ 极度超卖抄底(高风险)", "建议轻仓试探"] reasons = buy_signals + ["📊 波段信号: 极度超卖抄底(高风险)", "建议轻仓试探"]
signal_grade = 'C' # 最高C级 signal_grade = 'C' # 最高C级
elif trend_phase == 'overbought': elif trend_phase == 'overbought':
reasons = ['极度超买,不宜追多'] reasons = ['极度超买,不宜追多']
@ -850,23 +994,35 @@ class SignalAnalyzer:
if trend_phase == 'correction' and signal_weights['sell'] >= 3: if trend_phase == 'correction' and signal_weights['sell'] >= 3:
action = 'sell' action = 'sell'
confidence = min(40 + signal_weights['sell'] * 10, 95) confidence = min(40 + signal_weights['sell'] * 10, 95)
reasons = sell_signals + [f"下跌趋势反弹({trend_strength})"] reasons = sell_signals + [f"📊 波段信号: 下跌趋势反弹({trend_strength})"]
signal_grade = 'A' if confidence >= 80 else ('B' if confidence >= 60 else 'C') signal_grade = 'A' if confidence >= 80 else ('B' if confidence >= 60 else 'C')
elif trend_phase == 'impulse' and signal_weights['sell'] >= 4: elif trend_phase == 'impulse' and signal_weights['sell'] >= 4:
action = 'sell' action = 'sell'
confidence = min(30 + signal_weights['sell'] * 8, 80) confidence = min(30 + signal_weights['sell'] * 8, 80)
reasons = sell_signals + ["主跌浪追空"] reasons = sell_signals + ["📊 波段信号: 主跌浪追空"]
signal_grade = 'B' if confidence >= 60 else 'C' signal_grade = 'B' if confidence >= 60 else 'C'
elif trend_phase == 'overbought' and signal_weights['sell'] >= 3: elif trend_phase == 'overbought' and signal_weights['sell'] >= 3:
# 极度超买时允许做空,但降低置信度并提示风险 # 极度超买时允许做空,但降低置信度并提示风险
action = 'sell' action = 'sell'
confidence = min(30 + signal_weights['sell'] * 8, 70) # 最高70% confidence = min(30 + signal_weights['sell'] * 8, 70) # 最高70%
reasons = sell_signals + ["⚠️ 极度超买摸顶(高风险)", "建议轻仓试探"] reasons = sell_signals + ["📊 波段信号: 极度超买摸顶(高风险)", "建议轻仓试探"]
signal_grade = 'C' # 最高C级 signal_grade = 'C' # 最高C级
elif trend_phase == 'oversold': elif trend_phase == 'oversold':
reasons = ['极度超卖,不宜追空'] reasons = ['极度超卖,不宜追空']
else: # neutral # ==================== 5.5 短线信号检测(中长线无信号时)====================
# 如果中长线没有触发信号,检查短线超跌反弹/超涨回落机会
if action == 'hold':
short_term = self._analyze_short_term_signal(m5_data, m15_data, trend_direction)
if short_term['action'] != 'hold' and short_term['confidence'] >= 50:
action = short_term['action']
confidence = short_term['confidence']
reasons = short_term['reasons']
signal_type = 'short_term'
signal_grade = 'C' # 短线信号最高C级
# 如果还是没有信号
if action == 'hold' and trend_direction == 'neutral':
reasons = ['趋势不明确,观望'] reasons = ['趋势不明确,观望']
# ==================== 6. 5M 精确入场确认 ==================== # ==================== 6. 5M 精确入场确认 ====================
@ -895,12 +1051,14 @@ class SignalAnalyzer:
# 记录详细日志(简化版,详细日志在 crypto_agent 中输出) # 记录详细日志(简化版,详细日志在 crypto_agent 中输出)
if action != 'hold': if action != 'hold':
logger.debug(f"信号详情: {action} {confidence}% {signal_grade} | 买权重={signal_weights['buy']:.1f} 卖权重={signal_weights['sell']:.1f}") type_text = "短线" if signal_type == 'short_term' else "波段"
logger.debug(f"信号详情: [{type_text}] {action} {confidence}% {signal_grade} | 买权重={signal_weights['buy']:.1f} 卖权重={signal_weights['sell']:.1f}")
return { return {
'action': action, 'action': action,
'confidence': confidence, 'confidence': confidence,
'signal_grade': signal_grade, 'signal_grade': signal_grade,
'signal_type': signal_type, # 'swing' 波段 | 'short_term' 短线
'reasons': reasons, 'reasons': reasons,
'indicators': indicators, 'indicators': indicators,
'trend_info': { 'trend_info': {

View File

@ -107,6 +107,8 @@ class FeishuService:
- price: 当前价格 - price: 当前价格
- trend: 趋势方向 - trend: 趋势方向
- confidence: 信号强度 (0-100) - confidence: 信号强度 (0-100)
- signal_type: 'swing' | 'short_term'
- signal_grade: 'A' | 'B' | 'C' | 'D'
- indicators: 技术指标数据 - indicators: 技术指标数据
- llm_analysis: LLM 分析结果可选 - llm_analysis: LLM 分析结果可选
- stop_loss: 建议止损价 - stop_loss: 建议止损价
@ -124,18 +126,25 @@ class FeishuService:
price = signal.get('price', 0) price = signal.get('price', 0)
trend = signal.get('trend', 'neutral') trend = signal.get('trend', 'neutral')
confidence = signal.get('confidence', 0) confidence = signal.get('confidence', 0)
signal_type = signal.get('signal_type', 'swing')
signal_grade = signal.get('signal_grade', 'D')
indicators = signal.get('indicators', {}) indicators = signal.get('indicators', {})
llm_analysis = signal.get('llm_analysis', '') llm_analysis = signal.get('llm_analysis', '')
stop_loss = signal.get('stop_loss', 0) stop_loss = signal.get('stop_loss', 0)
take_profit = signal.get('take_profit', 0) take_profit = signal.get('take_profit', 0)
reasons = signal.get('reasons', [])
# 信号类型文本
type_text = "📈 短线信号" if signal_type == 'short_term' else "📊 波段信号"
type_hint = "(快进快出,建议轻仓)" if signal_type == 'short_term' else "(趋势跟踪,可适当持仓)"
# 确定标题和颜色 # 确定标题和颜色
if action == 'buy': if action == 'buy':
title = f"🟢 买入信号 - {symbol}" title = f"🟢 买入信号 - {symbol} [{type_text}]"
color = "green" color = "green"
action_text = "做多" action_text = "做多"
elif action == 'sell': elif action == 'sell':
title = f"🔴 卖出信号 - {symbol}" title = f"🔴 卖出信号 - {symbol} [{type_text}]"
color = "red" color = "red"
action_text = "做空" action_text = "做空"
else: else:
@ -150,16 +159,35 @@ class FeishuService:
'neutral': '震荡 ↔️' 'neutral': '震荡 ↔️'
}.get(trend, '未知') }.get(trend, '未知')
# 等级图标
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '', 'D': ''}.get(signal_grade, '')
# 构建内容 # 构建内容
content_parts = [ content_parts = [
f"**信号类型**: {type_text} {type_hint}",
f"**信号等级**: {signal_grade} {grade_icon}",
f"**当前价格**: ${price:,.2f}", f"**当前价格**: ${price:,.2f}",
f"**趋势方向**: {trend_text}", f"**趋势方向**: {trend_text}",
f"**信号强度**: {confidence}%", f"**置信度**: {confidence}%",
]
# 添加触发原因
if reasons:
content_parts.extend([
"",
"---",
"",
"**触发原因**:",
])
for reason in reasons[:5]: # 最多显示5个原因
content_parts.append(f"{reason}")
content_parts.extend([
"", "",
"---", "---",
"", "",
"**技术指标**:" "**技术指标**:"
] ])
# 添加技术指标 # 添加技术指标
if indicators: if indicators: