该策略投资于来自CoinMarketCap.com的加密货币,要求有可用的网络采用数据,剔除稳定币和零交易量币种。首先计算每周具有余额地址的增长率,然后根据该增长率对加密货币进行排序,分为五组。策略做多增长率最高的组,做空最低的组,按价值加权,每周再平衡。

策略概述

投资领域由来自CoinMarketCap.com的加密货币构成,这些加密货币有可用的网络采用数据。稳定币、价格为零、市值为零或在所有期间内交易量为零的币种被排除在外。网络采用指标来自Intotheblock。首先,计算具有余额地址的每周增长率,定义为最新的具有余额的总地址数与前一周的具有余额的总地址数的对数差异。接着,根据具有余额地址的增长率对加密货币进行排序,并将其分为五个分组。做多具有余额地址增长率最高的分组,做空增长率最低的分组。策略按价值加权,并每周进行再平衡。

策略合理性

地址数量可以衡量加密货币的采用情况。本文研究了两个衡量标准:总地址数和具有余额的总地址数,但具有余额的总地址数更符合常理,是更好的采用指标。研究表明,具有余额地址增长率最高的策略表现优于那些增长率最低的策略。总的来说,基于单个加密货币的基本网络数据构建交易策略是可行的。

论文来源

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

<摘要>

我们记录了超过4000种加密货币中的特征性异常。我们发现,加密货币的回报在大市值组中表现出动量效应,但在其他规模组中表现出反转效应。我们识别出强烈的加密货币价值和网络采用溢价,从中我们得出了两个新的因子,加入到了加密市场、规模和动量因子中。最终的C-5模型在定价加密资产横截面方面优于现有模型,这在大多数标准测试中得到了证明。我们还首次对大约700种加密货币进行了基于其经济功能的综合分类,并使用国际资产定价工具展示了跨代币类别的强分割性。


回测表现

年化收益率66.86%
波动率17.08%
Beta0.031
夏普比率3.91
索提诺比率-0.215
最大回撤N/A
胜率59%

完整python代码

from AlgorithmImports import *
from typing import List, Dict
#endregion

import data_tools

class CrosssectionalMomentumInLargeCryptos(QCAlgorithm):

    def Initialize(self) -> None:
        self.SetStartDate(2015, 1, 1)
        self.SetCash(1_000_000)
        
        self.period: int = 7         # need n of daily addresses
        self.quantile: int = 5
        self.portfolio_percentage: float = .1
        self.leverage: int = 10

        self.cryptos: List[str] = [
            "ANT",  # Aragon
            "BAT",  # Basic Attention Token
            "BTC",  # Bitcoin
            "DAI",  # Dai
            "DASH", # Dash
            "DOGE", # Dogecoin
            "EOS",  # EOS
            "ETH",  # Ethereum
            "FUN",  # FUN Token
            "LTC",  # Litecoin
            "MKR",  # Maker
            "OMG",  # OMG Network
            "SNT",  # Status
            "ZEC",  # Zcash
            "ZRX"   # Ox
        ]
        
        self.data: Dict[str, data_tools.SymbolData] = {}
        self.weight: Dict[str, float] = {}
        self.SetBrokerageModel(BrokerageName.Bitfinex)
        
        for crypto in self.cryptos:
            crypto_symbol: str = crypto + 'USD'
            data: Securities = self.AddCrypto(crypto_symbol, Resolution.Daily, Market.Bitfinex)
            data.SetLeverage(self.leverage)
            
            network_symbol: Symbol = self.AddData(data_tools.CryptoNetworkData, crypto, Resolution.Daily).Symbol
            address_symbol: Symbol = self.AddData(data_tools.CryptoAddressesData, crypto, Resolution.Daily).Symbol
            self.data[crypto_symbol] = data_tools.SymbolData(network_symbol, address_symbol, self.period)

        self.rebalance_flag: bool = False
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.Schedule.On(self.DateRules.WeekStart("BTCUSD"), self.TimeRules.At(9, 30), self.Rebalance)

    def OnData(self, data: Slice) -> None:
        curr_date: datetime.date = self.Time.date()
        
        # daily updating of crypto prices and market capitalization(CapMrktCurUSD)
        for crypto, symbol_obj in self.data.items():
            network_symbol: Symbol = symbol_obj.network_symbol
            address_symbol: Symbol = symbol_obj.address_symbol
            
            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, curr_date)
                
            if address_symbol in data and data[address_symbol]:
                # get value of total with balance addresses 
                addresses_count: int = data[address_symbol].Value
                if addresses_count != 0.:
                    self.data[crypto].update_addresses_count_values(addresses_count, curr_date)

        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():
            if ticker in data and data[ticker]:
                self.SetHoldings(ticker, w * self.portfolio_percentage)

        self.rebalance_flag = False
        self.weight.clear()

    def Rebalance(self) -> None:
        self.rebalance_flag = True

        curr_date: datetime.date = self.Time.date()
        crypto_data_last_update_date: Dict[Symbol, datetime.date] = data_tools.CryptoNetworkData.get_last_update_date()
        crypto_addresses_last_update_date: Dict[Symbol, datetime.date] = data_tools.CryptoAddressesData.get_last_update_date()
        
        address_growth: Dict[str, float] = {}
        
        for crypto, symbol_obj in self.data.items():
            network_symbol: Symbol = symbol_obj.network_symbol
            address_symbol: Symbol = symbol_obj.address_symbol

            if network_symbol not in crypto_data_last_update_date or address_symbol not in crypto_addresses_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] or \
                self.Securities[address_symbol].GetLastData() and self.Time.date() > crypto_addresses_last_update_date[address_symbol]:
                continue

            if symbol_obj.is_ready():
                # calculate address growth for current crypto
                if symbol_obj.cap_mrkt_cur_usd != 0:
                    address_growth[crypto] = symbol_obj.address_growth()
        
        # not enough cryptos for selection    
        if len(address_growth) < self.quantile:
            self.Liquidate()    
            return
    
        # perform selection
        quantile: int = int(len(address_growth) / self.quantile)
        sorted_by_perf: List[str] = [x[0] for x in sorted(address_growth.items(), key=lambda item: item[1])]
        
        # long highest quantile
        long: List[str] = sorted_by_perf[-quantile:]
        # short lowest 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