1
This commit is contained in:
parent
9a0061c06a
commit
3e4b09724e
211
scripts/apply_recommended_config.py
Normal file
211
scripts/apply_recommended_config.py
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
#!/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]] = {
|
||||||
|
"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()
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user