From 9a0061c06acd8cdab2fc7bf8f49aefa9957cb9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sat, 21 Mar 2026 09:23:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=B1=E5=AD=90=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/routes/config.py | 36 ++++ backend/config_manager.py | 8 + backend/sync_global_config_defaults.py | 12 ++ config/current_suggestions.json | 6 + config/shadow_mode_tracking.json | 5 + frontend/src/components/GlobalConfig.jsx | 12 ++ scripts/shadow_mode_analyzer.py | 35 ++++ trading_system/config.py | 8 + trading_system/position_manager.py | 25 +++ trading_system/shadow_mode.py | 226 +++++++++++++++++++++++ trading_system/strategy.py | 39 ++++ 11 files changed, 412 insertions(+) create mode 100644 config/current_suggestions.json create mode 100644 config/shadow_mode_tracking.json create mode 100644 scripts/shadow_mode_analyzer.py create mode 100644 trading_system/shadow_mode.py diff --git a/backend/api/routes/config.py b/backend/api/routes/config.py index b8f1072..d757c00 100644 --- a/backend/api/routes/config.py +++ b/backend/api/routes/config.py @@ -779,6 +779,42 @@ async def get_global_configs( "category": "strategy", "description": "做空:相对位置 pos 低于此值则不开仓(默认约拒绝最下 38% 区域)。越大越严。", }, + "SHADOW_MODE_AUTO_APPLY": { + "value": False, + "type": "boolean", + "category": "strategy", + "description": "影子模式半自动:按 config/current_suggestions.json 跳过黑名单/差时段,并对加仓减仓名单调整杠杆。", + }, + "SHADOW_MODE_MIN_CONFIDENCE": { + "value": 0.7, + "type": "number", + "category": "strategy", + "description": "影子跟踪置信度下限(读 shadow_mode_tracking.json),低于则不应用建议。", + }, + "SHADOW_MODE_SUGGESTIONS_PATH": { + "value": "config/current_suggestions.json", + "type": "string", + "category": "strategy", + "description": "优化建议 JSON 路径(相对项目根)。", + }, + "SHADOW_MODE_TRACKING_PATH": { + "value": "config/shadow_mode_tracking.json", + "type": "string", + "category": "strategy", + "description": "影子跟踪准确率 JSON 路径。", + }, + "SHADOW_MODE_INCREASE_LEVERAGE_MULT": { + "value": 1.5, + "type": "number", + "category": "strategy", + "description": "increase_position 名单杠杆乘数。", + }, + "SHADOW_MODE_DECREASE_LEVERAGE_MULT": { + "value": 0.5, + "type": "number", + "category": "strategy", + "description": "decrease_position 名单杠杆乘数。", + }, } for k, meta in ADDITIONAL_STRATEGY_DEFAULTS.items(): if k not in result: diff --git a/backend/config_manager.py b/backend/config_manager.py index 8c254de..c935fb0 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -974,6 +974,14 @@ class ConfigManager: 'ENTRY_PULLBACK_MAX_LONG_IN_RANGE': eff_get('ENTRY_PULLBACK_MAX_LONG_IN_RANGE', 0.62), 'ENTRY_PULLBACK_MIN_SHORT_IN_RANGE': eff_get('ENTRY_PULLBACK_MIN_SHORT_IN_RANGE', 0.38), + # 影子模式半自动化 + 'SHADOW_MODE_AUTO_APPLY': eff_get('SHADOW_MODE_AUTO_APPLY', False), + 'SHADOW_MODE_MIN_CONFIDENCE': eff_get('SHADOW_MODE_MIN_CONFIDENCE', 0.7), + 'SHADOW_MODE_SUGGESTIONS_PATH': eff_get('SHADOW_MODE_SUGGESTIONS_PATH', 'config/current_suggestions.json'), + 'SHADOW_MODE_TRACKING_PATH': eff_get('SHADOW_MODE_TRACKING_PATH', 'config/shadow_mode_tracking.json'), + 'SHADOW_MODE_INCREASE_LEVERAGE_MULT': eff_get('SHADOW_MODE_INCREASE_LEVERAGE_MULT', 1.5), + 'SHADOW_MODE_DECREASE_LEVERAGE_MULT': eff_get('SHADOW_MODE_DECREASE_LEVERAGE_MULT', 0.5), + # 当前交易预设(让 trading_system 能知道是哪种模式) 'TRADING_PROFILE': profile, diff --git a/backend/sync_global_config_defaults.py b/backend/sync_global_config_defaults.py index 90095af..8100a23 100644 --- a/backend/sync_global_config_defaults.py +++ b/backend/sync_global_config_defaults.py @@ -79,6 +79,18 @@ DEFAULTS_TO_SYNC = [ "description": "做多:区间相对位置上限制0~1,默认0.62。"}, {"config_key": "ENTRY_PULLBACK_MIN_SHORT_IN_RANGE", "config_value": "0.38", "config_type": "number", "category": "strategy", "description": "做空:区间相对位置下限制0~1,默认0.38。"}, + {"config_key": "SHADOW_MODE_AUTO_APPLY", "config_value": "false", "config_type": "boolean", "category": "strategy", + "description": "影子模式:按 current_suggestions.json 自动应用黑名单/差时段/杠杆调整。"}, + {"config_key": "SHADOW_MODE_MIN_CONFIDENCE", "config_value": "0.7", "config_type": "number", "category": "strategy", + "description": "影子跟踪置信度下限。"}, + {"config_key": "SHADOW_MODE_SUGGESTIONS_PATH", "config_value": "config/current_suggestions.json", "config_type": "string", "category": "strategy", + "description": "优化建议 JSON 路径。"}, + {"config_key": "SHADOW_MODE_TRACKING_PATH", "config_value": "config/shadow_mode_tracking.json", "config_type": "string", "category": "strategy", + "description": "影子跟踪 JSON 路径。"}, + {"config_key": "SHADOW_MODE_INCREASE_LEVERAGE_MULT", "config_value": "1.5", "config_type": "number", "category": "strategy", + "description": "加仓名单杠杆乘数。"}, + {"config_key": "SHADOW_MODE_DECREASE_LEVERAGE_MULT", "config_value": "0.5", "config_type": "number", "category": "strategy", + "description": "减仓名单杠杆乘数。"}, ] diff --git a/config/current_suggestions.json b/config/current_suggestions.json new file mode 100644 index 0000000..188a787 --- /dev/null +++ b/config/current_suggestions.json @@ -0,0 +1,6 @@ +{ + "blacklist": [], + "increase_position": [], + "decrease_position": [], + "worst_hours": [] +} diff --git a/config/shadow_mode_tracking.json b/config/shadow_mode_tracking.json new file mode 100644 index 0000000..ff4e6dc --- /dev/null +++ b/config/shadow_mode_tracking.json @@ -0,0 +1,5 @@ +{ + "accuracy": 1.0, + "rolling_accuracy": 1.0, + "notes": "由 shadow_mode_analyzer / 人工更新;低于 SHADOW_MODE_MIN_CONFIDENCE 时不自动应用建议" +} diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index 911b8e0..195c17f 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -75,6 +75,12 @@ const KEY_LABELS = { ENTRY_PULLBACK_MIN_BARS: '回撤过滤最少K线数', ENTRY_PULLBACK_MAX_LONG_IN_RANGE: '做多区间位置上限(0~1)', ENTRY_PULLBACK_MIN_SHORT_IN_RANGE: '做空区间位置下限(0~1)', + SHADOW_MODE_AUTO_APPLY: '影子模式自动应用建议', + SHADOW_MODE_MIN_CONFIDENCE: '影子模式最小置信度', + SHADOW_MODE_SUGGESTIONS_PATH: '影子建议JSON路径', + SHADOW_MODE_TRACKING_PATH: '影子跟踪准确率路径', + SHADOW_MODE_INCREASE_LEVERAGE_MULT: '影子加仓名单杠杆乘数', + SHADOW_MODE_DECREASE_LEVERAGE_MULT: '影子减仓名单杠杆乘数', } // 配置项详细说明(用于导出的策略配置分析,含建议与参数说明) @@ -684,6 +690,12 @@ const GlobalConfig = () => { ENTRY_PULLBACK_MIN_BARS: { value: 5, type: 'number', category: 'strategy', description: '至少几根 K 才启用回撤过滤。' }, ENTRY_PULLBACK_MAX_LONG_IN_RANGE: { value: 0.62, type: 'number', category: 'strategy', description: '做多:区间相对位置上限制(0~1),默认 0.62。' }, ENTRY_PULLBACK_MIN_SHORT_IN_RANGE: { value: 0.38, type: 'number', category: 'strategy', description: '做空:区间相对位置下限制(0~1),默认 0.38。' }, + SHADOW_MODE_AUTO_APPLY: { value: false, type: 'boolean', category: 'strategy', description: '是否按 current_suggestions.json 自动跳过黑名单/差时段并调整杠杆。' }, + SHADOW_MODE_MIN_CONFIDENCE: { value: 0.7, type: 'number', category: 'strategy', description: 'shadow_mode_tracking.json 置信度低于此值则不应用。' }, + SHADOW_MODE_SUGGESTIONS_PATH: { value: 'config/current_suggestions.json', type: 'string', category: 'strategy', description: '优化建议 JSON(相对项目根)。' }, + SHADOW_MODE_TRACKING_PATH: { value: 'config/shadow_mode_tracking.json', type: 'string', category: 'strategy', description: '跟踪准确率 JSON。' }, + SHADOW_MODE_INCREASE_LEVERAGE_MULT: { value: 1.5, type: 'number', category: 'strategy', description: 'increase_position 名单杠杆乘数。' }, + SHADOW_MODE_DECREASE_LEVERAGE_MULT: { value: 0.5, type: 'number', category: 'strategy', description: 'decrease_position 名单杠杆乘数。' }, } const loadConfigs = async () => { diff --git a/scripts/shadow_mode_analyzer.py b/scripts/shadow_mode_analyzer.py new file mode 100644 index 0000000..2abae9a --- /dev/null +++ b/scripts/shadow_mode_analyzer.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +""" +影子模式分析(占位入口)。 + +实际流程建议: +1. 由 `daily_report.py` 或独立分析脚本根据历史成交生成 `config/current_suggestions.json`; +2. 更新 `config/shadow_mode_tracking.json` 中的 accuracy / rolling_accuracy; +3. 在全局配置中开启 `SHADOW_MODE_AUTO_APPLY=true` 后,交易进程将自动读取建议。 + +本仓库提供 JSON schema 见 `config/current_suggestions.json`。 +""" +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SUG = ROOT / "config" / "current_suggestions.json" +TRACK = ROOT / "config" / "shadow_mode_tracking.json" + + +def main(): + print("影子模式分析占位脚本。") + print(f" 建议文件: {SUG}") + print(f" 跟踪文件: {TRACK}") + if SUG.is_file(): + with open(SUG, "r", encoding="utf-8") as f: + data = json.load(f) + print(" 当前建议摘要:", {k: len(v) if isinstance(v, list) else v for k, v in data.items()}) + else: + print(" (尚未生成 current_suggestions.json)") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/trading_system/config.py b/trading_system/config.py index 5cdeb8a..630d1cb 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -198,6 +198,14 @@ DEFAULT_TRADING_CONFIG = { 'NO_OPEN_HOURS_BJ': '', 'ONE_WAY_POSITION_ONLY': True, # 账号固定为单向持仓模式,不传 positionSide,不检测对冲模式(避免 -4061) + # ===== 影子模式半自动化(config/current_suggestions.json,由 daily_report / 分析脚本生成)===== + 'SHADOW_MODE_AUTO_APPLY': False, # True:按建议自动跳过黑名单/差时段,并对加仓/减仓名单调整杠杆倍数 + 'SHADOW_MODE_MIN_CONFIDENCE': 0.7, # 低于此置信度(shadow_mode_tracking.json 中 accuracy 等)则不应用建议 + 'SHADOW_MODE_SUGGESTIONS_PATH': 'config/current_suggestions.json', # 相对项目根目录 + 'SHADOW_MODE_TRACKING_PATH': 'config/shadow_mode_tracking.json', + 'SHADOW_MODE_INCREASE_LEVERAGE_MULT': 1.5, # increase_position 名单:杠杆 × 此值(上限仍受 MAX_LEVERAGE 约束) + 'SHADOW_MODE_DECREASE_LEVERAGE_MULT': 0.5, # decrease_position 名单:杠杆 × 此值 + 'MAX_POSITION_PERCENT': 0.20, # 单笔仓位上限20%(作为风控熔断,实际仓位由固定风险模型决定) 'MAX_TOTAL_POSITION_PERCENT': 0.80, # 总仓位80%(避免满仓,留有余地) 'MIN_POSITION_PERCENT': 0.01, # 最小仓位1% diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 2bb4656..e941c2f 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -343,6 +343,7 @@ class PositionManager: klines: Optional[List] = None, bollinger: Optional[Dict] = None, entry_context: Optional[Dict] = None, + shadow_leverage_multiplier: float = 1.0, ) -> Optional[Dict]: """ 开仓 @@ -393,6 +394,30 @@ class PositionManager: except Exception as e: logger.debug(f"{symbol} 开仓前持仓数复核异常(继续): {e}") + # 影子模式:对动态杠杆再乘系数(加仓/减仓名单) + smx = float(shadow_leverage_multiplier or 1.0) + if abs(smx - 1.0) > 1e-9: + old_lev = int(leverage) + adj_lev = int(round(leverage * smx)) + max_lev = int(config.TRADING_CONFIG.get('MAX_LEVERAGE', 125) or 125) + min_lev = max(1, int(config.TRADING_CONFIG.get('MIN_LEVERAGE', 1) or 1)) + adj_lev = max(min_lev, min(adj_lev, max_lev)) + pa = "none" + if entry_context and isinstance(entry_context.get("shadow_mode_applied"), dict): + pa = entry_context["shadow_mode_applied"].get("position_adjustment") or "none" + reason_txt = "" + if entry_context and isinstance(entry_context.get("shadow_mode_applied"), dict): + smd = entry_context["shadow_mode_applied"] + if pa == "increase" and smd.get("increase_detail"): + reason_txt = str(smd["increase_detail"].get("reason") or smd["increase_detail"].get("symbol") or "") + elif pa == "decrease" and smd.get("decrease_detail"): + reason_txt = str(smd["decrease_detail"].get("reason") or "") + logger.info( + f"{symbol} [影子模式] 应用动态仓位调整:{smx}x({old_lev}x→{adj_lev}x,position={pa}" + f"{', 建议: ' + reason_txt if reason_txt else ''})" + ) + leverage = adj_lev + # 设置杠杆(确保为 int,避免动态杠杆传入 float 导致 API/range 报错) actual_leverage = await self.client.set_leverage(symbol, int(leverage)) diff --git a/trading_system/shadow_mode.py b/trading_system/shadow_mode.py new file mode 100644 index 0000000..a1e3da9 --- /dev/null +++ b/trading_system/shadow_mode.py @@ -0,0 +1,226 @@ +""" +影子模式半自动化:读取 config/current_suggestions.json,在开仓前应用黑名单/差时段/杠杆倍数建议。 + +与 aggregate_trade_stats 的 STATS_* 互补:本模块面向 daily_report / 影子分析产出的 JSON。 +""" +from __future__ import annotations + +import json +import logging +import os +import time +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +logger = logging.getLogger(__name__) + +# 项目根目录(trading_system/ 的上一级) +_PROJECT_ROOT = Path(__file__).resolve().parent.parent + + +def _project_root() -> Path: + return _PROJECT_ROOT + + +def _safe_read_json(path: Path) -> Optional[dict]: + try: + if not path.is_file(): + return None + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + except Exception as e: + logger.debug(f"读取 JSON 失败 {path}: {e}") + return None + + +_suggestions_cache: Tuple[float, Optional[dict]] = (0.0, None) +_tracking_cache: Tuple[float, Optional[dict]] = (0.0, None) +_CACHE_TTL_SEC = 45.0 + + +def _get_suggestions(trading_config: dict) -> dict: + global _suggestions_cache + path_str = trading_config.get("SHADOW_MODE_SUGGESTIONS_PATH") or "config/current_suggestions.json" + path = Path(path_str) + if not path.is_absolute(): + path = _project_root() / path + now = time.time() + if _suggestions_cache[1] is not None and now - _suggestions_cache[0] < _CACHE_TTL_SEC: + return _suggestions_cache[1] + data = _safe_read_json(path) + if data is None: + data = { + "blacklist": [], + "increase_position": [], + "decrease_position": [], + "worst_hours": [], + } + _suggestions_cache = (now, data) + return data + + +def _get_tracking_confidence(trading_config: dict) -> float: + global _tracking_cache + path_str = trading_config.get("SHADOW_MODE_TRACKING_PATH") or "config/shadow_mode_tracking.json" + path = Path(path_str) + if not path.is_absolute(): + path = _project_root() / path + now = time.time() + if _tracking_cache[1] is not None and now - _tracking_cache[0] < _CACHE_TTL_SEC: + data = _tracking_cache[1] + else: + data = _safe_read_json(path) or {} + _tracking_cache = (now, data) + # 兼容多种字段名 + for key in ("accuracy", "rolling_accuracy", "confidence", "min_confidence"): + v = data.get(key) + if v is not None: + try: + return float(v) + except (TypeError, ValueError): + pass + return 1.0 + + +def _norm_symbol(s: str) -> str: + return (s or "").strip().upper() + + +def _find_blacklist_entry(suggestions: dict, symbol: str) -> Optional[dict]: + sym = _norm_symbol(symbol) + for item in suggestions.get("blacklist") or []: + if not isinstance(item, dict): + continue + if _norm_symbol(str(item.get("symbol", ""))) == sym: + return item + return None + + +def _in_worst_hours(suggestions: dict, hour_bj: int) -> Optional[dict]: + for item in suggestions.get("worst_hours") or []: + if not isinstance(item, dict): + continue + h = item.get("hour") + try: + hi = int(h) + except (TypeError, ValueError): + continue + if hi == int(hour_bj): + return item + return None + + +def _position_list_has(suggestions: dict, key: str, symbol: str) -> Optional[dict]: + sym = _norm_symbol(symbol) + for item in suggestions.get(key) or []: + if not isinstance(item, dict): + continue + if _norm_symbol(str(item.get("symbol", ""))) == sym: + return item + return None + + +def evaluate_shadow_mode( + symbol: str, + hour_bj: int, + trading_config: dict, +) -> Dict[str, Any]: + """ + 评估影子模式:是否允许自动开仓、杠杆乘子、写入 entry_context 的 shadow_mode_applied。 + + 当 SHADOW_MODE_AUTO_APPLY=False 或置信度不足时:不拦截、不调整杠杆,但标记 skipped。 + """ + auto = bool(trading_config.get("SHADOW_MODE_AUTO_APPLY", False)) + min_conf = float(trading_config.get("SHADOW_MODE_MIN_CONFIDENCE", 0.7) or 0.0) + min_conf = max(0.0, min(1.0, min_conf)) + + inc_mul = float(trading_config.get("SHADOW_MODE_INCREASE_LEVERAGE_MULT", 1.5) or 1.5) + dec_mul = float(trading_config.get("SHADOW_MODE_DECREASE_LEVERAGE_MULT", 0.5) or 0.5) + inc_mul = max(0.1, min(inc_mul, 3.0)) + dec_mul = max(0.1, min(dec_mul, 2.0)) + + applied = { + "blacklist_check": "skipped", + "hour_check": "skipped", + "position_adjustment": "none", + "suggestion_source": "current_suggestions.json", + } + + if not auto: + return { + "allowed": True, + "skip_reason": None, + "leverage_multiplier": 1.0, + "shadow_mode_applied": {**applied, "reason": "SHADOW_MODE_AUTO_APPLY=false"}, + } + + conf = _get_tracking_confidence(trading_config) + if conf < min_conf: + logger.info( + f"影子模式:跟踪置信度 {conf:.2f} < 最小要求 {min_conf:.2f},本笔不自动应用建议" + ) + return { + "allowed": True, + "skip_reason": None, + "leverage_multiplier": 1.0, + "shadow_mode_applied": { + **applied, + "reason": f"confidence {conf:.2f} < {min_conf:.2f}", + }, + } + + suggestions = _get_suggestions(trading_config) + + # 黑名单 + bl = _find_blacklist_entry(suggestions, symbol) + if bl is not None: + applied["blacklist_check"] = "blocked" + reason = bl.get("reason") or "shadow blacklist" + return { + "allowed": False, + "skip_reason": reason, + "leverage_multiplier": 1.0, + "shadow_mode_applied": {**applied, "blacklist_detail": bl}, + } + applied["blacklist_check"] = "passed" + + # 差时段 + wh = _in_worst_hours(suggestions, hour_bj) + if wh is not None: + applied["hour_check"] = "blocked" + avg_pnl = wh.get("avg_pnl", wh.get("avgPnl", "")) + return { + "allowed": False, + "skip_reason": f"worst_hour_{hour_bj}", + "leverage_multiplier": 1.0, + "shadow_mode_applied": {**applied, "worst_hour_detail": wh}, + "log_avg_pnl": avg_pnl, + } + applied["hour_check"] = "passed" + + # 杠杆:减仓优先于加仓(同时命中时更保守) + mult = 1.0 + dec_item = _position_list_has(suggestions, "decrease_position", symbol) + inc_item = _position_list_has(suggestions, "increase_position", symbol) + if dec_item is not None: + mult *= dec_mul + applied["position_adjustment"] = "decrease" + applied["decrease_detail"] = dec_item + elif inc_item is not None: + mult *= inc_mul + applied["position_adjustment"] = "increase" + applied["increase_detail"] = inc_item + + return { + "allowed": True, + "skip_reason": None, + "leverage_multiplier": mult, + "shadow_mode_applied": applied, + } + + +def invalidate_cache() -> None: + """配置热更新时可调用(可选)。""" + global _suggestions_cache, _tracking_cache + _suggestions_cache = (0.0, None) + _tracking_cache = (0.0, None) diff --git a/trading_system/strategy.py b/trading_system/strategy.py index 6cdeaeb..5bb6c25 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -14,6 +14,7 @@ try: from .signal_filters import SignalFilterContext, apply_signal_filters from . import config from .symbol_policy import resolve_symbol_trading_policy + from .shadow_mode import evaluate_shadow_mode, invalidate_cache as shadow_invalidate_cache except ImportError: from binance_client import BinanceClient from market_scanner import MarketScanner @@ -22,6 +23,7 @@ except ImportError: from signal_filters import SignalFilterContext, apply_signal_filters import config from symbol_policy import resolve_symbol_trading_policy + from shadow_mode import evaluate_shadow_mode, invalidate_cache as shadow_invalidate_cache logger = logging.getLogger(__name__) @@ -110,6 +112,10 @@ class TradingStrategy: config._config_manager.reload_from_redis() config.TRADING_CONFIG = config._get_trading_config() logger.debug("配置已从Redis重新加载") + try: + shadow_invalidate_cache() + except Exception: + pass except Exception as e: logger.warning(f"从Redis重新加载配置失败: {e}") @@ -241,6 +247,26 @@ class TradingStrategy: except Exception as e: logger.warning(f"{symbol} 应用信号过滤插件时出错(忽略,回退为原逻辑): {e}") + # 影子模式半自动化:优化黑名单 / 差时段(跳过自动开仓;推荐已生成) + shadow_mode_result = self._check_shadow_mode_filters(symbol) + if not shadow_mode_result.get("allowed", True): + sm_ap = shadow_mode_result.get("shadow_mode_applied") or {} + if sm_ap.get("blacklist_check") == "blocked": + br = shadow_mode_result.get("skip_reason") or "" + logger.info(f"{symbol} 命中优化黑名单,跳过自动开仓(建议:{br})") + elif sm_ap.get("hour_check") == "blocked": + bj_h = timezone(timedelta(hours=8)) + hh = datetime.now(bj_h).hour + avg_txt = shadow_mode_result.get("log_avg_pnl", "") + logger.info( + f"{symbol} 命中差时段 ({hh}:00),跳过自动开仓(平均亏损:{avg_txt} USDT)" + ) + else: + logger.info( + f"{symbol} 影子模式限制,跳过自动开仓:{shadow_mode_result.get('skip_reason', '')}" + ) + continue + # 确定交易方向(基于技术指标) trade_direction = trade_signal['direction'] # 4H 下跌禁止开多(熊市/保守方案:避免逆势抄底) @@ -328,6 +354,9 @@ class TradingStrategy: entry_context['macd_histogram'] = macd_hist if symbol_info.get('atr') is not None: entry_context['atr'] = symbol_info.get('atr') + # 影子模式执行记录(每笔开仓) + sm_applied = dict(shadow_mode_result.get("shadow_mode_applied") or {}) + entry_context["shadow_mode_applied"] = sm_applied # 开仓(使用改进的仓位管理) position = await self.position_manager.open_position( @@ -343,6 +372,7 @@ class TradingStrategy: klines=symbol_info.get('klines'), # 传递K线数据用于动态止损 bollinger=symbol_info.get('bollinger'), # 传递布林带数据用于动态止损 entry_context=entry_context, + shadow_leverage_multiplier=float(shadow_mode_result.get("leverage_multiplier") or 1.0), ) if position: @@ -583,6 +613,15 @@ class TradingStrategy: return False return True + + def _check_shadow_mode_filters(self, symbol: str) -> Dict: + """ + 检查影子模式过滤条件:黑名单、差时段、杠杆乘子(见 config/current_suggestions.json)。 + 返回 evaluate_shadow_mode 的字典。 + """ + bj = timezone(timedelta(hours=8)) + hour_bj = datetime.now(bj).hour + return evaluate_shadow_mode(symbol, hour_bj, config.TRADING_CONFIG) async def _analyze_trade_signal(self, symbol_info: Dict) -> Dict: """