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') decision_type = paper_decision.get('decision', 'HOLD')
if decision_type == '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: else:
logger.info(f"\n📊 【执行模拟交易】") logger.info(f"\n📊 【执行模拟交易】")
@ -689,7 +692,10 @@ class CryptoAgent:
await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True) await self._send_signal_notification(market_signal, paper_decision, current_price, is_paper=True)
paper_executed = True paper_executed = True
else: 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': elif decision_type == 'CLOSE':
await self._execute_close(paper_decision, paper_trading=True) await self._execute_close(paper_decision, paper_trading=True)
paper_executed = True paper_executed = True
@ -703,7 +709,10 @@ class CryptoAgent:
decision_type = real_decision.get('decision', 'HOLD') decision_type = real_decision.get('decision', 'HOLD')
if decision_type == '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: else:
logger.info(f"\n💰 【执行实盘交易】") logger.info(f"\n💰 【执行实盘交易】")
@ -716,7 +725,10 @@ class CryptoAgent:
await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False) await self._send_signal_notification(market_signal, real_decision, current_price, is_paper=False)
real_executed = True real_executed = True
else: 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': elif decision_type == 'CLOSE':
await self._execute_close(real_decision, paper_trading=False) await self._execute_close(real_decision, paper_trading=False)
real_executed = True real_executed = True
@ -1402,6 +1414,73 @@ class CryptoAgent:
if self.settings.telegram_enabled: if self.settings.telegram_enabled:
await self.telegram.send_message(message) 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]: async def analyze_once(self, symbol: str) -> Dict[str, Any]:
"""单次分析(用于测试或手动触发)""" """单次分析(用于测试或手动触发)"""
data = self.binance.get_multi_timeframe_data(symbol) data = self.binance.get_multi_timeframe_data(symbol)

View File

@ -13,6 +13,7 @@
""" """
import json import json
import re import re
import pandas as pd
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List
from datetime import datetime from datetime import datetime
from app.utils.logger import logger from app.utils.logger import logger
@ -329,6 +330,12 @@ class MarketSignalAnalyzer:
else: else:
context_parts.append("量价状态: 平量 ") 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) return "\n".join(context_parts)
async def _get_news_context(self, symbol: str) -> str: async def _get_news_context(self, symbol: str) -> str:
@ -544,3 +551,45 @@ class MarketSignalAnalyzer:
'timestamp': datetime.now().isoformat(), 'timestamp': datetime.now().isoformat(),
'error': '信号分析失败' '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)