目標
上支QC平台影片介紹了如何利用它們推薦的設計模式,QC平台稱這個設計模式為Alpha Framework。我們這次會延伸上次所使用的動能指標,來建立一個簡單的動能策略,這邊我們想要達到,(1)內建的動能指標能不能幫助我們提升投資效率,(2)之後我們能不能延伸這個想法來完善這個策略(3)藉由這個簡單的例子更熟悉QC的API。
動能指標
首先,讓我們先了解QC內建的動能指標是怎樣被定義的,由QC的API說明、以及原始碼可以判斷,假設是三天的動能指標(例如:mom = self.mom(Symbol(‘SPY’),3,Resolution.Daily),這每次呼叫mom.Current.Value會得到最新的價格減掉3天前的價格。某種程度在衡量價格上升的速度。 當然,這個動能指標是QC內建的動能指標,有很多能衡量價格上升速度的方式,像是短線壓長線,這些都可以當作是動能指標。
動能策略
所謂的動能策略是基於動能高就投資,動能低就不投資的想法,這個想法的背後是基於現在價格暴衝的資產,很有可能之後也暴衝的機會也比較大,可以想成強者更有可能持續強,弱者更有可能持續弱。
有不少的研究顯示,動能因子可以帶來更高的報酬,那為什麼動能指標有可能有用,以下提供幾個可能的解釋方式
解釋一:對資訊的反應慢
一般的非專業投資人可能對某些資訊的反應較慢,舉例來說,如果一個公司的收益大於預期,一般的非專業投資人可能沒辦法第一時間買到這個公司的資產,所以大型機構與專業投資人會第一時間買到這公司的股票,促使這個公司的動能上升,這時候非專業投資人再慢慢消化這個資訊,促使股票價格進一步上漲。
解釋二:心理因素
第二種解釋是從眾效應,當一個資產的動能增加,可能代表很多人買進這個資產,此時就可能產生從眾效應,更多人的選擇買入,進一步使得價格上升。
解釋三:承擔暴跌的風險
前兩個解釋都假設市場有某種的沒效率,像是資訊傳遞較慢,以及有些不理性的心理因素,但動能因子的較高報酬也有可能來自於投資人對於暴跌風險的考量,因為衝得較快的資產都常在市場不好的時候也跌得比較多,為了要補貼暴跌風險,動能較高的資產需要提供較高的報酬才能吸引投資人入場。
策略邏輯
這邊的想法很簡單,我們的交易池是QC提供的流動性高的科技ETF,使用小時的價格資料,每個禮拜調倉一次,計算最高動能的前8支ETF買進持有,計算從2015-05開始的回測表現。
細節會在下一支影片補充。
交易池
交易池是QC平台所提供的流動性科技ETF,可以從連結中看到裏面包含了
“XLK”,”QQQ”, “SOXX”, “IGV”,”VGT”, “QTEC”, “FDN”, “FXL”, “TECL”, “TECS”, “SOXL”, “SOXS”,”SKYY”, “SMH”,”KWEB””FTEC”
與買入持有的比較
下圖是買入持有的損益圖,可以看到用動能選ETF似乎真得帶給我們較多利潤。
上次影片的小補充
上次的影片大家可能會有一個疑問,我們寫得策略多久會調倉一次,也就是多久會觸發update裡面寫好的程式? 答案是QC平台的回測引擎是事件驅動的回測引擎,對於update方法來說,每次資料的更新就是一個新的事件,因此,更新的頻率會跟我們最高的資料頻率有關,因為我們上次將所有的資料設為日資料,所以會變成每天更新一次倉位。
回測表現
下一支影片在分析一下這個策略的表現。
程式細節
在下一支影片會介紹程式碼的一些細節以及為何要這樣設定。
程式碼
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
class MOMAlphaModel(AlphaModel):
def __init__(self):
self.mom = []
self.week = 0
self.hour = 11
def OnSecuritiesChanged(self,algorithm,changes):
for security in changes.AddedSecurities:
symbol = security.Symbol
self.mom.append({
"symbol":symbol,
"indicator":algorithm.MOM(symbol,7,Resolution.Daily)
})
def Update(self,algorithm,data):
if (algorithm.Time.hour != self.hour) or (algorithm.Time.weekday() != self.week):
return []
ordered = sorted(self.mom,key= lambda kv:kv['indicator'].Current.Value,reverse=True)
selected = [asset['symbol'] for asset in ordered[:8]]
insights = []
for asset in ordered:
if asset['symbol'] in selected:
insights.append(Insight.Price(asset['symbol'],timedelta(days=7),InsightDirection.Up))
else:
insights.append(Insight.Price(asset['symbol'],timedelta(days=7),InsightDirection.Flat))
return insights
class MultidimensionalDynamicChamber(QCAlgorithm):
def Initialize(self):
# Set Start Date so that backtest has 5+ years of data
self.SetStartDate(2015, 5, 1)
# self.SetEndDate(2020,2,1)
# No need to set End Date as the final submission will be tested
# up until the review date
# Set $1m Strategy Cash to trade significant AUM
self.SetCash(1000000)
# Add a relevant benchmark, with the default being SPY
# self.AddEquity('SPY',Resolution.Daily)
self.SetBenchmark('SPY')
# Use the Alpha Streams Brokerage Model, developed in conjunction with
# funds to model their actual fees, costs, etc.
# Please do not add any additional reality modelling, such as Slippage, Fees, Buying Power, etc.
self.SetBrokerageModel(AlphaStreamsBrokerageModel())
self.SetExecution(ImmediateExecutionModel())
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(rebalance =self.DateRules.Every(DayOfWeek.Monday)))
self.SetAlpha(MOMAlphaModel())
self.SetUniverseSelection(TechnologyETFUniverse())
self.UniverseSettings.Resolution = Resolution.Hour