1
This commit is contained in:
parent
cf2dcfe2c7
commit
efc0abf5cb
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user