影子模式
This commit is contained in:
parent
c6103522be
commit
9a0061c06a
|
|
@ -779,6 +779,42 @@ async def get_global_configs(
|
||||||
"category": "strategy",
|
"category": "strategy",
|
||||||
"description": "做空:相对位置 pos 低于此值则不开仓(默认约拒绝最下 38% 区域)。越大越严。",
|
"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():
|
for k, meta in ADDITIONAL_STRATEGY_DEFAULTS.items():
|
||||||
if k not in result:
|
if k not in result:
|
||||||
|
|
|
||||||
|
|
@ -974,6 +974,14 @@ class ConfigManager:
|
||||||
'ENTRY_PULLBACK_MAX_LONG_IN_RANGE': eff_get('ENTRY_PULLBACK_MAX_LONG_IN_RANGE', 0.62),
|
'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),
|
'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_system 能知道是哪种模式)
|
||||||
'TRADING_PROFILE': profile,
|
'TRADING_PROFILE': profile,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,18 @@ DEFAULTS_TO_SYNC = [
|
||||||
"description": "做多:区间相对位置上限制0~1,默认0.62。"},
|
"description": "做多:区间相对位置上限制0~1,默认0.62。"},
|
||||||
{"config_key": "ENTRY_PULLBACK_MIN_SHORT_IN_RANGE", "config_value": "0.38", "config_type": "number", "category": "strategy",
|
{"config_key": "ENTRY_PULLBACK_MIN_SHORT_IN_RANGE", "config_value": "0.38", "config_type": "number", "category": "strategy",
|
||||||
"description": "做空:区间相对位置下限制0~1,默认0.38。"},
|
"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": "减仓名单杠杆乘数。"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
6
config/current_suggestions.json
Normal file
6
config/current_suggestions.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"blacklist": [],
|
||||||
|
"increase_position": [],
|
||||||
|
"decrease_position": [],
|
||||||
|
"worst_hours": []
|
||||||
|
}
|
||||||
5
config/shadow_mode_tracking.json
Normal file
5
config/shadow_mode_tracking.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"accuracy": 1.0,
|
||||||
|
"rolling_accuracy": 1.0,
|
||||||
|
"notes": "由 shadow_mode_analyzer / 人工更新;低于 SHADOW_MODE_MIN_CONFIDENCE 时不自动应用建议"
|
||||||
|
}
|
||||||
|
|
@ -75,6 +75,12 @@ const KEY_LABELS = {
|
||||||
ENTRY_PULLBACK_MIN_BARS: '回撤过滤最少K线数',
|
ENTRY_PULLBACK_MIN_BARS: '回撤过滤最少K线数',
|
||||||
ENTRY_PULLBACK_MAX_LONG_IN_RANGE: '做多区间位置上限(0~1)',
|
ENTRY_PULLBACK_MAX_LONG_IN_RANGE: '做多区间位置上限(0~1)',
|
||||||
ENTRY_PULLBACK_MIN_SHORT_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_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_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。' },
|
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 () => {
|
const loadConfigs = async () => {
|
||||||
|
|
|
||||||
35
scripts/shadow_mode_analyzer.py
Normal file
35
scripts/shadow_mode_analyzer.py
Normal file
|
|
@ -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()
|
||||||
|
|
@ -198,6 +198,14 @@ DEFAULT_TRADING_CONFIG = {
|
||||||
'NO_OPEN_HOURS_BJ': '',
|
'NO_OPEN_HOURS_BJ': '',
|
||||||
'ONE_WAY_POSITION_ONLY': True, # 账号固定为单向持仓模式,不传 positionSide,不检测对冲模式(避免 -4061)
|
'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_POSITION_PERCENT': 0.20, # 单笔仓位上限20%(作为风控熔断,实际仓位由固定风险模型决定)
|
||||||
'MAX_TOTAL_POSITION_PERCENT': 0.80, # 总仓位80%(避免满仓,留有余地)
|
'MAX_TOTAL_POSITION_PERCENT': 0.80, # 总仓位80%(避免满仓,留有余地)
|
||||||
'MIN_POSITION_PERCENT': 0.01, # 最小仓位1%
|
'MIN_POSITION_PERCENT': 0.01, # 最小仓位1%
|
||||||
|
|
|
||||||
|
|
@ -343,6 +343,7 @@ class PositionManager:
|
||||||
klines: Optional[List] = None,
|
klines: Optional[List] = None,
|
||||||
bollinger: Optional[Dict] = None,
|
bollinger: Optional[Dict] = None,
|
||||||
entry_context: Optional[Dict] = None,
|
entry_context: Optional[Dict] = None,
|
||||||
|
shadow_leverage_multiplier: float = 1.0,
|
||||||
) -> Optional[Dict]:
|
) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
开仓
|
开仓
|
||||||
|
|
@ -393,6 +394,30 @@ class PositionManager:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"{symbol} 开仓前持仓数复核异常(继续): {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 报错)
|
# 设置杠杆(确保为 int,避免动态杠杆传入 float 导致 API/range 报错)
|
||||||
actual_leverage = await self.client.set_leverage(symbol, int(leverage))
|
actual_leverage = await self.client.set_leverage(symbol, int(leverage))
|
||||||
|
|
||||||
|
|
|
||||||
226
trading_system/shadow_mode.py
Normal file
226
trading_system/shadow_mode.py
Normal file
|
|
@ -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)
|
||||||
|
|
@ -14,6 +14,7 @@ try:
|
||||||
from .signal_filters import SignalFilterContext, apply_signal_filters
|
from .signal_filters import SignalFilterContext, apply_signal_filters
|
||||||
from . import config
|
from . import config
|
||||||
from .symbol_policy import resolve_symbol_trading_policy
|
from .symbol_policy import resolve_symbol_trading_policy
|
||||||
|
from .shadow_mode import evaluate_shadow_mode, invalidate_cache as shadow_invalidate_cache
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from binance_client import BinanceClient
|
from binance_client import BinanceClient
|
||||||
from market_scanner import MarketScanner
|
from market_scanner import MarketScanner
|
||||||
|
|
@ -22,6 +23,7 @@ except ImportError:
|
||||||
from signal_filters import SignalFilterContext, apply_signal_filters
|
from signal_filters import SignalFilterContext, apply_signal_filters
|
||||||
import config
|
import config
|
||||||
from symbol_policy import resolve_symbol_trading_policy
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -110,6 +112,10 @@ class TradingStrategy:
|
||||||
config._config_manager.reload_from_redis()
|
config._config_manager.reload_from_redis()
|
||||||
config.TRADING_CONFIG = config._get_trading_config()
|
config.TRADING_CONFIG = config._get_trading_config()
|
||||||
logger.debug("配置已从Redis重新加载")
|
logger.debug("配置已从Redis重新加载")
|
||||||
|
try:
|
||||||
|
shadow_invalidate_cache()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"从Redis重新加载配置失败: {e}")
|
logger.warning(f"从Redis重新加载配置失败: {e}")
|
||||||
|
|
||||||
|
|
@ -241,6 +247,26 @@ class TradingStrategy:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"{symbol} 应用信号过滤插件时出错(忽略,回退为原逻辑): {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']
|
trade_direction = trade_signal['direction']
|
||||||
# 4H 下跌禁止开多(熊市/保守方案:避免逆势抄底)
|
# 4H 下跌禁止开多(熊市/保守方案:避免逆势抄底)
|
||||||
|
|
@ -328,6 +354,9 @@ class TradingStrategy:
|
||||||
entry_context['macd_histogram'] = macd_hist
|
entry_context['macd_histogram'] = macd_hist
|
||||||
if symbol_info.get('atr') is not None:
|
if symbol_info.get('atr') is not None:
|
||||||
entry_context['atr'] = symbol_info.get('atr')
|
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(
|
position = await self.position_manager.open_position(
|
||||||
|
|
@ -343,6 +372,7 @@ class TradingStrategy:
|
||||||
klines=symbol_info.get('klines'), # 传递K线数据用于动态止损
|
klines=symbol_info.get('klines'), # 传递K线数据用于动态止损
|
||||||
bollinger=symbol_info.get('bollinger'), # 传递布林带数据用于动态止损
|
bollinger=symbol_info.get('bollinger'), # 传递布林带数据用于动态止损
|
||||||
entry_context=entry_context,
|
entry_context=entry_context,
|
||||||
|
shadow_leverage_multiplier=float(shadow_mode_result.get("leverage_multiplier") or 1.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
if position:
|
if position:
|
||||||
|
|
@ -583,6 +613,15 @@ class TradingStrategy:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
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:
|
async def _analyze_trade_signal(self, symbol_info: Dict) -> Dict:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user