#!/usr/bin/env python3 """ LPS V2 单独 Phase2(不改 Spring、不合并组合) 同一 WFO / Regime / 成本模型。 目标: net PF > 1.2;频率约 5-15/year。 """ from __future__ import annotations import json import logging import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[3] sys.path.insert(0, str(ROOT)) from user_data.Chan.scripts.wyckoff_phase2_compare import ( # noqa: E402 BRANCHES, WFO, install_offline_markets, run_bt, ) OUT = ROOT / "user_data/Chan/scripts/wyckoff_lps_v2_phase2_result.json" COMPARE = ROOT / "user_data/Chan/scripts/wyckoff_phase2_compare_result.json" def main() -> None: logging.getLogger("freqtrade").setLevel(logging.ERROR) install_offline_markets() br = next(b for b in BRANCHES if b["name"] == "LPS_V2") print(f"===== {br['name']} ({br['strategy']}) — LPS-only Phase2 =====", flush=True) block = {"version": "LPS_V2", "wfo": {}, "regimes": {}, "cost_stress": {}, "target": br["target"]} print("--- WFO ---", flush=True) for wname, tr in WFO: r = run_bt(br["strategy"], br["config"], tr) block["wfo"][wname] = {"timerange": tr, **r} print( f" {wname:<8} profit={r['profit_pct']:>7.2f}% n={r['trades']:<3} " f"dd={r['dd_pct']:.1f}% pf={r['pf']:.2f} wr={r['winrate']:.1f}%", flush=True, ) print("--- Regime ---", flush=True) for mode in ["trend", "bull", "bear", "range", "all"]: r = run_bt(br["strategy"], br["config"], "20230101-", regime=mode) block["regimes"][mode] = r print( f" {mode:<6} profit={r['profit_pct']:>7.2f}% n={r['trades']:<3} " f"pf={r['pf']:.2f} (loaded={r['regime_loaded']})", flush=True, ) print("--- Cost (net PF) ---", flush=True) for label, fee, extra in [ ("fee_5bps", 0.0005, 0.0), ("fee_5bps+slip_5bps", 0.0005, 0.0005), ("fee_10bps+slip_10bps", 0.0010, 0.0010), ]: r = run_bt(br["strategy"], br["config"], "20230101-", fee=fee, extra_cost=extra) block["cost_stress"][label] = r flag = "OK" if r["pf"] >= br["target"]["pf"] else ("WEAK" if r["pf"] >= 1.0 else "FAIL") print( f" {label:<22} profit={r['profit_pct']:>7.2f}% n={r['trades']:<3} " f"pf={r['pf']:.2f} [{flag}]", flush=True, ) full = block["wfo"]["full"] mid = block["cost_stress"]["fee_5bps+slip_5bps"] tpy = full["trades"] / 3.6 trend_pf = block["regimes"]["trend"]["pf"] range_pf = block["regimes"]["range"]["pf"] block["verdict"] = { "full_pf": full["pf"], "full_dd": full["dd_pct"], "trades_per_year": tpy, "net_mid_pf": mid["pf"], "target_pf_ok": mid["pf"] >= br["target"]["pf"], "target_dd_ok": full["dd_pct"] <= br["target"]["dd"], "freq_ok": 5.0 <= tpy <= 15.0, "regime_logic_ok": trend_pf >= range_pf, # 趋势应不差于横盘 "status": "PASS" if (mid["pf"] >= br["target"]["pf"] and full["dd_pct"] <= br["target"]["dd"]) else "FAIL", "hypothesis": "4h native SOS → 1h LPS", } print("\n===== Verdict =====") print(json.dumps(block["verdict"], ensure_ascii=False, indent=2)) OUT.write_text(json.dumps(block, indent=2, ensure_ascii=False)) # 合并进 compare 结果(保留 Spring,覆盖 LPS) if COMPARE.exists(): prev = json.loads(COMPARE.read_text()) else: prev = {"branches": {}} prev.setdefault("branches", {})["LPS_V2"] = block # 清理旧 LPS_V1 key 的活跃地位,保留作历史可手动看 spring = prev["branches"].get("Spring_V1", {}).get("verdict", {}) prev["system_status"] = { "spring": "BASELINE FROZEN / PASS + Limited Evidence", "lps": block["verdict"]["status"], "spring_tpy": spring.get("trades_per_year"), "lps_tpy": tpy, "next": "若 LPS PASS → 组合层;否则 Spring-only", } COMPARE.write_text(json.dumps(prev, indent=2, ensure_ascii=False)) print(f"\nSaved {OUT}") print("system_status:", json.dumps(prev["system_status"], ensure_ascii=False)) if __name__ == "__main__": main()