Added new configurations for a layered profit locking system, allowing for gradual profit protection at specified thresholds. Introduced manual trading options, including reduced and blocked symbol lists, to enhance trading strategy flexibility. Updated relevant backend and frontend components to reflect these changes, improving risk management and user control over trading activities.
135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
"""
|
||
交易对分层策略:
|
||
- 正常:按原策略处理
|
||
- reduced:缩小仓位,并在策略侧提高信号门槛
|
||
- blocked:直接禁止自动开仓
|
||
"""
|
||
import logging
|
||
import re
|
||
from typing import Any, Dict, Set
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def _parse_symbol_list(raw: Any) -> Set[str]:
|
||
"""兼容 string/list/dict 三种配置格式,统一转成大写 symbol 集合。"""
|
||
if raw is None:
|
||
return set()
|
||
|
||
if isinstance(raw, dict):
|
||
if "symbols" in raw:
|
||
return _parse_symbol_list(raw.get("symbols"))
|
||
if "items" in raw:
|
||
return _parse_symbol_list(raw.get("items"))
|
||
return set()
|
||
|
||
if isinstance(raw, str):
|
||
parts = re.split(r"[\s,;,\n\r\t]+", raw)
|
||
return {p.strip().upper() for p in parts if p and p.strip()}
|
||
|
||
if isinstance(raw, list):
|
||
items: Set[str] = set()
|
||
for item in raw:
|
||
if isinstance(item, str):
|
||
sym = item.strip()
|
||
elif isinstance(item, dict):
|
||
sym = str(item.get("symbol") or item.get("value") or "").strip()
|
||
else:
|
||
sym = ""
|
||
if sym:
|
||
items.add(sym.upper())
|
||
return items
|
||
|
||
return set()
|
||
|
||
|
||
def _safe_float(value: Any, default: float) -> float:
|
||
try:
|
||
return float(value)
|
||
except (TypeError, ValueError):
|
||
return default
|
||
|
||
|
||
def _safe_int(value: Any, default: int) -> int:
|
||
try:
|
||
return int(value)
|
||
except (TypeError, ValueError):
|
||
return default
|
||
|
||
|
||
def resolve_symbol_trading_policy(symbol: str) -> Dict[str, Any]:
|
||
"""
|
||
合并手动名单与统计名单,返回统一的交易对分层结果。
|
||
|
||
返回字段:
|
||
- blocked: 是否禁止自动开仓
|
||
- reduced: 是否进入减仓观察名单
|
||
- position_factor: reduced 时建议的仓位系数
|
||
- signal_boost: reduced 时建议提高的最小信号门槛
|
||
- sources: 命中的来源,便于日志定位
|
||
"""
|
||
policy = {
|
||
"blocked": False,
|
||
"reduced": False,
|
||
"position_factor": 1.0,
|
||
"signal_boost": 0,
|
||
"sources": [],
|
||
}
|
||
symbol = (symbol or "").strip().upper()
|
||
if not symbol:
|
||
return policy
|
||
|
||
try:
|
||
from config_manager import GlobalStrategyConfigManager
|
||
|
||
mgr = GlobalStrategyConfigManager()
|
||
stats_symbol = mgr.get("STATS_SYMBOL_FILTERS", {}) or {}
|
||
|
||
manual_blocked = _parse_symbol_list(mgr.get("MANUAL_BLOCKED_SYMBOLS", ""))
|
||
manual_reduced = _parse_symbol_list(mgr.get("MANUAL_REDUCED_SYMBOLS", ""))
|
||
|
||
manual_position_factor = _safe_float(
|
||
mgr.get("MANUAL_REDUCED_SYMBOL_POSITION_FACTOR", 0.5), 0.5
|
||
)
|
||
manual_position_factor = max(0.05, min(manual_position_factor, 1.0))
|
||
|
||
manual_signal_boost = _safe_int(
|
||
mgr.get("MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST", 1), 1
|
||
)
|
||
manual_signal_boost = max(0, min(manual_signal_boost, 5))
|
||
|
||
if symbol in manual_blocked:
|
||
policy["blocked"] = True
|
||
policy["sources"].append("manual_blocked")
|
||
|
||
if symbol in manual_reduced:
|
||
policy["reduced"] = True
|
||
policy["position_factor"] = min(policy["position_factor"], manual_position_factor)
|
||
policy["signal_boost"] = max(policy["signal_boost"], manual_signal_boost)
|
||
policy["sources"].append("manual_reduced")
|
||
|
||
for item in (stats_symbol.get("blacklist") or []):
|
||
if (item.get("symbol") or "").strip().upper() != symbol:
|
||
continue
|
||
mode = (item.get("mode") or "reduced").strip().lower()
|
||
if mode == "hard":
|
||
policy["blocked"] = True
|
||
policy["sources"].append("stats_hard")
|
||
else:
|
||
policy["reduced"] = True
|
||
policy["position_factor"] = min(policy["position_factor"], 0.5)
|
||
policy["signal_boost"] = max(policy["signal_boost"], 1)
|
||
policy["sources"].append("stats_reduced")
|
||
break
|
||
except Exception as e:
|
||
logger.debug(f"{symbol} 读取交易对分层策略失败: {e}")
|
||
|
||
if policy["blocked"]:
|
||
policy["reduced"] = False
|
||
policy["position_factor"] = 1.0
|
||
policy["signal_boost"] = 0
|
||
|
||
# 去重并保留顺序,便于日志阅读。
|
||
policy["sources"] = list(dict.fromkeys(policy["sources"]))
|
||
return policy
|