"""低波動選股策略 — 與 /blog/low-volatility-strategy 同口徑的完整可重現程式。

回測視窗 2008-01-01 ~ 2026-06-20。執行前安裝 finlab 並登入(finlab 會自動引導):

    pip install finlab
    python strategy.py

包含:低波動 vs 高波動異象實測,以及風險調整報酬優化版
(風險調整報酬 + ROE + 營收成長 + 複合動能、季換股、前 15)。
"""
import finlab
from finlab import data
from finlab.backtest import sim

finlab.login()  # finlab 會自動引導登入

adj = data.get("etl:adj_close")
volume = data.get("price:成交股數")
roe = data.get("fundamental_features:ROE稅後")
yoy = data.get("monthly_revenue:去年同月增減(%)")

ret = adj.pct_change()
vol60 = ret.rolling(60).std()            # 60 日波動率
liquid = volume.average(20) > 100 * 1000  # 平均日成交 > 100 張
universe = liquid & adj.notna()
vol_u = vol60[universe]

# 低波動異象:60 日波動率最低 30 檔 vs 最高 30 檔,月換股
pos_low = vol_u.rank(axis=1, ascending=True) <= 30
pos_high = vol_u.rank(axis=1, ascending=False) <= 30
sim(pos_low, resample="M", fee_ratio=0.001425, tax_ratio=0.003).display()
sim(pos_high, resample="M", fee_ratio=0.001425, tax_ratio=0.003).display()

# 風險調整報酬優化版:個股 6M 報酬/波動最高,加品質與複合動能,前 15、季換股
ret6m = adj.pct_change(120)
vol120 = ret.rolling(120).std()
mom_3m = adj.pct_change(60)
risk_adj = ret6m / vol120
pool = universe & (roe > 5) & (yoy > 0) & (mom_3m > 0) & (ret6m > 0)
position = risk_adj[pool].is_largest(15)
report = sim(position, resample="Q", fee_ratio=0.001425, tax_ratio=0.003)
report.display()
print(report.get_stats())
