前言
最近寫了不少以動量因子為核心的量化策略,結(jié)果收益和回撤都不太理想
跑去社區(qū)發(fā)了個帖子貼了下自己的回測情況,有人推薦我看看這本書,遂有如下經(jīng)歷
原書思想
簡單點說,整個策略是期望基于動量因子尋找到有上升趨勢的股票,買入并持有直到趨勢消失,從而在牛市中獲取超額利潤
作者以標(biāo)普500作為股票池,他的理由是:
一只股票能加入到指數(shù)的部分原因是其過去價格走勢強勁。當(dāng)股票從指數(shù)中退出時,通常是價格表現(xiàn)不佳,跌到所要求市值之下。這使得標(biāo)普500指數(shù)和其他大多指數(shù)一樣在一定程度上都是動量策略。
關(guān)于動量效應(yīng)的解釋:
當(dāng)一只股票的股價上漲一段時間后,繼續(xù)上漲的可能性要高于回落的可能性,比其他股票上漲的快的股票會繼續(xù)比其他股票上漲的快
而且作者不是僅僅是通過計算股票的年化收益率作為動量因子來篩選股票(后面我會列舉具體的計算步驟),他還有一些附加條件,比如:
股票價格必須高于其100日均線。如果不是,說明它并不符合動量的標(biāo)準(zhǔn)。因為在上漲的市場中,排名靠前的股票價格都遠(yuǎn)遠(yuǎn)高于其100日均線,但是如果在熊市或者牛熊市轉(zhuǎn)換之際,上漲的股票很少,這條規(guī)則可以確保你不會買入那些橫盤或者下跌的股票。
注意價格缺口。如果某只股票在過去90天里有超過15%的價格缺口(股價大幅變動并伴隨極少的交易量),那么它也會被取消買入資格,因為如果你不排除這一情況,就有可能買入并非真正動量股的股票。比如說短期沖擊可能導(dǎo)致股價大幅波動,有時即使我們對年化收益率做了一定的修正,仍無法抵消這一影響,這就與我們希望買入穩(wěn)步上升的股票的初衷相違背了。
歸根結(jié)底,對動量因子的應(yīng)用,其實就是一種趨勢跟隨的實操方案,而趨勢跟隨的弊端及補償方案,原書中也提到過:
當(dāng)市場橫盤整理或是快速切換方向的時候,趨勢跟隨者便會虧損。對于個別市場或行業(yè),這一現(xiàn)象可能會持續(xù)多年。在極端情況下,甚至?xí)掷m(xù)十年。趨勢跟隨的核心前提是基于多元化。通過同時交易多種不同類別的資產(chǎn),其獲得成功的概率是非常高的,以至于有足夠的資金來彌補在某些資產(chǎn)類別上的損失
這一點在實際回測中也被很好的印證了,基本跑不過大盤

上圖15年熊市末期到18年末的收益情況,紅線是滬深300收益,藍(lán)線是策略收益
當(dāng)然,除了依據(jù)動量因子給池中的股票做排名之外,作者也提到了頭寸規(guī)模
他說的這句話一定要畫上重點:我們不是分配資金,而是分配風(fēng)險
很多人給資產(chǎn)分配不同的權(quán)重時往往容易忽視背后的邏輯,要記住,我們正在做的是均衡風(fēng)險,而不是表面上的資金分配
最后,關(guān)于賣出時機,這一點不是動量策略要考慮的,因此書中并未提及止損方法
回測的具體步驟
首先貼下代碼邏輯,這是我最近的心得之一,回測前一定要畫好流程圖,后面擼代碼時效率會高很多

