“该策略投资于过去36个月回报最高的ETF的最高十分位数,并做空最低十分位数,使用价值加权投资组合,每月重新平衡。”

I. 策略概要

该投资范围包括在美国CRSP数据库上市的ETF,市值至少2000万美元,股价高于1美元。ETF根据过去36个月的累计回报进行排序。投资者做多回报最高的十分位ETF,做空回报最低的十分位ETF。投资组合采用价值加权,每月重新平衡。

II. 策略合理性

该研究回顾了推动ETF动量的各种因素。研究发现,ETF动量回报呈现出持续性,随后出现反转,利润在78个月后逐渐消失。与股票动量不同,ETF动量不能用与基准股票策略或宏观经济风险的协变来解释。即使在标准普尔500指数回报较低的时期,该策略也持续产生正回报。股票特征和流动性风险也不能解释ETF动量。值得注意的是,ETF动量在持有大盘股的ETF中更为显著。这表明ETF中的动量与股票动量不同,可能由其他市场动态驱动。

III. 来源论文

ETF Momentum [点击查看论文]

<摘要>

我们发现,当根据过去两到四年的回报对ETF进行排序时,动量利润在经济上是巨大的。基于ETF动量的价值加权多空策略每月可产生高达1.20%的Carhart(1997)四因子阿尔法。无论是横截面股票动量还是与宏观经济和流动性风险的协变都无法解释ETF动量。相反,持有期后的回报最符合延迟过度反应的行为故事。虽然ETF动量在多次交易成本调整后仍然存在,但由于利润波动且集中在具有高特质波动性或持有分析师覆盖率低的股票的ETF中,因此可能难以套利。

IV. 回测表现

年化回报16.08%
波动率25.78%
β值0.39
夏普比率0.62
索提诺比率0.325
最大回撤N/A
胜率53%

V. 完整的 Python 代码

from AlgorithmImports import *
class ETFMomentum(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2008, 1, 1)
        self.SetCash(100000)
        self.market = self.AddEquity('SPY', Resolution.Daily).Symbol
        
        # Daily ROC data with market cap.
        self.data = {}
        self.period = 36 * 21
        self.SetWarmUp(self.period)
        
        # Data source: https://etfdb.com/screener/#page=4&tab=overview&sort_by=assets&sort_direction=desc&asset_class=equity&regions=U.S.&inception_on_start=2005-01-01&inception_on_end=2020-09-01
        csv_string_file = self.Download('data.quantpedia.com/backtesting_data/economic/us_equities.csv')
        
        # header: symbol;etf_name;total_assets_mm;previous_closing_price
        lines = csv_string_file.split('\r\n')
        for line in lines[1:]:
            line_split = line.split(';')
            
            etf_symbols = line_split[0]
            market_cap = float(line_split[2])  # $MM
            last_price = float(line_split[3])
            
            if market_cap > 20 and last_price > 1:
                data = self.AddEquity(etf_symbols, Resolution.Daily)
                data.SetLeverage(5)
                
                self.data[etf_symbols] = (self.ROC(etf_symbols, self.period, Resolution.Daily), market_cap)
                
        self.Schedule.On(self.DateRules.MonthStart(self.market), self.TimeRules.AfterMarketOpen(self.market), self.Rebalance)
        
    def Rebalance(self):
        performance = {x : self.data[x][0].Current.Value for x in self.data if self.data[x][0].IsReady}
        sorted_by_performance = sorted(performance.items(), key = lambda x: x[1], reverse = True)
        decile = int(len(sorted_by_performance) / 10)
        
        # Symbol, market cap tuples.
        long = [(x[0], self.data[x[0]][1]) for x in sorted_by_performance[:decile]]
        short = [(x[0], self.data[x[0]][1]) for x in sorted_by_performance[-decile:]]
        
        # Market cap weighting.
        weight = {}
        total_market_cap_long = sum([x[1] for x in long])
        for symbol, market_cap in long:
            weight[symbol] = market_cap / total_market_cap_long
        
        total_market_cap_short = sum([x[1] for x in short])
        for symbol, market_cap in short:
            weight[symbol] = -market_cap / total_market_cap_short   
        
        # Trade execution.
        invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in weight:
                self.Liquidate(symbol)
        
        for symbol, w in weight.items():
            if self.Securities[symbol].Price != 0 and self.Securities[symbol].IsTradable:
                self.SetHoldings(symbol, w)

发表评论

了解 Quant Buffet 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读