update
This commit is contained in:
parent
56c0eb1357
commit
e408d1c9e4
@ -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'):
|
||||||
|
|||||||
@ -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': {
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user