This commit is contained in:
aaron 2026-02-06 00:34:08 +08:00
parent 2f246491e9
commit 993666eed5
3 changed files with 566 additions and 26 deletions

View File

@ -59,7 +59,15 @@ class CryptoAgent:
async def run(self): async def run(self):
"""主运行循环 - 在5的倍数分钟执行""" """主运行循环 - 在5的倍数分钟执行"""
self.running = True self.running = True
logger.info("加密货币智能体开始运行...")
# 启动横幅
logger.info("\n" + "=" * 60)
logger.info("🚀 加密货币交易信号智能体")
logger.info("=" * 60)
logger.info(f" 监控交易对: {', '.join(self.symbols)}")
logger.info(f" 运行模式: 每5分钟整点执行 (:00, :05, :10, ...)")
logger.info(f" LLM阈值: {self.llm_threshold * 100:.0f}%")
logger.info("=" * 60 + "\n")
# 发送启动通知 # 发送启动通知
await self.feishu.send_text( await self.feishu.send_text(
@ -74,21 +82,27 @@ class CryptoAgent:
wait_seconds = self._get_seconds_until_next_5min() wait_seconds = self._get_seconds_until_next_5min()
if wait_seconds > 0: if wait_seconds > 0:
next_run = datetime.now() + timedelta(seconds=wait_seconds) next_run = datetime.now() + timedelta(seconds=wait_seconds)
logger.info(f"等待 {wait_seconds} 秒,下次运行时间: {next_run.strftime('%H:%M:%S')}") logger.info(f"等待 {wait_seconds} 秒,下次运行: {next_run.strftime('%H:%M:%S')}")
await asyncio.sleep(wait_seconds) await asyncio.sleep(wait_seconds)
# 执行分析 # 执行分析
run_time = datetime.now() run_time = datetime.now()
logger.info(f"=== 开始分析 [{run_time.strftime('%H:%M:%S')}] ===") logger.info("\n" + "=" * 60)
logger.info(f"⏰ 定时任务执行 [{run_time.strftime('%Y-%m-%d %H:%M:%S')}]")
logger.info("=" * 60)
for symbol in self.symbols: for symbol in self.symbols:
await self.analyze_symbol(symbol) await self.analyze_symbol(symbol)
logger.info("\n" + "" * 60)
logger.info(f"✅ 本轮分析完成,共分析 {len(self.symbols)} 个交易对")
logger.info("" * 60 + "\n")
# 等待几秒确保不会在同一分钟内重复执行 # 等待几秒确保不会在同一分钟内重复执行
await asyncio.sleep(2) await asyncio.sleep(2)
except Exception as e: except Exception as e:
logger.error(f"分析循环出错: {e}") logger.error(f"分析循环出错: {e}")
await asyncio.sleep(10) # 出错后等待10秒再继续 await asyncio.sleep(10) # 出错后等待10秒再继续
def stop(self): def stop(self):
@ -104,18 +118,36 @@ class CryptoAgent:
symbol: 交易对 'BTCUSDT' symbol: 交易对 'BTCUSDT'
""" """
try: try:
logger.info(f"开始分析 {symbol}...") # 分隔线
logger.info(f"\n{'' * 50}")
logger.info(f"📊 {symbol} 分析开始")
logger.info(f"{'' * 50}")
# 1. 获取多周期数据 # 1. 获取多周期数据
data = self.binance.get_multi_timeframe_data(symbol) data = self.binance.get_multi_timeframe_data(symbol)
if not self._validate_data(data): if not self._validate_data(data):
logger.warning(f"{symbol} 数据不完整,跳过分析") logger.warning(f"⚠️ {symbol} 数据不完整,跳过分析")
return return
# 获取当前价格
current_price = float(data['5m'].iloc[-1]['close'])
price_change_24h = self._calculate_price_change(data['1h'])
logger.info(f"💰 当前价格: ${current_price:,.2f} ({price_change_24h})")
# 2. 分析趋势1H + 4H- 返回详细趋势信息 # 2. 分析趋势1H + 4H- 返回详细趋势信息
logger.info(f"\n📈 【趋势分析】")
trend = self.analyzer.analyze_trend(data['1h'], data['4h']) trend = self.analyzer.analyze_trend(data['1h'], data['4h'])
trend_direction = trend.get('direction', 'neutral') if isinstance(trend, dict) else trend trend_direction = trend.get('direction', 'neutral') if isinstance(trend, dict) else trend
trend_strength = trend.get('strength', 'unknown') if isinstance(trend, dict) else 'unknown'
trend_phase = trend.get('phase', 'unknown') if isinstance(trend, dict) else 'unknown'
# 趋势方向图标
trend_icon = {'bullish': '🟢 看涨', 'bearish': '🔴 看跌', 'neutral': '⚪ 震荡'}.get(trend_direction, '')
phase_text = {'impulse': '主升/主跌浪', 'correction': '回调/反弹', 'oversold': '极度超卖',
'overbought': '极度超买', 'sideways': '横盘'}.get(trend_phase, trend_phase)
logger.info(f" 方向: {trend_icon} | 强度: {trend_strength} | 阶段: {phase_text}")
# 3. 检查趋势变化 # 3. 检查趋势变化
last_direction = self.last_trends.get(symbol, {}) last_direction = self.last_trends.get(symbol, {})
@ -126,16 +158,54 @@ class CryptoAgent:
self.last_trends[symbol] = trend self.last_trends[symbol] = trend
# 4. 分析进场信号15M 为主5M 辅助) # 4. 分析进场信号15M 为主5M 辅助,传入 1H 数据用于支撑阻力位)
signal = self.analyzer.analyze_entry_signal(data['5m'], data['15m'], trend) logger.info(f"\n🎯 【信号分析】")
signal = self.analyzer.analyze_entry_signal(data['5m'], data['15m'], trend, data['1h'])
signal['symbol'] = symbol signal['symbol'] = symbol
signal['trend'] = trend_direction signal['trend'] = trend_direction
signal['trend_info'] = trend if isinstance(trend, dict) else {'direction': trend} signal['trend_info'] = trend if isinstance(trend, dict) else {'direction': trend}
signal['price'] = float(data['5m'].iloc[-1]['close']) signal['price'] = current_price
signal['timestamp'] = datetime.now() signal['timestamp'] = datetime.now()
# 输出信号详情
action_icon = {'buy': '🟢 买入', 'sell': '🔴 卖出', 'hold': '⏸️ 观望'}.get(signal['action'], '')
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '', 'D': ''}.get(signal.get('signal_grade', 'D'), '')
logger.info(f" 信号: {action_icon} | 置信度: {signal['confidence']}% | 等级: {signal.get('signal_grade', 'D')} {grade_icon}")
# 输出触发原因
if signal.get('reasons'):
logger.info(f" 原因: {', '.join(signal['reasons'][:5])}") # 最多显示5个原因
# 输出权重详情
weights = signal.get('signal_weights', {})
if weights:
logger.info(f" 权重: 买入={weights.get('buy', 0):.1f} | 卖出={weights.get('sell', 0):.1f}")
# 输出K线形态
patterns = signal.get('patterns', {})
if patterns.get('bullish_patterns') or patterns.get('bearish_patterns'):
all_patterns = patterns.get('bullish_patterns', []) + patterns.get('bearish_patterns', [])
logger.info(f" 形态: {', '.join(all_patterns)}")
# 输出成交量分析
vol = signal.get('volume_analysis', {})
if vol:
vol_icon = {'high': '📈', 'low': '📉', 'normal': ''}.get(vol.get('volume_signal', 'normal'), '')
confirm_text = '✓ 确认' if vol.get('volume_confirms') else '✗ 未确认'
logger.info(f" 成交量: {vol_icon} {vol.get('volume_signal', 'normal')} | {confirm_text}")
# 输出支撑阻力位
levels = signal.get('levels', {})
if levels.get('nearest_support') or levels.get('nearest_resistance'):
support = f"${levels['nearest_support']:,.2f}" if levels.get('nearest_support') else '-'
resistance = f"${levels['nearest_resistance']:,.2f}" if levels.get('nearest_resistance') else '-'
logger.info(f" 关键位: 支撑={support} | 阻力={resistance}")
# 5. 检查是否需要发送信号 # 5. 检查是否需要发送信号
if self._should_send_signal(symbol, signal): if self._should_send_signal(symbol, signal):
logger.info(f"\n📤 【发送通知】")
# 6. 计算止损止盈 # 6. 计算止损止盈
atr = float(data['15m'].iloc[-1].get('atr', 0)) atr = float(data['15m'].iloc[-1].get('atr', 0))
if atr > 0: if atr > 0:
@ -143,9 +213,11 @@ class CryptoAgent:
signal['price'], signal['action'], atr signal['price'], signal['action'], atr
) )
signal.update(sl_tp) signal.update(sl_tp)
logger.info(f" 止损: ${signal['stop_loss']:,.2f} | 止盈: ${signal['take_profit']:,.2f}")
# 7. LLM 深度分析(置信度超过阈值时) # 7. LLM 深度分析(置信度超过阈值时)
if signal['confidence'] >= self.llm_threshold * 100: if signal['confidence'] >= self.llm_threshold * 100:
logger.info(f" 🤖 触发 LLM 深度分析...")
llm_result = await self.analyzer.llm_analyze(data, signal, symbol) llm_result = await self.analyzer.llm_analyze(data, signal, symbol)
# 处理 LLM 分析结果 # 处理 LLM 分析结果
@ -158,6 +230,7 @@ class CryptoAgent:
if recommendation.get('action') == 'wait': if recommendation.get('action') == 'wait':
signal['confidence'] = min(signal['confidence'], 40) signal['confidence'] = min(signal['confidence'], 40)
signal['llm_analysis'] = llm_result.get('summary', 'LLM 建议观望') signal['llm_analysis'] = llm_result.get('summary', 'LLM 建议观望')
logger.info(f" 🤖 LLM 建议: 观望")
else: else:
# 使用 LLM 的止损止盈建议 # 使用 LLM 的止损止盈建议
if recommendation.get('stop_loss'): if recommendation.get('stop_loss'):
@ -167,6 +240,7 @@ class CryptoAgent:
elif recommendation.get('take_profit'): elif recommendation.get('take_profit'):
signal['take_profit'] = recommendation['take_profit'] signal['take_profit'] = recommendation['take_profit']
signal['llm_analysis'] = llm_result.get('summary', '') signal['llm_analysis'] = llm_result.get('summary', '')
logger.info(f" 🤖 LLM 建议: {recommendation.get('action', 'N/A')}")
else: else:
signal['llm_analysis'] = llm_result.get('summary', llm_result.get('raw', '')[:200]) signal['llm_analysis'] = llm_result.get('summary', llm_result.get('raw', '')[:200])
@ -178,10 +252,34 @@ class CryptoAgent:
self.last_signals[symbol] = signal self.last_signals[symbol] = signal
self.signal_cooldown[symbol] = datetime.now() self.signal_cooldown[symbol] = datetime.now()
logger.info(f"{symbol} 发送{signal['action']}信号,置信度: {signal['confidence']}%") action_text = '买入' if signal['action'] == 'buy' else '卖出'
logger.info(f" ✅ 已发送 {action_text} 信号通知")
else:
logger.info(f" ⏸️ 置信度不足({signal['confidence']}%),不发送通知")
else:
# 输出为什么不发送
if signal['action'] == 'hold':
logger.info(f"\n⏸️ 结论: 观望,无交易机会")
elif signal['confidence'] < 50:
logger.info(f"\n⏸️ 结论: 置信度不足({signal['confidence']}%),继续观望")
else:
logger.info(f"\n⏸️ 结论: 信号冷却中,跳过")
except Exception as e: except Exception as e:
logger.error(f"分析 {symbol} 出错: {e}") logger.error(f"❌ 分析 {symbol} 出错: {e}")
import traceback
logger.error(traceback.format_exc())
def _calculate_price_change(self, h1_data: pd.DataFrame) -> str:
"""计算24小时价格变化"""
if len(h1_data) < 24:
return "N/A"
price_now = h1_data.iloc[-1]['close']
price_24h_ago = h1_data.iloc[-24]['close']
change = ((price_now - price_24h_ago) / price_24h_ago) * 100
if change >= 0:
return f"+{change:.2f}%"
return f"{change:.2f}%"
def _validate_data(self, data: Dict[str, pd.DataFrame]) -> bool: def _validate_data(self, data: Dict[str, pd.DataFrame]) -> bool:
"""验证数据完整性""" """验证数据完整性"""

