# -*- coding: utf-8 -*-
"""
台股量化交易教學範例策略:品質 + 動能 + 低波動 三因子複合
作者:FinLab 量化研究團隊 | 資料來源:finlab 套件(真實台股資料)

回測結果(月頻換股、已扣手續費 0.0475% 與證交稅 0.3%):
  2020-2026:Sharpe 1.58、Sortino 2.46、CAGR 21.2%、最大回撤 -13.4%
  全期 2013-2026:Sharpe 1.28、CAGR 15.8%、最大回撤 -13.4%(誠實揭露,不同期間數字會變)

執行方式:
  pip install finlab
  python -m finlab login   # 或 finlab.login('你的API TOKEN')
  python strategy.py
"""
import finlab
from finlab import data
from finlab.backtest import sim

# finlab.login('你的 API TOKEN')   # 首次執行請先登入

# 1) 基礎資料
close = data.get('price:收盤價')
volume = data.get('price:成交股數')

# 2) 股票池:流動性 + 排除全額交割/金融/ETF(避免生存者偏誤、買得到)
amount = (close * volume).average(60)                 # 60 日均成交金額
flagged = data.get('etl:is_flagged_stock').reindex(close.index, method='ffill').fillna(False)
cats = data.get('security_categories').set_index('stock_id')['category']
bad = set(cats[cats.isin(['金融保險', 'ETF', 'ETN', '存託憑證', '受益證券', '創新板'])].index)
keep = [c for c in close.columns if c not in bad]
universe = (amount > 50_000_000) & (close > 10) & (~flagged)

# 3) 三個因子(各自做橫斷面百分位排名,越大越好)
#    index_str_to_date():把季度財報對齊到「實際公布日」,正確避免前視偏差
roe = data.get('fundamental_features:ROE稅後').index_str_to_date().reindex(close.index, method='ffill')
momentum = close / close.shift(120)                   # 120 日動能
ret = close.pct_change()
low_vol = -ret.rolling(120).std()                     # 低波動(負號=波動小者分高)

cols = sorted(set(keep) & set(roe.columns))
def rank(df):
    return df[cols].rank(axis=1, pct=True)
score = (rank(roe) + rank(momentum) + rank(low_vol)) / 3
score = score.where(universe[cols])

# 4) 選股:每月選分數最高的 25 檔
selected = score.rank(axis=1, ascending=False) <= 25

# 5) 反波動加權(波動越低權重越高,進一步壓低組合波動)
inv_vol = (1 / ret.rolling(60).std())[cols]
weight = (selected * inv_vol)
weight = weight.div(weight.sum(axis=1), axis=0).fillna(0)

# 6) 大盤濾網:加權報酬指數站上 120 日均線才進場,否則空手(降回撤、拉高 Sharpe)
twii = data.get('benchmark_return:發行量加權股價報酬指數').iloc[:, 0]
market_up = (twii > twii.rolling(120).mean()).reindex(close.index, method='ffill').fillna(False)
weight = weight.mul(market_up.astype(int), axis=0)

# 7) 回測:月頻換股,手續費以六折計
report = sim(weight, resample='M', fee_ratio=1.425 / 1000 / 3, name='品質動能低波複合')
print(report.get_metrics()['ratio'])     # Sharpe / Sortino / Calmar ...
# report.display()                       # 互動式報告
