"""M00 教学图：成本拖累与杠杆对夏普的影响。

数字全部来自正文手算例，不是历史回测。
"""

from __future__ import annotations

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

# Windows 中文黑体；缺字时 matplotlib 会回退，负号单独关掉 unicode 减号。
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False

OUT = Path(__file__).resolve().parent / "cost_and_leverage.png"

# 与 src/quant/backtest/metrics.py 一致：单边换手 = 成交额 / 2 / 净值。
# 单边成本打在每一元成交上，所以拖累 = 2 × 年化单边换手 × 单边成本。
COST_PER_SIDE = 0.0015
one_way = np.linspace(0.0, 60.0, 400)
drag = 2.0 * one_way * COST_PER_SIDE

monthly = 0.5 * 12  # 每月换掉一半仓位
daily = 0.20 * 252  # 每个交易日单边换手 20%

# 杠杆：资产 μ=12%、σ=15%、rf=4%。股权收益 = L*μ − (L−1)*借款利率。
mu, sigma, rf = 0.12, 0.15, 0.04
leverage = np.linspace(1.0, 3.0, 200)


def sharpe(lev: np.ndarray, borrow: float) -> np.ndarray:
    equity_ret = lev * mu - (lev - 1.0) * borrow
    excess = equity_ret - rf
    vol = lev * sigma
    return excess / vol


fig, axes = plt.subplots(1, 2, figsize=(11.2, 4.6))

ax = axes[0]
ax.plot(one_way, drag * 100, color="#1f4e79", lw=2.0)
ax.scatter(
    [monthly, daily],
    [2 * monthly * COST_PER_SIDE * 100, 2 * daily * COST_PER_SIDE * 100],
    color=["#2a9d8f", "#c1121f"],
    zorder=3,
    s=42,
)
ax.annotate(
    f"月度换仓一半\n单边换手 {monthly:.0f}x → {2 * monthly * COST_PER_SIDE * 100:.1f}%/年",
    xy=(monthly, 2 * monthly * COST_PER_SIDE * 100),
    xytext=(12, 6.2),
    fontsize=9,
    arrowprops={"arrowstyle": "->", "color": "#2a9d8f"},
    color="#1d3557",
)
ax.annotate(
    f"日度单边 20%\n{daily:.1f}x → {2 * daily * COST_PER_SIDE * 100:.1f}%/年",
    xy=(daily, 2 * daily * COST_PER_SIDE * 100),
    xytext=(28, 10.5),
    fontsize=9,
    arrowprops={"arrowstyle": "->", "color": "#c1121f"},
    color="#1d3557",
)
ax.set_xlabel("年化单边换手（倍）")
ax.set_ylabel("年化成本拖累（%）")
ax.set_title("单边成本 15 bps 时，换手如何吃掉收益")
ax.set_xlim(0, 60)
ax.set_ylim(0, 20)
ax.grid(True, alpha=0.3)

ax = axes[1]
ax.plot(leverage, sharpe(leverage, rf), color="#1f4e79", lw=2.0, label="借款利率 = 无风险 4%")
ax.plot(
    leverage,
    sharpe(leverage, 0.05),
    color="#c1121f",
    lw=2.0,
    label="借款利率 = 5%（融资利差 1%）",
)
ax.axhline(0.08 / 0.15, color="#6c757d", ls="--", lw=1.0)
ax.set_xlabel("杠杆倍数 L（每 1 元本金买入的资产）")
ax.set_ylabel("夏普比率")
ax.set_title("杠杆不创造夏普；融资利差会压低夏普")
ax.set_xlim(1, 3)
ax.legend(frameon=False, fontsize=8)
ax.grid(True, alpha=0.3)

fig.suptitle("M00 手算例：成本与杠杆（非历史回测）", fontsize=12)
fig.tight_layout()
fig.savefig(OUT, dpi=140, bbox_inches="tight")
print(f"wrote {OUT}")
print("monthly_drag", 2 * monthly * COST_PER_SIDE)
print("daily_drag", 2 * daily * COST_PER_SIDE)
print("sharpe_1x", (mu - rf) / sigma)
print("sharpe_2x_rf", float(sharpe(np.array([2.0]), rf)[0]))
print("sharpe_2x_5pct", float(sharpe(np.array([2.0]), 0.05)[0]))
print("sqrt252", float(np.sqrt(252)))
nav = np.array([100.0, 110.0, 105.0, 90.0, 95.0, 120.0])
peak = np.maximum.accumulate(nav)
dd = nav / peak - 1.0
print("mdd", float(dd.min()), "path", dd.tolist())
r = 0.0004
print("arith", r * 252)
print("geom", (1 + r) ** 252 - 1)
print("vol", 0.01 * np.sqrt(252))
