Price-based Value in Cryptocurrencies
Log in to collectAcademic paper
Value Premium, Network Adoption, and Factor Pricing of Crypto Assets
Lin William Cong; George Andrew Karolyi; Ke Tang; Weiyi Zhao
- Cornell University
- ?Cornell University - Samuel Curtis Johnson Graduate School of Management
- Tsinghua University
- ?Institute of Economics, School of Social Sciences, Tsinghua University
Strategy in a nutshell
The investment universe consists of cryptocurrencies from CoinMarketCap.com. Stablecoins, and coins with zero price, market capitalization, or trading volume across all periods, are excluded. The value measure is defined as the negative of the past 52-week return. Cryptocurrencies are sorted into decile portfolios based on this value measure. The strategy goes long the top decile (most undervalued) and short the bottom decile (most overvalued). Portfolios are rebalanced weekly and weighted by market value.
Economic rationale
Traditional equity-based value metrics, such as cash flows or book-to-market ratios, are not applicable to cryptocurrencies. Instead, relative value is estimated using the definition from Asness, Moskowitz, and Pedersen (2013), where value in hard-to-value asset classes is the negative of long-term returns. This approach identifies relatively cheap and expensive assets, under the assumption that undervalued cryptocurrencies will outperform overvalued ones over time.
Backtest performance
Full Python code
from AlgorithmImports import *
import data_tools
from typing import List, Dict
#endregion
class PricebasedValueinCryptocurrencies(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
self.period: int = 52 * 7
self.quantiles: int = 5
self.percentage_traded: int = .2
self.leverage: int = 10
self.cryptos: Dict[str, str] = {
"ANTUSD": "ANT", # Aragon
"BATUSD": "BAT", # Basic Attention Token
"BTCUSD": "BTC", # Bitcoin
"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.SetBrokerageModel(BrokerageName.Bitfinex)
for crypto, ticker in self.cryptos.items():
data: Securities = self.AddCrypto(crypto, Resolution.Daily, Market.Bitfinex)
data.SetFeeModel(data_tools.CustomFeeModel())
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:
crypto_data_last_update_date: Dict[Symbol, datetime.date] = data_tools.CryptoNetworkData.get_last_update_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
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
self.data[crypto].update_cap(cap_mrkt_cur_usd)
if not self.rebalance_flag:
return
performance: Dict[str, float] = {}
for crypto, symbol_obj in self.data.items():
if crypto in data and data[crypto]:
# crypto doesn't have enough data
if self.Securities[symbol_obj.network_symbol].GetLastData() and self.Time.date() > crypto_data_last_update_date[symbol_obj.network_symbol]:
self.Liquidate()
continue
if not symbol_obj.is_ready():
continue
# calculate performance for current crypto
performance[crypto] = symbol_obj.performance()
# not enough cryptos for selection
if len(performance) < self.quantiles:
return
self.rebalance_flag = False
# perform selection
quantile: int = int(len(performance) / self.quantiles)
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]
# trade execution
for i, portfolio in enumerate([long, short]):
for symbol in portfolio:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, round(((-1) ** i) / len(portfolio) * self.percentage_traded, 2))
def Rebalance(self) -> None:
self.rebalance_flag = True