投資理財經典《漫步華爾街》作者推崇長期持有一籃子股票的指數型投資。
買進並持有大盤,只要大盤長期來說有向上的趨勢,加上持有的時間「夠久」,幾乎一定能獲利。夠久是多久呢?以美國股票為例,在 1950-2013 年間任何時刻一次買入,持有 15 年,就能保證獲利了。
台股應該也有類似的現象。我試著以台灣 50 (0050) 為例,練習用 pandas 來回答以下問題:
持有 0050 不同時間 (一個月、一年、三年、五年) 的獲利機會多少?平均年化利率為多少?
先上答案:
從 2007/12/31 開始,於任何一天買入,持有 0050 不同時間的獲利機會與年化報酬如下表:
持有時間 | 1 個月 | 1年 | 3 年 | 5 年 |
---|---|---|---|---|
獲利機會 | 59% | 69.9% | 89.7% | 93% |
平均年化獲利 | 7.35% | 8.26% | 7.5% | 7.3% |
因為容易找得到的資料從 2007 年底開始,數據不像美國股市統計起來這麼有代表性。但同樣可以看見,長期持有的獲利機會比起持有短時間大幅提高。
事前準備
1. 找資料
我選擇在 Yahoo! finance 下載 0050 的資料。在歷史資料內,時間範圍選擇 maximum ,可以下載所有年度的價格資訊,可惜 0050 的資料只從 2007 年底開始。我們將使用「還原價格」幫助我們計算。
2. 看資料
import datetime
import pandas as pd
import matplotlib.pyplot as plt
data = pd.read_csv("0050.TW.csv")
data.head()
使用 pandas 的 read_csv 函數,可以讀取 csv 表格。看一下資料內容如下:
我們計算獲利最重要的還原收盤價 Adj Close 已包含在內,這讓我們不用去處理除權息。
3. 建立方便計算的表格
我們想回答的問題是,持有 n 天後的獲利是多少。若 n 天後是假日,表格內是沒有資料的。為了方便計算,我們需要做一張含有所有日期的表格,並在假日填入下一開盤日的價格。也就是說,如果預計賣出那天是假日,就以下一開盤日的價格當做賣出價。
# 以資料最早的那天做為開始日,並以今天當做結束日,建立一個只有日期的表格
start_date = data["Date"][0]
end_date = datetime.date.today().isoformat()
date_rng = pd.date_range(start=start_date, end=end_date, freq='D')
df = pd.DataFrame(date_rng,columns=['Date'])
再一行,就能把空表格和價格表以日期為索引,合併成一張,此時假日的價格仍是空的。
df = df.set_index('Date').join(data.set_index('Date'))
使用
bfill()
函數,把數值往回填入無數值的格子。這樣就把下一營業日的價格回填了。df = df.bfill()
表格最末可能是假日,無更多資料可以回填。直接捨棄這部分。
# 去除 N/A
df = df.dropna()
以上三行可以串接起來,一次搞定:
df = df.set_index('Date').join(data.set_index('Date')).bfill().dropna()
表格現在如下,多出了 1/1,並已填入 1/2 的價格了。至此,每一列之間都是固定一天的差距了。
輕鬆得到想要的資料
接下來就是 pandas 操作表格方便的地方!把表格平移 30 列 (30 天) ,除以原表格的值,就變成任一天買進,持有 30 天的獲利率了。
# 一行得到時間範圍內,持有 30 天的報酬
df_30d_rate=(df["Adj Close"].shift(-30)/df["Adj Close"]).dropna()
df_30d_rate.describe()
結果如下:
count 4399.000000
mean 1.006127
std 0.053622
min 0.631027
25% 0.978523
50% 1.011056
75% 1.038577
max 1.247586
Name: Adj Close, dtype: float64
在這 4399 個日子中,持有 30 天賣掉的平均獲利是 0.61%,一年大約是 0.61% * 12,約 7.3%。把曲線畫出來看看,振動的相當厲害呀!
df_30d_rate.plot()
如果是持有五年呢?除了振動較小外,只在 2008 年出現虧損 (期末/期初<1),其他時間都是賺錢的。這也意味只要分批買入,例如以定期定額的方式進行,就可以避免買在最貴的時候,得到平均的報酬。
勝率是多少呢?這裡,用 dataframe/series 的篩選功能.
# filter 會是一個 true/false 的 series, >1 的日子也就是賺錢的日子會是 true
filter = df_30d_rate > 1
# 可以用 filter 來篩選資料,算出賺錢機會
print(df_30d_rate[filter].count()/df_30d_rate.count())
# 簡寫如下
print(df_30d_rate[df_30d_rate>1].count()/df_30d_rate.count())
約有 59% 機會賺錢。
0.5917253921345761
重覆用以上的方法,就能回答一開始的問題了。記得轉為年化報酬時,要把複利的效應考慮進來。
沒有留言
張貼留言