auto_trade_sys/scripts/apply_recommended_config.py
薇薇安 a08b8d31c6 feat(config): Add auto trade symbol whitelist functionality
Introduced a new configuration option for an auto trade symbol whitelist, allowing only specified contracts to execute automatic trades. Updated relevant components to support this feature, enhancing control over automated trading strategies. The whitelist can be configured to restrict or allow trades based on user-defined criteria, improving risk management and trading precision.
2026-04-18 21:32:59 +08:00

214 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
一键批量应用全局策略配置(推荐配置 / 自定义配置)。
用途:
- 避免在前端逐项手动修改配置;
- 支持默认推荐值、外部 JSON 覆盖、命令行临时覆盖;
- 自动写入 global_strategy_config并尽量同步到 Redis 配置缓存。
示例:
python scripts/apply_recommended_config.py
python scripts/apply_recommended_config.py --dry-run
python scripts/apply_recommended_config.py --from config/my_batch.json
python scripts/apply_recommended_config.py --set MIN_SIGNAL_STRENGTH=9 --set SHADOW_MODE_AUTO_APPLY=true
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List
PROJECT_ROOT = Path(__file__).resolve().parent.parent
BACKEND_DIR = PROJECT_ROOT / "backend"
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
# 默认推荐配置(可按你的风格长期维护)
DEFAULT_RECOMMENDED: Dict[str, Dict[str, Any]] = {
# 空=不限制;非空则仅名单内合约自动下单(窄宇宙),见 config/strategy_narrow_universe.json
"AUTO_TRADE_SYMBOL_WHITELIST": {"value": "", "type": "string", "category": "strategy"},
"AUTO_TRADE_ONLY_TRENDING": {"value": True, "type": "boolean", "category": "strategy"},
"BETA_FILTER_ENABLED": {"value": True, "type": "boolean", "category": "strategy"},
"BETA_FILTER_THRESHOLD": {"value": -0.01, "type": "number", "category": "strategy"},
"MIN_SIGNAL_STRENGTH": {"value": 8, "type": "number", "category": "strategy"},
"RANGING_MARKET_SIGNAL_BOOST": {"value": 2, "type": "number", "category": "strategy"},
"NO_OPEN_HOURS_BJ": {"value": "1,2,3,4,5,6,8,9,17,18,19", "type": "string", "category": "risk"},
"MANUAL_BLOCKED_SYMBOLS": {"value": "", "type": "string", "category": "strategy"},
"MANUAL_REDUCED_SYMBOLS": {
"value": "HYPEUSDT,XANUSDT,WIFUSDT,ANKRUSDT,RIVERUSDT,POLYXUSDT,PIPPINUSDT,AINUSDT,ANIMEUSDT,COSUSDT,GUNUSDT,HYPERUSDT",
"type": "string",
"category": "strategy",
},
"MANUAL_REDUCED_SYMBOL_POSITION_FACTOR": {"value": 0.5, "type": "number", "category": "strategy"},
"MANUAL_REDUCED_SYMBOL_SIGNAL_BOOST": {"value": 1, "type": "number", "category": "strategy"},
"ENTRY_PULLBACK_FILTER_ENABLED": {"value": True, "type": "boolean", "category": "strategy"},
"ENTRY_PULLBACK_INTERVAL": {"value": "", "type": "string", "category": "strategy"},
"ENTRY_PULLBACK_LOOKBACK_BARS": {"value": 24, "type": "number", "category": "strategy"},
"ENTRY_PULLBACK_MIN_BARS": {"value": 5, "type": "number", "category": "strategy"},
"ENTRY_PULLBACK_MAX_LONG_IN_RANGE": {"value": 0.58, "type": "number", "category": "strategy"},
"ENTRY_PULLBACK_MIN_SHORT_IN_RANGE": {"value": 0.42, "type": "number", "category": "strategy"},
"SHADOW_MODE_AUTO_APPLY": {"value": True, "type": "boolean", "category": "strategy"},
"SHADOW_MODE_MIN_CONFIDENCE": {"value": 0.7, "type": "number", "category": "strategy"},
"SHADOW_MODE_INCREASE_LEVERAGE_MULT": {"value": 1.3, "type": "number", "category": "strategy"},
"SHADOW_MODE_DECREASE_LEVERAGE_MULT": {"value": 0.5, "type": "number", "category": "strategy"},
"SHADOW_MODE_SUGGESTIONS_PATH": {"value": "config/current_suggestions.json", "type": "string", "category": "strategy"},
"SHADOW_MODE_TRACKING_PATH": {"value": "config/shadow_mode_tracking.json", "type": "string", "category": "strategy"},
}
def parse_cli_value(raw: str) -> Any:
v = raw.strip()
low = v.lower()
if low in ("true", "false"):
return low == "true"
try:
if "." in v:
return float(v)
return int(v)
except ValueError:
return v
def infer_type(v: Any) -> str:
if isinstance(v, bool):
return "boolean"
if isinstance(v, (int, float)):
return "number"
return "string"
def load_external_json(path: Path) -> Dict[str, Dict[str, Any]]:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, list):
out: Dict[str, Dict[str, Any]] = {}
for item in data:
if not isinstance(item, dict) or "key" not in item:
continue
key = str(item["key"]).strip()
if not key:
continue
out[key] = {
"value": item.get("value"),
"type": item.get("type") or infer_type(item.get("value")),
"category": item.get("category") or "strategy",
"description": item.get("description"),
}
return out
if isinstance(data, dict):
out = {}
for key, val in data.items():
if isinstance(val, dict) and "value" in val:
out[key] = {
"value": val.get("value"),
"type": val.get("type") or infer_type(val.get("value")),
"category": val.get("category") or "strategy",
"description": val.get("description"),
}
else:
out[key] = {
"value": val,
"type": infer_type(val),
"category": "strategy",
"description": None,
}
return out
raise ValueError("外部 JSON 必须是对象或数组")
def main() -> None:
parser = argparse.ArgumentParser(description="批量应用全局策略配置")
parser.add_argument("--from", dest="from_path", default="", help="外部 JSON 配置文件路径")
parser.add_argument(
"--set",
action="append",
default=[],
help="临时覆盖项,格式 KEY=VALUE可重复多次",
)
parser.add_argument("--dry-run", action="store_true", help="只打印,不落库")
parser.add_argument("--updated-by", default="apply_recommended_config", help="写入 updated_by")
args = parser.parse_args()
merged = dict(DEFAULT_RECOMMENDED)
if args.from_path:
from_path = Path(args.from_path)
if not from_path.is_absolute():
from_path = PROJECT_ROOT / from_path
if not from_path.is_file():
raise FileNotFoundError(f"未找到配置文件: {from_path}")
ext = load_external_json(from_path)
merged.update(ext)
for item in args.set:
if "=" not in item:
raise ValueError(f"--set 参数格式错误: {item}(应为 KEY=VALUE")
k, raw = item.split("=", 1)
key = k.strip()
value = parse_cli_value(raw)
if not key:
raise ValueError(f"--set key 不能为空: {item}")
base = merged.get(key, {})
merged[key] = {
"value": value,
"type": base.get("type") or infer_type(value),
"category": base.get("category") or "strategy",
"description": base.get("description"),
}
print(f"将应用 {len(merged)} 个配置项dry_run={args.dry_run}")
for key in sorted(merged.keys()):
meta = merged[key]
print(f" - {key} = {meta.get('value')} [{meta.get('type')}/{meta.get('category')}]")
if args.dry_run:
return
from database.models import GlobalStrategyConfig
mgr = None
try:
import config_manager
if hasattr(config_manager, "GlobalStrategyConfigManager"):
mgr = config_manager.GlobalStrategyConfigManager()
except Exception:
mgr = None
ok, fail = 0, 0
for key, meta in merged.items():
try:
GlobalStrategyConfig.set(
key,
meta.get("value"),
meta.get("type") or infer_type(meta.get("value")),
meta.get("category") or "strategy",
meta.get("description"),
updated_by=args.updated_by,
)
if mgr is not None:
try:
mgr._set_to_redis(key, meta.get("value"))
mgr._cache[key] = meta.get("value")
except Exception:
pass
ok += 1
except Exception as e:
fail += 1
print(f"[失败] {key}: {e}")
print(f"\n完成:成功 {ok},失败 {fail}")
if fail > 0:
sys.exit(1)
if __name__ == "__main__":
main()