# -*- coding: utf-8 -*-
"""RSI 在台股的正確用法:強勢動能選股 + 市場廣度純風控(非教科書的超賣接刀)。

核心洞見:台股的 RSI 是動能指標,不是均值回歸指標。
  - 教科書「RSI<30 超賣買進」在台股研究段近乎零報酬(接刀沒有均值回歸保護)。
  - 反過來「橫截面取 RSI 最高的強勢股」(強者恆強)才有真實 edge。
策略(完全不含基本面、全程不加槓桿):
  - 趨勢池:可交易宇宙內,同時站上季線(close>MA60)與半年線(close>MA120)。
  - 選股(RSI 唯一因子):池內取 RSI(28) 最高的 13 檔,等權(1/13)、月再平衡。
  - 廣度純風控:每日算宇宙站上季線的個股比例,>0.40 滿倉、<=0.40 把整籃
    曝險縮到半倉(其餘轉現金,不加碼、不放空、不開槓桿),次日回升即恢復滿倉。

資料來源:finlab 套件(price / etl:adj_close)。finlab 會在需要資料時自動引導登入。
回測區間 2015-01 ~ 2026-06。過去績效不代表未來,不構成投資建議。
"""
import numpy as np
import pandas as pd
import finlab
from finlab import data
from finlab.backtest import sim

finlab.login()  # 依套件指示完成登入即可

# 1) 載入價量資料
close = data.get("price:收盤價")
amount = data.get("price:成交金額")
volume = data.get("price:成交股數")

# 2) 可交易宇宙:成交額前 300 大、收盤 > 10 元、60 日均量 > 100 萬股,
#    排除 ETF(00 開頭)與權證/KY(非 4 碼純數字)
liquidity_amt = amount.rolling(60).mean()
liquidity_vol = volume.rolling(60).mean()
is_common = pd.Series({sid: (str(sid)[:4].isdigit() and not str(sid).startswith("00"))
                       for sid in close.columns})
universe = liquidity_amt.is_largest(300) & (close > 10) & (liquidity_vol > 1_000_000) & is_common

# 3) RSI(28) 與均線(TA-Lib,本地計算)
rsi = data.indicator("RSI", timeperiod=28)
ma60 = close.rolling(60).mean()
ma120 = close.rolling(120).mean()

# 4) 趨勢池:同時站上季線與半年線(先用絕對趨勢框定方向)
trend_pool = universe & (close > ma60) & (close > ma120)

# 5) 池內取 RSI(28) 最高的 13 檔(動能選股,強者恆強),等權、月再平衡
basket = rsi.where(trend_pool).rank(axis=1, ascending=False) <= 13
weights = basket.astype(float).div(basket.sum(axis=1), axis=0).fillna(0.0)
weights = weights.resample("M").last()

# 6) 市場廣度:宇宙中站上季線的個股比例(逐日)
above_ma60 = (close > ma60) & universe
breadth = above_ma60.sum(axis=1) / universe.sum(axis=1)

# 7) 廣度純風控:廣度 <= 0.40 把整籃曝險縮到半倉(剩餘轉現金,不加槓桿)
exposure = np.where(breadth > 0.40, 1.0, 0.5)
exposure = pd.Series(exposure, index=breadth.index)
position = weights.mul(exposure.reindex(weights.index, method="ffill"), axis=0)

# 8) 回測(月再平衡,台股預設成本:手續費 + 賣出證交稅 0.3%)
report = sim(position, resample="M", upload=False)
print(report.get_stats())
