“该策略针对市值≥5000万美元且上一周交易量≥500万美元的加密货币。每周计算高动量指标(最高价格与当周收盘价的差异),并根据过去1周、2周、4周的动量排序,构建3×5分位数投资组合。做多Q5、做空Q1,形成长短投资组合,并以1/3比例组合,所有投资组合每周等权重调整。”
资产类别:加密货币 | 地区:全球 | 频率:每周 | 市场:加密货币 | 关键词:加密货币
策略概述
投资范围包括市值>=5000万美元且上一周交易量>=500万美元的大型且流动性高的加密货币。(数据可从Coinmarketcap.com获取。)
<交易流程>
- 在每周结束时计算高动量指标 ℎ𝑚𝑜𝑚𝑡,ℎ,计算公式为过去ℎ周内的最高日内价格𝐻𝑡,ℎ的自然对数(ln)减去当周收盘价𝐶𝑡的自然对数(ln)。(见论文第4-5页)
- 根据过去1周、2周和4周的高动量排序,构建3×5的分位数投资组合(Q1到Q5)。
- 实施稳健性测试:创建三个初步的Q5-Q1长短投资组合(根据前一周收盘价与过去1周、2周、4周内最高价格的差距进行排序)。
- 最终的等权投资组合由前三个初步投资组合以相同的1/3比例组成。
所有投资组合每周进行调整,且均为等权重。
策略合理性
该论文的主要贡献在于发现,以前报道的短期反转效应主要是小型且流动性差的加密货币的特征,而大且流动性高的加密货币表现出统计显著的短期动量效应。不过有趣的是,超过4周的回报表现出反转效应,这可能是由于初始反应不足或延迟反应过度的假设所解释。尽管观察到的短期反转似乎主要由低交易量和低流动性驱动,但动量效应的真正来源仍不明确,市值(中型币的交易量支持)可能仍不是最佳代理变量。因此,分析其他潜在的动量效应预测因素,如市场关注度、情绪或机构交易活动,可能具有重要意义。
论文来源
Impact of Size and Volume on Cryptocurrency Momentum and Reversal [点击浏览原文]
- Milan Fičura, Gonul Colak, 布拉格经济大学 – 财务与会计学院,萨塞克斯大学;汉肯经济学院
<摘要>
我们分析了加密货币的市值和交易量如何影响其回报的动量和反转动态。研究表明,之前报告的每周回报反转仅发生在小型且流动性差的加密货币中(t-stat = -7.31),而大型且流动性高的加密货币则表现出每周动量效应(t-stat = 2.33)。长期回报表现出反转效应,但对大且流动性高的加密货币来说并不显著。我们进一步分析了高动量对未来加密货币回报的影响,测量标准为前一周收盘价与k周最高价格之间的差距。高动量此前未在加密货币市场中进行过分析,我们证明其在预测未来回报方面优于常规动量。


