1
This commit is contained in:
parent
dba6e569c6
commit
6ac175038e
@ -109,7 +109,9 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
# 加密货币交易智能体配置
|
# 加密货币交易智能体配置
|
||||||
crypto_symbols: str = "BTCUSDT,ETHUSDT" # 监控的交易对,逗号分隔
|
crypto_symbols: str = "BTCUSDT,ETHUSDT" # 监控的交易对,逗号分隔
|
||||||
crypto_llm_threshold: float = 0.70 # 触发 LLM 分析的置信度阈值
|
crypto_llm_threshold: float = 0.70 # 旧的统一阈值,兼容保留
|
||||||
|
crypto_intraday_signal_threshold: float = 0.68 # 日内信号置信度阈值
|
||||||
|
crypto_trend_signal_threshold: float = 0.72 # 趋势信号置信度阈值
|
||||||
|
|
||||||
# 价格监控模式配置
|
# 价格监控模式配置
|
||||||
use_bitget_websocket: bool = True # 是否使用 Bitget WebSocket 实时价格(默认 False 使用 Binance 轮询)
|
use_bitget_websocket: bool = True # 是否使用 Bitget WebSocket 实时价格(默认 False 使用 Binance 轮询)
|
||||||
@ -119,14 +121,14 @@ class Settings(BaseSettings):
|
|||||||
crypto_min_volatility_percent: float = 0.5 # 最小波动率(百分比),低于此值跳过分析
|
crypto_min_volatility_percent: float = 0.5 # 最小波动率(百分比),低于此值跳过分析
|
||||||
crypto_min_price_range_percent: float = 0.3 # 最小价格变动范围(百分比),低于此值跳过分析
|
crypto_min_price_range_percent: float = 0.3 # 最小价格变动范围(百分比),低于此值跳过分析
|
||||||
crypto_5m_surge_threshold: float = 1.0 # 5分钟突发波动阈值(百分比),超过此值即使1小时波动率低也会触发分析
|
crypto_5m_surge_threshold: float = 1.0 # 5分钟突发波动阈值(百分比),超过此值即使1小时波动率低也会触发分析
|
||||||
crypto_intraday_llm_cooldown_minutes: int = 15 # 日内 LLM 分析冷却时间
|
crypto_intraday_llm_cooldown_minutes: int = 8 # 日内 LLM 分析冷却时间
|
||||||
crypto_trend_llm_cooldown_minutes: int = 60 # 趋势 LLM 分析冷却时间
|
crypto_trend_llm_cooldown_minutes: int = 25 # 趋势 LLM 分析冷却时间
|
||||||
crypto_force_llm_surge_threshold: float = 1.2 # 15分钟突发波动强制触发 LLM 的阈值
|
crypto_force_llm_surge_threshold: float = 1.2 # 15分钟突发波动强制触发 LLM 的阈值
|
||||||
crypto_force_llm_trade_zone_pct: float = 0.25 # 接近关键交易区时强制触发 LLM 的距离阈值
|
crypto_force_llm_trade_zone_pct: float = 0.25 # 接近关键交易区时强制触发 LLM 的距离阈值
|
||||||
crypto_event_analysis_enabled: bool = True # 是否启用实时行情事件触发分析
|
crypto_event_analysis_enabled: bool = True # 是否启用实时行情事件触发分析
|
||||||
crypto_event_analysis_window_minutes: int = 5 # 实时行情异动检测窗口
|
crypto_event_analysis_window_minutes: int = 5 # 实时行情异动检测窗口
|
||||||
crypto_event_analysis_price_change_percent: float = 0.8 # 检测窗口内涨跌超过该阈值触发日内分析
|
crypto_event_analysis_price_change_percent: float = 0.8 # 检测窗口内涨跌超过该阈值触发日内分析
|
||||||
crypto_event_analysis_cooldown_minutes: int = 10 # 同一交易对事件触发分析冷却
|
crypto_event_analysis_cooldown_minutes: int = 5 # 同一交易对事件触发分析冷却
|
||||||
|
|
||||||
# Brave Search API 配置
|
# Brave Search API 配置
|
||||||
brave_api_key: str = ""
|
brave_api_key: str = ""
|
||||||
|
|||||||
@ -503,7 +503,8 @@ class CryptoAgent:
|
|||||||
last_symbol = self._analysis_monitor.get("last_analysis_symbol") or "-"
|
last_symbol = self._analysis_monitor.get("last_analysis_symbol") or "-"
|
||||||
last_status = self._analysis_monitor.get("last_analysis_status") or "unknown"
|
last_status = self._analysis_monitor.get("last_analysis_status") or "unknown"
|
||||||
last_detail = self._analysis_monitor.get("last_analysis_detail") or "最近一轮分析已完成"
|
last_detail = self._analysis_monitor.get("last_analysis_detail") or "最近一轮分析已完成"
|
||||||
threshold = self.settings.crypto_llm_threshold * 100
|
intraday_threshold = self._get_signal_threshold_pct(signal_type='short_term')
|
||||||
|
trend_threshold = self._get_signal_threshold_pct(signal_type='medium_term')
|
||||||
|
|
||||||
title = "💓 [分析心跳] 系统运行正常"
|
title = "💓 [分析心跳] 系统运行正常"
|
||||||
content = "\n".join([
|
content = "\n".join([
|
||||||
@ -513,7 +514,7 @@ class CryptoAgent:
|
|||||||
f"**完成分析**: {symbol_completed} 个交易对",
|
f"**完成分析**: {symbol_completed} 个交易对",
|
||||||
f"**跳过分析**: {symbol_skipped} 次",
|
f"**跳过分析**: {symbol_skipped} 次",
|
||||||
f"**分析异常**: {symbol_errors} 次",
|
f"**分析异常**: {symbol_errors} 次",
|
||||||
f"**信号阈值**: {threshold:.0f}%",
|
f"**信号阈值**: 日内 {intraday_threshold:.0f}% / 趋势 {trend_threshold:.0f}%",
|
||||||
f"**最近分析对象**: {last_symbol}",
|
f"**最近分析对象**: {last_symbol}",
|
||||||
f"**最近状态**: {last_status}",
|
f"**最近状态**: {last_status}",
|
||||||
f"**最近说明**: {last_detail}",
|
f"**最近说明**: {last_detail}",
|
||||||
@ -615,20 +616,9 @@ class CryptoAgent:
|
|||||||
return False, ""
|
return False, ""
|
||||||
|
|
||||||
def _resolve_llm_lanes_for_symbol(self, symbol: str, data: Dict[str, pd.DataFrame]) -> tuple[List[str], Dict[str, Any], str]:
|
def _resolve_llm_lanes_for_symbol(self, symbol: str, data: Dict[str, pd.DataFrame]) -> tuple[List[str], Dict[str, Any], str]:
|
||||||
now = datetime.now()
|
|
||||||
state = self._get_lane_state(symbol)
|
state = self._get_lane_state(symbol)
|
||||||
force, force_reason = self._detect_force_llm_trigger(symbol, data)
|
force, force_reason = self._detect_force_llm_trigger(symbol, data)
|
||||||
lanes: List[str] = []
|
lanes: List[str] = ["intraday", "trend"]
|
||||||
|
|
||||||
intraday_last = self._parse_iso_datetime(state.get("last_intraday_at"))
|
|
||||||
trend_last = self._parse_iso_datetime(state.get("last_trend_at"))
|
|
||||||
intraday_cooldown = timedelta(minutes=self.settings.crypto_intraday_llm_cooldown_minutes)
|
|
||||||
trend_cooldown = timedelta(minutes=self.settings.crypto_trend_llm_cooldown_minutes)
|
|
||||||
|
|
||||||
if force or intraday_last is None or not state.get("cached_intraday") or now - intraday_last >= intraday_cooldown:
|
|
||||||
lanes.append("intraday")
|
|
||||||
if force or trend_last is None or not state.get("cached_trend") or now - trend_last >= trend_cooldown:
|
|
||||||
lanes.append("trend")
|
|
||||||
|
|
||||||
cached_results = {}
|
cached_results = {}
|
||||||
if state.get("cached_intraday"):
|
if state.get("cached_intraday"):
|
||||||
@ -636,9 +626,6 @@ class CryptoAgent:
|
|||||||
if state.get("cached_trend"):
|
if state.get("cached_trend"):
|
||||||
cached_results["trend"] = state["cached_trend"]
|
cached_results["trend"] = state["cached_trend"]
|
||||||
|
|
||||||
if not lanes and not cached_results:
|
|
||||||
lanes = ["intraday", "trend"]
|
|
||||||
|
|
||||||
state["last_force_reason"] = force_reason if force else ""
|
state["last_force_reason"] = force_reason if force else ""
|
||||||
return lanes, cached_results, force_reason
|
return lanes, cached_results, force_reason
|
||||||
|
|
||||||
@ -1245,7 +1232,7 @@ class CryptoAgent:
|
|||||||
f"📊 **监控交易对**: {len(self.symbols)} 个",
|
f"📊 **监控交易对**: {len(self.symbols)} 个",
|
||||||
f" {', '.join(self.symbols)}",
|
f" {', '.join(self.symbols)}",
|
||||||
f"⏰ **运行频率**: 每5分钟轻扫描",
|
f"⏰ **运行频率**: 每5分钟轻扫描",
|
||||||
f"🧊 **LLM 冷却**: 日内 {self.settings.crypto_intraday_llm_cooldown_minutes} 分钟 / 趋势 {self.settings.crypto_trend_llm_cooldown_minutes} 分钟",
|
f"🧠 **LLM 执行**: 每轮双 lane 直接分析,不做冷却缓存",
|
||||||
f"💰 **交易系统**: 已启用(后台统一监控)",
|
f"💰 **交易系统**: 已启用(后台统一监控)",
|
||||||
f"🎯 **分析维度**: 技术面 + 资金面 + 情绪面",
|
f"🎯 **分析维度**: 技术面 + 资金面 + 情绪面",
|
||||||
]
|
]
|
||||||
@ -1497,25 +1484,6 @@ class CryptoAgent:
|
|||||||
price_change_24h = self._calculate_price_change(data['1h'])
|
price_change_24h = self._calculate_price_change(data['1h'])
|
||||||
logger.info(f"💰 当前价格: ${current_price:,.2f} ({price_change_24h})")
|
logger.info(f"💰 当前价格: ${current_price:,.2f} ({price_change_24h})")
|
||||||
|
|
||||||
paper_symbol_cooldown = None
|
|
||||||
if self.settings.paper_trading_enabled:
|
|
||||||
paper_symbol_cooldown = self._refresh_symbol_trade_cooldown('PaperTrading', symbol)
|
|
||||||
if paper_symbol_cooldown.get('should_cool_down'):
|
|
||||||
cooldown_until = paper_symbol_cooldown.get('cooldown_until')
|
|
||||||
cooldown_text = cooldown_until.isoformat() if cooldown_until else ''
|
|
||||||
self._record_analysis_event(
|
|
||||||
"symbol_trade_cooldown",
|
|
||||||
symbol=symbol,
|
|
||||||
status="warning",
|
|
||||||
detail=paper_symbol_cooldown.get('reason', '交易对处于冷却中'),
|
|
||||||
extra={
|
|
||||||
"platform": "PaperTrading",
|
|
||||||
"losing_streak": paper_symbol_cooldown.get('losing_streak', 0),
|
|
||||||
"cooldown_until": cooldown_text,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
logger.info(f"⏸️ 模拟盘交易对冷却: {paper_symbol_cooldown.get('reason')} | until={cooldown_text}")
|
|
||||||
|
|
||||||
# 1.5. 波动率检查(节省 LLM 调用)
|
# 1.5. 波动率检查(节省 LLM 调用)
|
||||||
should_analyze, volatility_reason, volatility = self._check_volatility(symbol, data)
|
should_analyze, volatility_reason, volatility = self._check_volatility(symbol, data)
|
||||||
if not should_analyze:
|
if not should_analyze:
|
||||||
@ -1545,24 +1513,19 @@ class CryptoAgent:
|
|||||||
logger.info(f"\n🤖 【第一阶段:市场信号分析】 lanes={lanes_to_run or ['cache_only']}")
|
logger.info(f"\n🤖 【第一阶段:市场信号分析】 lanes={lanes_to_run or ['cache_only']}")
|
||||||
if force_reason:
|
if force_reason:
|
||||||
logger.info(f" ⚡ 强制触发 LLM: {force_reason}")
|
logger.info(f" ⚡ 强制触发 LLM: {force_reason}")
|
||||||
elif not lanes_to_run:
|
|
||||||
logger.info(" 🧊 LLM 冷却中,使用上一轮 lane 缓存结果")
|
|
||||||
|
|
||||||
if lanes_to_run:
|
self._bump_analysis_stat("llm_analyses")
|
||||||
self._bump_analysis_stat("llm_analyses")
|
for lane in lanes_to_run:
|
||||||
for lane in lanes_to_run:
|
self._bump_lane_call(lane)
|
||||||
self._bump_lane_call(lane)
|
|
||||||
else:
|
|
||||||
self._bump_analysis_stat("cache_only_runs")
|
|
||||||
|
|
||||||
self._record_analysis_event(
|
self._record_analysis_event(
|
||||||
"llm_lane_plan",
|
"llm_lane_plan",
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
status="info",
|
status="info",
|
||||||
detail=force_reason or (f"本轮执行 lane: {', '.join(lanes_to_run)}" if lanes_to_run else "LLM 冷却中,使用缓存 lane 结果"),
|
detail=force_reason or f"本轮执行 lane: {', '.join(lanes_to_run)}",
|
||||||
extra={
|
extra={
|
||||||
"lanes_to_run": lanes_to_run,
|
"lanes_to_run": lanes_to_run,
|
||||||
"cache_only": not bool(lanes_to_run),
|
"cache_only": False,
|
||||||
"force_reason": force_reason,
|
"force_reason": force_reason,
|
||||||
"trigger_source": trigger_source,
|
"trigger_source": trigger_source,
|
||||||
},
|
},
|
||||||
@ -1627,19 +1590,19 @@ class CryptoAgent:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# 检查是否有达到阈值的交易信号
|
# 检查是否有达到阈值的交易信号
|
||||||
threshold = self.settings.crypto_llm_threshold * 100 # 转换为百分比
|
valid_signals = self._filter_valid_trade_signals(trade_signals)
|
||||||
valid_signals = [s for s in trade_signals if s.get('confidence', 0) >= threshold]
|
|
||||||
|
|
||||||
if not valid_signals:
|
if not valid_signals:
|
||||||
self._bump_analysis_stat("threshold_filtered_runs")
|
self._bump_analysis_stat("threshold_filtered_runs")
|
||||||
self._analysis_monitor["last_analysis_completed_at"] = datetime.now().isoformat()
|
self._analysis_monitor["last_analysis_completed_at"] = datetime.now().isoformat()
|
||||||
self._analysis_monitor["last_analysis_status"] = "completed"
|
self._analysis_monitor["last_analysis_status"] = "completed"
|
||||||
self._analysis_monitor["last_analysis_detail"] = f"完成分析,但无信号达到阈值 {threshold}%"
|
thresholds_text = f"日内 {self._get_signal_threshold_pct(signal_type='short_term'):.0f}% / 趋势 {self._get_signal_threshold_pct(signal_type='medium_term'):.0f}%"
|
||||||
|
self._analysis_monitor["last_analysis_detail"] = f"完成分析,但无信号达到阈值 {thresholds_text}"
|
||||||
self._record_analysis_event(
|
self._record_analysis_event(
|
||||||
"symbol_analysis_completed",
|
"symbol_analysis_completed",
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
status="success",
|
status="success",
|
||||||
detail=f"完成分析,但无信号达到阈值 {threshold}%",
|
detail=f"完成分析,但无信号达到阈值 {thresholds_text}",
|
||||||
extra={
|
extra={
|
||||||
"trade_signals": len(trade_signals),
|
"trade_signals": len(trade_signals),
|
||||||
"valid_signals": 0,
|
"valid_signals": 0,
|
||||||
@ -1648,12 +1611,12 @@ class CryptoAgent:
|
|||||||
"post_regime_lane_signal_counts": market_signal.get("post_regime_lane_signal_counts") or {},
|
"post_regime_lane_signal_counts": market_signal.get("post_regime_lane_signal_counts") or {},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
logger.info(f"\n⏸️ 结论: 无交易信号达到置信度阈值 ({threshold}%),继续观望")
|
logger.info(f"\n⏸️ 结论: 无交易信号达到分周期阈值(日内 {self._get_signal_threshold_pct(signal_type='short_term'):.0f}% / 趋势 {self._get_signal_threshold_pct(signal_type='medium_term'):.0f}%),继续观望")
|
||||||
return
|
return
|
||||||
|
|
||||||
self._bump_analysis_stat("valid_signal_runs")
|
self._bump_analysis_stat("valid_signal_runs")
|
||||||
self._bump_analysis_stat("valid_signals_total", len(valid_signals))
|
self._bump_analysis_stat("valid_signals_total", len(valid_signals))
|
||||||
logger.info(f"\n✅ 发现 {len(valid_signals)} 个有效交易信号(达到 {threshold}% 阈值)")
|
logger.info(f"\n✅ 发现 {len(valid_signals)} 个有效交易信号(按周期阈值过滤)")
|
||||||
for signal in valid_signals:
|
for signal in valid_signals:
|
||||||
logger.info(
|
logger.info(
|
||||||
f" - {signal.get('timeframe', signal.get('type', 'unknown'))} | "
|
f" - {signal.get('timeframe', signal.get('type', 'unknown'))} | "
|
||||||
@ -2036,18 +1999,34 @@ class CryptoAgent:
|
|||||||
profile['setup_type'] = setup_type
|
profile['setup_type'] = setup_type
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
|
def _get_signal_threshold_pct(self, signal: Optional[Dict[str, Any]] = None, signal_type: Optional[str] = None) -> float:
|
||||||
|
resolved_type = signal_type or (signal or {}).get('timeframe') or (signal or {}).get('type') or 'medium_term'
|
||||||
|
if resolved_type == 'short_term':
|
||||||
|
threshold = getattr(self.settings, 'crypto_intraday_signal_threshold', None)
|
||||||
|
elif resolved_type == 'medium_term':
|
||||||
|
threshold = getattr(self.settings, 'crypto_trend_signal_threshold', None)
|
||||||
|
else:
|
||||||
|
threshold = None
|
||||||
|
|
||||||
|
if threshold is None:
|
||||||
|
threshold = getattr(self.settings, 'crypto_llm_threshold', 0.70)
|
||||||
|
return float(threshold) * 100
|
||||||
|
|
||||||
|
def _filter_valid_trade_signals(self, signals: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
|
return [
|
||||||
|
signal for signal in (signals or [])
|
||||||
|
if signal.get('action') in {'buy', 'sell'}
|
||||||
|
and float(signal.get('confidence', 0) or 0) >= self._get_signal_threshold_pct(signal=signal)
|
||||||
|
]
|
||||||
|
|
||||||
async def _execute_decisions(self, paper_decision: Dict[str, Any],
|
async def _execute_decisions(self, paper_decision: Dict[str, Any],
|
||||||
bitget_decisions: Dict[str, Dict[str, Any]],
|
bitget_decisions: Dict[str, Dict[str, Any]],
|
||||||
market_signal: Dict[str, Any], current_price: float):
|
market_signal: Dict[str, Any], current_price: float):
|
||||||
"""执行交易决策(模拟盘 + Bitget 独立)"""
|
"""执行交易决策(模拟盘 + Bitget 独立)"""
|
||||||
# 保存本轮所有达到阈值的可交易信号,避免分流后只落一条信号
|
# 保存本轮所有达到阈值的可交易信号,避免分流后只落一条信号
|
||||||
threshold = self.settings.crypto_llm_threshold * 100
|
|
||||||
symbol = market_signal.get('symbol')
|
symbol = market_signal.get('symbol')
|
||||||
signals = market_signal.get('signals', [])
|
signals = market_signal.get('signals', [])
|
||||||
valid_signals = [
|
valid_signals = self._filter_valid_trade_signals(signals)
|
||||||
signal for signal in signals
|
|
||||||
if signal.get('action') in {'buy', 'sell'} and signal.get('confidence', 0) >= threshold
|
|
||||||
]
|
|
||||||
|
|
||||||
for signal in valid_signals:
|
for signal in valid_signals:
|
||||||
signal_to_save = signal.copy()
|
signal_to_save = signal.copy()
|
||||||
@ -2673,12 +2652,8 @@ class CryptoAgent:
|
|||||||
current_price: float):
|
current_price: float):
|
||||||
"""发送市场信号通知(第一阶段)- 调用前已确保有有效信号"""
|
"""发送市场信号通知(第一阶段)- 调用前已确保有有效信号"""
|
||||||
try:
|
try:
|
||||||
# 获取配置的阈值
|
|
||||||
threshold = self.settings.crypto_llm_threshold * 100 # 转换为百分比
|
|
||||||
|
|
||||||
# 过滤达到阈值的信号(防御性检查)
|
|
||||||
signals = market_signal.get('signals', [])
|
signals = market_signal.get('signals', [])
|
||||||
valid_signals = [s for s in signals if s.get('confidence', 0) >= threshold]
|
valid_signals = self._filter_valid_trade_signals(signals)
|
||||||
|
|
||||||
if not valid_signals:
|
if not valid_signals:
|
||||||
return
|
return
|
||||||
@ -4017,12 +3992,6 @@ class CryptoAgent:
|
|||||||
blocked_reasons = regime_profile.get('no_trade_reasons') or ['当前市场状态不允许交易']
|
blocked_reasons = regime_profile.get('no_trade_reasons') or ['当前市场状态不允许交易']
|
||||||
return False, f"市场状态过滤: {';'.join(blocked_reasons[:2])}"
|
return False, f"市场状态过滤: {';'.join(blocked_reasons[:2])}"
|
||||||
|
|
||||||
symbol_cooldown = self._get_symbol_trade_cooldown(platform_name, signal.get('symbol', ''))
|
|
||||||
if symbol_cooldown and symbol_cooldown.get('should_cool_down'):
|
|
||||||
cooldown_until = symbol_cooldown.get('cooldown_until')
|
|
||||||
until_text = cooldown_until.isoformat() if cooldown_until else ''
|
|
||||||
return False, f"交易对冷却中: {symbol_cooldown.get('reason', '').strip()} {until_text}".strip()
|
|
||||||
|
|
||||||
setup_passed, setup_reason = self._check_setup_execution_constraints(signal)
|
setup_passed, setup_reason = self._check_setup_execution_constraints(signal)
|
||||||
if not setup_passed:
|
if not setup_passed:
|
||||||
return False, f"setup 过滤: {setup_reason}"
|
return False, f"setup 过滤: {setup_reason}"
|
||||||
@ -4095,12 +4064,6 @@ class CryptoAgent:
|
|||||||
elif signal_action == 'sell' and fr_pct < -0.05:
|
elif signal_action == 'sell' and fr_pct < -0.05:
|
||||||
return False, f"资金费率过冷 {fr_pct:.4f}%(做空拥挤),拒绝做空"
|
return False, f"资金费率过冷 {fr_pct:.4f}%(做空拥挤),拒绝做空"
|
||||||
|
|
||||||
# 6. 连败降温检查(不拒绝,但降低仓位)
|
|
||||||
streak_info = self._check_losing_streak(platform_name)
|
|
||||||
if streak_info.get('should_cool_down'):
|
|
||||||
signal['_streak_margin_multiplier'] = streak_info['margin_multiplier']
|
|
||||||
logger.warning(f"[{platform_name}] ⚠️ {streak_info['reason']}")
|
|
||||||
|
|
||||||
return True, "通过风控检查"
|
return True, "通过风控检查"
|
||||||
|
|
||||||
def execute_signal_with_rules(self, signal: Dict[str, Any],
|
def execute_signal_with_rules(self, signal: Dict[str, Any],
|
||||||
@ -4440,20 +4403,10 @@ class CryptoAgent:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
confidence = signal.get('confidence', 0)
|
confidence = signal.get('confidence', 0)
|
||||||
# 使用配置文件中的阈值
|
threshold = self._get_signal_threshold_pct(signal=signal)
|
||||||
threshold = self.settings.crypto_llm_threshold * 100 # 转换为百分比
|
|
||||||
if confidence < threshold:
|
if confidence < threshold:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 检查冷却时间(30分钟内不重复发送相同方向的信号)
|
|
||||||
if symbol in self.signal_cooldown:
|
|
||||||
cooldown_end = self.signal_cooldown[symbol] + timedelta(minutes=30)
|
|
||||||
if datetime.now() < cooldown_end:
|
|
||||||
if symbol in self.last_signals:
|
|
||||||
if self.last_signals[symbol].get('action') == action:
|
|
||||||
logger.debug(f"{symbol} 信号冷却中,跳过")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _review_and_adjust_positions(
|
async def _review_and_adjust_positions(
|
||||||
@ -4560,11 +4513,7 @@ class CryptoAgent:
|
|||||||
)
|
)
|
||||||
|
|
||||||
signals = market_signal.get('signals', [])
|
signals = market_signal.get('signals', [])
|
||||||
threshold = self.settings.crypto_llm_threshold * 100
|
valid_signals = self._filter_valid_trade_signals(signals)
|
||||||
valid_signals = [
|
|
||||||
signal for signal in signals
|
|
||||||
if signal.get('action') in {'buy', 'sell'} and signal.get('confidence', 0) >= threshold
|
|
||||||
]
|
|
||||||
|
|
||||||
execution_preview: Dict[str, Any] = {}
|
execution_preview: Dict[str, Any] = {}
|
||||||
|
|
||||||
@ -4627,8 +4576,10 @@ class CryptoAgent:
|
|||||||
'execution_guardian': self.execution_guardian.get_status(),
|
'execution_guardian': self.execution_guardian.get_status(),
|
||||||
'llm_schedule': {
|
'llm_schedule': {
|
||||||
'scan_interval_minutes': 5,
|
'scan_interval_minutes': 5,
|
||||||
'intraday_cooldown_minutes': self.settings.crypto_intraday_llm_cooldown_minutes,
|
'intraday_cooldown_minutes': 0,
|
||||||
'trend_cooldown_minutes': self.settings.crypto_trend_llm_cooldown_minutes,
|
'trend_cooldown_minutes': 0,
|
||||||
|
'intraday_signal_threshold': self._get_signal_threshold_pct(signal_type='short_term'),
|
||||||
|
'trend_signal_threshold': self._get_signal_threshold_pct(signal_type='medium_term'),
|
||||||
'force_surge_threshold': self.settings.crypto_force_llm_surge_threshold,
|
'force_surge_threshold': self.settings.crypto_force_llm_surge_threshold,
|
||||||
'force_trade_zone_pct': self.settings.crypto_force_llm_trade_zone_pct,
|
'force_trade_zone_pct': self.settings.crypto_force_llm_trade_zone_pct,
|
||||||
'event_analysis_enabled': self.settings.crypto_event_analysis_enabled,
|
'event_analysis_enabled': self.settings.crypto_event_analysis_enabled,
|
||||||
|
|||||||
@ -90,15 +90,15 @@ class RegimeEngine:
|
|||||||
{
|
{
|
||||||
"regime_key": "transition",
|
"regime_key": "transition",
|
||||||
"market_state_label": "过渡市",
|
"market_state_label": "过渡市",
|
||||||
"tradability": "avoid",
|
"tradability": "selective",
|
||||||
"risk_mode": "defensive",
|
"risk_mode": "defensive",
|
||||||
"allowed_lanes": [],
|
"allowed_lanes": ["short_term"],
|
||||||
"preferred_lanes": [],
|
"preferred_lanes": ["short_term", "medium_term"],
|
||||||
"allowed_setups": [],
|
"allowed_setups": ["breakout_pullback", "breakout_confirmation", "range_reversal"],
|
||||||
"preferred_entry_types": [],
|
"preferred_entry_types": ["limit", "market"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
no_trade_reasons.append("趋势与震荡切换期,方向优势不足")
|
no_trade_reasons.append("趋势与震荡切换期,只做边界确认和突破回踩")
|
||||||
|
|
||||||
elif range_regime in {"weak_trend", "strong_trend"} and trend_direction in {"uptrend", "downtrend"}:
|
elif range_regime in {"weak_trend", "strong_trend"} and trend_direction in {"uptrend", "downtrend"}:
|
||||||
allow_reversal = bool(reversal_detection.get("is_reversing")) and trend_strength != "strong"
|
allow_reversal = bool(reversal_detection.get("is_reversing")) and trend_strength != "strong"
|
||||||
@ -127,7 +127,7 @@ class RegimeEngine:
|
|||||||
"risk_mode": "normal" if range_regime == "strong_trend" else "reduced",
|
"risk_mode": "normal" if range_regime == "strong_trend" else "reduced",
|
||||||
"allowed_lanes": ["medium_term", "short_term"],
|
"allowed_lanes": ["medium_term", "short_term"],
|
||||||
"preferred_lanes": ["medium_term", "short_term"],
|
"preferred_lanes": ["medium_term", "short_term"],
|
||||||
"allowed_setups": ["trend_continuation_pullback"],
|
"allowed_setups": ["trend_continuation_pullback", "deep_pullback_continuation"],
|
||||||
"preferred_entry_types": ["limit", "market"],
|
"preferred_entry_types": ["limit", "market"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -42,6 +42,14 @@ class SetupPolicy:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if setup_type not in allowed_setups:
|
if setup_type not in allowed_setups:
|
||||||
|
if tradability == "selective" and self._allow_selective_fallback(signal, lane=lane):
|
||||||
|
kept.append({
|
||||||
|
**signal,
|
||||||
|
"setup_type": setup_type,
|
||||||
|
"setup_basis": setup_basis,
|
||||||
|
"entry_basis": entry_basis,
|
||||||
|
})
|
||||||
|
continue
|
||||||
reasons.append(f"{setup_type} 不在允许 setup 内")
|
reasons.append(f"{setup_type} 不在允许 setup 内")
|
||||||
reason_counts[f"setup_blocked:{setup_type}"] += 1
|
reason_counts[f"setup_blocked:{setup_type}"] += 1
|
||||||
continue
|
continue
|
||||||
@ -55,6 +63,28 @@ class SetupPolicy:
|
|||||||
|
|
||||||
return kept, reasons, dict(reason_counts)
|
return kept, reasons, dict(reason_counts)
|
||||||
|
|
||||||
|
def _allow_selective_fallback(self, signal: Dict[str, Any], *, lane: str) -> bool:
|
||||||
|
confidence = float(signal.get("confidence", 0) or 0)
|
||||||
|
entry_type = signal.get("entry_type", "market")
|
||||||
|
location_tag = ((signal.get("market_location") or {}).get("location_tag") or "")
|
||||||
|
volume_context = signal.get("volume_price_context") or {}
|
||||||
|
pullback_quality = volume_context.get("pullback_quality") or signal.get("pullback_quality")
|
||||||
|
breakout_quality = volume_context.get("breakout_quality") or signal.get("breakout_quality")
|
||||||
|
|
||||||
|
if lane == "medium_term" and entry_type == "limit" and confidence >= 74:
|
||||||
|
if location_tag in {"near_long_zone", "near_short_zone", "near_range_support", "near_range_resistance"}:
|
||||||
|
return True
|
||||||
|
if pullback_quality == "healthy_pullback":
|
||||||
|
return True
|
||||||
|
|
||||||
|
if lane == "short_term" and confidence >= 72:
|
||||||
|
if breakout_quality in {"acceptance_breakout_up", "acceptance_breakout_down"}:
|
||||||
|
return True
|
||||||
|
if entry_type == "limit" and location_tag in {"near_long_zone", "near_short_zone", "near_range_support", "near_range_resistance"}:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def _infer_setup_type(self, signal: Dict[str, Any]) -> str:
|
def _infer_setup_type(self, signal: Dict[str, Any]) -> str:
|
||||||
lane = signal.get("timeframe") or signal.get("type") or "medium_term"
|
lane = signal.get("timeframe") or signal.get("type") or "medium_term"
|
||||||
action = signal.get("action")
|
action = signal.get("action")
|
||||||
|
|||||||
@ -3701,7 +3701,8 @@
|
|||||||
<div class="runtime-summary-meta">
|
<div class="runtime-summary-meta">
|
||||||
<div class="runtime-summary-row"><span>最近分析说明</span><strong>${monitor.last_analysis_detail || '-'}</strong></div>
|
<div class="runtime-summary-row"><span>最近分析说明</span><strong>${monitor.last_analysis_detail || '-'}</strong></div>
|
||||||
<div class="runtime-summary-row"><span>最近完成轮次</span><strong>${monitor.last_cycle_completed_at ? `${relativeTime(monitor.last_cycle_completed_at)} / ${formatTime(monitor.last_cycle_completed_at)}` : '暂无'}</strong></div>
|
<div class="runtime-summary-row"><span>最近完成轮次</span><strong>${monitor.last_cycle_completed_at ? `${relativeTime(monitor.last_cycle_completed_at)} / ${formatTime(monitor.last_cycle_completed_at)}` : '暂无'}</strong></div>
|
||||||
<div class="runtime-summary-row"><span>日内 / 趋势冷却</span><strong>${schedule.intraday_cooldown_minutes || '-'}m / ${schedule.trend_cooldown_minutes || '-'}m</strong></div>
|
<div class="runtime-summary-row"><span>日内 / 趋势冷却</span><strong>${schedule.intraday_cooldown_minutes ?? '-'}m / ${schedule.trend_cooldown_minutes ?? '-'}m</strong></div>
|
||||||
|
<div class="runtime-summary-row"><span>日内 / 趋势阈值</span><strong>${schedule.intraday_signal_threshold || '-'}% / ${schedule.trend_signal_threshold || '-'}%</strong></div>
|
||||||
<div class="runtime-summary-row"><span>事件分析</span><strong>${schedule.event_analysis_enabled ? '开启' : '关闭'}</strong></div>
|
<div class="runtime-summary-row"><span>事件分析</span><strong>${schedule.event_analysis_enabled ? '开启' : '关闭'}</strong></div>
|
||||||
<div class="runtime-summary-row"><span>触发 / LLM</span><strong>${funnel.total_triggers || 0} / ${funnel.llm_analyses || 0}</strong></div>
|
<div class="runtime-summary-row"><span>触发 / LLM</span><strong>${funnel.total_triggers || 0} / ${funnel.llm_analyses || 0}</strong></div>
|
||||||
<div class="runtime-summary-row"><span>波动率跳过 / 数据异常</span><strong>${funnel.volatility_skips || 0} / ${funnel.data_invalid_skips || 0}</strong></div>
|
<div class="runtime-summary-row"><span>波动率跳过 / 数据异常</span><strong>${funnel.volatility_skips || 0} / ${funnel.data_invalid_skips || 0}</strong></div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user