‘Smart Beta’之低波動率策略(附程式碼)【量子金服投研平臺出品】
引 言
Smart Beta,可以簡單的翻譯成聰明貝塔,又稱智慧型投資策略,正如其名字所言,這是一個更“聰明”的指數跟隨策略。具體來說,是在傳統的指數投資基礎上,透過系統性的方法,對指數中選股和權重進行最佳化,取得跑贏傳統指數投資的成果。
本文將介紹使用最為廣泛的一個利用低波動特性構建的smart beta投資組合策略並加以實證,幫助大家瞭解熟悉這一投資工具。
概念簡介
(1)傳統主動型和被動型投資
通常來講,投資者一般有兩種方式可以投資
股票市場
——主動型和被動型。所謂主動型,也就是傳統意義上的選股擇時,如果眼光毒辣,時機精準,這種方法就有可能獲得驚人的投資回報。當然相對的,這種投資方法的風險非常高,很有可能損失慘重,血本無歸。越來越多的研究表明,專業的基金經理都幾乎不可能在長期內跑贏
大盤
,更何況對市場一無所知的散戶。
此外靠選股為生的基金經理會收取基金投資人1。5%-2%的手續費,算上這筆費用,本來就不能跑贏大盤的基金投資回報,反倒還低於大盤走勢了。於是投資者把目光投向 “被動型”投資,這些策略直接投資於指數,並不“積極”地進行選股,只是跟蹤大盤的走勢。這樣的策略能夠穩定的取得與大盤相當的收益,風險很小,當然潛在的收益也有所侷限。
(2)Smart Beta
傳統的“被動型”指數投資策略也有自己的問題,在資產的權重配置上往往達不到最最佳化。比如按照股票的市值來分配投資組合裡面股票的權重。其結果就是上漲的股票,因為市值增加,反倒被給予了更高的權重。這樣不斷操作下來,傳統的指數投資可能會買入更多估值高的股票。
於是“Smart Beta”策略應運而生,它仍然屬於廣義上的“被動投資”,但是加入了一些“主動型”的投資策略,試圖透過透明的、基於規則的、量化的方法投資特定領域,來捕捉風險溢價。例如,對於傳統的標普500追蹤策略,一些Smart Beta策略提出,選出其中100只低波動率股票進行投資,且每隻股票的權重根據波動率的倒數來決定,也就是波動越小的股票,就投資越多。像這樣的在指數投資的基礎上,加入一些小的變化,就是Smart Beta最常見的策略。
(3)低波動率異象
傳統觀點(例如CAPM理論)認為,風險與收益成正比,高收益往往意味著高風險。然而在股票市場上,卻時時存在著一種低波動率異象,及低波動率的股票能夠帶來更高的收益。
對此學界爭論多年,可能存在的解釋有:
市場槓桿限制:高風險偏好的投資者傾向於選擇高波動的股票;
博彩假說
:非理性心理使得投資者期待像購買彩票一樣博取高回報;
分析師一致預期:賣方分析師對高波動股票過於樂觀,使得當前股價過高;
跟蹤誤差:低波動股票可能會增大指數跟蹤誤差,使得
指數基金
經理不願意買入。
策略思路
利用低波動股票高收益的“異象”構建Smart Beta指數增強策略。
這裡我們使用波動率排序策略,具體的實現方法為:
(1) 計算母指數成分股在過去一年的波動率;
(2) 按照波動率,將股票分成5檔;
(3) 選取波動率最小的一檔股票;
(4) 按照股票波動率的倒數進行加權,構建組合,即對於母指數我們將選取滬深300指數和
中證500指數
兩個指數分別進行驗證。
策略實現
1)
低波動率Smart Beta策略
母指數:滬深300指數
回測時間:2014.01.01~ 2017.09.01
回測時長:45個月
調倉週期:半年
收益曲線
收益歸因
業績分析
2)
低波動率Smart Beta策略
母指數:中證500指數
回測時間:2014.01.01~ 2017.09.01
回測時長:45個月
調倉週期:半年
收益曲線
收益歸因
業績分析
從回測結果可以發現,我們的Smart Beta策略確實取得了’smarter’的成果。滬深300增強策略在三年內取得了58。19%(相對於滬深300指數)的累計超額收益,勝率達到62。2%。而中證500增強策略在三年期的回測內取得了43。59%(相對於中證500指數)的累計超額收益,勝率達到了59。3%。雖然兩個策略都產生了比較大的回撤,但這主要是由於2015年股災造成的股市整體下行。Smart Beta的主體仍然是Beta,不可能逆大盤而行。
小 結
Smart Beta策略既保留了被動型投資的一些優點,包括風險分散、流動性較好、透明度較高和比“主動型”投資更便宜等,又試著用系統性的方式尋找可以跑贏市場的策略。這看似結合了主動和被動投資的雙重優點,但是作為投資者必須、也應該意識到,世界上沒有免費的午餐。
Smart Beta在享有以上優勢的同時,也存在著以下幾個弊端:
1) 交易成本高於ETF:Smart Beta由於需要有較高的買入賣出操作頻率才能實現跟蹤自己設計的策略目的,其費用至少是傳統ETF投資的6倍,因為實行這些策略仍然涉及到研究、調倉、流動性等問題。
2) 未必能找到有效的‘smart’因子:能解釋市場波動的因子未必能預測市場波動。除了整個大盤走勢之外,金融研究人員已經發行了至少300多個能解釋個體資產波動的因子。基於此,一些Smart Beta投資策略提高投資組合對於其中幾個因子的敏感度,希望捕捉高於大盤的收益。但是並不是所有能解釋波動的因子,都能帶來收益。也不是所有能帶來收益的因子,都適合結合到指數投資策略之中。
3) 面臨更高的風險:一些研究對比了傳統的ETF和Smart Beta的走勢和波動,發現雖然看起來Smart Beta策略帶來了更高的回報,但是下跌的時候跌的也更狠。把風險考慮進來,Smart Beta並沒有超額回報,只是承擔了更高的風險。
這也告訴我們,在實際投資中,我們一定要注意充分認識每一個策略的利弊,這樣才可以做出正確的判斷,選擇出最合適的投資工具。
參考文獻
廣發證券 《“Smart Beta”之“低風險高收益”的低波動率策略》
Code
1)
Smart 300
# -*- coding:utf-8 -*-
from CloudQuant import SDKCoreEngine # 匯入
量子金服SD
K
from CloudQuant import AssetType
from CloudQuant import QuoteCycle
from CloudQuant import OrderType
import numpy as np # 使用numpy
import pandas as pd
np。seterr(invalid=‘ignore’)
config = {
‘username’: ‘username’,
‘password’: ‘password’,
‘rootpath’: ‘c:/cStrategy’, # 客戶端所在路徑 ‘assetType’: AssetType。Stock,
‘initCapitalStock’: 10000000, #
初始資金
# ‘assetType’: AssetType。Future,
# ‘initCapitalFuture’: 10000000000, # 初始資金 ‘startDate’: 20140101, # 交易開始日期 ‘endDate’: 20170901, # 交易結束日期 ‘cycle’: QuoteCycle。D, # 回放粒度為1分鐘線 ‘feeRate’: 0。001,
‘feeLimit’: 5,
‘strategyName’: ‘smart300’, # 策略名 “logfile”: “ma。log”,
‘dealByVolume’: True
}
HoldingPeriod=125
def initial(sdk):
sdk。setGlobal(‘c’, 1)
def initPerDay(sdk):
pass
def strategy(sdk):
count = sdk。getGlobal(‘c’)
if count == 1:
# 清空持倉 pos = sdk。getPositions()
stock_owned = [i。code for i in pos]
quote = sdk。getQuotes(stock_owned)
sellOrder = []
for item in pos:
stock = item。code
q = quote[stock]
if q:
sellOrder。append([stock, q。current, item。optPosition, -1])
sdk。makeOrders(sellOrder)
stock_list
=sdk。getStockList()
stock_list=np。array(stock_list)
hs300_member = sdk。getFactorData(“LZ_CN_STKA_INDEX_HS300MEMBER”)[-1]
condition=hs300_member==1
hs300_stock_list=stock_list[condition]
hs300_stock_list=list(
hs300_stock_list
)
hist=sdk。getLatest(hs300_stock_list,250)
volatility=pd。Series([0。0]*len(hs300_stock_list),index=hs300_stock_list)
for stock in hist:
price=[i。close for i in hist[stock]]
vol=np。std(price)
volatility[stock]=vol
volatility=volatility。sort_values()
n=int(len(hs300_stock_list)/5)
volatility_min=volatility。iloc[:n]
total_vol=sum(volatility_min)
weight=volatility_min。apply(lambda x: x/total_vol)
buyOrder=[]
budget=sdk。getAccountInfo()。availableCash
quote=sdk。getQuotes(list(volatility_min。index))
for stock in quote。keys():
price=quote[stock]。open
sub_budget=budget*weight[stock]
volume=sub_budget/price//100*100
buyOrder。append([stock,price,volume,1])
sdk。makeOrders(buyOrder)
count = count + 1
if count == HoldingPeriod:
count = 0
sdk。setGlobal(‘c’, count)
def main():
# 將
策略函式
加入 config[‘initial’] = initial
config[‘strategy’] = strategy
config[‘preparePerDay’] = initPerDay
# 啟動SDK
SDKCoreEngine(**config)。run()
if __name__ == “__main__”:
main()
2)
Smart 500
# -*- coding:utf-8 -*-
from CloudQuant import SDKCoreEngine # 匯入量子金服SDK
from CloudQuant import AssetType
from CloudQuant import QuoteCycle
from CloudQuant import OrderType
import numpy as np # 使用numpy
import pandas as pd
np。seterr(invalid=‘ignore’)
config = {
‘username’: ‘username’,
‘password’: ‘password’,
‘rootpath’: ‘c:/cStrategy’, # 客戶端所在路徑 ‘assetType’: AssetType。Stock,
‘initCapitalStock’: 10000000, # 初始資金
# ‘assetType’: AssetType。Future,
# ‘initCapitalFuture’: 10000000000, # 初始資金 ‘startDate’: 20140101, # 交易開始日期 ‘endDate’: 20170901, # 交易結束日期 ‘cycle’: QuoteCycle。D, # 回放粒度為1分鐘線 ‘feeRate’: 0。001,
‘feeLimit’: 5,
‘strategyName’: ‘smart500’, # 策略名 “logfile”: “ma。log”,
‘dealByVolume’: True
}
HoldingPeriod=125
def initial(sdk):
sdk。setGlobal(‘c’, 1)
def initPerDay(sdk):
pass
def strategy(sdk):
count = sdk。getGlobal(‘c’)
if count == 1:
# 清空持倉 pos = sdk。getPositions()
stock_owned = [i。code for i in pos]
quote = sdk。getQuotes(stock_owned)
sellOrder = []
for item in pos:
stock = item。code
q = quote[stock]
if q:
sellOrder。append([stock, q。current, item。optPosition, -1])
sdk。makeOrders(sellOrder)
stock_list=sdk。getStockList()
stock_list=np。array(stock_list)
zz500_member = sdk。getFactorData(“LZ_CN_STKA_INDEX_CSI500MEMBER”)[-1]
condition=zz500_member==1
zz500_stock_list=stock_list[condition]
zz500_stock_list=list
(zz500_stock_list)
hist=sdk。getLatest(zz500_stock_list,250)
volatility=pd。Series([0。0]*len(zz500_stock_list),index=zz500_stock_list)
for stock in hist:
price=[i。close for i in hist[stock]]
vol=np。std(price)
volatility[stock]=vol
volatility=volatility。sort_values()
n=int(len(zz500_stock_list)/5)
volatility_min=volatility。iloc[:n]
total_vol=sum(volatility_min)
weight=volatility_min。apply(lambda x: x/total_vol)
buyOrder=[]
budget=sdk。getAccountInfo()。availableCash
quote=sdk。getQuotes(list(volatility_min。index))
for stock in quote。keys():
price=quote[stock]。open
sub_budget=budget*weight[stock]
volume=sub_budget/price//100*100
buyOrder。append([stock,price,volume,1])
sdk。makeOrders(buyOrder)
count = count + 1
if count == HoldingPeriod:
count = 0
sdk。setGlobal(‘c’, count)
def main():
# 將策略函式加入 config[‘initial’] = initial
config[‘strategy’] = strategy
config[‘preparePerDay’] = initPerDay
# 啟動SDK
SDKCoreEngine(**config)。run()
if __name__ == “__main__”:
main()
上一篇:上巳節福利之宋引默番外
下一篇:崑劇簡介-崑劇的起源與發展