"""M10 教学图：两条信号合成后的相关系数，以及不同持有期扣费后的 α。

两条曲线都是公式。持有期 IC 是和该期限收益的相关，波动按 √时间缩放。
"""

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 / "alpha_and_blend.png"


def norm_sf(z: float) -> float:
    return 0.5 * (1.0 - math.erf(z / math.sqrt(2.0)))


def blend_ic(ic: np.ndarray, rho: float, weights: np.ndarray) -> float:
    cov = np.array([[1.0, rho], [rho, 1.0]])
    var = float(weights @ cov @ weights)
    cov_u = float(weights @ ic)
    return cov_u / math.sqrt(var)


def optimal_weights(ic: np.ndarray, rho: float) -> np.ndarray:
    cov = np.array([[1.0, rho], [rho, 1.0]])
    # 相关很高时较弱信号的权重可以变负。图右侧上升来自这个空头，不是免费的相关。
    return np.linalg.inv(cov) @ ic


def optimal_ic(ic: np.ndarray, rho: float) -> float:
    weights = optimal_weights(ic, rho)
    return math.sqrt(float(ic @ weights))


def horizon_alpha(ic_h: float, horizon: int, z: float, annual_vol: float) -> float:
    # 算术年化：持有期期望乘以一年能放进几个持有期。波动用 √(h/12)，月份独立。
    return ic_h * annual_vol * math.sqrt(12.0 / horizon) * z


def main() -> None:
    print("tail_z_1.5", f"{norm_sf(1.5):.4f}")
    alpha_ann = 0.04 * 0.25 * 1.5
    print("alpha_matched", f"{alpha_ann:.4f}")
    print("ic_for_30pct", f"{0.30 / (0.25 * 1.5):.2f}")
    monthly_vol = 0.25 / math.sqrt(12.0)
    alpha_m = 0.03 * monthly_vol * 1.5
    print(
        "monthly_vol", f"{monthly_vol:.4f}",
        "alpha_m", f"{alpha_m:.6f}",
        "times_12", f"{alpha_m * 12:.4f}",
    )
    print("mismatch_annual_vol", f"{0.03 * 0.25 * 1.5:.5f}")

    resid = np.array([-0.5, 0.5, -0.5, 0.5])
    print("resid_std", f"{float(resid.std(ddof=1)):.4f}")

    ic = np.array([0.04, 0.03])
    rho = 0.5
    equal = blend_ic(ic, rho, np.array([1.0, 1.0]))
    by_ic = blend_ic(ic, rho, ic)
    opt = optimal_ic(ic, rho)
    print("blend_equal", f"{equal:.4f}", "blend_by_ic", f"{by_ic:.4f}", "blend_opt", f"{opt:.4f}")
    # IC 信息比率的权重回答稳定性，不是使合成相关最大的那一组。
    icir = blend_ic(ic, rho, np.array([0.4, 0.6]))
    print("blend_icir", f"{icir:.4f}")
    print("orth_ceiling", f"{math.sqrt(float(ic @ ic)):.6f}")
    half_life = np.array([0.05, 0.025, 0.0125])
    print("half_life_mean", f"{float(half_life.mean()):.6f}")
    raw_sum = 0.04 + 0.03
    print("raw_sum_over_opt", f"{raw_sum / opt:.3f}")
    for rho_w in (0.5, 0.95):
        weights = optimal_weights(ic, rho_w)
        print(
            "w_opt", f"{rho_w:.2f}",
            f"{weights[0]:.4f}", f"{weights[1]:.4f}",
        )

    horizons = [1, 3, 6, 12]
    ics = [0.05, 0.035, 0.02, 0.01]
    # 每次调仓单边换手 40%。往返 30 bps = 每侧 15 bps，与 M00 的拖累公式一致。
    one_way = 0.40
    for side, label in ((0.0015, "us"), (0.0025, "hk")):
        for horizon, ic_h in zip(horizons, ics):
            gross = horizon_alpha(ic_h, horizon, 1.0, 0.25)
            n_reb = 12.0 / horizon
            drag = 2.0 * (n_reb * one_way) * side
            print(
                label, "h", horizon,
                "gross", f"{gross:.4f}",
                "drag", f"{drag:.4f}",
                "net", f"{gross - drag:.4f}",
            )

    rhos = np.linspace(0.0, 0.95, 20)
    equal_line = [blend_ic(ic, r, np.ones(2)) for r in rhos]
    opt_line = [optimal_ic(ic, r) for r in rhos]
    fig, axes = plt.subplots(1, 2, figsize=(11.2, 4.8))
    ax = axes[0]
    ax.plot(rhos, equal_line, color="#1f4e79", label="等权后再标准化")
    ax.plot(rhos, opt_line, color="#c1121f", label="按协方差加权")
    ax.axhline(0.04, color="#666666", lw=0.8, ls="--")
    ax.set_xlabel("两条信号的相关系数")
    ax.set_ylabel("合成后与收益的相关")
    ax.legend(frameon=False, fontsize=8)
    ax.grid(True, alpha=0.3)
    ax = axes[1]
    xpos = np.arange(len(horizons))
    gross = [horizon_alpha(ic_h, h, 1.0, 0.25) * 100 for h, ic_h in zip(horizons, ics)]
    net_us = []
    net_hk = []
    for h, g in zip(horizons, gross):
        n_reb = 12.0 / h
        net_us.append(g - 2.0 * n_reb * one_way * 0.0015 * 100)
        net_hk.append(g - 2.0 * n_reb * one_way * 0.0025 * 100)
    width = 0.25
    ax.bar(xpos - width, gross, width=width, color="#1f4e79", label="毛 α")
    ax.bar(xpos, net_us, width=width, color="#2a9d8f", label="每侧 15 bps")
    ax.bar(xpos + width, net_hk, width=width, color="#c1121f", label="每侧 25 bps")
    ax.set_xticks(xpos)
    ax.set_xticklabels(["1 个月", "3 个月", "6 个月", "12 个月"])
    ax.set_ylabel("年化 α（%）")
    ax.legend(frameon=False, fontsize=8)
    ax.grid(True, axis="y", alpha=0.3)
    fig.suptitle("M10 信号合成与持有期：公式，不是回测", fontsize=12)
    fig.tight_layout()
    fig.savefig(OUT, dpi=140, bbox_inches="tight")
    print("wrote", OUT)


if __name__ == "__main__":
    main()
