此文章為VIP限定

前言
市面上的高股息 ETF 百花齊放,這次要來復刻 00919 群益台灣精選高息 ETF,如果 0056 是台股存股族的啟蒙,00919 就像下一代改裝版。它主打「精準高息、精準卡位、精準領息」三大賣點,看似簡單,其實暗藏許多量化細節。本文帶你:
- 完整拆解官方邏輯,一步步復刻 00919 的選股流程。
- 用量化工具驗證:驗證相關性和重疊率。
- 再往前優化:刪掉雜訊因子、加入高效指標,打造報酬更高、回撤更低的 00919 優化版。
一、00919 三大「精準」拆解
查看基金介紹發現有幾個特色
- 精準高息:用「實際宣告」取代「預估數字」
00919 選擇鎖定「已公告現金股利」的企業,從確定的金額計算真實殖利率,進一步提升股息來源的可靠度與穩定性。
- 精準卡位:提前布局的策略優勢
00919 採雙階段審核機制,五月、十二月雙審核,透過這種快與早並重的選股機制,達到真正「買在除息前」與「走在市場前」的投資節奏。
- 精準領息:每一分股息都不浪費
選股時機對應企業除息時程,投資人持有期間能真正參與除息、獲取現金配息,讓每一分錢都落袋為安。
名詞解釋 – 殖利率:每股現金股利 ÷ 股價。殖利率越高,代表用相對便宜的股價就能拿到較高現金回報。
二、復刻 00919:研究流程
- 資料擷取
- 00919 公開說明書
- 邏輯拆解
- 採樣母體 → 流動性 / 財務健全性 → 股利資訊 → 排序 → 替換規則。
三、復刻 00919 的關鍵步驟
- 初始採樣母體:
- 臺灣上市與上櫃普通股股票為基礎。
- 選取發行市值前 300 大股票。
- 基本條件篩選:
- 日平均成交金額需高於 8,000 萬元。
- 近四季稅後股東權益報酬率 (ROE) 皆為正數。
- 股利資訊篩選(五月定審限定):
- 排除董事會尚未決定股利金額的公司。
- 排除已除息且於審核生效日前已完成發放的股票。
- 排序並選取成分股:
- 五月定審:依近四季股利率排序,選出前 30 檔。
- 十二月定審:依據預估股利率排序,選出前 30 檔。
def calculate_ranks(date, is_may_review=True):
"""計算特定審核日期的股票排名"""
nearest_date = get_nearest_past_trading_date(date, all_trading_dates)
if nearest_date is None:
return pd.Series(dtype=float) # 空序列
if is_may_review: # 5月定審
# 計算排名 - 5月定審使用股利率
score = (市值.rank(axis=1, pct=True) + 股利率.rank(axis=1, pct=True) + yield_ratio.rank(axis=1, pct=True))[conds & (board_cash_dividend > 0)]
else: # 12月定審
# 計算排名 - 12月定審使用預估股利率
score = (市值.rank(axis=1, pct=True) + 預估股利率.rank(axis=1, pct=True) + yield_ratio.rank(axis=1, pct=True))[conds]
if nearest_date not in score.index:
return pd.Series(dtype=float) # 空序列
return score.loc[nearest_date].dropna().rank(ascending=False, method='min')
- 成分股替換規則:
- 排名前 15 名直接納入成分股。
- 既有成分股若跌出 46 名以外則剔除。
- 排名 16 至 45 名的股票列為候補,優先保留既有成分股。
- 十二月定審單次最多更替 8 檔股票。
# 建立空的結果DataFrame
position = pd.DataFrame(False, index=effective_dates, columns=close.columns)
# 處理所有調倉日期和排名數據
all_ranks = {}
valid_dates = []
for date in effective_dates:
try:
is_may_review = (date.month == 5)
ranks = calculate_ranks(date, is_may_review)
if not ranks.empty:
all_ranks[date] = ranks
valid_dates.append(date)
except Exception as e:
print(f"處理 {date} 時發生錯誤: {e}")
valid_dates = sorted(valid_dates) # 確保日期順序
if not valid_dates:
print("沒有有效的調倉日期,無法進行模擬")
else:
# 使用 Pandas 向量化處理成分股替換
prev_components = None
target_component_count = 30 # 設定目標成分股數量
for i, date in enumerate(valid_dates):
ranks = all_ranks[date]
# 1. 排名在第15名以內者納入成分股
top_15 = set(ranks[ranks <= 15].index)
# 2. 排名16至45名為候補名單
candidates = set(ranks[(ranks > 15) & (ranks <= 45)].index)
# 暫定的成分股 (先加入前15名)
current_components_tentative = set(top_15)
# 如果不是第一次調倉
if prev_components is not None:
# 加入排名16-45之間的既有成分股
existing_in_candidates = prev_components.intersection(candidates)
current_components_tentative.update(existing_in_candidates)
# 對於12月定審,額外限制最多替換8檔
if date.month == 12:
# 計算基於 Top15 + 既有候補 所得的新增股票
added = current_components_tentative - prev_components
# 如果新增超過8檔,需要減少替換數量
if len(added) > 8:
# 取出新增的股票並按排名排序 (rank越小越好)
added_with_rank = pd.Series({stock: ranks.get(stock, float('inf')) for stock in added})
# 只保留排名最好的前8名新增的股票
to_keep = set(added_with_rank.sort_values().index[:8])
to_remove_due_to_limit = added - to_keep
# 從暫定名單中移除因超過8檔限制而被剔除的股票
current_components_tentative = current_components_tentative - to_remove_due_to_limit
# --- 補滿至目標數量 ---
num_needed = target_component_count - len(current_components_tentative)
if num_needed > 0:
# 找出所有排名16-45,但尚未被選入的股票
remaining_candidates = candidates - current_components_tentative
if remaining_candidates: # 確保還有候選股可補
# 依排名排序這些候選股
remaining_candidates_with_rank = pd.Series({stock: ranks.get(stock, float('inf')) for stock in remaining_candidates})
sorted_remaining_candidates = remaining_candidates_with_rank.sort_values().index
# 選取排名最好的 num_needed 檔來補滿
stocks_to_add = set(sorted_remaining_candidates[:num_needed])
current_components_tentative.update(stocks_to_add)
# --- 補滿邏輯結束 ---
# 最終確認的成分股
current_components = current_components_tentative
# 設定成分股
# 確保只設定存在的欄位
valid_cols = [col for col in current_components if col in position.columns]
position.loc[date, valid_cols] = True
# 更新前一期成分股
prev_components = current_components
# 5. 前向填充空值,確保非調倉日也有持股
position = position.loc[valid_dates]
- 生效日期計算:
- 審核基準日後第 5 個交易日正式生效。
def calculate_review_dates(trading_dates: pd.DatetimeIndex):
"""
計算台灣高股息指數的審核與調倉日期
台灣高股息指數規則:
- 5月: 第17個交易日為審核基準日,審核資料截至5月第10個交易日
- 12月: 第7個交易日為審核基準日,審核資料截至11月最後交易日
- 生效日: 審核基準日後第5個交易日
"""
review_dates_info = []
start_year = trading_dates[0].year
end_year = trading_dates[-1].year
for year in range(start_year, end_year + 1):
# 5月定審
may_review_basis_day = get_trading_day_of_month(year, 5, 17, trading_dates)
may_data_cutoff_day = get_trading_day_of_month(year, 5, 10, trading_dates)
if may_review_basis_day and may_data_cutoff_day:
target_effective_day_index = trading_dates.searchsorted(may_review_basis_day) + 5
if target_effective_day_index < len(trading_dates):
may_effective_day_target = trading_dates[target_effective_day_index]
may_effective_day = get_nearest_future_trading_date(may_effective_day_target, trading_dates)
if may_effective_day:
review_dates_info.append({
'year': year,
'month': 5,
'cutoff_date': may_data_cutoff_day,
'basis_date': may_review_basis_day,
'effective_date': may_effective_day
})
# 12月定審
dec_review_basis_day = get_trading_day_of_month(year, 12, 7, trading_dates)
nov_data_cutoff_day = get_last_trading_day_of_month(year, 11, trading_dates)
if dec_review_basis_day and nov_data_cutoff_day:
target_effective_day_index = trading_dates.searchsorted(dec_review_basis_day) + 5
if target_effective_day_index < len(trading_dates):
dec_effective_day_target = trading_dates[target_effective_day_index]
dec_effective_day = get_nearest_future_trading_date(dec_effective_day_target, trading_dates)
if dec_effective_day:
review_dates_info.append({
'year': year,
'month': 12,
'cutoff_date': nov_data_cutoff_day,
'basis_date': dec_review_basis_day,
'effective_date': dec_effective_day
})
# 按生效日期排序
review_dates_info = sorted(review_dates_info, key=lambda x: x['effective_date'])
# 移除無效日期
review_dates_info = [info for info in review_dates_info if info['effective_date'] is not None]
if not review_dates_info:
raise ValueError("無法計算出任何有效的審核與生效日期,請檢查日期計算邏輯或資料範圍。")
return review_dates_info
四、復刻結果
在經過一連串比對後,復刻而得的策略股池與原版 00919 報酬率曲線有高度相關。這意味著,我們的 復刻版 00919 確實能有效重現 00919 的選股結果。
相關性分析:

