"""用 research.py 的實跑輸出重建圖表：pip install plotly kaleido pandas。"""
import json
from pathlib import Path

import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots

OUT = Path(__file__).resolve().parent
METRICS = json.loads((OUT / "metrics.json").read_text())
NAMES = {"follow_5": "跟進持有5日", "follow_10": "跟進持有10日", "follow_20": "跟進持有20日",
         "rebound_5": "賣後買進5日", "rebound_10": "賣後買進10日", "rebound_20": "賣後買進20日",
         "exclude_monthly-revenue-momentum": "排除：營收動能", "exclude_small-cap-effect": "排除：極小型股",
         "exclude_operating-margin-three-factor": "排除：營益率三因子"}


def save(fig, name, title):
    fig.update_layout(width=1280, height=720, template="plotly_white",
                      font=dict(family="Arial, Noto Sans TC, PingFang TC", size=18, color="#172033"),
                      title=dict(text=title, font=dict(size=20), x=0.04, y=0.96),
                      margin=dict(l=95, r=70, t=125, b=175),
                      legend=dict(orientation="h", y=-0.16, x=0))
    fig.update_xaxes(title_standoff=16, automargin=True)
    fig.update_yaxes(title_standoff=16, automargin=True)
    fig.add_annotation(text=f"FinLab｜分點資料與還原權息股價｜資料截至{METRICS['window'].split('/')[1]}",
                       xref="paper", yref="paper", x=0, y=-0.32, showarrow=False, font=dict(size=14, color="#64748b"))
    fig.write_image(OUT / name, scale=2)


def summary():
    rows = METRICS["observed_branches"][:8]
    values = [r["rate"] * 100 for r in rows]
    fig = go.Figure(go.Bar(x=values, y=[r["branch"] for r in rows], orientation="h", marker_color="#2563EB",
                           text=[f"{x:.1f}%" for x in values], textposition="outside", cliponaxis=False))
    fig.add_vline(x=50, line_dash="dash", line_color="#64748b")
    fig.add_annotation(x=50, y=1.1, xref="x", yref="paper", text="原定門檻50%", showarrow=False)
    fig.update_yaxes(autorange="reversed")
    fig.update_xaxes(title="可觀察的隔日反手率（%）", range=[0, 60])
    save(fig, "chart1_branches.png", "隔日沖券商：120日可觀察反手率，最高的分點仍低於50%")


def heatmap(fig, col, design, value, **style):
    """Draw one lookback × threshold grid; value(row) is the number shown in each cell."""
    table = pd.DataFrame([{"lookback": r["lookback"], "threshold": r["threshold"], "value": value(r)}
                          for r in METRICS["grids"][design]])
    matrix = table.pivot(index="lookback", columns="threshold", values="value")
    fig.add_trace(go.Heatmap(z=matrix.values, x=[f"{t:.0%}" for t in matrix.columns],
                             y=[f"{n}日" for n in matrix.index], **style), row=1, col=col)
    fig.update_xaxes(title="反手比例門檻", row=1, col=col)


def coverage():
    designs = ["follow_10", "rebound_10"]
    zmax = max(1, *(r["signal_stock_days"] for d in designs for r in METRICS["grids"][d]))
    fig = make_subplots(rows=1, cols=2, subplot_titles=["大買訊號", "大賣訊號"], horizontal_spacing=0.18)
    for i, design in enumerate(designs, 1):
        heatmap(fig, i, design, lambda r: r["signal_stock_days"],
                colorscale="Blues", zmin=0, zmax=zmax, texttemplate="%{z}", showscale=False)
    save(fig, "chart2_signal_count.png", "隔日沖分點訊號太少：九組參數的股票×日期數")


def exclude_grid():
    keys = [k for k in NAMES if k.startswith("exclude_")]
    fig = make_subplots(rows=1, cols=3, subplot_titles=[NAMES[k] for k in keys], horizontal_spacing=0.11)
    for i, key in enumerate(keys, 1):
        heatmap(fig, i, key, lambda r: r["base"]["full"]["strategy"]["sharpe"] - r["base"]["full"]["benchmark"]["sharpe"],
                zmid=0, zmin=-0.02, zmax=0.02, colorscale="RdBu", texttemplate="%{z:.3f}", showscale=i == 3,
                colorbar=dict(title="夏普差", thickness=15))
    save(fig, "chart4_exclude_grid.png",
         "選股策略排除隔日沖大買股：九組鄰近參數<br><sup>格內為排除版日夏普減去原策略日夏普；正值才改善</sup>")


def curves():
    table = pd.read_csv(OUT / "data.csv", index_col="date", parse_dates=True)
    fig = go.Figure()
    lines = [("follow_10@60-0.4", "大買後跟進10日", "#2563EB"),
             ("rebound_10@60-0.4", "大賣後買進10日", "#10B981"),
             ("0050", "0050含息", "#9CA3AF")]
    for key, name, color in lines:
        fig.add_trace(go.Scatter(x=table.index, y=table[key] / table[key].iloc[0], name=name,
                                 line=dict(color=color, width=3)))
    fig.update_yaxes(title="共同起點淨值（1＝初始本金）")
    save(fig, "chart5_equity.png", "60日／40%鄰近參數：訊號稀少，大部分本金留現金")


def events():
    fig = go.Figure()
    labels = {"next_open_close": "隔天開盤到收盤", "close_5": "訊號收盤後5日",
              "close_10": "訊號收盤後10日", "close_20": "訊號收盤後20日"}
    for kind, label, color in [("follow", "大買訊號", "#2563EB"), ("rebound", "大賣訊號", "#10B981")]:
        rows = [METRICS["events_grid"]["60-0.4"][kind][k] for k in labels]
        values = [r["mean"] * 100 for r in rows]
        fig.add_trace(go.Bar(x=list(labels.values()), y=values, name=label, marker_color=color,
                            text=[f"{v:.2f}% (n={r['n']})" for v, r in zip(values, rows)], textposition="outside"))
    fig.update_yaxes(title="逐筆事件平均含息報酬（%）")
    fig.update_layout(barmode="group")
    save(fig, "chart3_events.png", "60日／40%事後事件統計：未扣成本，收盤價無法回頭成交")


summary()
coverage()
exclude_grid()
curves()
events()
