"""高股息四因子（殖利率＋ROE＋動能＋低波）：跑到最新資料的版本（/strategies/high-dividend-four-factor 的數字來源）。

條件與 /blog/ai-agent-quant-trading-tools/lobster-strategy.py 的「四因子 前15」（龍蝦場）相同，只是不截斷結束日：
- 候選池：近 20 日平均成交金額大於 1,000 萬，且殖利率大於 3%
- 殖利率、ROE、120 日動能、120 日波動度（越低越好）四項在池內做百分位排名後相加
- 取分數前 15 檔，依分數加權，2018 年起每月換股，sim() 預設手續費 0.1425% 與證交稅 0.3%

統計口徑：日夏普 = 日報酬平均 / 標準差 × √252，不扣無風險利率；
0050 含息 = etl:adj_close 還原價買進持有，與策略同一段期間。
執行後會在目前目錄輸出 data.csv（淨值曲線）與 metrics.json（統計）。

    pip install finlab
    python strategy.py

finlab 需要資料時會自動引導登入，不需要在程式裡填 token。
"""

import json

import pandas as pd

import finlab
from finlab import data
from finlab.backtest import sim

STRATEGY_NAME = "高股息四因子（殖利率＋ROE＋動能＋低波）"
START = "2018-01-01"
TOP_N = 15       # 持股檔數
YIELD_MIN = 3    # 殖利率門檻（%）
LOOKBACK = 120   # 動能與波動回看交易日


def build_report():
    close = data.get("price:收盤價")
    volume = data.get("price:成交股數")
    dividend_yield = data.get("price_earning_ratio:殖利率(%)").reindex(close.index, method="ffill")
    roe = data.get("fundamental_features:ROE稅後").index_str_to_date()
    roe = roe.reindex(close.index, method="ffill")  # 季財報對齊公布時點後補到日頻

    liquid = ((close * volume).rolling(20).mean() > 10_000_000) & close.notna()
    candidates = liquid & (dividend_yield > YIELD_MIN)

    def rank_pct(factor, ascending=True):
        return factor.where(candidates).rank(axis=1, pct=True, ascending=ascending)

    momentum = close.pct_change(LOOKBACK, fill_method=None)
    volatility = close.pct_change(fill_method=None).rolling(LOOKBACK).std()
    score = (rank_pct(dividend_yield) + rank_pct(roe) + rank_pct(momentum)
             + rank_pct(volatility, ascending=False))

    in_top = score.rank(axis=1, ascending=False) <= TOP_N
    weight = score.where(in_top, 0).clip(lower=0)
    weight = weight.div(weight.sum(axis=1).replace(0, float("nan")), axis=0).fillna(0)
    return sim(weight[weight.index >= START], resample="M", upload=False, name=STRATEGY_NAME)


def curve_stats(curve: pd.Series) -> dict:
    curve = curve / curve.iloc[0]
    daily = curve.pct_change().dropna()
    years = (curve.index[-1] - curve.index[0]).days / 365.25
    return {
        "cagr": curve.iloc[-1] ** (1 / years) - 1,
        "daily_sharpe": daily.mean() / daily.std() * 252 ** 0.5,
        "max_drawdown": (curve / curve.cummax() - 1).min(),
        "total_return": curve.iloc[-1] - 1,
    }


def main() -> None:
    finlab.login()
    bench_all = data.get("etl:adj_close")["0050"].dropna()
    report = build_report()

    # 從第一次有部位的那天起算，0050 含息用同一段期間
    creturn = report.creturn.dropna()
    first_move = (creturn != creturn.iloc[0]).argmax()
    creturn = creturn.iloc[max(first_move - 1, 0):]
    bench = bench_all.loc[creturn.index[0]:creturn.index[-1]]

    strategy_stats = curve_stats(creturn)
    bench_stats = curve_stats(bench)
    holdings = (report.position.fillna(0) != 0).sum(axis=1)
    strategy_stats["avg_holdings"] = holdings[holdings > 0].mean()

    start, end = creturn.index[0].date(), creturn.index[-1].date()
    print(f"期間 {start} 至 {end}")
    for label, s in [("策略", strategy_stats), ("0050 含息", bench_stats)]:
        print(f"{label}：年化 {s['cagr']:.2%}，日夏普 {s['daily_sharpe']:.2f}，最大回撤 {s['max_drawdown']:.2%}")

    curves = pd.DataFrame({STRATEGY_NAME: creturn / creturn.iloc[0], "0050含息": bench / bench.iloc[0]})
    curves.index.name = "date"
    curves.round(6).to_csv("data.csv")
    metrics = {
        "window": f"{start}/{end}",
        "star": STRATEGY_NAME,
        "variants": {STRATEGY_NAME: {k: round(float(v), 6) for k, v in strategy_stats.items()}},
        "benchmark_0050": {k: round(float(v), 6) for k, v in bench_stats.items()},
        "run_date": str(pd.Timestamp.today().date()),
        "finlab_version": finlab.__version__,
        "generated_by": "static/blog/ai-agent-quant-trading-tools/high-dividend-four-factor/strategy.py",
    }
    with open("metrics.json", "w", encoding="utf-8") as f:
        json.dump(metrics, f, ensure_ascii=False, indent=2)


if __name__ == "__main__":
    main()
