"""現金股利調高選股：月營收動能選股，近 60 個交易日內董事會調高現金股利的候選股優先。

與 https://finlab.finance/blog/cash-dividend-increase-stock-selection 同一份程式。
回測從 2019-01 起（FinLab 的董事會股利決議日從 2019 年開始收錄）到最新資料。

執行前先安裝 finlab，需要資料時 finlab 會自動引導登入：

    pip install finlab
    python strategy.py
"""
import numpy as np
import pandas as pd
from finlab import data
from finlab.backtest import sim

close = data.get("price:收盤價")
rev = data.get("monthly_revenue:當月營收")
board = pd.DataFrame(data.get("board_dividend_announcement"))

# 1. 月營收動能：近三個月平均營收高於近十二個月平均營收
rev_up = rev.average(3) > rev.average(12)

# 2. 股價趨勢：收盤價同時站上 20、60、120 日均線
trend = (
    (close > close.average(20))
    & (close > close.average(60))
    & (close > close.average(120))
)

# 3. 董事會決議的每股現金股利，比去年同一期（年度、上下半年或同一季）多 10% 以上
period = board["股利所屬年(季)度"].astype(str)
board["year"] = period.str.extract(r"^(\d+)年")[0].astype(float)
board["part"] = period.str.replace(r"^\d+年", "", regex=True)
board["cash"] = (
    board["盈餘分配之現金股利(元/股)"].fillna(0)
    + board["法定盈餘公積發放之現金(元/股)"].fillna(0)
    + board["資本公積發放之現金(元/股)"].fillna(0)
)
board = board.dropna(subset=["year"]).sort_values("key_date")
board = board.drop_duplicates(["stock_id", "股利所屬年(季)度"], keep="last")
last_year = board.set_index(["stock_id", "year", "part"])["cash"]
board["last_year_cash"] = last_year.reindex(
    list(zip(board["stock_id"], board["year"] - 1, board["part"]))
).to_numpy()
decided = board["董事會決議（擬議）股利分派日"]
raised = board[(decided >= "2019-01-01") & (board["last_year_cash"] > 0)
               & (board["cash"] > board["last_year_cash"] * 1.1)]

# 決議日當天（假日公告算前一個交易日）起 60 個交易日內，算「近期調高股利」
recent = np.zeros(close.shape, dtype=bool)
start = close.index.searchsorted(raised["董事會決議（擬議）股利分派日"], side="right") - 1
for stock_id, i in zip(raised["stock_id"], start):
    if stock_id in close.columns and i >= 0:
        recent[i:i + 60, close.columns.get_loc(stock_id)] = True
recent = pd.DataFrame(recent, index=close.index, columns=close.columns)

# 4. 五日平均漲跌幅排名，近期調高股利的候選股排前面，取前 10 檔
momentum = (close / close.shift() - 1).rolling(5).mean()
score = momentum + recent * 1000
position = score[trend & rev_up].is_largest(10).loc["2019-01-01":]

# 每月營收公布後（月初第 11 天）換股，單檔 10% 停損，手續費與證交稅用 sim 預設
report = sim(
    position,
    resample="M",
    resample_offset="11D",
    stop_loss=0.1,
    upload=False,
    name="月營收動能＋現金股利調高優先",
)
print(report.get_stats())
