
Table of Contents
造市商具備高頻交易基礎、低廉的成本結構與即時的申購/買回權限,是折溢價套利市場的主力。而一般投資人則面臨資訊落後、手續費與交易稅等多重限制,即便發現套利機會,也未必能及時進場、有效獲利。
本篇文章以元大台灣 50(0050 ETF)為例,透過歷史資料模擬兩種角色在折溢價策略中的實際操作,觀察在不同進出場門檻與成本結構下的績效差異。藉由勝率、報酬分布與回撤風險等指標,我們希望釐清:這場「看似無風險的套利」,究竟是誰能真正抓住機會、穩定獲利?
ETF 的市場價格由投資人透過交易所買賣決定,淨值則反映基金所持資產的實際價值。當市場需求瞬間變動、或標的資產報價時效落差時,ETF 的成交價格可能偏離其淨值,形成「折價」或「溢價」狀態。
為維持 ETF 價格與淨值的接近,造市商(Market Makers)與授權參與者(Authorized Participants, APs)可透過申購與贖回機制進行套利。例如,當 ETF 出現溢價,造市商可同步賣出 ETF 並買入一籃子標的資產,最終以淨值價格贖回 ETF,以此賺取套利利差。反之,當出現折價時,則可進行反向操作。
此類套利機制有助於價格回歸合理水準,提升市場效率。然而,實務中套利空間往往轉瞬即逝,成功套利不僅仰賴即時報價與高速交易執行,也受到手續費、交易稅與流動性等限制條件的影響,使不同市場參與者面臨截然不同的實作挑戰。
本研究以 2020 年 12 月 31 日作為資料切割點,將整體歷史資料劃分為訓練集與測試集,避免策略在參數估計階段出現未來資料遞延使用的情況。回測標的為 0050 ETF,套利邏輯基於該 ETF 的市場價格與淨值之間的折溢價情形。
在進出場判斷上,本研究僅使用訓練集中的「折溢價比率」資料計算平均值與標準差作為判斷基準。當日折溢價比率若顯著偏離訓練期均值超過一倍標準差,即視為價格短期失衡的訊號,觸發套利操作:若市場價格相對淨值明顯低估,則買進 ETF 並賣出對應成分股組合;反之亦然。
透過此簡化且可執行的策略設計,本研究得以從真實市場結構中拆解出造市商與一般投資人之間在實際套利執行上的可能差異。
import matplotlib.pyplot as plt
import matplotlib as mpl
import tejapi
import pandas as pd
import numpy as np
tejapi.ApiConfig.api_key = "your key"
tejapi.ApiConfig.api_base = "your base"
data_use = tejapi.get('TWN/ANAV', coid = '0050', mdate={'gte':'2010-01-01', 'lte':'2025-06-09'})
data = data_use.copy()
data['mdate'] = pd.to_datetime(data['mdate']) # 保險起見,先確保是 datetime 格式
data['mdate'] = data['mdate'].dt.strftime('%Y-%m-%d') # 轉成你要的字串格式
data['mdate'] = pd.to_datetime(data['mdate'])
data.to_csv(f'0050_Net_VALUE.csv')
data_use = data[['mdate', 'fld004', 'fld005', 'fld006', 'fld007']].copy()
data_use['NAV_ret'] = data_use['fld004'].pct_change().fillna(0)
data_use['MV_ret'] = data_use['fld005'].pct_change().fillna(0)
split_date = '2021-01-04'
train_data = data_use[data_use['mdate'] < pd.to_datetime(split_date)].copy()
test_data = data_use[data_use['mdate'] >= pd.to_datetime(split_date)].copy()
mean = train_data['fld007'].mean()
std = train_data['fld007'].std()
mpl.rcParams['text.color'] = 'black'
plt.style.use('ggplot')
fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(20, 16), sharex=False)
axes[0].plot(data_use['mdate'], data_use['fld004'], label = 'Net Value')
axes[0].plot(data_use['mdate'], data_use['fld005'], label = 'Market Value')
axes[0].axvline(x = pd.to_datetime(split_date), color = 'black', linestyle = '--', label = 'Split Date')
axes[0].set_title('0050 ETF NET_MARKET Value', fontsize=18)
axes[0].legend()
axes[1].bar(data_use['mdate'], data_use['fld006'])
axes[1].axvline(x = pd.to_datetime(split_date), color = 'black', linestyle = '--')
axes[1].set_title(f'Price Premium (Discount) in NTD')

