“投资范围包括来自CoinMarketCap的加密货币,排除稳定币、价格和市场资本化为零的币种。识别市值大于100万美元的大市值样本后,根据过去两周的回报将加密货币排序为十分位组合。做多表现最好的十分位,做空表现最差的十分位,策略按市值加权并每周重新平衡。”
资产类别:加密货币 | 地区:全球 | 频率:每周 | 市场:加密货币 | 关键词:动量,加密货币
策略概述
投资范围包括来自CoinMarketCap.com的加密货币。排除稳定币、价格为零、市场资本化为零或交易量为零的币种。市场资本和价格数据来自CoinMarketCap。第一步,识别市值大于100万美元的大市值样本。其次,在大市值样本中,根据过去两周的回报(动量)将加密货币排序为十分位组合。做多表现最好的十分位,做空表现最差的十分位。该策略按市值加权并每周重新平衡。
策略合理性
该效应通过投资组合排序方法被识别,并被发现具有高度统计显著性,但论文没有提供具体的经济/行为原因。然而,动量的传统解释是羊群效应以及过度或不足反应。此外,动量策略往往跟随趋势,鉴于回溯期较短,似乎可以在如此动态的市场(如加密货币市场)中识别趋势。
论文来源
Value Premium, Network Adoption, and Factor Pricing of Crypto Assets [点击浏览原文]
- Lin William Cong,George Andrew Karolyi,Ke Tang,Weiyi Zhao,康奈尔大学 – Samuel Curtis Johnson研究生院,国家经济研究局(NBER),清华大学,清华大学经济学院
<摘要>
我们记录了4000多种加密货币的特征性异常。我们发现加密货币在大市值组别中表现出动量效应,而在其他市值组别中表现出反转效应。我们识别出强大的加密货币价值和网络采用溢价,并从中得出两个新因子,添加到加密货币市场、市值和动量因子中。结果的C-5模型在定价加密资产的横截面时优于现有模型,正如大多数标准测试中所揭示的那样。我们还提供了对约700种加密货币的首次全面分类,基于其经济功能,并使用国际资产定价工具展示了不同代币类别之间的强大分割。


回测表现
| 年化收益率 | 144.06% |
| 波动率 | 22.13% |
| Beta | -0.042 |
| 夏普比率 | 6.5 |
| 索提诺比率 | 0.857 |
| 最大回撤 | N/A |
| 胜率 | 36% |
完整python代码
from AlgorithmImports import *
from typing import List, Dict
class CrosssectionalMomentumInLargeCryptos(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2015, 1, 1)
self.SetCash(1_000_000)
self.period: int = 14 # need n of daily prices
self.quantile: int = 3
self.portfolio_percentage: float = .1
self.leverage: int = 10
self.cryptos: Dict[str, str] = {
"ANTUSD": "ANT", # Aragon
"BATUSD": "BAT", # Basic Attention Token
"BTCUSD": "BTC", # Bitcoin
"BTGUSD": "BTG", # Bitcoin Gold
"DAIUSD": "DAI", # Dai
"DGBUSD": "DGB", # Dogecoin
"EOSUSD": "EOS", # EOS
"ETCUSD": "ETC", # Ethereum Classic
"ETHUSD": "ETH", # Ethereum
"FUNUSD": "FUN", # FUN Token
"LTCUSD": "LTC", # Litecoin
"MKRUSD": "MKR", # Maker
"NEOUSD": "NEO", # Neo
"OMGUSD": "OMG", # OMG Network
"SNTUSD": "SNT", # Status
"TRXUSD": "TRX", # Tron
"XLMUSD": "XLM", # Stellar
"XMRUSD": "XMR", # Monero
"XRPUSD": "XRP", # XRP
"XTZUSD": "XTZ", # Tezos
"XVGUSD": "XVG", # Verge
"ZECUSD": "ZEC", # Zcash
"ZRXUSD": "ZRX" # Ox
}
self.data: Dict[str, data_tools.SymbolData] = {}
self.weight: Dict[str, float] = {}
self.SetBrokerageModel(BrokerageName.Bitfinex)
for crypto, ticker in self.cryptos.items():
# GDAX is coinmarket, but it doesn't support this many cryptos, so we choose Bitfinex
data: Securities = self.AddCrypto(crypto, Resolution.Daily, Market.Bitfinex)
data.SetLeverage(self.leverage)
network_symbol: Symbol = self.AddData(data_tools.CryptoNetworkData, ticker, Resolution.Daily).Symbol
self.data[crypto] = data_tools.SymbolData(network_symbol, self.period)
self.rebalance_flag: bool = False
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.WeekStart("BTCUSD"), self.TimeRules.At(0,0), self.Rebalance)
def OnData(self, data: Slice) -> None:
# daily updating of crypto prices and market capitalization(CapMrktCurUSD)
for crypto, symbol_obj in self.data.items():
network_symbol: Symbol = symbol_obj.network_symbol
if crypto in data.Bars and data[crypto]:
# get crypto price
price: float = data.Bars[crypto].Value
self.data[crypto].update(price)
if network_symbol in data and data[network_symbol]:
# get market capitalization
cap_mrkt_cur_usd: float = data[network_symbol].Value
if cap_mrkt_cur_usd != 0:
self.data[crypto].update_cap(cap_mrkt_cur_usd)
if not self.rebalance_flag:
return
# trade execution
invested: List[str] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for ticker in invested:
if ticker not in self.weight:
self.Liquidate(ticker)
for ticker, w in self.weight.items():
self.SetHoldings(ticker, w)
self.rebalance_flag = False
self.weight.clear()
def Rebalance(self) -> None:
self.rebalance_flag = True
crypto_data_last_update_date: Dict[Symbol, datetime.date] = data_tools.CryptoNetworkData.get_last_update_date()
performance: Dict[str, float] = {}
for crypto, symbol_obj in self.data.items():
network_symbol: Symbol = symbol_obj.network_symbol
if network_symbol not in crypto_data_last_update_date:
continue
# crypto doesn't have enough data
if self.Securities[network_symbol].GetLastData() and self.Time.date() > crypto_data_last_update_date[network_symbol]:
self.Liquidate()
return
if symbol_obj.is_ready():
# calculate performance for current crypto
performance[crypto] = symbol_obj.performance()
# not enough cryptos for selection
if len(performance) < self.quantile:
self.Liquidate()
return
# perform selection
quantile: int = int(len(performance) / self.quantile)
sorted_by_perf: List[str] = [x[0] for x in sorted(performance.items(), key=lambda item: item[1])]
# long top quantile
long: List[str] = sorted_by_perf[-quantile:]
# short bottom quantile
short: List[str] = sorted_by_perf[:quantile]
# value weighting
for i, portfolio in enumerate([long, short]):
mc_sum:float = sum(list(map(lambda ticker: self.data[ticker].cap_mrkt_cur_usd, portfolio)))
for ticker in portfolio:
self.weight[ticker] = ((-1) ** i) * (self.data[ticker].cap_mrkt_cur_usd / mc_sum)
