"""M09 教学图：基本定律的上界，以及夏普平方和只在最优主动风险上成立。

两条曲线都是公式，不是回测。右图的市场与主动收益假定互不相关。
"""

from __future__ import annotations

import math
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

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

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


def transfer_coeff(score: np.ndarray, label: str) -> float:
    # 残差波动相同，所以转移系数就是得分和主动权重的相关。
    positive = np.clip(score, 0.0, None)
    long_only = positive / positive.sum()
    active = long_only - 0.25
    tc = float(np.corrcoef(score, active)[0, 1])
    # 夹角用已经印出的四位 TC，避免未舍入的 0.953463 再取一次反余弦。
    printed = float(f"{tc:.4f}")
    print(label, "tc", f"{printed:.4f}", "deg", f"{math.degrees(math.acos(printed)):.4f}")
    print(label, "active", " ".join(f"{v:.4f}" for v in active))
    return tc


def sharpe_path() -> tuple[np.ndarray, np.ndarray, float]:
    # 基准权重钉在 1，只缩放主动叠加。k=1 不是平方和公式的那一点。
    mu_b, sig_b = 0.08, 0.20
    alpha, omega = 0.015, 0.05
    k = np.linspace(0.0, 6.0, 601)
    mean = mu_b + alpha * k
    vol = np.sqrt(sig_b**2 + (omega * k) ** 2)
    sharpe = mean / vol
    k_star = (alpha / omega) / (mu_b / sig_b) * (sig_b / omega)
    print("sr_b", f"{mu_b / sig_b:.2f}", "ir", f"{alpha / omega:.2f}")
    print("k_star", f"{k_star:.2f}", "sr_at_k_star", f"{float(np.interp(k_star, k, sharpe)):.4f}")
    print("sr_at_k1", f"{float(np.interp(1.0, k, sharpe)):.4f}")
    print("sr_sum_sq", f"{math.sqrt((mu_b / sig_b) ** 2 + (alpha / omega) ** 2):.4f}")
    return k, sharpe, k_star


def main() -> None:
    breadth = np.array([12.0, 50.0, 240.0, 500.0, 600.0, 6000.0])
    for br in breadth:
        root = math.sqrt(br)
        print(
            "br", f"{br:.0f}",
            "ic03", f"{0.03 * root:.3f}",
            "ic05", f"{0.05 * root:.3f}",
            "ic05_tc05", f"{0.5 * 0.05 * root:.3f}",
        )
    transfer_coeff(np.array([2.0, 1.0, -1.0, -2.0]), "mild")
    transfer_coeff(np.array([3.0, 1.0, -0.5, -3.5]), "skewed")
    # 相关不看仓位大小。下面这组主动权重把多头放在得分更低的股票上。
    score = np.array([2.0, 1.0, -1.0, -2.0])
    mis = np.array([0.05, 0.45, -0.25, -0.25])
    mis_tc = float(f"{float(np.corrcoef(score, mis)[0, 1]):.4f}")
    print("misaligned tc", f"{mis_tc:.4f}", "deg", f"{math.degrees(math.acos(mis_tc)):.4f}")
    k, sharpe, k_star = sharpe_path()
    # 百分比口径：λ=0.1，IR=0.4。小数口径把 λ 乘 100。
    print("omega_pct", 0.4 / (2 * 0.1), "alpha_pct", 0.4 * 2.0, "va_pct", 0.4**2 / (4 * 0.1))

    fig, axes = plt.subplots(1, 2, figsize=(11.2, 4.8))
    ax = axes[0]
    grid = np.logspace(math.log10(12), math.log10(6000), 200)
    ax.plot(grid, 0.05 * np.sqrt(grid), color="#1f4e79", label="IC=0.05，TC=1")
    ax.plot(grid, 0.03 * np.sqrt(grid), color="#2a9d8f", label="IC=0.03，TC=1")
    ax.plot(grid, 0.5 * 0.05 * np.sqrt(grid), color="#c1121f", label="IC=0.05，TC=0.5")
    ax.axvline(500, color="#666666", lw=0.7, ls="--")
    ax.axvline(6000, color="#333333", lw=0.7, ls=":")
    ax.set_xscale("log")
    ax.set_xlabel("每年独立赌注 BR")
    ax.set_ylabel("信息比率上界")
    ax.legend(frameon=False, fontsize=8)
    ax.grid(True, alpha=0.3)
    ax = axes[1]
    ax.plot(k, sharpe, color="#1f4e79", label="基准权重为 1")
    ax.axhline(0.5, color="#c1121f", lw=1.0, label="两路最优组合 0.50")
    ax.axvline(k_star, color="#2a9d8f", lw=0.9, ls="--", label="k = 3")
    ax.set_xlabel("主动组合的倍数 k")
    ax.set_ylabel("组合夏普")
    ax.legend(frameon=False, fontsize=8)
    ax.grid(True, alpha=0.3)
    fig.suptitle("M09 基本定律上界，以及夏普平方和的到达点", fontsize=12)
    fig.tight_layout()
    fig.savefig(OUT, dpi=140, bbox_inches="tight")
    print("wrote", OUT)


if __name__ == "__main__":
    main()