復刻 00919 報酬 :

長期持有 00919 報酬 :

我們的「復刻版 00919」與官方版本高相關,證明邏輯拆解合理。
五、進階優化:
雖然我們已成功復刻 00919 的選股邏輯,但這僅是起點。在接下來的優化階段,我們希望進一步提升策略效率與穩定性。因此,我們先行剔除效度不高的條件,以降低雜訊與過度擬合(overfitting)的風險,同時納入經過 IC/IR 驗證、具高預測力的關鍵因子,讓策略在維持高息特性的同時,具備更好的長期報酬潛力。

IC/IR 指標告訴我們,有些條件不但沒幫忙,還在拖後腿,列如:
- 市值條件 IC 為 負值
- 流動性條件 IC 也是 負值
名詞小辭典 – IC / IR
• IC(Information Coefficient):因子對未來報酬的預測能力,>0 正向、<0 反向。
• IR(Information Ratio):IC 均值 ÷ IC 標準差,衡量穩定度,>1 代表因子「又準又穩」。
當我們發現市值與流動性條件的 IC 為負時,決定剔除。這樣的做法其實也呼應了我們作為一般投資人的一項優勢,不像 ETF 必須考量大規模資金進出時的流動性風險,我們可以更靈活地聚焦在報酬潛力本身,因此不再納入這些與 ETF 結構相關的限制性條件,也是合理的選擇。
接下來,我們回頭檢視先前 先前文章, 「復刻 0056 ETF」文章中表現優異的因子,將這些經驗延伸應用到本次策略中。透過重新分析其 IC 與 IR 指標,我們挑選出具有預測力的關鍵因子,將其納入排序邏輯中與原有條件結合。

