#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""「给组合配点银行避险」到底值不值 —— 银行防御腿实测(2018–2026)。

结论:
  · 相关性是前提——沪深300×中证银行 0.63(银行本就是沪深300 成分, 无分散); 但中证1000/创业板×银行仅 0.27-0.29。
  · 对成长/小盘, 配 20% 银行**砍回撤**(中证1000 −47→−40; 创业板 −57→−49), 但**夏普不升**(银行自身 2018-26 CAGR 0.4%/夏普 0.11, 削波动也削收益)。
  · 分年看是"护崩盘、封上行"的防御权衡: 崩盘年(2018/2022/2023)混合亏更少, 牛年(2020/2025)赚更少; 2024 银行领涨时反超。
  → 银行是**回撤保险不是收益增强**, 且只对成长/小盘有意义(大盘已含银行)。

数据:指数收盘 tushare index_daily(沪深300 000300.SH / 中证1000 000852.SH / 创业板 399006.SZ / 中证银行 399986.SZ),
      env TUSHARE_TOKEN / TUSHARE_API_URL, 无硬编码。口径:日度固定权重(近似连续再平衡), 不含成本。
运行:cd /root/cb-allotment && PYTHONPATH=scripts python3 my-app/public/reports/bank-sleeve/backtest.py
"""
import os
import time

import numpy as np
import pandas as pd
import tushare as ts


def get_pro():
    ts.set_token(os.environ["TUSHARE_TOKEN"])
    pro = ts.pro_api(os.environ["TUSHARE_TOKEN"])
    pro._DataApi__token = os.environ["TUSHARE_TOKEN"]
    pro._DataApi__http_url = os.environ["TUSHARE_API_URL"]
    return pro


def load(pro, code):
    d = None
    for _ in range(4):
        try:
            d = pro.index_daily(ts_code=code, start_date="20180101", end_date="20260701")
            if d is not None and len(d):
                break
        except Exception:
            time.sleep(3)
    d = d[["trade_date", "close"]].sort_values("trade_date").reset_index(drop=True)
    d["dt"] = pd.to_datetime(d["trade_date"], format="%Y%m%d")
    d["ret"] = d["close"].pct_change()
    return d.set_index("dt")["ret"]


def stat(r):
    nav = (1 + r).cumprod()
    yrs = len(r) / 244
    return nav.iloc[-1] ** (1 / yrs) - 1, r.mean() / r.std() * np.sqrt(244), (nav / nav.cummax() - 1).min()


def main():
    pro = get_pro()
    bank = load(pro, "399986.SZ")
    for nm, code in [("沪深300", "000300.SH"), ("中证1000", "000852.SH"), ("创业板", "399006.SZ")]:
        eq = load(pro, code)
        df = pd.concat([eq.rename("eq"), bank.rename("bk")], axis=1).dropna()
        corr = df["eq"].corr(df["bk"])
        print(f"\n=== {nm} + 中证银行 (日收益相关 {corr:.2f}) ===")
        print(f"{'配比':20} {'年化':>7} {'夏普':>6} {'MDD':>7}")
        for w in (0.0, 0.2, 0.3, 0.4):
            c, s, m = stat((1 - w) * df["eq"] + w * df["bk"])
            print(f"  {int((1-w)*100)}%{nm}+{int(w*100)}%银行 {c*100:6.1f}% {s:6.2f} {m*100:6.0f}%")

    # 分年: 创业板 vs 80/20(护崩盘封上行)
    cyb = load(pro, "399006.SZ")
    df = pd.concat([cyb.rename("eq"), bank.rename("bk")], axis=1).dropna()
    df["y"] = df.index.year
    print("\n创业板 vs 80%创业板+20%银行 · 分年收益:")
    for y, g in df.groupby("y"):
        pure = (1 + g["eq"]).prod() - 1
        mix = (1 + (0.8 * g["eq"] + 0.2 * g["bk"])).prod() - 1
        print(f"  {y}: 纯 {pure*100:+.1f}%  混合 {mix*100:+.1f}%")


if __name__ == "__main__":
    main()