本研究採用「折溢價比率」作為進出場判斷依據,而非「折溢價差」,其主要原因在於:折溢價差會隨著 ETF 價格的絕對水準而改變尺度,導致相同比率的偏離在價格高點時呈現出更大的數值體量。以 0050 為例,當價格上漲至 150 元以上時,即便折溢價比率僅為 1%~2%,折溢價差就可能出現 1~2 元的偏差,看似顯著,實則相對於淨值的偏離程度並不大。因此,使用比率作為判斷依據能更準確反映實際的套利空間。
plt.figure(figsize = (20, 6))
plt.hist(train_data['fld007'], bins = 30, edgecolor = 'white')
plt.axvline(x = mean, label = "Mean", color = 'black', linestyle = '--')
plt.title(f'Premium (Discount) in percent Distribution')
plt.legend()
plt.show()

若要區分造市商與一般投資人的視角,兩者主要的差異在於交易成本的存在與否。對一般投資人而言,必須出現足夠的價差,才會產生進場套利的誘因;而從造市商的角度來看,造市商的主要任務在於提供市場流動性,並盡力維持價格穩定。因此,在本次的實作中,我設定當價差超過三倍標準差時,才足以吸引一般投資人進場;而對造市商而言,進場門檻則設為一倍標準差。
本研究以較寬鬆的一倍標準差當作造市商進場穩定價格的依據。因為造市商在創造ETF 流動性時,不需要付證券交易稅以及交易手續費。我也會在下圖展示若以相同的標準(一倍標準差)考慮進手續費的話,累積報酬率結果如何。
本段程式碼主要實作 ETF 套利策略的報酬計算,分為單期報酬與累積報酬兩部分。其核心邏輯是根據交易訊號,決定在 NAV(基金淨值端)以及 MV(市場價格端)所應採取的多空部位,並據此計算各自的每日報酬,再合併成整體策略的績效表現。
在策略中,若前一日發出多方訊號,表示投資人已進場做多,隔日將計入 NAV 端的正報酬,並同時在 MV 端計入負報酬,因為套利邏輯通常是做多 NAV、做空 MV,以捕捉價差收斂的利潤。反之,若當天出現空方訊號,代表今日才進場做空,NAV 端會計入負報酬,而 MV 端則計入正報酬,反映做空操作下,從價格下跌中獲利的邏輯。若無交易訊號,則兩端皆不計入報酬。
接著,程式會將每日的 NAV 端及 MV 端報酬分別做累積,計算自策略開始以來的淨值變化,亦即累積報酬。最後,再將 NAV 與 MV 的單期報酬相加,得到整體策略的每日總報酬,並進一步計算策略整體的累積報酬,藉以評估此套利策略的長期績效。
整體而言,此段程式的目的是透過拆解多空部位在 NAV 與 MV 端的變動,精確計算出 ETF 套利策略在每日及累積層面的收益表現,以便後續進行績效評估或策略優化。
sta = 1
test_data['signal'] = np.where(test_data['fld007'] < mean - sta * std, -1, # fld007 太低
np.where(test_data['fld007'] > mean + sta * std, 1, 0)) # fld007 太高
test_data['b_ret_nav'] = np.where(test_data['signal'].shift(1) == 1, test_data['NAV_ret'],
np.where(test_data['signal'].shift(1) == -1, -test_data['NAV_ret'], 0))
test_data['b_ret_mv'] = np.where(test_data['signal'].shift(1) == 1, -test_data['MV_ret'],
np.where(test_data['signal'].shift(1) == -1, test_data['MV_ret'], 0))
test_data['c_ret_nav'] = (1 + test_data['b_ret_nav']).cumprod() - 1
test_data['c_ret_mv'] = (1 + test_data['b_ret_mv']).cumprod() - 1
test_data['b_ret'] = test_data['b_ret_mv'] + test_data['b_ret_nav']
test_data['c_ret'] = (1 + test_data['b_ret']).cumprod() - 1
cost = (0.001425 * 2 * 0.18 + 0.001) # ETF 交易稅為 0.001
test_data['b_ret_nav_fee'] = np.where(test_data['signal'].shift(1) == 1, test_data['NAV_ret'] - cost,
np.where(test_data['signal'].shift(1) == -1, -test_data['NAV_ret']- cost, 0))
test_data['b_ret_mv_fee'] = np.where(test_data['signal'].shift(1) == 1, -test_data['MV_ret'] - cost,
np.where(test_data['signal'].shift(1) == -1, test_data['MV_ret']- cost, 0))
test_data['b_ret_fee'] = test_data['b_ret_mv_fee'] + test_data['b_ret_nav_fee']
test_data['c_ret_fee'] = (1 + test_data['b_ret_fee']).cumprod() - 1
plt.style.use('ggplot')
fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(18, 14), sharex=False)
axes[0].plot(test_data['mdate'], test_data['c_ret'], label='Strategy (Without Cost)')
axes[0].set_title(f'0050 ETF Arbitrage Cumulative Returns')
axes[0].plot(test_data['mdate'], test_data['c_ret_fee'], label='Strategy (With Cost)')
axes[0].legend()
axes[1].bar(test_data['mdate'], test_data['fld007'], label='fld007')
axes[1].axhline(y = mean, color = 'green', label = 'Mean', linestyle = '--')
axes[1].axhline(y = mean + sta*std, color = 'green', label = 'Upper bound', linestyle = '--')
axes[1].axhline(y = mean - sta*std, color = 'green', label = 'Lower bound', linestyle = '--')
axes[1].set_title(f'Price Premium (Discount) in percent')
axes[1].legend()
axes[2].hist(test_data['signal'], bins=[-1.5, -0.5, 0.5, 1.5], edgecolor='white', align='mid', color = '#348ABD')
axes[2].set_xticks([-1, 0, 1])
axes[2].set_xticklabels(['Short', 'Neutral', 'Long'])
axes[2].set_title('Signal Distribution')
plt.tight_layout()
plt.show()
test_data.to_csv('0050ETF_Arbitrage.csv', index=False)
plt.figure(figsize=(20, 6))
plt.plot(test_data['mdate'], test_data['c_ret_nav'], label='NAV side')
plt.plot(test_data['mdate'], test_data['c_ret_mv'], label='Market side')
plt.legend()
plt.title('Contribution from NAV and Market')
plt.show()
test_data['position'] = test_data['signal'].shift(1)
test_data['trade_id'] = (test_data['position'] != test_data['position'].shift()).cumsum()
grouped = test_data.groupby('trade_id')
trade_summary = grouped.agg({
'b_ret': 'sum',
'position': 'first',
'mdate': ['first', 'last', 'count']
})
# 攤平欄位名稱
trade_summary.columns = ['_'.join(col).strip() if isinstance(col, tuple) else col for col in trade_summary.columns]
# 重設索引
trade_summary = trade_summary.reset_index()
plt.figure(figsize=(20, 6))
plt.hist(trade_summary['b_ret_sum'], bins=20, edgecolor='white')
plt.title("Distribution of Trade Returns")
plt.show()
trades_with_result = trade_summary[trade_summary['b_ret_sum'] != 0]
win_rate = (trades_with_result['b_ret_sum'] > 0).mean()
avg_win = trade_summary[trade_summary['b_ret_sum'] > 0]['b_ret_sum'].mean()
avg_loss = trade_summary[trade_summary['b_ret_sum'] < 0]['b_ret_sum'].mean()
profit_factor = abs(avg_win / avg_loss)
print(f"勝率(Win Rate):{win_rate:.2%}")
print(f"平均獲利(Avg Win):{avg_win:.5f}")
print(f"平均虧損(Avg Loss):{avg_loss:.5f}")
print(f"報酬因子(Profit Factor):{profit_factor:.2f}")

