月營收動能+現金股利調高優先
月營收動能選股(近三月平均營收高於近十二月平均、站上 20/60/120 日均線、五日平均漲跌幅最強 10 檔,每月營收公布後換股、10% 停損),近 60 個交易日內董事會決議每股現金股利比去年同期多 10% 以上的候選股優先入選。2019 年至 2026 年台股回測年化報酬 59.9%、日夏普 1.83、最大回撤 -27.8%;同視窗原版月營收動能 54.3%、1.52,0050 含息 28.5%、1.30。
投資警語:本文僅供教學參考,不構成投資建議。過去績效不代表未來表現,投資有風險。
回測績效
- 年化報酬率
- 59.9%
- 日夏普
- 1.83
- 最大回撤
- -27.9%
- 總報酬率
- 3642%
- 平均持股
- 10.0 檔
回測含手續費 0.1425% 與賣出證交稅 0.3%(finlab 套件 sim() 預設成本),日夏普未扣無風險利率。策略淨值自資料可得日(2019-02-11)起算。
淨值曲線(對數座標)
月營收動能+現金股利調高優先0050 買進持有(含息)
與 0050 買進持有(含息)對照
| 對象 | 年化報酬率 | 日夏普 | 最大回撤 | 統計視窗 |
|---|---|---|---|---|
| 月營收動能+現金股利調高優先 | 59.9% | 1.83 | -27.9% | 2019-02-11 〜 2026-10-30 |
| 0050 買進持有(含息) | 28.5% | 1.30 | -34.0% | 2019-02-11 〜 2026-10-30 |
完整策略程式碼
安裝 finlab 套件後即可直接執行重現(finlab.login() 會自動引導登入,不需手動填 token)。想用 AI 輔助安裝與跑第一次回測,可以走安裝教學。
展開程式碼(strategy.py)
"""現金股利調高選股:月營收動能選股,近 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())
在 Studio 裡用同一套真實資料調整條件、重跑回測。