View File

@ -85,6 +85,376 @@ class SignalAnalyzer:
"""初始化信号分析器""" """初始化信号分析器"""
logger.info("信号分析器初始化完成") logger.info("信号分析器初始化完成")
# ==================== K线形态识别 ====================
def _detect_candlestick_patterns(self, df: pd.DataFrame) -> Dict[str, Any]:
"""
识别 K 线形态
Args:
df: K线数据至少需要3根K线
Returns:
{
'bullish_patterns': [...], # 看涨形态
'bearish_patterns': [...], # 看跌形态
'pattern_weight': float # 形态权重
}
"""
if len(df) < 3:
return {'bullish_patterns': [], 'bearish_patterns': [], 'pattern_weight': 0}
bullish = []
bearish = []
weight = 0
# 获取最近3根K线
curr = df.iloc[-1]
prev = df.iloc[-2]
prev2 = df.iloc[-3]
curr_body = curr['close'] - curr['open']
curr_body_abs = abs(curr_body)
curr_range = curr['high'] - curr['low']
prev_body = prev['close'] - prev['open']
prev_body_abs = abs(prev_body)
# 避免除零
if curr_range == 0:
curr_range = 0.0001
# === 锤子线 / 倒锤子线 ===
upper_shadow = curr['high'] - max(curr['open'], curr['close'])
lower_shadow = min(curr['open'], curr['close']) - curr['low']
# 锤子线:下影线长,实体小,出现在下跌后
if lower_shadow > curr_body_abs * 2 and upper_shadow < curr_body_abs * 0.5:
if prev_body < 0: # 前一根是阴线
bullish.append("锤子线")
weight += 1.5
# 倒锤子线:上影线长,实体小,出现在下跌后
if upper_shadow > curr_body_abs * 2 and lower_shadow < curr_body_abs * 0.5:
if prev_body < 0:
bullish.append("倒锤子线")
weight += 1
# 上吊线:锤子线形态但出现在上涨后
if lower_shadow > curr_body_abs * 2 and upper_shadow < curr_body_abs * 0.5:
if prev_body > 0:
bearish.append("上吊线")
weight -= 1.5
# === 吞没形态 ===
# 看涨吞没:阳线实体完全包住前一根阴线
if curr_body > 0 and prev_body < 0:
if curr['open'] <= prev['close'] and curr['close'] >= prev['open']:
if curr_body_abs > prev_body_abs * 1.2:
bullish.append("看涨吞没")
weight += 2
# 看跌吞没:阴线实体完全包住前一根阳线
if curr_body < 0 and prev_body > 0:
if curr['open'] >= prev['close'] and curr['close'] <= prev['open']:
if curr_body_abs > prev_body_abs * 1.2:
bearish.append("看跌吞没")
weight -= 2
# === 十字星 ===
if curr_body_abs < curr_range * 0.1: # 实体很小
if upper_shadow > curr_range * 0.3 and lower_shadow > curr_range * 0.3:
# 十字星本身是中性的需要结合前一根K线判断
if prev_body > 0:
bearish.append("十字星(上涨后)")
weight -= 1
elif prev_body < 0:
bullish.append("十字星(下跌后)")
weight += 1
# === 早晨之星 / 黄昏之星 (3根K线形态) ===
prev2_body = prev2['close'] - prev2['open']
prev_range = prev['high'] - prev['low'] if prev['high'] != prev['low'] else 0.0001
# 早晨之星:大阴线 + 小实体(星) + 大阳线
if prev2_body < 0 and abs(prev2_body) > prev_range * 0.5: # 第一根大阴线
if abs(prev_body) < prev_range * 0.3: # 第二根小实体
if curr_body > 0 and curr_body_abs > curr_range * 0.5: # 第三根大阳线
if curr['close'] > (prev2['open'] + prev2['close']) / 2:
bullish.append("早晨之星")
weight += 2.5
# 黄昏之星:大阳线 + 小实体(星) + 大阴线
if prev2_body > 0 and prev2_body > prev_range * 0.5:
if abs(prev_body) < prev_range * 0.3:
if curr_body < 0 and curr_body_abs > curr_range * 0.5:
if curr['close'] < (prev2['open'] + prev2['close']) / 2:
bearish.append("黄昏之星")
weight -= 2.5
return {
'bullish_patterns': bullish,
'bearish_patterns': bearish,
'pattern_weight': weight
}
# ==================== 支撑阻力位计算 ====================
def _calculate_support_resistance(self, df: pd.DataFrame, current_price: float) -> Dict[str, Any]:
"""
计算支撑位和阻力位
Args:
df: K线数据建议使用1H或4H数据
current_price: 当前价格
Returns:
{
'supports': [支撑位1, 支撑位2],
'resistances': [阻力位1, 阻力位2],
'nearest_support': float,
'nearest_resistance': float,
'at_support': bool,
'at_resistance': bool
}
"""
if len(df) < 20:
return {
'supports': [], 'resistances': [],
'nearest_support': 0, 'nearest_resistance': 0,
'at_support': False, 'at_resistance': False
}
# 方法1使用近期高低点
highs = df['high'].tail(50).values
lows = df['low'].tail(50).values
# 找局部高点和低点
local_highs = []
local_lows = []
for i in range(2, len(highs) - 2):
# 局部高点
if highs[i] > highs[i-1] and highs[i] > highs[i-2] and \
highs[i] > highs[i+1] and highs[i] > highs[i+2]:
local_highs.append(highs[i])
# 局部低点
if lows[i] < lows[i-1] and lows[i] < lows[i-2] and \
lows[i] < lows[i+1] and lows[i] < lows[i+2]:
local_lows.append(lows[i])
# 方法2使用均线作为动态支撑阻力
ma20 = df['ma20'].iloc[-1] if 'ma20' in df.columns and pd.notna(df['ma20'].iloc[-1]) else 0
ma50 = df['ma50'].iloc[-1] if 'ma50' in df.columns and pd.notna(df['ma50'].iloc[-1]) else 0
# 方法3布林带
bb_upper = df['bb_upper'].iloc[-1] if 'bb_upper' in df.columns and pd.notna(df['bb_upper'].iloc[-1]) else 0
bb_lower = df['bb_lower'].iloc[-1] if 'bb_lower' in df.columns and pd.notna(df['bb_lower'].iloc[-1]) else 0
# 合并所有支撑位(低于当前价格)
all_supports = []
if local_lows:
all_supports.extend([l for l in local_lows if l < current_price])
if ma20 and ma20 < current_price:
all_supports.append(ma20)
if ma50 and ma50 < current_price:
all_supports.append(ma50)
if bb_lower and bb_lower < current_price:
all_supports.append(bb_lower)
# 合并所有阻力位(高于当前价格)
all_resistances = []
if local_highs:
all_resistances.extend([h for h in local_highs if h > current_price])
if ma20 and ma20 > current_price:
all_resistances.append(ma20)
if ma50 and ma50 > current_price:
all_resistances.append(ma50)
if bb_upper and bb_upper > current_price:
all_resistances.append(bb_upper)
# 排序并去重(合并相近的价位)
supports = sorted(set(all_supports), reverse=True)[:3] # 最近的3个支撑
resistances = sorted(set(all_resistances))[:3] # 最近的3个阻力
# 找最近的支撑和阻力
nearest_support = supports[0] if supports else 0
nearest_resistance = resistances[0] if resistances else 0
# 判断是否在支撑/阻力位附近1%范围内)
at_support = nearest_support > 0 and abs(current_price - nearest_support) / current_price < 0.01
at_resistance = nearest_resistance > 0 and abs(current_price - nearest_resistance) / current_price < 0.01
return {
'supports': supports,
'resistances': resistances,
'nearest_support': nearest_support,
'nearest_resistance': nearest_resistance,
'at_support': at_support,
'at_resistance': at_resistance
}
# ==================== 成交量分析 ====================
def _analyze_volume(self, df: pd.DataFrame) -> Dict[str, Any]:
"""
分析成交量
Args:
df: K线数据需要包含 volume, volume_ma20, volume_ratio
Returns:
{
'volume_signal': 'high' | 'normal' | 'low',
'volume_trend': 'increasing' | 'decreasing' | 'stable',
'volume_confirms': bool, # 成交量是否确认价格走势
'volume_weight': float
}
"""
if len(df) < 5 or 'volume' not in df.columns:
return {
'volume_signal': 'normal',
'volume_trend': 'stable',
'volume_confirms': False,
'volume_weight': 0
}
latest = df.iloc[-1]
prev = df.iloc[-2]
# 量比判断
volume_ratio = latest.get('volume_ratio', 1)
if pd.isna(volume_ratio):
volume_ratio = 1
if volume_ratio > 2:
volume_signal = 'high'
elif volume_ratio < 0.5:
volume_signal = 'low'
else:
volume_signal = 'normal'
# 成交量趋势最近5根K线
recent_volumes = df['volume'].tail(5)
if len(recent_volumes) >= 5:
first_half = recent_volumes.iloc[:2].mean()
second_half = recent_volumes.iloc[-2:].mean()
if second_half > first_half * 1.3:
volume_trend = 'increasing'
elif second_half < first_half * 0.7:
volume_trend = 'decreasing'
else:
volume_trend = 'stable'
else:
volume_trend = 'stable'
# 判断成交量是否确认价格走势
price_up = latest['close'] > prev['close']
volume_up = latest['volume'] > prev['volume']
# 价涨量增 或 价跌量缩 = 确认
volume_confirms = (price_up and volume_up) or (not price_up and not volume_up)
# 计算权重
weight = 0
if volume_signal == 'high' and volume_confirms:
weight = 1.5
elif volume_signal == 'high' and not volume_confirms:
weight = -0.5 # 放量但不确认,可能是假突破
elif volume_signal == 'low':
weight = -0.5 # 缩量,信号可靠性降低
return {
'volume_signal': volume_signal,
'volume_trend': volume_trend,
'volume_confirms': volume_confirms,
'volume_weight': weight
}
# ==================== 5M 精确入场 ====================
def _analyze_5m_entry(self, m5_data: pd.DataFrame, action: str) -> Dict[str, Any]:
"""
使用 5M 数据寻找精确入场点
Args:
m5_data: 5分钟K线数据
action: 'buy' 'sell'
Returns:
{
'entry_confirmed': bool,
'entry_reasons': [...],
'entry_weight': float
}
"""
if len(m5_data) < 5:
return {'entry_confirmed': False, 'entry_reasons': [], 'entry_weight': 0}
latest = m5_data.iloc[-1]
prev = m5_data.iloc[-2]
reasons = []
weight = 0
# 获取指标
rsi = latest.get('rsi', 50)
prev_rsi = prev.get('rsi', 50)
macd = latest.get('macd', 0)
macd_signal = latest.get('macd_signal', 0)
prev_macd = prev.get('macd', 0)
prev_macd_signal = prev.get('macd_signal', 0)
if action == 'buy':
# 5M RSI 从超卖回升
if pd.notna(rsi) and pd.notna(prev_rsi):
if rsi < 40 and rsi > prev_rsi:
reasons.append("5M RSI回升")
weight += 1
if rsi < 30:
reasons.append("5M RSI超卖")
weight += 0.5
# 5M MACD 金叉
if pd.notna(macd) and pd.notna(prev_macd):
if prev_macd <= prev_macd_signal and macd > macd_signal:
reasons.append("5M MACD金叉")
weight += 1.5
# 5M K线企稳阳线
if latest['close'] > latest['open']:
if prev['close'] < prev['open']: # 前一根是阴线
reasons.append("5M阳线反转")
weight += 1
elif action == 'sell':
# 5M RSI 从超买回落
if pd.notna(rsi) and pd.notna(prev_rsi):
if rsi > 60 and rsi < prev_rsi:
reasons.append("5M RSI回落")
weight += 1
if rsi > 70:
reasons.append("5M RSI超买")
weight += 0.5
# 5M MACD 死叉
if pd.notna(macd) and pd.notna(prev_macd):
if prev_macd >= prev_macd_signal and macd < macd_signal:
reasons.append("5M MACD死叉")
weight += 1.5
# 5M K线见顶阴线
if latest['close'] < latest['open']:
if prev['close'] > prev['open']:
reasons.append("5M阴线反转")
weight += 1
entry_confirmed = weight >= 2
return {
'entry_confirmed': entry_confirmed,
'entry_reasons': reasons,
'entry_weight': weight
}
def analyze_trend(self, h1_data: pd.DataFrame, h4_data: pd.DataFrame) -> Dict[str, Any]: def analyze_trend(self, h1_data: pd.DataFrame, h4_data: pd.DataFrame) -> Dict[str, Any]:
""" """
分析趋势方向和强度波段交易优化版 分析趋势方向和强度波段交易优化版
@ -280,14 +650,21 @@ class SignalAnalyzer:
return final_score, detail_str return final_score, detail_str
def analyze_entry_signal(self, m5_data: pd.DataFrame, m15_data: pd.DataFrame, def analyze_entry_signal(self, m5_data: pd.DataFrame, m15_data: pd.DataFrame,
trend: Dict[str, Any]) -> Dict[str, Any]: trend: Dict[str, Any], h1_data: pd.DataFrame = None) -> Dict[str, Any]:
""" """
分析 15M 进场信号波段交易优化版 分析 15M 进场信号波段交易优化版 - 增强版
新增功能
- 成交量确认
- K线形态识别
- 支撑阻力位判断
- 5M精确入场
Args: Args:
m5_data: 5分钟K线数据用于精确入场 m5_data: 5分钟K线数据用于精确入场
m15_data: 15分钟K线数据主要入场周期 m15_data: 15分钟K线数据主要入场周期
trend: 趋势分析结果 trend: 趋势分析结果
h1_data: 1小时K线数据用于支撑阻力位计算可选
Returns: Returns:
{ {
@ -295,7 +672,10 @@ class SignalAnalyzer:
'confidence': 0-100, 'confidence': 0-100,
'signal_grade': 'A' | 'B' | 'C' | 'D', 'signal_grade': 'A' | 'B' | 'C' | 'D',
'reasons': [...], 'reasons': [...],
'indicators': {...} 'indicators': {...},
'patterns': {...},
'levels': {...},
'volume_analysis': {...}
} }
""" """
if m5_data.empty or m15_data.empty: if m5_data.empty or m15_data.empty:
@ -313,12 +693,15 @@ class SignalAnalyzer:
trend_strength = trend.get('strength', 'moderate') trend_strength = trend.get('strength', 'moderate')
m15_latest = m15_data.iloc[-1] m15_latest = m15_data.iloc[-1]
current_price = float(m15_latest['close'])
# 收集信号 # 收集信号
buy_signals = [] buy_signals = []
sell_signals = [] sell_signals = []
signal_weights = {'buy': 0, 'sell': 0} signal_weights = {'buy': 0, 'sell': 0}
# ==================== 1. 传统技术指标信号 ====================
# === RSI 信号 === # === RSI 信号 ===
if 'rsi' in m15_latest and pd.notna(m15_latest['rsi']): if 'rsi' in m15_latest and pd.notna(m15_latest['rsi']):
rsi = m15_latest['rsi'] rsi = m15_latest['rsi']
@ -326,7 +709,6 @@ class SignalAnalyzer:
buy_signals.append(f"RSI超卖({rsi:.1f})") buy_signals.append(f"RSI超卖({rsi:.1f})")
signal_weights['buy'] += 2 signal_weights['buy'] += 2
elif rsi < 40 and len(m15_data) >= 2: elif rsi < 40 and len(m15_data) >= 2:
# RSI 从低位回升
prev_rsi = m15_data.iloc[-2].get('rsi', 50) prev_rsi = m15_data.iloc[-2].get('rsi', 50)
if pd.notna(prev_rsi) and rsi > prev_rsi: if pd.notna(prev_rsi) and rsi > prev_rsi:
buy_signals.append(f"RSI回升({prev_rsi:.1f}{rsi:.1f})") buy_signals.append(f"RSI回升({prev_rsi:.1f}{rsi:.1f})")
@ -345,15 +727,12 @@ class SignalAnalyzer:
prev = m15_data.iloc[-2] prev = m15_data.iloc[-2]
if 'macd' in m15_latest and 'macd_signal' in m15_latest: if 'macd' in m15_latest and 'macd_signal' in m15_latest:
if pd.notna(m15_latest['macd']) and pd.notna(prev['macd']): if pd.notna(m15_latest['macd']) and pd.notna(prev['macd']):
# 金叉
if prev['macd'] <= prev['macd_signal'] and m15_latest['macd'] > m15_latest['macd_signal']: if prev['macd'] <= prev['macd_signal'] and m15_latest['macd'] > m15_latest['macd_signal']:
buy_signals.append("MACD金叉") buy_signals.append("MACD金叉")
signal_weights['buy'] += 2 signal_weights['buy'] += 2
# 死叉
elif prev['macd'] >= prev['macd_signal'] and m15_latest['macd'] < m15_latest['macd_signal']: elif prev['macd'] >= prev['macd_signal'] and m15_latest['macd'] < m15_latest['macd_signal']:
sell_signals.append("MACD死叉") sell_signals.append("MACD死叉")
signal_weights['sell'] += 2 signal_weights['sell'] += 2
# MACD 柱状图缩小(趋势减弱)
elif abs(m15_latest['macd_hist']) < abs(prev['macd_hist']) * 0.7: elif abs(m15_latest['macd_hist']) < abs(prev['macd_hist']) * 0.7:
if m15_latest['macd_hist'] > 0: if m15_latest['macd_hist'] > 0:
sell_signals.append("MACD动能减弱") sell_signals.append("MACD动能减弱")
@ -372,7 +751,6 @@ class SignalAnalyzer:
elif m15_latest['close'] > m15_latest['bb_upper']: elif m15_latest['close'] > m15_latest['bb_upper']:
sell_signals.append("触及布林上轨") sell_signals.append("触及布林上轨")
signal_weights['sell'] += 1.5 signal_weights['sell'] += 1.5
# 突破中轨
elif len(m15_data) >= 2: elif len(m15_data) >= 2:
prev_close = m15_data.iloc[-2]['close'] prev_close = m15_data.iloc[-2]['close']
if prev_close < bb_middle and m15_latest['close'] > bb_middle: if prev_close < bb_middle and m15_latest['close'] > bb_middle:
@ -401,7 +779,47 @@ class SignalAnalyzer:
sell_signals.append("KDJ死叉") sell_signals.append("KDJ死叉")
signal_weights['sell'] += 0.5 signal_weights['sell'] += 0.5
# === 根据趋势和阶段决定动作 === # ==================== 2. K线形态识别 ====================
patterns = self._detect_candlestick_patterns(m15_data)
if patterns['bullish_patterns']:
buy_signals.extend(patterns['bullish_patterns'])
signal_weights['buy'] += patterns['pattern_weight']
if patterns['bearish_patterns']:
sell_signals.extend(patterns['bearish_patterns'])
signal_weights['sell'] += abs(patterns['pattern_weight'])
# ==================== 3. 成交量分析 ====================
volume_analysis = self._analyze_volume(m15_data)
if volume_analysis['volume_signal'] == 'high':
if volume_analysis['volume_confirms']:
# 放量确认
if signal_weights['buy'] > signal_weights['sell']:
buy_signals.append(f"放量确认(量比{m15_latest.get('volume_ratio', 1):.1f})")
signal_weights['buy'] += volume_analysis['volume_weight']
else:
sell_signals.append(f"放量确认(量比{m15_latest.get('volume_ratio', 1):.1f})")
signal_weights['sell'] += volume_analysis['volume_weight']
else:
# 放量不确认,可能是假信号
if signal_weights['buy'] > signal_weights['sell']:
buy_signals.append("放量但不确认(警惕)")
signal_weights['buy'] += volume_analysis['volume_weight'] # 负权重
else:
sell_signals.append("放量但不确认(警惕)")
signal_weights['sell'] += volume_analysis['volume_weight']
# ==================== 4. 支撑阻力位分析 ====================
levels = {}
if h1_data is not None and not h1_data.empty:
levels = self._calculate_support_resistance(h1_data, current_price)
if levels.get('at_support') and trend_direction == 'bullish':
buy_signals.append(f"触及支撑位({levels['nearest_support']:.2f})")
signal_weights['buy'] += 1.5
if levels.get('at_resistance') and trend_direction == 'bearish':
sell_signals.append(f"触及阻力位({levels['nearest_resistance']:.2f})")
signal_weights['sell'] += 1.5
# ==================== 5. 根据趋势和阶段决定动作 ====================
action = 'hold' action = 'hold'
confidence = 0 confidence = 0
reasons = [] reasons = []
@ -410,13 +828,11 @@ class SignalAnalyzer:
# 波段交易核心逻辑:在回调中寻找入场机会 # 波段交易核心逻辑:在回调中寻找入场机会
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 + ["主升浪追多"]
@ -426,7 +842,6 @@ class SignalAnalyzer:
elif trend_direction == 'bearish': elif trend_direction == 'bearish':
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})"]
@ -440,18 +855,36 @@ class SignalAnalyzer:
reasons = ['极端行情,等待企稳'] reasons = ['极端行情,等待企稳']
else: # neutral else: # neutral
# 震荡市不交易
reasons = ['趋势不明确,观望'] reasons = ['趋势不明确,观望']
# ==================== 6. 5M 精确入场确认 ====================
if action != 'hold' and not m5_data.empty:
entry_5m = self._analyze_5m_entry(m5_data, action)
if entry_5m['entry_confirmed']:
confidence = min(confidence + 10, 95)
reasons.extend(entry_5m['entry_reasons'])
if signal_grade == 'B':
signal_grade = 'A'
elif signal_grade == 'C':
signal_grade = 'B'
elif entry_5m['entry_weight'] < 1:
# 5M 没有确认,降低置信度
confidence = max(confidence - 10, 0)
reasons.append("5M未确认入场")
if not reasons: if not reasons:
reasons = ['信号不足,继续观望'] reasons = ['信号不足,继续观望']
# 收集指标数据 # 收集指标数据
indicators = {} indicators = {}
for col in ['rsi', 'macd', 'macd_signal', 'macd_hist', 'k', 'd', 'j', 'close', 'ma20']: for col in ['rsi', 'macd', 'macd_signal', 'macd_hist', 'k', 'd', 'j', 'close', 'ma20', 'volume_ratio']:
if col in m15_latest and pd.notna(m15_latest[col]): if col in m15_latest and pd.notna(m15_latest[col]):
indicators[col] = float(m15_latest[col]) indicators[col] = float(m15_latest[col])
# 记录详细日志(简化版,详细日志在 crypto_agent 中输出)
if action != 'hold':
logger.debug(f"信号详情: {action} {confidence}% {signal_grade} | 买权重={signal_weights['buy']:.1f} 卖权重={signal_weights['sell']:.1f}")
return { return {
'action': action, 'action': action,
'confidence': confidence, 'confidence': confidence,
@ -462,7 +895,11 @@ class SignalAnalyzer:
'direction': trend_direction, 'direction': trend_direction,
'phase': trend_phase, 'phase': trend_phase,
'strength': trend_strength 'strength': trend_strength
} },
'patterns': patterns,
'volume_analysis': volume_analysis,
'levels': levels,
'signal_weights': signal_weights
} }
async def llm_analyze(self, data: Dict[str, pd.DataFrame], signal: Dict[str, Any], async def llm_analyze(self, data: Dict[str, pd.DataFrame], signal: Dict[str, Any],

View File

@ -143,6 +143,11 @@ class BinanceService:
# ATR # ATR
df['atr'] = self._calculate_atr(df['high'], df['low'], df['close']) df['atr'] = self._calculate_atr(df['high'], df['low'], df['close'])
# 成交量均线
df['volume_ma5'] = self._calculate_ma(df['volume'], 5)
df['volume_ma20'] = self._calculate_ma(df['volume'], 20)
df['volume_ratio'] = df['volume'] / df['volume_ma20'] # 量比
return df return df
@staticmethod @staticmethod