1. 程式人生 > >量化投資學習筆記05——檢驗計算回測指標程式

量化投資學習筆記05——檢驗計算回測指標程式

因為對前面計算回測指標的程式的準確性還有疑問,我決定再驗證一次。驗證的方法是找一個帶資料的完整的程式,先實現其程式,再用它的資料和我的程式計算,對比一下二者的結果。
在知乎上找到一篇,https://zhuanlan.zhihu.com/p/55425806 是用貴州茅臺,工商銀行和中國平安三隻股票做回測。我照著其程式寫了,計算結果與文章中的一致。

接下來就用pyalgotrade框架和我自己的封裝來寫了。因為pyalgotrade_tushare只能按整年進行資料抓取,把文章中的日期改為到2018年12月31日(而不是原文中的2019年1月18日)。
但是用我的程式算那些指標還是與文章裡算的不對,尤其β值居然是0。我還是找現成的庫吧。
找了一圈,發現一個我能用的:empyrical。但是嘗試以後,發現還是有問題,如圖:


用pyalgotrade文件裡的例子做測試,前面是pyalgotrade裡的回測結果,然後用其計算的收益率作為輸入,用empyrical庫計算其它指標,最大回撤是一致的,αβ值不知道是否正確,夏普比率相差太多啦。
於是決定回到最基礎的辦法:自己編個資料,按照這些指標的定義手算,再用程式驗證吧。
本文以下參考《量化投資:以python為工具》一書相關章節。
假設有股票test,初始價格為1,每天漲1元,一共10天。基準指數base初始價格也是1,每天漲0.5元,一共10天。第一天各買入1股,計算每天的收益率。為了保持一致,假定初始投入2.5元,再少pyalgotrade會報現金不足。
test.csv

Date,Open,Close,High,Low,Volume
2019-01-01,2.369,1.0,9,0,1877797.0
2019-01-02,2.358,2.0,9,0,4404445.0
2019-01-03,2.335,3.0,9,0,4834089.0
2019-01-04,2.27,4.0,9,0,1525888.0
2019-01-05,2.287,5.0,9,0,2050543.0
2019-01-06,2.28,6.0,9,0,3371288.0
2019-01-07,2.269,7.0,9,0,3701781.0
2019-01-08,2.255,8.0,9,0,4884821.0
2019-01-09,2.239,9.0,19,0,2509259.0
2019-01-10,2.253,10.0,19,0,3339884.0
2019-01-10,2.253,10.0,19,0,3339884.0

base.csv

Date,Open,Close,High,Low,Volume
2019-01-01,2.369,1.0,9,0,1877797.0
2019-01-02,2.358,1.5,9,0,4404445.0
2019-01-03,2.335,2.0,9,0,4834089.0
2019-01-04,2.27,2.5,9,0,1525888.0
2019-01-05,2.287,3.0,9,0,2050543.0
2019-01-06,2.28,3.5,9,0,3371288.0
2019-01-07,2.269,4.0,9,0,3701781.0
2019-01-08,2.255,4.5,9,0,4884821.0
2019-01-09,2.239,5.0,9,0,2509259.0
2019-01-10,2.253,5.5,9,0,3339884.0

計算中只用Close一列,最低最高價也修改了,其它的保持原狀,目的是使pyalgotrade框架能夠使用資料。
接下來從收益和收益率開始計算。
資產的收益率是指投入某資產所能產生的收益與當初投資成本的比例。
收益率=投資收益/投資成本
期間投資收益=期末價格-期初價格+其它收益
期間收益率=期間收益/期初價格
每天為一期,為了跟pyalgotrade框架一致,第一天決策,次日才交易。
test的收益率
第一天: 2.5-2.5/2.5 = 0.0
第二天: 2.5-2.5/2.5 = 0.0
第三天: 3.5-2.5/2.5 = 0.4
第四天: 4.5-3.5/3.5 = 0.2857142857142857
以此類推。
base的收益率:
第一天: 2.5-2.5/2.5 = 0.0
第二天: 2.5-2.5/2.5 = 0.0
第三天: 3.0-2.5/2.5 = 0.2
第四天: 3.5-3.0/3.0 = 0.1666666666666667
以此類推。
現在用python算一下。
先讀取資料

