目录
透過前兩篇【資料科學練功坊】的文章跟大家分享了如何使用Numpy及Pandas來對資料做初步的處裡,然後再告訴大家說在計算報酬率時應該使用的是調整後股價而非調整前才不會導致數據失真。因此,在了解這些東西之後,這次我們就是要用到上述的這些工具來對大家常常聽到的【定期定額】投資方法進行回測,並讓大家看到複利的效果!!
回測(Backtesting),簡單來說就是在你手上有了一個策略(可以是突發奇想也可以是過去著名大師的某項策略)之後,透過選取標的、買入/賣出的信號、投資組合等方式搭配歷史資料對該策略進行測試,了解這個策略在過去的表現為何,並拿來做未來投資決策的重要依據判斷之一。舉例:
策略假設:只要公司ROE超過15%,那我就進行買入,一年買一次,低於15%時就賣出。
這樣的假設想法其實就可以構成一個所謂的回測,因為你有了買入/賣出的條件,只要有了歷史資料後針對上述提到的信號點就可以開始做測試。但回測還是有一些⚠需要注意的地方⚠,這邊我們最後會再跟各位分享。
近年來,定期定額存股的投資策略在投資人或散戶當中越來越盛行,但什麼是定期定額?這樣的投資方式真的好嗎?這種想法出現的時候就是回測派上用場的地方拉~
正式進入回測之前,我們先把上述兩個名詞的意思搞定:
1. 0050(ETF): 追蹤臺灣50指數的被動式管理,成分股包含臺灣上市股票市值前五十大的個股。
2. 定期定額:每隔固定一段時間,買進一筆固定的金額的投資策略。
本次策略設定為:
每月月初投入10000元新台幣,為期5年,期間不領回,忽略不計過程中任何交易成本。
import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.ticker as ticker import matplotlib.transforms as transforms
import datetime
import tejapi tejapi.ApiConfig.api_key = "your key"
TW0050 = tejapi.get( 'TWN/EWPRCD', coid = '0050', mdate={'gte':'2016-01-01', 'lte':'2020-12-31'}, opts={'columns': ['mdate','close_adj']}, paginate=True )
TW0050['mdate'] = TW0050.loc[:,'mdate'].astype('datetime64[ns]') TW0050 = TW0050.set_index('mdate')
##資料從日資料轉換為月資料 TW0050_monthly = TW0050.resample('MS').first()
##將歷史資料最後一天補回來 TW0050_monthly = TW0050_monthly.append(TW0050.iloc[-1]) TW0050_monthly
上面這段程式碼可以看到,一開始從TEJ API抓下資料後(2016-2020),我們先將日期資料轉成index後,透過pandas.resample()功能,將日資料轉為月資料。但因為我想了解這段期間最後一天的報酬率, 因此我將2020-12-31的資料補回我們的歷史資料裡面。
##每月購買股數(無條件捨去)= 10000/月收盤價 TW0050_monthly['每月購買股數']= np.floor(10000 / TW0050_monthly['close_adj'])
TW0050_monthly['每月購買股數'][-1] = 0
##累積股數 TW0050_monthly['累積股數'] = TW0050_monthly['每月購買股數'].cumsum()
##累積持股價值=累積股數*當月股價 TW0050_monthly['累積價值(月)'] = round(TW0050_monthly['累積股數'] * TW0050_monthly['close_adj'],2)
##資產原始價值=10000*累積月份 TW0050_monthly['原始價值(月)'] = [10000*i for i in range(len(TW0050_monthly.index))]
TW0050_monthly['原始價值(月)'] = TW0050_monthly['原始價值(月)'].shift(-1)
TW0050_monthly['原始價值(月)'][-1] = TW0050_monthly['原始價值(月)'][-2]
##累積報酬率 TW0050_monthly['累積報酬(月)'] = round((TW0050_monthly['累積價值(月)']-TW0050_monthly['原始價值(月)'])/TW0050_monthly['原始價值(月)'], 6) +1
由上面的幾個數學式子可看到說,其實我們就是分別去計算每個月10000在當時能買多少股數的0050然後隨著時間推移,那些累積股數的價值分別為多少,並與該時間點如果沒有買0050時的原資產價值進行比較,最後可以得到這個結論:
從2016年1月開始每月買入約等於價值新台幣10000的0050ETF,過程中不計交易成本,經過5年後總持有股數來到8444股,累積價值約為1,010,261,累積報酬率為68%。
僅透過文字可能感受不太清楚,我們用視覺化的方式看一下資產的變化。
plt.figure(figsize = (10, 6)) plt.plot(TW0050_monthly['累積價值(月)'], lw=1.5, label = '0050') plt.plot(TW0050_monthly['原始價值(月)'], lw=1.5, label = 'Original Asset') plt.xlabel('Time(Year)') plt.ylabel('Value') plt.legend(loc = 0) plt.grid() ##增加網格
可以發現說其實大部分資產的增加都出現在去年,除掉去年之外資產的成長是較為平緩的,這也可以發現說其實定期定額使資產增加的效果出現其實需要較為長時間的持有。
那如果我們想知道每個月的報酬率變化該怎麼辦呢?這邊的程式碼比較複雜一點點,我們之後會再專門寫一篇關於資料視覺化的主題,這邊讀者如果不清楚可以一行一行執行看看,如果有問題可以再詢問我們!
##月報酬計算 ret = np.log(TW0050_monthly['累積報酬(月)']/TW0050_monthly['累積報酬(月)'].shift(1))
fig, ax = plt.subplots() ret.plot(kind="bar", figsize=(12,6), stacked=True, ax=ax)
##建立一個空陣列,長度為資料總月份的長度 ticklabels = ['']*len(TW0050_monthly.index)
##每6個月展示出其月份跟日期 ticklabels[::6] = [item.strftime('%b %d') for item in TW0050_monthly.index[::6]]
##每12個月額外展示出年份 ticklabels[::12] = [item.strftime('%b %dn%Y') for item in TW0050_monthly.index[::12]]
ax.xaxis.set_major_formatter(ticker.FixedFormatter(ticklabels)) plt.gcf().autofmt_xdate()
##新增平均月報酬的水平線 ax.axhline(ret.mean(), c='r')
plt.xlabel('Month') plt.ylabel('Return') plt.title('0050 Monthly return')
plt.show()
可以看到說除了去年之外,其實大多月分的報酬率都算是為在一個穩定的區間之內。而上面這些操作主要是因為我們如果每個月份都要顯示在圖表上的話會太過擁擠,因此上面這些操作主要是為了避險這種狀況而設計的~
最後我們可能可以透過一些績效/統計指標來對這項定期定額的投資策略進行一個分析:
##年化報酬率 cagr = (TW0050_monthly['累積報酬(月)'][-1]) ** (1/5) -1
##年化標準差 std = ret.std()*np.sqrt(12)
##Sharpe Ratio(假設無風險利率為1%) shapre_ratio = (cagr-0.01)/std
##最大回撤 roll_max = TW0050_monthly['累積價值(月)'].cummax() monthly_dd =TW0050_monthly['累積價值(月)']/roll_Max - 1.0 max_dd = monthly_dd.cummin()
##表格 pd.DataFrame(columns=['0050'], index=['年化報酬率(%)', '年化標準差(%)', '夏普比率', '期間最大回撤(%)'], data = np.round(np.array([100*cagr, 100*std, sharpe_ratio, 100*max_dd[-1]]),2))
因此,根據我們的投資方式可以了解到說5年的期間,定期定額0050可以拿到接近11%的年化報酬率,這是相當驚人的數值,畢竟銀行定存一年可能只有約1%,透過複利的效果,5年就將原本的資產變成原先的1.6倍,波動性以及夏普比率(每承受一單位總風險會產生多少額外報酬)也是表現優異。
但以缺點來說,因為是該ETF主要購買市值高的股票原因,所以仍然會受到covid-19這類系統性風險造成的波動對資產造成回撤影響,再者就是何時應該獲利了解或者出場就取決於投資人的決策。
因此定期定額對於一般投資人來說的確是一個不差的投資方式,但要有這樣的結果就必須要有很強的紀律性,也就是每個月該時刻無論無何都得進場買入,並需要長期持有才能看到這樣的結果,也因此較不適合對於沒有足夠閒置資金的投資人進行這項操作。
我們透過python將策略進行了回測,並透過表格及視覺化的方式呈現了結果。但過去並不能表示未來,這只是能讓投資者們對自己的想法或是策略用過去進行一個佐證,並對未來做一個參考的依據。
但要做出一個成功的回測,我們仍需要注意像是資料品質、資料長度、程式是否有BUG、交易成本是否忽略過多、若是使用基本面還會有資料時間軸等等的相關問題。上述這些問題只要有一個地方出錯都會造成回測的失真,如果還依據這個結果將資金丟入市場,最嚴重就是造成虧損,所以一定要再三注意❗️️️️ ❗️️️️ ❗️️️️ ❗️️️️ ❗️️️️
我們之後也會再分享以回測為主題的文章,如果讀者們有甚麼想要回測看看的策略也可以跟我們說~最後,如果喜歡本篇文章的內容請幫我們點擊下方圖示 ,給予我們更多支持與鼓勵,有任何的問題都歡迎在下方留言/來信,我們會盡快回覆大家
想要一個"穩定""品質高""資料長度長"的資料源該怎麼辦呢?TEJ API就是你最好的選擇!!
有任何使用上的問題都歡迎與我們聯繫:聯絡資訊