上圖中的第一張子圖的紅線來看,是無交易成本的一倍標準差累積報酬率圖表,代表著造市商的視角。從結果來看,此套利策略似乎可以為造市商帶來一些收益,但並非暴利的程度。與此同時,對於造市商而言這些報酬並非主要目的,維持ETF市場的價格穩定以及提供流動性才是首要目的,圖中的報酬率有點像是間接收益的概念。
從第一張圖表的藍線來看,這是考慮了交易成本的累積報酬率,我們可以清楚地看出,考慮進交易成本後,整體的累積報酬率是呈現直線下降,這也顯示出一般投資人無法跟著造市商進出買賣,因為大部分的投資報酬都會被交易成本侵蝕殆盡,甚至出現損失的局面。

此圖展示了策略報酬在「淨值端」與「市值端」的累積貢獻情形。可以明顯觀察到,主要的報酬來源集中於市值端,顯示出市值具有較強的均值回歸能力。另一方面,淨值端的貢獻則相對不穩定,甚至在多數期間呈現負值。此現象亦反映實務上的市場機制:當 ETF 出現折溢價時,成分股的價格往往較ETF市值先行反應,使得最終套利收益主要來自於市值的回補。

從上圖得知,一般投資人無法以造市商的標準進場操作,因為高昂的交易成本會侵蝕報酬率。因此我們收緊進場的標準,以3倍標準差為一般投資人的進場依據,由於折溢價比率要超過3倍標準差才進場,因此單筆的報酬率預期會比1倍時來得高,預期可以付出手續費後保留部分報酬率,但是進場機會也會隨之減少。