# 從檔案中讀取資料
test_df = pd.read_csv("test.csv", index_col = "Date")
print(test_df)
base_df = pd.read_csv("base.csv", index_col = "Date")
print(base_df)
# 提取收盤價資訊
test_close = test_df["Close"]
base_close = base_df[["Close"]]
# test_close.name = "Close"
# base_close.name = "Close"
print(test_close, base_close)
# 計算每日收益率
# 初始投資
cash_test = 2.5
cash_base = 2.5
# 每期市值
position_test = []
position_base = []
print(test_close.values[0], base_close.values[0])
for i in range(len(test_close)):
    if i == 0:
        position_test.append(cash_test)
        position_base.append(cash_base)
        continue
    elif i == 1:
        cash_test = cash_test - test_close[0]
        cash_base = cash_base - base_close.values[0][0]
    if cash_test <= 0 or cash_base <= 0:
        print("現金不足,退出")
    position_test.append(cash_test + test_close[i-1])
    position_base.append(cash_base + base_close.values[i-1][0])
  
print(position_test, position_base)
test_return = []
base_return = []
test_return.append(0.0)
base_return.append(0.0)
for i in range(1, len(position_test)):
    print(i, position_test[i], position_test[i-1])
    test_return.append((position_test[i] - position_test[i-1])/position_test[i-1])
    base_return.append((position_base[i] - position_base[i-1])/position_base[i-1])

計算結果

對了。接下來計算年化收益率
年化收益率是把當前收益率(日、周、月收益率等)換算成年收益率,方便投資人比較不同期限的投資。這只是理論上的收益率,並不是投資人真正能獲得的收益率。
持有T期收益為Rt, 一年有m個單期,則年化收益率為(Rt/T)*m
如果考慮複利,年化收益率=(1+Rt)**(1/(T/m))-1

# 計算年化收益率
np_test_return = np.array(test_return)
np_base_return = np.array(base_return)
annret_test = (1+np_test_return).cumprod()[-1]**(245/311) - 1
annret_base = (1+np_base_return).cumprod()[-1]**(245/311) - 1
print(annret_test, annret_base)
print("empyrical")
annret_test_ep = ep.annual_return(np_test_return)
annret_base_ep = ep.annual_return(np_base_return)
print(annret_test_ep, annret_base_ep)

結果
2.097306384013633 1.1227960188906434
empyrical裡有計算年化收益率的函式,試試。
5080215298974669.0 28663443858.08141
差別好大,感覺年化收益率蠻坑的,理財,保險等機構都喜歡用這個概念,先略過吧。後面用不到。
接下來看風險指標
首先可以用收益率的標準差來衡量。

#衡量風險
# 標準差
print(np_test_return.std(), np_base_return.std())

結果
0.11597007874921565 0.06111509424879022
前者的風險更大。
最大回撤,因為我的資料就沒有回撤,而且幾個庫計算的最大回撤值幾乎一樣,就不自己寫了。用empyrical庫試試。

# 最大回撤
print(ep.max_drawdown(np_test_return), ep.max_drawdown(np_base_return))

