投资范围包括来自CoinMarketCap的加密货币,排除稳定币、价格和市场资本化为零的币种。识别市值大于100万美元的大市值样本后,根据过去两周的回报将加密货币排序为十分位组合。做多表现最好的十分位,做空表现最差的十分位,策略按市值加权并每周重新平衡。

策略概述

投资范围包括来自CoinMarketCap.com的加密货币。排除稳定币、价格为零、市场资本化为零或交易量为零的币种。市场资本和价格数据来自CoinMarketCap。第一步,识别市值大于100万美元的大市值样本。其次,在大市值样本中,根据过去两周的回报(动量)将加密货币排序为十分位组合。做多表现最好的十分位,做空表现最差的十分位。该策略按市值加权并每周重新平衡。

策略合理性

该效应通过投资组合排序方法被识别,并被发现具有高度统计显著性,但论文没有提供具体的经济/行为原因。然而,动量的传统解释是羊群效应以及过度或不足反应。此外,动量策略往往跟随趋势,鉴于回溯期较短,似乎可以在如此动态的市场(如加密货币市场)中识别趋势。

论文来源

Value Premium, Network Adoption, and Factor Pricing of Crypto Assets [点击浏览原文]

<摘要>

我们记录了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)

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading