This commit is contained in:
aaron 2026-02-24 01:50:56 +08:00
parent cf2dcfe2c7
commit efc0abf5cb
2 changed files with 132 additions and 4 deletions

View File

@ -675,7 +675,10 @@ class CryptoAgent:
decision_type = paper_decision.get('decision', 'HOLD')
if decision_type == 'HOLD':
logger.info(f"\n📊 模拟交易: {paper_decision.get('reasoning', '观望')}")
reasoning = paper_decision.get('reasoning', '观望')
logger.info(f"\n📊 模拟交易: {reasoning}")
# 有信号但决策为 HOLD发送未执行通知
await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True)
else:
logger.info(f"\n📊 【执行模拟交易】")
@ -689,7 +692,10 @@ class CryptoAgent:
await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True)
paper_executed = True
else:
logger.warning(f" ⚠️ 模拟交易未执行,跳过通知")
# 有信号但订单创建失败,发送未执行通知
reason = result.get('message', '订单创建失败') if result else '订单创建失败'
await self._notify_signal_not_executed(market_signal, paper_decision, current_price, is_paper=True, reason=reason)
logger.warning(f" ⚠️ 模拟交易未执行,已发送通知")
elif decision_type == 'CLOSE':
await self._execute_close(paper_decision, paper_trading=True)
paper_executed = True
@ -703,7 +709,10 @@ class CryptoAgent:
decision_type = real_decision.get('decision', 'HOLD')
if decision_type == 'HOLD':
logger.info(f"\n💰 实盘交易: {real_decision.get('reasoning', '观望')}")
reasoning = real_decision.get('reasoning', '观望')
logger.info(f"\n💰 实盘交易: {reasoning}")
# 有信号但决策为 HOLD发送未执行通知
await self._notify_signal_not_executed(market_signal, real_decision, current_price, is_paper=False)
else:
logger.info(f"\n💰 【执行实盘交易】")
@ -716,7 +725,10 @@ class CryptoAgent:
await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False)
real_executed = True
else:
logger.warning(f" ⚠️ 实盘交易未执行,跳过通知")
# 有信号但订单创建失败,发送未执行通知
reason = result.get('message', '订单创建失败') if result else '订单创建失败'
await self._notify_signal_not_executed(market_signal, real_decision, current_price, is_paper=False, reason=reason)
logger.warning(f" ⚠️ 实盘交易未执行,已发送通知")
elif decision_type == 'CLOSE':
await self._execute_close(real_decision, paper_trading=False)
real_executed = True
@ -1402,6 +1414,73 @@ class CryptoAgent:
if self.settings.telegram_enabled:
await self.telegram.send_message(message)
async def _notify_signal_not_executed(
self,
market_signal: Dict[str, Any],
decision: Dict[str, Any],
current_price: float,
is_paper: bool = True,
reason: str = ""
):
"""发送有信号但未执行交易的通知"""
try:
symbol = market_signal.get('symbol')
account_type = "📊 模拟" if is_paper else "💰 实盘"
# 获取最佳信号
best_signal = self._get_best_signal_from_market(market_signal)
if not best_signal:
return
confidence = best_signal.get('confidence', 0)
entry_type = best_signal.get('entry_type', 'market')
entry_zone = best_signal.get('entry_zone', current_price)
# 决策信息
decision_type = decision.get('decision', 'HOLD')
decision_reasoning = decision.get('reasoning', reason)
# 方向图标
action = best_signal.get('action', 'wait')
if action == 'buy':
action_icon = '🟢'
action_text = '做多'
elif action == 'sell':
action_icon = '🔴'
action_text = '做空'
else:
action_icon = ''
action_text = '观望'
# 构建标题
title = f"{account_type} {symbol} 信号未执行"
# 构建内容
content_parts = [
f"{action_icon} **信号**: {action_text} | 📈 信心度: **{confidence}%**",
f"",
f"**入场方式**: {entry_type}",
f"**建议入场价**: ${entry_zone:,.2f}" if isinstance(entry_zone, (int, float)) else f"**建议入场价**: {entry_zone}",
f"**当前价格**: ${current_price:,.2f}",
f"",
f"⚠️ **未执行原因**:",
f"{decision_reasoning}",
]
content = "\n".join(content_parts)
# 发送通知
if self.settings.feishu_enabled:
await self.feishu.send_card(title, content, "orange")
if self.settings.telegram_enabled:
message = f"{title}\n\n{content}"
await self.telegram.send_message(message)
logger.info(f" 📤 已发送信号未执行通知: {decision_type} - {decision_reasoning[:50]}")
except Exception as e:
logger.warning(f"发送信号未执行通知失败: {e}")
async def analyze_once(self, symbol: str) -> Dict[str, Any]:
"""单次分析(用于测试或手动触发)"""
data = self.binance.get_multi_timeframe_data(symbol)

View File

@ -13,6 +13,7 @@
"""
import json
import re
import pandas as pd
from typing import Dict, Any, Optional, List
from datetime import datetime
from app.utils.logger import logger
@ -329,6 +330,12 @@ class MarketSignalAnalyzer:
else:
context_parts.append("量价状态: 平量 ")
# 波动率分析
volatility_analysis = self._analyze_volatility(data)
if volatility_analysis:
context_parts.append(f"\n## 波动率分析")
context_parts.append(volatility_analysis)
return "\n".join(context_parts)
async def _get_news_context(self, symbol: str) -> str:
@ -544,3 +551,45 @@ class MarketSignalAnalyzer:
'timestamp': datetime.now().isoformat(),
'error': '信号分析失败'
}
def _analyze_volatility(self, data: Dict[str, pd.DataFrame]) -> str:
"""分析波动率变化"""
df = data.get('1h')
if df is None or len(df) < 24 or 'atr' not in df.columns:
return ""
lines = []
# ATR 变化趋势
recent_atr = df['atr'].iloc[-6:].mean() # 最近 6 根
older_atr = df['atr'].iloc[-12:-6].mean() # 之前 6 根
if pd.isna(recent_atr) or pd.isna(older_atr) or older_atr == 0:
return ""
atr_change = (recent_atr - older_atr) / older_atr * 100
current_atr = float(df['atr'].iloc[-1])
current_price = float(df['close'].iloc[-1])
atr_percent = current_atr / current_price * 100
lines.append(f"当前 ATR: ${current_atr:.2f} ({atr_percent:.2f}%)")
if atr_change > 20:
lines.append(f"**波动率扩张**: ATR 上升 {atr_change:.0f}%,趋势可能启动")
elif atr_change < -20:
lines.append(f"**波动率收缩**: ATR 下降 {abs(atr_change):.0f}%,可能即将突破")
else:
lines.append(f"波动率稳定: ATR 变化 {atr_change:+.0f}%")
# 布林带宽度
if 'bb_upper' in df.columns and 'bb_lower' in df.columns:
bb_width = (float(df['bb_upper'].iloc[-1]) - float(df['bb_lower'].iloc[-1])) / current_price * 100
bb_width_prev = (float(df['bb_upper'].iloc[-6]) - float(df['bb_lower'].iloc[-6])) / float(df['close'].iloc[-6]) * 100
if bb_width < bb_width_prev * 0.8:
lines.append(f"**布林带收口**: 宽度 {bb_width:.1f}%,变盘信号")
elif bb_width > bb_width_prev * 1.2:
lines.append(f"**布林带开口**: 宽度 {bb_width:.1f}%,趋势延续")
return "\n".join(lines)