update
This commit is contained in:
parent
2f246491e9
commit
993666eed5
@ -59,7 +59,15 @@ class CryptoAgent:
|
||||
async def run(self):
|
||||
"""主运行循环 - 在5的倍数分钟执行"""
|
||||
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(
|
||||
@ -74,21 +82,27 @@ class CryptoAgent:
|
||||
wait_seconds = self._get_seconds_until_next_5min()
|
||||
if wait_seconds > 0:
|
||||
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)
|
||||
|
||||
# 执行分析
|
||||
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:
|
||||
await self.analyze_symbol(symbol)
|
||||
|
||||
logger.info("\n" + "─" * 60)
|
||||
logger.info(f"✅ 本轮分析完成,共分析 {len(self.symbols)} 个交易对")
|
||||
logger.info("─" * 60 + "\n")
|
||||
|
||||
# 等待几秒确保不会在同一分钟内重复执行
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"分析循环出错: {e}")
|
||||
logger.error(f"❌ 分析循环出错: {e}")
|
||||
await asyncio.sleep(10) # 出错后等待10秒再继续
|
||||
|
||||
def stop(self):
|
||||
@ -104,18 +118,36 @@ class CryptoAgent:
|
||||
symbol: 交易对,如 'BTCUSDT'
|
||||
"""
|
||||
try:
|
||||
logger.info(f"开始分析 {symbol}...")
|
||||
# 分隔线
|
||||
logger.info(f"\n{'─' * 50}")
|
||||
logger.info(f"📊 {symbol} 分析开始")
|
||||
logger.info(f"{'─' * 50}")
|
||||
|
||||
# 1. 获取多周期数据
|
||||
data = self.binance.get_multi_timeframe_data(symbol)
|
||||
|
||||
if not self._validate_data(data):
|
||||
logger.warning(f"{symbol} 数据不完整,跳过分析")
|
||||
logger.warning(f"⚠️ {symbol} 数据不完整,跳过分析")
|
||||
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)- 返回详细趋势信息
|
||||
logger.info(f"\n📈 【趋势分析】")
|
||||
trend = self.analyzer.analyze_trend(data['1h'], data['4h'])
|
||||
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. 检查趋势变化
|
||||
last_direction = self.last_trends.get(symbol, {})
|
||||
@ -126,16 +158,54 @@ class CryptoAgent:
|
||||
|
||||
self.last_trends[symbol] = trend
|
||||
|
||||
# 4. 分析进场信号(15M 为主,5M 辅助)
|
||||
signal = self.analyzer.analyze_entry_signal(data['5m'], data['15m'], trend)
|
||||
# 4. 分析进场信号(15M 为主,5M 辅助,传入 1H 数据用于支撑阻力位)
|
||||
logger.info(f"\n🎯 【信号分析】")
|
||||
signal = self.analyzer.analyze_entry_signal(data['5m'], data['15m'], trend, data['1h'])
|
||||
signal['symbol'] = symbol
|
||||
signal['trend'] = trend_direction
|
||||
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()
|
||||
|
||||
# 输出信号详情
|
||||
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. 检查是否需要发送信号
|
||||
if self._should_send_signal(symbol, signal):
|
||||
logger.info(f"\n📤 【发送通知】")
|
||||
|
||||
# 6. 计算止损止盈
|
||||
atr = float(data['15m'].iloc[-1].get('atr', 0))
|
||||
if atr > 0:
|
||||
@ -143,9 +213,11 @@ class CryptoAgent:
|
||||
signal['price'], signal['action'], atr
|
||||
)
|
||||
signal.update(sl_tp)
|
||||
logger.info(f" 止损: ${signal['stop_loss']:,.2f} | 止盈: ${signal['take_profit']:,.2f}")
|
||||
|
||||
# 7. LLM 深度分析(置信度超过阈值时)
|
||||
if signal['confidence'] >= self.llm_threshold * 100:
|
||||
logger.info(f" 🤖 触发 LLM 深度分析...")
|
||||
llm_result = await self.analyzer.llm_analyze(data, signal, symbol)
|
||||
|
||||
# 处理 LLM 分析结果
|
||||
@ -158,6 +230,7 @@ class CryptoAgent:
|
||||
if recommendation.get('action') == 'wait':
|
||||
signal['confidence'] = min(signal['confidence'], 40)
|
||||
signal['llm_analysis'] = llm_result.get('summary', 'LLM 建议观望')
|
||||
logger.info(f" 🤖 LLM 建议: 观望")
|
||||
else:
|
||||
# 使用 LLM 的止损止盈建议
|
||||
if recommendation.get('stop_loss'):
|
||||
@ -167,6 +240,7 @@ class CryptoAgent:
|
||||
elif recommendation.get('take_profit'):
|
||||
signal['take_profit'] = recommendation['take_profit']
|
||||
signal['llm_analysis'] = llm_result.get('summary', '')
|
||||
logger.info(f" 🤖 LLM 建议: {recommendation.get('action', 'N/A')}")
|
||||
else:
|
||||
signal['llm_analysis'] = llm_result.get('summary', llm_result.get('raw', '')[:200])
|
||||
|
||||
@ -178,10 +252,34 @@ class CryptoAgent:
|
||||
self.last_signals[symbol] = signal
|
||||
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:
|
||||
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:
|
||||
"""验证数据完整性"""
|
||||
|
||||
@ -85,6 +85,376 @@ class SignalAnalyzer:
|
||||
"""初始化信号分析器"""
|
||||
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]:
|
||||
"""
|
||||
分析趋势方向和强度(波段交易优化版)
|
||||
@ -280,14 +650,21 @@ class SignalAnalyzer:
|
||||
return final_score, detail_str
|
||||
|
||||
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:
|
||||
m5_data: 5分钟K线数据(用于精确入场)
|
||||
m15_data: 15分钟K线数据(主要入场周期)
|
||||
trend: 趋势分析结果
|
||||
h1_data: 1小时K线数据(用于支撑阻力位计算,可选)
|
||||
|
||||
Returns:
|
||||
{
|
||||
@ -295,7 +672,10 @@ class SignalAnalyzer:
|
||||
'confidence': 0-100,
|
||||
'signal_grade': 'A' | 'B' | 'C' | 'D',
|
||||
'reasons': [...],
|
||||
'indicators': {...}
|
||||
'indicators': {...},
|
||||
'patterns': {...},
|
||||
'levels': {...},
|
||||
'volume_analysis': {...}
|
||||
}
|
||||
"""
|
||||
if m5_data.empty or m15_data.empty:
|
||||
@ -313,12 +693,15 @@ class SignalAnalyzer:
|
||||
trend_strength = trend.get('strength', 'moderate')
|
||||
|
||||
m15_latest = m15_data.iloc[-1]
|
||||
current_price = float(m15_latest['close'])
|
||||
|
||||
# 收集信号
|
||||
buy_signals = []
|
||||
sell_signals = []
|
||||
signal_weights = {'buy': 0, 'sell': 0}
|
||||
|
||||
# ==================== 1. 传统技术指标信号 ====================
|
||||
|
||||
# === RSI 信号 ===
|
||||
if 'rsi' in m15_latest and pd.notna(m15_latest['rsi']):
|
||||
rsi = m15_latest['rsi']
|
||||
@ -326,7 +709,6 @@ class SignalAnalyzer:
|
||||
buy_signals.append(f"RSI超卖({rsi:.1f})")
|
||||
signal_weights['buy'] += 2
|
||||
elif rsi < 40 and len(m15_data) >= 2:
|
||||
# RSI 从低位回升
|
||||
prev_rsi = m15_data.iloc[-2].get('rsi', 50)
|
||||
if pd.notna(prev_rsi) and rsi > prev_rsi:
|
||||
buy_signals.append(f"RSI回升({prev_rsi:.1f}→{rsi:.1f})")
|
||||
@ -345,15 +727,12 @@ class SignalAnalyzer:
|
||||
prev = m15_data.iloc[-2]
|
||||
if 'macd' in m15_latest and 'macd_signal' in m15_latest:
|
||||
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']:
|
||||
buy_signals.append("MACD金叉")
|
||||
signal_weights['buy'] += 2
|
||||
# 死叉
|
||||
elif prev['macd'] >= prev['macd_signal'] and m15_latest['macd'] < m15_latest['macd_signal']:
|
||||
sell_signals.append("MACD死叉")
|
||||
signal_weights['sell'] += 2
|
||||
# MACD 柱状图缩小(趋势减弱)
|
||||
elif abs(m15_latest['macd_hist']) < abs(prev['macd_hist']) * 0.7:
|
||||
if m15_latest['macd_hist'] > 0:
|
||||
sell_signals.append("MACD动能减弱")
|
||||
@ -372,7 +751,6 @@ class SignalAnalyzer:
|
||||
elif m15_latest['close'] > m15_latest['bb_upper']:
|
||||
sell_signals.append("触及布林上轨")
|
||||
signal_weights['sell'] += 1.5
|
||||
# 突破中轨
|
||||
elif len(m15_data) >= 2:
|
||||
prev_close = m15_data.iloc[-2]['close']
|
||||
if prev_close < bb_middle and m15_latest['close'] > bb_middle:
|
||||
@ -401,7 +779,47 @@ class SignalAnalyzer:
|
||||
sell_signals.append("KDJ死叉")
|
||||
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'
|
||||
confidence = 0
|
||||
reasons = []
|
||||
@ -410,13 +828,11 @@ class SignalAnalyzer:
|
||||
# 波段交易核心逻辑:在回调中寻找入场机会
|
||||
if trend_direction == 'bullish':
|
||||
if trend_phase == 'correction' and signal_weights['buy'] >= 3:
|
||||
# 上涨趋势 + 回调 + 买入信号 = 最佳做多机会
|
||||
action = 'buy'
|
||||
confidence = min(40 + signal_weights['buy'] * 10, 95)
|
||||
reasons = buy_signals + [f"上涨趋势回调({trend_strength})"]
|
||||
signal_grade = 'A' if confidence >= 80 else ('B' if confidence >= 60 else 'C')
|
||||
elif trend_phase == 'impulse' and signal_weights['buy'] >= 4:
|
||||
# 主升浪中追多需要更强信号
|
||||
action = 'buy'
|
||||
confidence = min(30 + signal_weights['buy'] * 8, 80)
|
||||
reasons = buy_signals + ["主升浪追多"]
|
||||
@ -426,7 +842,6 @@ class SignalAnalyzer:
|
||||
|
||||
elif trend_direction == 'bearish':
|
||||
if trend_phase == 'correction' and signal_weights['sell'] >= 3:
|
||||
# 下跌趋势 + 反弹 + 卖出信号 = 最佳做空机会
|
||||
action = 'sell'
|
||||
confidence = min(40 + signal_weights['sell'] * 10, 95)
|
||||
reasons = sell_signals + [f"下跌趋势反弹({trend_strength})"]
|
||||
@ -440,18 +855,36 @@ class SignalAnalyzer:
|
||||
reasons = ['极端行情,等待企稳']
|
||||
|
||||
else: # neutral
|
||||
# 震荡市不交易
|
||||
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:
|
||||
reasons = ['信号不足,继续观望']
|
||||
|
||||
# 收集指标数据
|
||||
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]):
|
||||
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 {
|
||||
'action': action,
|
||||
'confidence': confidence,
|
||||
@ -462,7 +895,11 @@ class SignalAnalyzer:
|
||||
'direction': trend_direction,
|
||||
'phase': trend_phase,
|
||||
'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],
|
||||
|
||||
@ -143,6 +143,11 @@ class BinanceService:
|
||||
# ATR
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
|
||||
Loading…
Reference in New Issue
Block a user