可以發現,股利率、預估股利率、cash_dividend_annual 以及 ROE 都展現出良好的表現,屬於具有預測力的優質因子。
接下來,我們將這些表現突出的因子進行整合,以組合分析的方式評估其綜合效果,並驗證是否能進一步提升選股策略的穩定性與報酬潛力。





可以發現,無論是在 60 天或 120 天的時間尺度下,ROE 與年化股息率的組合表現穩定且亮眼。因此我們將這組優勢因子正式納入五月與十二月的選股排序邏輯中。接著,我們進一步精簡成分股數量,並取消複雜的替換規則,打造出更簡潔、集中、操作效率更高的「終極策略版本」。這樣的設計,能兼顧高勝率與操作彈性,真正貼近一般投資人的實戰需求。
六、優化後的成績單
- 年化報酬率: 提高至約 47%,相較於原版明顯領先。
- 夏普比率: 從遠本的 1.16 提高至約 1.83,相較於原版明顯領先。
更難能可貴的是在差不多的最大下跌風險下,提高報酬率和穩定性,不必日日擔心股價大幅震盪。如此一來,投資人就能同時享有領股利的安心感,又能捕捉股價成長的爆發力。
獲利能力分析:

抗風險能力分析:

風險報酬分析:

勝率期望值分析:

在不犧牲「領息」特色的前提下,獲利更高、風險更低。
七、行動建議
- 進階派:跟著本文邏輯,自行用FinLab 復刻並優化。
- 客製派:把優化後因子丟進量化平台,持續調參,打造專屬高息策略。
風險提醒:高息 ≠ 無風險。股息遞延、宏觀逆風都可能影響績效。請按自身資金配置與風險承受度操作。
結語:高息,也可以很聰明
復刻只是起點,希望這篇文章能點燃你的研究慾望,讓「領息」不再只是被動等待,而成為可以精雕細琢、持續進化的好策略。
若你已經迫不及待想動手,接下來:
- 依本文框架補上程式碼段並回測驗證。
- 加入自己的好因子和條件,調整策略。
祝你在下一個除息季,順利把每一分股息都握在手中!