回测表现
| 年化收益率 | 13.61% |
| 波动率 | 6.49% |
| Beta | -0.044 |
| 夏普比率 | 2.1 |
| 索提诺比率 | N/A |
| 最大回撤 | N/A |
| 胜率 | 48% |
完整python代码
from AlgorithmImports import *
from typing import List, Dict
import numpy as np
# endregion
class HighMomentuminLiquidCryptocurrencies(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
self.market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.crypto_tickers:List[str] = ['BTCUSD', 'ETHUSD', 'SOLUSD', 'ADAUSD', 'XRPUSD', 'DOTUSD', 'DOGEUSD', 'LUNAUSD', 'AVAXUSD', 'UNIUSD',
'LINKUSD', 'LTCUSD', 'BCHABCUSD', 'BSVUSD', 'FILUSD', 'XLMUSD', 'XTZUSD', 'NEOUSD', 'ATOMUSD', 'IOTAUSD',
'ETCUSD', 'DASHUSD', 'EGLDUSD', 'AAVEUSD', 'ENJUSD', 'EOSUSD', 'MKRUSD', 'MANAUSD', 'SNXUSD', 'FTTUSD',
'OMGUSD', 'SUSHIUSD', 'YFIUSD', 'WBTCUSD', 'XMRUSD', 'ZECUSD', 'ZRXUSD', 'XRAUSD', 'AMPLUSD', 'GRTUSD',
'DGBUSD', '1INCHUSD']
self.data:Dict[Symbol, SymbolData] = {}
self.week_periods:List[int] = [7, 14, 21]
self.selection_flag:bool = False
self.quantile:int = 5
self.leverage:int = 2
self.portion:float = .33
self.percentage_traded:float = .2
self.SetWarmup(self.week_periods[2], Resolution.Daily)
# data subscription
for ticker in self.crypto_tickers:
data = self.AddCrypto(ticker, Resolution.Daily, Market.Bitfinex)
data.SetLeverage(self.leverage)
self.data[ticker] = SymbolData(self.week_periods[2])
self.Schedule.On(self.DateRules.WeekEnd(self.market), self.TimeRules.BeforeMarketClose(self.market), self.Selection)
def OnData(self, data: Slice) -> None:
# store daily prices
for ticker in self.crypto_tickers:
if ticker in self.data:
if ticker in data and data[ticker]:
self.data[ticker].update_price(data[ticker].Close, data[ticker].High)
if self.IsWarmingUp: return
if not self.selection_flag:
return
self.selection_flag = False
hmom_by_period_long:Dict[int, List[Symbol]] = {}
hmom_by_period_short:Dict[int, List[Symbol]] = {}
# sort and divide into periods and quantiles
for period in self.week_periods:
hmom:dict[Symbol, float] = {}
for ticker, item in self.data.items():
if self.data[ticker].is_ready():
hmom[ticker] = self.data[ticker].get_week_highmomentum(period)
if len(hmom) >= self.quantile:
sorted_hmom = sorted(hmom, key=hmom.get, reverse=True)
quantile:int = len(sorted_hmom) // self.quantile
hmom_by_period_long[period] = sorted_hmom[:quantile]
hmom_by_period_short[period] = sorted_hmom[-quantile:]
trade_quantities:Dict[Symbol, float] = {}
# trade quantities calculation
for i, hmom_lst in enumerate( [list(hmom_by_period_long.values()), list(hmom_by_period_short.values())] ):
for lst in hmom_lst:
for ticker in lst:
if ticker in data and data[ticker]:
quantity:float = ((self.Portfolio.TotalPortfolioValue / len(lst)) * self.portion) // data[ticker].Price
if ticker not in trade_quantities:
trade_quantities[ticker] = 0
# long
if i == 0: trade_quantities[ticker] += quantity
# short
else: trade_quantities[ticker] -= quantity
# trade execution
stocks_invested:List[Symbol] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for ticker in stocks_invested:
if ticker not in trade_quantities:
self.Liquidate(ticker)
for ticker, new_quantity in trade_quantities.items():
if self.Portfolio[ticker].Invested:
quantity:float = new_quantity * self.percentage_traded - self.Portfolio[ticker].Quantity
if abs(quantity) >= 1.:
self.MarketOrder(ticker, quantity)
else:
self.MarketOrder(ticker, new_quantity * self.percentage_traded)
def Selection(self) -> None:
self.selection_flag = True
class SymbolData():
def __init__(self, period:int) -> None:
self._period:int = period
self._daily_price:RollingWindow = RollingWindow[float](period)
self._high_price:RollingWindow = RollingWindow[float](period)
def update_price(self, price:float, high:float) -> None:
self._daily_price.Add(price)
self._high_price.Add(high)
def is_ready(self) -> bool:
return self._daily_price.IsReady and self._high_price.IsReady
def get_week_highmomentum(self, period:int) -> float:
prices:np.ndarray = np.array([x for x in self._daily_price])
high:np.ndarray = np.array([x for x in self._high_price])
return np.log(prices[0]) - np.log(max(high[:period]))