結果都是0.0
現在來計算策略的α和β值,其來自資本資產定價模型(CAPM),Rq為資產組合的收益,Rf為無風險資產收益,Rm為市場資產組合收益(一般以大盤指數代表),有如下關係:
E(Rq) - Rf = βqm(E(Rm) - Rf)
即,β值為策略收益與無風險收益之差與和市場平均收益與無風險收益之差的比值。
βqm又等於σ(Rq,Rm)/σ²(Rm),前者為資產組合收益率與市場投資組合收益率之間的協方差,後者為市場投資組合的方差,β值反映出投資組合的系統性風險。若β=1,則策略與市場的波動性是一致的,若β絕對值小於1,則策略的波動性小於市場,若β絕對值大於1,則策略的波動性大於市場。單隻股票的期望收益是無風險收益加上系統性風險溢酬。非系統風險可以通過分散投資消除。
將模型寫成不含期望值的形式:
Rit - Rft = α + β(Rmt - Rft) + ε
Rit,Rft,Rmt分別為個股收益率,無風險收益率和市場收益率,對這些資料進行線性迴歸,可以得到α和β值的估計值。β值可以解釋個股過去收益率與風險的關係,根據這個模型,所有資產α值都應為0,若顯著異於0,則個股有異常收益。Alpha值代表收益率勝過大盤的部分。

# 計算αβ值
# 先將兩個收益率合併到一起
Ret = pd.merge(pd.DataFrame(base_return), pd.DataFrame(test_return),  left_index = True, right_index = True, how = "inner")
print(Ret)
# 計算無風險收益
rf = 1.036**(1/360) - 1.0
print(rf)
# 計算股票超額收益率和市場風險溢酬
Eret = Ret - rf
print(Eret)
# 接下來進行擬合
model = sm.OLS(np_test_return, sm.add_constant(np_base_return))
result = model.fit()
print(result.summary())

計算結果,α為-0.0279,β為1.8462,再用empyrical算一遍。

print("empyrical")
alpha, beta = ep.alpha_beta(np_test_return, np_base_return, 0.036)
print(alpha, beta)

計算結果
0.8273048650075308 1.8426052351694182
還是相差很多。β值很不確定,同一股票不同時期的β值相差很大。用歷史資料計算β值,對投資的指導意義不大。所以,也許可以不用再糾結了。
最後算夏普值。
夏普比率就是一個可以同時對收益與風險加以綜合考慮的三大經典指標之一。 投資中有一個常規的特 點,即投資標的的預期報酬越高,投資人所能忍受的波動風險越高;反之,預期報酬越低,波動風險也越低。所以理性的投資人選擇投資標的與投資組合的主要目的為:在固定所能承受的風險下,追求最大的報酬;或在固定的預期報酬下,追求最低的風險。
。理性的投資者將選擇並持有有效的投資組合,即那些在給定的風險水平下使期望回報最大化的投資組合,或那些在給定期望回報率的水平上使風險最小化的投資組合。解釋起來非常簡單,他認為投資者在建立有風險的投資組合時,至少應該要求投資回報達到無風險投資的回報,或者更多。
夏普比率目的是計算投資組合每承受一單位總風險,會產生多少的超額報酬。夏普指數代表投資人每多承擔一分風險,可以拿到幾分超額報酬;若為正值,代表基金報酬率高過波動風險;若為負值,代表基金操作風險大過於報酬率。這樣一來,每個投資組合都可以計算Sharpe Ratio, 即投資回報與多冒風險的比例,這個比例越高,投資組合越佳。夏普比率沒有基準點,因此其大小本身沒有意義,只有在與其他組合的比較中才有價值。

# 計算夏普比率
sharpe = (np_test_return.mean() - 0.03)/np_test_return.std()*np.sqrt(252)
print(sharpe)

結果
17.79285680812303
再用empyrical算一次
16.879786078470694
兩個結果是基本一致的。
接下來,就再用pyalgotrade回測一下吧。
程式碼我就不往上放了,只放結果。


還是有差異。尤其是α和β值。看了一下程式碼,在pyalgotrade的回測程式碼裡增加了輸出,發現成交價是2.36元,不是我想的2.0元。是因為這個原因嗎?唉,不糾結了,就這樣吧。用pyalgotrade回測算收益率、投資收益、夏普值、最大回撤等,用empyrical算α和β值。
本文程式碼:
https://github.com/zwdnet/MyQuant/tree/master/05
自己算指標在index.py裡,用pyalgotrade回測在pyat_index.py裡