"""净值曲线图:策略 vs 买入持有(对数轴),标注逻辑退出区间与 2026 OOS 分界。 用法:python make_chart.py [tag] (默认 final,读 results//{curve.csv,stats.json}) """ import csv import json import sys from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt # noqa: E402 matplotlib.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"] matplotlib.rcParams["axes.unicode_minus"] = False HERE = Path(__file__).resolve().parent tag = sys.argv[1] if len(sys.argv) > 1 else "final" res = HERE / "results" / tag rows = list(csv.DictReader(open(res / "curve.csv", encoding="utf-8"))) dates = [r["date"] for r in rows] V = [float(r["V"]) for r in rows] B = [float(r["bh_V"]) for r in rows] stats = json.loads((res / "stats.json").read_text(encoding="utf-8")) exit_spans = stats["exit_spans"] has_oos = "oos" in stats fig, ax = plt.subplots(figsize=(11.5, 6), dpi=150) ax.plot(dates, B, lw=1.2, color="#b8b8b8", label="买入持有 (LBMA PM)") ax.plot(dates, V, lw=1.6, color="#c8912a", label="金仓 v2.1 策略") ax.set_yscale("log") for lo, hi in exit_spans: ax.axvspan(lo, hi, color="#c0392b", alpha=0.13, lw=0) if has_oos: ax.axvline("2026-01-01", color="#4569a0", ls="--", lw=1.1) xmax = len(dates) - 1 ax.annotate("2026 样本外 →", xy=(0.865, 0.045), xycoords="axes fraction", fontsize=9, color="#4569a0") ax.annotate("红色底纹 = 逻辑退出区间\n(pace < 102 吨/季)", xy=(0.02, 0.74), xycoords="axes fraction", fontsize=9, color="#c0392b") tick_idx = [i for i, d in enumerate(dates) if d[5:7] == "01" and (i == 0 or dates[i - 1][5:7] != "01")] step = max(1, len(tick_idx) // 8) ax.set_xticks([dates[i] for i in tick_idx[::step]]) ax.set_xticklabels([dates[i][:4] for i in tick_idx[::step]], fontsize=9) ax.set_ylabel("净值(V0 = 10,000,对数轴)", fontsize=10) ax.set_title(f"金仓 v2.1 全历史回测 {dates[0]} → {dates[-1]}(USD,含 0.05% 单边成本)", fontsize=12) ax.grid(alpha=0.25, which="both") ax.legend(loc="upper left", fontsize=10) fig.tight_layout() out = res / "equity.png" fig.savefig(out) print(f"已写 {out}")