Computing Power Factor in Cryptocurrencies
Log in to collectAcademic paper
Do Fundamentals Drive Cryptocurrency Prices?
Siddharth M. Bhambhwani; Stefanos Delikouras; George M. Korniotis
- Washington College
- University of Miami
- ?University of Miami - Department of Finance
- ?Miami Business School
- ?University of Miami - Behavioral Decision Making Cluster
Strategy in a nutshell
Trades Bitcoin, Ethereum, Litecoin, Monero, and Dash using weekly changes in computing power as a fundamental factor. Builds factor-mimicking portfolios via OLS regression on log computing power growth, aggregated by prior market capitalization, and rebalanced weekly to align with cryptocurrency network dynamics.
Economic rationale
Computing power underpins blockchain security and transaction efficiency. Cointegration between price and computing power drives mean-reverting, pro-cyclical returns, providing a fundamental risk premium that outperforms sentiment- or momentum-based strategies.
Backtest performance
Annualised return15.6%
Volatility10.82%
Beta0.071
Sharpe ratio1.44
Sortino ratio0.74
Win rate56%
Full Python code
from AlgorithmImports import *
from typing import Dict, List
class ComputingPowerFactorinCryptocurrencies(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2015, 1, 1)
self.SetCash(1_000_000)
self.period: int = 14
self.percentage_traded: float = 0.1
self.segment: int = 7
self.crypto_symbols: Dict[str, str] = {
'BTC' : 'BTCUSD',
'ETH' : 'ETHUSD',
'LTC' : 'LTCUSD',
}
# Daily hash rate.
self.hash_rate: Dict[Symbol, RollingWindow] = {}
for crypto, ticker in self.crypto_symbols.items():
data = self.AddCrypto(ticker, Resolution.Daily, Market.Bitfinex)
self.AddData(CryptoNetworkData, crypto, Resolution.Daily)
# Crypto data.
self.hash_rate[crypto] = RollingWindow[float](self.period)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.settings.daily_precise_end_time = False
self.SetWarmUp(self.period, Resolution.Daily)
def OnData(self, data: Slice) -> None:
crypto_data_last_update_date: Dict[Symbol, datetime.date] = CryptoNetworkData.get_last_update_date()
# Store daily data.
for crypto, ticker in self.crypto_symbols.items():
if crypto in data and data[crypto]:
hash_rate: float = data[crypto].Value
if hash_rate != 0:
self.hash_rate[crypto].Add(hash_rate)
if self.Time.date().weekday() != 0 and self.Time.hour == 0:
return
growth: Dict[str, float] = {}
for crypto, ticker in self.crypto_symbols.items():
if self.Securities[crypto].GetLastData() and self.Time.date() > crypto_data_last_update_date[crypto]:
self.liquidate(ticker)
continue
if self.hash_rate[crypto].IsReady:
if all(x == 0 for x in list(self.hash_rate[crypto])):
continue
week_t1_hash_rates: List[float] = [np.log(x) for x in self.hash_rate[crypto]][:self.segment]
week_t2_hash_rates: List[float] = [np.log(x) for x in self.hash_rate[crypto]][self.segment:]
growth[ticker] = np.mean(week_t1_hash_rates) - np.mean(week_t2_hash_rates)
total_growth_abs: float = sum(abs(x[1]) for x in growth.items())
weight: Dict[str, float] = {x[0] : (x[1] / total_growth_abs) for x in growth.items()}
# trade
invested: List[str] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in weight:
self.Liquidate(symbol)
portfolio:List[PortfolioTarget] = [PortfolioTarget(ticker, w * self.percentage_traded) for ticker, w in weight.items() if ticker in data and data[ticker] and w > 0]
self.SetHoldings(portfolio)
# Crypto network data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
# Data source: https://coinmetrics.io/community-network-data/
class CryptoNetworkData(PythonData):
_last_update_date: Dict[Symbol, datetime.date] = {}
@staticmethod
def get_last_update_date() -> Dict[Symbol, datetime.date]:
return CryptoNetworkData._last_update_date
def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLiveMode: bool) -> SubscriptionDataSource:
return SubscriptionDataSource(f"data.quantpedia.com/backtesting_data/crypto/{config.Symbol.Value}_network_data.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
# File exmaple:
# date,AdrActCnt,AdrBal1in100KCnt,AdrBal1in100MCnt,AdrBal1in10BCnt,AdrBal1in10KCnt,AdrBal1in10MCnt,AdrBal1in1BCnt,AdrBal1in1KCnt,AdrBal1in1MCnt,AdrBalCnt,AdrBalNtv0.001Cnt,AdrBalNtv0.01Cnt,AdrBalNtv0.1Cnt,AdrBalNtv100Cnt,AdrBalNtv100KCnt,AdrBalNtv10Cnt,AdrBalNtv10KCnt,AdrBalNtv1Cnt,AdrBalNtv1KCnt,AdrBalNtv1MCnt,AdrBalUSD100Cnt,AdrBalUSD100KCnt,AdrBalUSD10Cnt,AdrBalUSD10KCnt,AdrBalUSD10MCnt,AdrBalUSD1Cnt,AdrBalUSD1KCnt,AdrBalUSD1MCnt,AssetEODCompletionTime,BlkCnt,BlkSizeMeanByte,BlkWghtMean,BlkWghtTot,CapAct1yrUSD,CapMVRVCur,CapMVRVFF,CapMrktCurUSD,CapMrktFFUSD,CapRealUSD,DiffLast,DiffMean,FeeByteMeanNtv,FeeMeanNtv,FeeMeanUSD,FeeMedNtv,FeeMedUSD,FeeTotNtv,FeeTotUSD,FlowInExNtv,FlowInExUSD,FlowOutExNtv,FlowOutExUSD,FlowTfrFromExCnt,HashRate,HashRate30d,IssContNtv,IssContPctAnn,IssContPctDay,IssContUSD,IssTotNtv,IssTotUSD,NDF,NVTAdj,NVTAdj90,NVTAdjFF,NVTAdjFF90,PriceBTC,PriceUSD,ROI1yr,ROI30d,RevAllTimeUSD,RevHashNtv,RevHashRateNtv,RevHashRateUSD,RevHashUSD,RevNtv,RevUSD,SER,SplyAct10yr,SplyAct180d,SplyAct1d,SplyAct1yr,SplyAct2yr,SplyAct30d,SplyAct3yr,SplyAct4yr,SplyAct5yr,SplyAct7d,SplyAct90d,SplyActEver,SplyActPct1yr,SplyAdrBal1in100K,SplyAdrBal1in100M,SplyAdrBal1in10B,SplyAdrBal1in10K,SplyAdrBal1in10M,SplyAdrBal1in1B,SplyAdrBal1in1K,SplyAdrBal1in1M,SplyAdrBalNtv0.001,SplyAdrBalNtv0.01,SplyAdrBalNtv0.1,SplyAdrBalNtv1,SplyAdrBalNtv10,SplyAdrBalNtv100,SplyAdrBalNtv100K,SplyAdrBalNtv10K,SplyAdrBalNtv1K,SplyAdrBalNtv1M,SplyAdrBalUSD1,SplyAdrBalUSD10,SplyAdrBalUSD100,SplyAdrBalUSD100K,SplyAdrBalUSD10K,SplyAdrBalUSD10M,SplyAdrBalUSD1K,SplyAdrBalUSD1M,SplyAdrTop100,SplyAdrTop10Pct,SplyAdrTop1Pct,SplyCur,SplyExpFut10yr,SplyFF,SplyMiner0HopAllNtv,SplyMiner0HopAllUSD,SplyMiner1HopAllNtv,SplyMiner1HopAllUSD,TxCnt,TxCntSec,TxTfrCnt,TxTfrValAdjNtv,TxTfrValAdjUSD,TxTfrValMeanNtv,TxTfrValMeanUSD,TxTfrValMedNtv,TxTfrValMedUSD,VelCur1yr,VtyDayRet180d,VtyDayRet30d
# 2009-01-09,19,19,19,19,19,19,19,19,19,19,19,19,19,0,0,19,0,19,0,0,0,0,0,0,0,0,0,0,1614334886,19,215,860,16340,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,9.44495122962963E-7,0,950,36500,100,0,950,0,1,0,0,0,0,1,0,0,0,0,11641.53218269,1005828380.584716757433,0,0,950,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,950,950,950,950,950,950,950,950,950,950,950,950,950,0,0,0,0,0,0,0,0,0,0,0,0,0,950,50,50,950,17070250,950,1000,0,1000,0,0,0,0,0,0,0,0,0,0,0,0,0
def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLiveMode: bool) -> BaseData:
data: CryptoNetworkData = CryptoNetworkData()
data.Symbol = config.Symbol
try:
cols:str = ['HashRate']
if not line[0].isdigit():
header_split = line.split(',')
self.col_index = [header_split.index(x) for x in cols]
return None
split = line.split(',')
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
for i, col in enumerate(cols):
data[col] = float(split[self.col_index[i]])
data.Value = float(split[self.col_index[0]])
if config.Symbol.Value not in CryptoNetworkData._last_update_date:
CryptoNetworkData._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()
if data.Time.date() > CryptoNetworkData._last_update_date[config.Symbol.Value]:
CryptoNetworkData._last_update_date[config.Symbol.Value] = data.Time.date()
except:
return None
return data