# -*- coding: utf-8 -*-
"""ADX(DMI)在台股的正確用法:強度濾網 × 方向濾網 + ADX-DI 廣度擇時(台股)。

核心洞見:ADX 只量「趨勢強度」、完全不量「方向」——它是 DX 的移動平均,
一檔正在「強力下跌」的股票 ADX 一樣很高。所以「裸選 ADX 最高的股票」會把
強下跌趨勢股也買進來,最大回撤一路探到 -70%。

正確用法分三層:
  ① 強度濾網:ADX > 20 確認「有沒有趨勢」。
  ② 方向濾網:+DI > -DI 確認「方向往上」(這是 DMI 體系的完整用法,
     用同源的 +DI/-DI 定方向,比外掛一個動能因子更乾淨)。
  ③ 風控擇時:用 ADX 自身建市場 regime 訊號——算全市場「強勢上升趨勢股」
     的家數佔比,低於門檻就清倉持現金。這把最大回撤從 -70% 壓到 -31%。

資料來源: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:成交金額")

# 2) 可交易宇宙:成交額前 300 大、收盤 > 10 元,排除 ETF(00 開頭)與權證 / KY(非 4 碼純數字)
liquidity = amount.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.is_largest(300) & (close > 10) & is_common

# 3) DMI 體系:ADX 量趨勢「強度」,+DI / -DI 量趨勢「方向」
adx = data.indicator("ADX", timeperiod=14)
plus_di = data.indicator("PLUS_DI", timeperiod=14)
minus_di = data.indicator("MINUS_DI", timeperiod=14)

# 4) 選股籃:ADX > 20(強趨勢)且 +DI > -DI(方向偏多),選 ADX 最高 30 檔等權
strong_and_bull = (adx > 20) & (plus_di > minus_di)
basket = (adx.where(universe & strong_and_bull)
             .rank(axis=1, ascending=False) <= 30)

# 5) ADX-DI 廣度擇時:全市場「ADX > 25 且 +DI > -DI」(強勢上升趨勢股)的家數佔比
strong_up = ((adx > 25) & (plus_di > minus_di)).where(universe)
breadth = strong_up.sum(axis=1) / universe.sum(axis=1)
risk_on = (breadth > 0.15)   # 佔比 > 0.15 才持倉,否則整籃清空持現金

# 6) 套擇時:risk-off 的交易日清空整個籃子(空手 / 現金)
risk_on_daily = risk_on.reindex(basket.index).ffill().fillna(False)
mask = pd.DataFrame(np.repeat(risk_on_daily.values[:, None], basket.shape[1], axis=1),
                    index=basket.index, columns=basket.columns)
position = basket & mask

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