# -*- coding: utf-8 -*-
"""回測報酬率計算規則：可重現驗證腳本。

對應文章：
  https://finlab.finance/blog/finlab-backtest-return-calculation

執行（安裝一次 pip install finlab 後）：
  python strategy.py

內容：
  1. 0050 原始價 vs 向後還原價：除息日與一拆四的數字對照
  2. 示範策略（月頻市值前 5 等權）：finlab sim() 預設費稅回測
  3. 手續費敏感度：同一策略在三檔費率下的年化報酬
  4. 單筆交易手算驗證：還原價 ×(1-fee)×(1-fee-tax) 對上引擎輸出

投資警語：本程式僅供量化研究與教學用途，過去績效不代表未來表現，
不構成任何投資建議。
"""
import pandas as pd

import finlab
from finlab import data
from finlab.backtest import sim

# finlab 會在需要資料時自動引導登入
START = "2018-01-01"
END = "2026-06-09"   # 文章發佈時的資料快照日；重跑時可改成最新日期
FEE = 0.001425       # 台股手續費牌價（買賣各收一次）
TAX = 0.003          # 證交稅（賣出時收）


# ---------- 1. 原始價 vs 向後還原價 ----------
close = data.get("price:收盤價")
adj = data.get("etl:adj_close")
close = close[close.index <= END]
adj = adj[adj.index <= END]

c50 = close["0050"].dropna()
a50 = adj["0050"].dropna()

c50w = c50[c50.index >= START]
a50w = a50[a50.index >= START]
print("0050 累積報酬（2018-01 起）")
print(f"  原始收盤價：{(c50w.iloc[-1] / c50w.iloc[0] - 1) * 100:+.1f}%（被一拆四扭曲，不可用）")
print(f"  向後還原價：{(a50w.iloc[-1] / a50w.iloc[0] - 1) * 100:+.1f}%（含息含分割的真實報酬）")


# ---------- 2. 示範策略：月頻市值前 5 等權 ----------
# 僅為驗證計算規則的示範，不是推薦策略
mv = data.get("etl:market_value")
mv = mv[mv.index <= END]

position = mv.is_largest(5)
position = position[position.index >= START]

report = sim(position, resample="M", upload=False)


def stats(creturn):
    """sim() 的權益曲線會延伸到執行當日，統計前先把頭尾截斷到固定的資料區間，
    再用純算術計算，確保不同日期重跑都得到同一組數字。"""
    cr = creturn[(creturn.index >= START) & (creturn.index <= END)]
    daily_ret = cr.pct_change().dropna()
    total = cr.iloc[-1] / cr.iloc[0] - 1
    years = (cr.index[-1] - cr.index[0]).days / 365.25
    cagr = (1 + total) ** (1 / years) - 1
    sharpe = daily_ret.mean() / daily_ret.std() * 252 ** 0.5
    max_drawdown = (cr / cr.cummax() - 1).min()
    return cagr, sharpe, max_drawdown


cagr, sharpe, max_drawdown = stats(report.creturn)
print("\n示範策略（月頻市值前 5 等權，預設費稅）")
print(f"  CAGR {cagr * 100:.2f}%｜日夏普 {sharpe:.2f}｜最大回撤 {max_drawdown * 100:.2f}%")


# ---------- 3. 手續費敏感度 ----------
print("\n手續費敏感度（證交稅固定 0.3%）")
for label, fee in [("牌價 0.1425%", FEE), ("打 6 折 0.0855%", FEE * 0.6), ("雙倍 0.285%", FEE * 2)]:
    rep = sim(position, resample="M", fee_ratio=fee, upload=False)
    fee_cagr, _, _ = stats(rep.creturn)
    print(f"  {label}：CAGR {fee_cagr * 100:.2f}%")


# ---------- 4. 單筆交易手算驗證 ----------
# 從示範策略報告取最後一筆已平倉交易
trades = report.get_trades().dropna(subset=["exit_date"]).sort_values("entry_date")
trade = trades.iloc[-1]
sid = str(trade["stock_id"]).split(" ")[0]
entry_date = pd.Timestamp(trade["entry_date"])
exit_date = pd.Timestamp(trade["exit_date"])
entry_sig = pd.Timestamp(trade["entry_sig_date"])
exit_sig = pd.Timestamp(trade["exit_sig_date"])

print(f"\n手算驗證標的：{trade['stock_id']}")
print(f"  進場訊號日 {entry_sig.date()} → 實際進場日 {entry_date.date()}（T+1 收盤）")
print(f"  出場訊號日 {exit_sig.date()} → 實際出場日 {exit_date.date()}（T+1 收盤）")

# 把同一筆交易單獨放進 sim()，跑一筆完整的買進賣出
single = pd.DataFrame(False, index=close.index, columns=[sid])
single.loc[(single.index >= entry_sig) & (single.index < exit_sig), sid] = True
single.loc[exit_sig, sid] = False

rep_single = sim(single, upload=False)
engine_net = float(rep_single.creturn.iloc[-1]) - 1

# 手算：還原價價差 × 買進費 × 賣出費稅
entry_adj = float(adj[sid].loc[entry_date])
exit_adj = float(adj[sid].loc[exit_date])
gross = exit_adj / entry_adj
hand_net = gross * (1 - FEE) * (1 - FEE - TAX) - 1

print(f"  還原價 {entry_adj:.4f} → {exit_adj:.4f}，價差報酬 {(gross - 1) * 100:+.4f}%")
print(f"  手算淨報酬 = 價差 ×(1-{FEE})×(1-{FEE}-{TAX}) = {hand_net * 100:+.4f}%")
print(f"  引擎淨報酬（權益曲線終值）        = {engine_net * 100:+.4f}%")
print(f"  差異 {abs(hand_net - engine_net) * 1e4:.2f} bp")
