"""M02 教学图：SPY 月末 10 个月均线，信号延迟一个月。

现金收益设为 0，不扣佣金。这是手算级的规则演示，不是实盘预期。
"""

from __future__ import annotations

import math
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False

ROOT = Path(__file__).resolve().parents[3]
SPY = ROOT / "Labs" / "momentum_plus_lab" / "seed_data" / "spy.csv"
OUT = Path(__file__).resolve().parent / "spy_ten_month_ma.png"
RF_ANNUAL = 0.02


def max_drawdown(nav: pd.Series) -> float:
    return float((nav / nav.cummax() - 1.0).min())


def summarize(ret: pd.Series, rf_annual: float) -> dict[str, float]:
    r = ret.dropna()
    years = len(r) / 12.0
    cagr = float((1.0 + r).prod() ** (1.0 / years) - 1.0)
    vol = float(r.std(ddof=1) * math.sqrt(12.0))
    excess = float(r.mean() - rf_annual / 12.0)
    sharpe = excess / float(r.std(ddof=1)) * math.sqrt(12.0)
    nav = (1.0 + r).cumprod()
    return {
        "cagr": cagr,
        "vol": vol,
        "sharpe": sharpe,
        "mdd": max_drawdown(nav),
        "n": float(len(r)),
    }


def main() -> None:
    px = pd.read_csv(SPY, parse_dates=["session"]).set_index("session")["adj_close"]
    px = px.astype(float).sort_index()
    month = px.resample("ME").last().dropna()
    sma = month.rolling(10).mean()
    # 月末才知道均线。shift(1) 后，本月收益只用上月末的信号。
    # 均线不足 10 个月时比较结果是 False，不能把它当成「已决定空仓」。
    held = (month > sma).where(sma.notna()).shift(1)
    mret = month.pct_change()
    both = pd.concat({"mkt": mret, "held": held}, axis=1).dropna()
    strat = both["mkt"] * both["held"].astype(float)
    bh = summarize(both["mkt"], RF_ANNUAL)
    st = summarize(strat, RF_ANNUAL)
    print("window", both.index.min().date(), both.index.max().date())
    print("buyhold", bh)
    print("faber", st)
    print("months_in", int(both["held"].sum()), "months", int(len(both)))

    lam = -0.07
    phi = 1.0 + lam
    half_discrete = math.log(0.5) / math.log(phi)
    half_cont = -math.log(2.0) / lam
    print("half_discrete", half_discrete, "half_cont", half_cont)

    levels = pd.Series(
        [100, 102, 101, 108, 112, 110, 115, 121, 118, 125, 130, 128, 140, 136],
        index=pd.RangeIndex(1, 15),
    )
    mom = levels.shift(1) / levels.shift(13) - 1.0
    print("mom_12_1_at_14", float(mom.loc[14]))
    print("tr13", float(levels.loc[13]), "tr1", float(levels.loc[1]))

    fig, ax = plt.subplots(figsize=(8.4, 4.4))
    nav_m = (1.0 + both["mkt"]).cumprod()
    nav_s = (1.0 + strat).cumprod()
    ax.plot(nav_m.index, nav_m, color="#6c757d", lw=1.4, label="买入持有")
    ax.plot(nav_s.index, nav_s, color="#1f4e79", lw=1.8, label="10 个月均线，次月才换仓")
    ax.set_yscale("log")
    ax.set_ylabel("净值（对数刻度，起点为 1）")
    ax.set_title("SPY 复权价：月末信号、下月才持有，现金收益为 0")
    ax.legend(frameon=False)
    ax.grid(True, alpha=0.3)
    fig.tight_layout()
    fig.savefig(OUT, dpi=140, bbox_inches="tight")
    print("wrote", OUT)


if __name__ == "__main__":
    main()
