#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""BTC 链上估值(MVRV)择时检验 —— "低估值抄底"到底行不行。

结论(BTC 2015-2026, MVRV 滚动 2 年分位 → 前向 90 日收益):完全反直觉、且稳健——
  · 低 MVRV(便宜, ≤20 分位): 前向90日 +4.9%/胜率41%; ≤10分位 −1.3%/38% —— 跑输基线(+22.2%)甚至为负。
  · 高 MVRV(贵, ≥80 分位): 前向90日 +43.1%/胜率72%; ≥90分位 +44.2%/75% —— 远超基线。
  → BTC 是动量资产: 高 MVRV=上涨趋势中(动量延续), 低 MVRV=熊市washout(恢复慢)。
    "低估值均值回归抄底"在 90 日尺度是反的。印证并扩展站上"MVRV顶部逐周期下移、别用固定阈值机械择时"。
  注: 全历史(非滚动)分位会被 2011-13 小市值早期污染(高MVRV后接着暴涨), 故用滚动2年分位+2015后。
  边界: 高 MVRV 最终仍会见顶崩盘, 但 90 日尺度动量压过均值回归; 择时的是"何时最强"非"何时崩"。

数据:MVRV=Coinmetrics 社区版 CapMVRVCur(直连, 免费);BTC 价格=data/btc_full_history.csv。
运行:cd /root/cb-allotment && python3 my-app/public/reports/btc-mvrv-timing/backtest.py
"""
import requests, pandas as pd, numpy as np

UA = {"User-Agent": "Mozilla/5.0"}


def main():
    url = ("https://community-api.coinmetrics.io/v4/timeseries/asset-metrics"
           "?assets=btc&metrics=CapMVRVCur&frequency=1d&page_size=10000&start_time=2011-01-01")
    rows = []; u = url
    for _ in range(6):
        r = requests.get(u, headers=UA, timeout=40).json()
        rows += r.get("data", []); u = r.get("next_page_url")
        if not u:
            break
    mv = pd.DataFrame(rows)
    mv["dt"] = pd.to_datetime(mv["time"]).dt.tz_localize(None).dt.normalize()
    mv["mvrv"] = pd.to_numeric(mv["CapMVRVCur"], errors="coerce")
    mv = mv.dropna(subset=["mvrv"]).set_index("dt")["mvrv"]
    p = pd.read_csv("data/btc_full_history.csv"); p["dt"] = pd.to_datetime(p["date"])
    p = p.set_index("dt")["close"]
    df = pd.DataFrame({"mvrv": mv, "px": p}).dropna()
    for h in (30, 90):
        df[f"fwd{h}"] = df["px"].shift(-h) / df["px"] - 1
    df["rpct"] = df["mvrv"].rolling(730, min_periods=365).apply(lambda x: (x.iloc[-1] >= x).mean() * 100, raw=False)
    d = df[df.index >= "2015-01-01"].dropna(subset=["rpct"])
    print(f"2015后 {len(d)}天. 当前 MVRV {d['mvrv'].iloc[-1]:.2f} 滚动2年分位 {d['rpct'].iloc[-1]:.0f}")

    def cond(name, mask):
        for h in (30, 90):
            r = d.loc[mask, f"fwd{h}"].dropna(); base = d[f"fwd{h}"].dropna()
            print(f"  {name:16} 前向{h}日 {r.mean()*100:+7.1f}% 胜{(r>0).mean()*100:3.0f}% n={len(r):4} (基线{base.mean()*100:+.1f}%)")

    print("① 诊断 · BTC 前向收益 按 MVRV 滚动2年分位(反直觉: 低估值反而弱):")
    cond("MVRV≤20分位(便宜)", d["rpct"] <= 20)
    cond("MVRV≤10分位", d["rpct"] <= 10)
    cond("MVRV≥80分位(贵)", d["rpct"] >= 80)
    cond("MVRV≥90分位", d["rpct"] >= 90)

    # ② 真调仓回测(周度, 0.3%/边换手): MVRV 各规则 vs 买入持有 BTC ——才是"能不能用"的口径
    d = d.copy(); d["ret"] = d["px"].pct_change().fillna(0)

    def bt(w, name, cost=0.003):
        w = pd.Series(w, index=d.index).clip(0, 1)
        wk = w.copy(); wk[d.index.weekday != 0] = np.nan
        wk = wk.ffill().shift(1).fillna(0)                       # 周一定仓, 次日生效
        r = wk * d["ret"] - wk.diff().abs().fillna(0) * cost
        nav = (1 + r).cumprod(); yrs = len(r) / 365
        cagr = nav.iloc[-1] ** (1 / yrs) - 1
        sh = r.mean() / r.std() * np.sqrt(365) if r.std() > 0 else 0
        mdd = (nav / nav.cummax() - 1).min()
        print(f"  {name:22} CAGR {cagr*100:+6.1f}% 夏普 {sh:.2f} MDD {mdd*100:4.0f}% 仓位{wk.mean()*100:.0f}%")

    print("\n② 调仓回测(周度·0.3%/边) · MVRV 规则 vs 买入持有——每条都跑输买持:")
    bt(1.0, "买入持有 BTC")
    bt((d["rpct"] < 90).astype(float), "逃顶: <90分位满仓否则空")
    bt((d["rpct"] >= 50).astype(float), "动量: ≥50分位满仓否则空")
    bt(1 - d["rpct"] / 100, "抄底逃顶: 权重=1−分位")
    bt(np.where(d["rpct"] < 90, 1.0, 0.5), "减仓: ≥90分位降半")


if __name__ == "__main__":
    main()