從圖中看以看出,將進場標準改為 3 倍標準差之後,投資機會大幅減少,但是可以轉取穩定的報酬率 。但是整體報酬率僅僅只有 12%,考慮進交易成本之後,累積報酬率只有7%左右。顯示出即便將進場標準設定的更加嚴謹,報酬率的呈現也不夠吸引一般投資人執行策略。而且進場時機非常的少,從第一張子圖中可以看出在2024-07之前的期間都沒有機會進場,僅僅只有2024-07之後才有進場機會。若投資人在整段期間投資於風險較低的債券資產,通過領息的方式大機率可以贏過 3 倍標準差的套利交易。
| 項目 | 造市商(一倍標準差) | 一般投資人(三倍標準差) |
| 勝率 | 100% | 100% |
| 平均獲利 | 0.00385 | 0.00739 |
| 平均損失 | – | – |
| 報酬因子 | – | – |
歡迎投資朋友參考,之後也會持續介紹使用 TEJ 資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購 TQuant Lab 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
溫馨提醒,本次分析僅供參考,不代表任何商品或投資上的建議。
回測的時間長度為2010-2025。

結果來看,若以造市商角度來看(第一張子圖),使用較低門檻值的標準差可以轉去最多的報酬,因為並無手續費以及交易稅的干擾,因此造市商在提供ETF流動性的同時可以穩定賺取報酬率,此報酬率的賺取的邏輯來自於市場的短期無效率。
一般投資人的視角(第二張子圖),使用用兩倍標準差作為門檻值會是獲取最高的報酬率,其餘的門檻值(扣除一倍標準差的情況)皆可賺取報酬率,但是報酬的多寡並不是那麼吸引人。以表現最好的線圖來看(紫色線),15 年的期間累積報酬率只有大約 40 %,年化報酬率只有大約 2.7 %左右,因此對於一般投資人來說並不足以吸引他們進行這類型的交易操作。
TEJ 知識金融學院正式上線—《TQuantLab 量化投資入門》課程強勢推出!
這門課程結合 TEJ 實證資料與專業量化方法,帶你從零開始掌握量化投資的核心概念,
協助金融從業人員、投資研究人員以及想強化投資邏輯的你,快速建立系統化分析能力!