"""月營收動能選股策略 — 與 /blog/monthly-revenue-momentum 同口徑的完整可重現程式。

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

    pip install finlab
    python strategy.py

三個版本對照(原始 10 檔集中 / 加流動性過濾 / 分散 20 檔 + 部位上限)與 0050 含息基準,
數字與文章、互動式回測報告完全一致。
"""
import finlab
from finlab import data
from finlab.backtest import sim

finlab.login()  # finlab 會自動引導登入,不需手動填 token

close = data.get("price:收盤價")
volume = data.get("price:成交股數")
rev = data.get("monthly_revenue:當月營收")

# 訊號:五日平均漲跌幅(動能)+ 站上 20/60/120 日均線(多頭趨勢)+ 近三月均營收 > 近十二月均營收
pct_change = (close / close.shift() - 1).rolling(5).mean()
trend = (close > close.average(60)) & (close > close.average(20)) & (close > close.average(120))
rev_up = rev.average(3) > rev.average(12)
liquid = volume.average(20) > 500 * 1000  # 平均日成交 > 500 張,排除低流動性個股

# 版本一:文章主策略 — 動能最強的 10 檔,月再平衡(營收公布後第 11 天),10% 停損
position = pct_change[trend & rev_up].is_largest(10).loc["2015-01-01":"2026-06-20"]
report = sim(position, resample="M", resample_offset="11D", stop_loss=0.1, upload=False, name="月營收動能10檔")
report.display()
print(report.get_stats())

# 版本三:分散 20 檔 + 單檔部位上限 8%(降低單一個股風險的替代版本)
position_div = pct_change[trend & rev_up & liquid].is_largest(20).loc["2015-01-01":"2026-06-20"]
report_div = sim(position_div, resample="M", resample_offset="11D", stop_loss=0.1,
                 position_limit=0.08, upload=False, name="月營收動能20檔")
report_div.display()
print(report_div.get_stats())