上面是根據(jù)原書的策略優(yōu)化后作出的第一版流程圖,實際回測時我是有部分改動的,后續(xù)會提到
下面講講核心算法
股票排名
指導(dǎo)思想:找到一類穩(wěn)步上升的股票,不僅隨著時間的推移獲得可觀的收益,而且還盡可能平穩(wěn)地移動
這里主要以兩個指標(biāo)為依據(jù):
- 股票年化收益 2. 股票的波動率
關(guān)于第一點,這里所說的年化收益其實是通過指數(shù)回歸計算日漲幅從而得到的,目的是為了量化動量這一指標(biāo),收益越高,動量越大
股票的波動率是借助r-squared這個判定系數(shù)來衡量價格序列與回歸直線的擬合程度,擬合性越差,判定系數(shù)越低,給最終分?jǐn)?shù)添加更高的懲罰
所以最后將二者乘積作為股票的分值
具體計算方法:
對價格序列取自然對數(shù)
對處理后的價格序列計算線性回歸方程
將方程的斜率作為日收益,再計算其250次方獲得年化收益
通過RSQ()函數(shù)計算判定系數(shù)(r-squared)
以上是原書的計算方法,詳情可參考我的代碼:
def get_socre(stock):
''' 基于股票年化收益和判定系數(shù)打分
Returns:
score (float): score of stock
'''
data = attribute_history(stock, g.stock_mean_day, '1d', ['close'])
y = data['log'] = np.log(data.close)
x = data['num'] = np.arange(data.log.size)
slope, intercept = np.polyfit(x, y, 1)
annualized_returns = math.pow(math.exp(slope), 250) - 1
r_squared = 1 - (sum((y - (slope * x + intercept))**2) / ((len(y) - 1) * np.var(y, ddof=1)))
return annualized_returns * r_squared
頭寸規(guī)模
關(guān)于波動的計算,作者引入ATR,即Average True Range(平均真實波幅)這一指標(biāo),來衡量股票價格波動
其中,TR =∣今日最高價 - 今日最低價∣和∣今日最高價 -昨日收盤價∣和∣今日最低價 - 昨日收盤價∣的最大值
原書ATR取近20日內(nèi)TR的均值
關(guān)于風(fēng)險因子,用于設(shè)定頭寸規(guī)模,舉個例子:
總資金10萬,風(fēng)險因子10個基點(0.001),目標(biāo)股價118.93,ATR為3.26(股價日振幅)
則,對應(yīng)股票應(yīng)持股數(shù)量 = 10萬 X 0.001 / ATR, 股票權(quán)重 = 持股數(shù)量 X 股價(118.93)
最后,設(shè)定一個閾值,一旦目標(biāo)股票當(dāng)前的資金暴露風(fēng)險與期望風(fēng)險相差大于閾值,則觸發(fā)再平衡操作,這么做的一個考量是為了減少換手率,防止過多的小額交易
參考代碼:
def get_expected_position(stock, context):
''' 根據(jù)ATR和風(fēng)險因子計算股票的期望倉位
Returns:
float: 目標(biāo)股票的期望投入金額
'''
data = attribute_history(stock, g.ATR_day+1, '1d',
['high', 'low', 'close'])
ATR = talib.ATR(data.high, data.low, data.close, timeperiod=g.ATR_day)[-1]
stock_price = data.close[-1]
expected_position = context.portfolio.total_value * g.risk_factor * stock_price / ATR
return expected_position
def get_diff_position(stock, context):
''' 股票的當(dāng)前倉位與期望倉位的差值百分比
Returns:
float
'''
expected_position = get_expected_position(stock, context)
now_position = context.portfolio.positions[stock].value
return abs(now_position / expected_position - 1)
最后
策略表現(xiàn):

本次回測只是復(fù)制原書的整個策略,作者選用的標(biāo)普500,我選取的滬深300,并未依據(jù)A股市場做任何變動
但是從牛市情況來看,原策略在國內(nèi)的表現(xiàn)也還是可圈可點的,至于熊市和震蕩市的表現(xiàn),就得結(jié)合其他策略做進一步的改進了
比如原策略對熊市的判斷是依據(jù)標(biāo)普500的200日均線,這一點用在A股的結(jié)果從上圖就能看出
關(guān)于參數(shù)的優(yōu)化,作者說的一些話我很中意,比如:
如果你運行一個優(yōu)化算法,你可能得出這樣的結(jié)論:一個237天或178天的移動平均是最有效的。那會讓你以為這跟未來一定是有關(guān)系的。其實你得到的就是一個在特定歷史時期下的曲線擬合。你真正要做的是仔細(xì)思考一些理念,而不是某個精確的數(shù)字。
做量化交易很容易陷入過度優(yōu)化的誤區(qū),我的觀念是定性首先比定量要重要,一個策略如果要求一套非常精確的規(guī)則和參數(shù),那這樣的策略通常不會是健壯的策略。
從這一點看,上面的動量策略的表現(xiàn)其實我是比較滿意的