
The investment universe consists of an entire sample of cryptos (this number reached as high as 618 by the end of 2021).
(Information on the underlying cryptocurrencies was manually collected; suggested sources include Brave New Coin (BNC) website or Coinmarketcap.com. Historical data of uncertainty measures of Jurado, Ludvigson and Ng (2015), Economic Financial Uncertainty (EFU) index [for example, available through https://www.sydneyludvigson.com/macro-and-financial-uncertainty-indexes .)
ASSET CLASS: cryptos | REGION: Global | FREQUENCY:
Monthly | MARKET: cryptos | KEYWORD: Cryptocurrency
I. STRATEGY IN A NUTSHELL
The strategy sorts cryptocurrencies into terciles each month based on their sensitivity to financial uncertainty (EFU). It goes long on low-beta cryptos and short on high-beta cryptos, forming value-weighted portfolios rebalanced monthly, with a suggested allocation of 10% of trading capital.
II. ECONOMIC RATIONALE
Cryptocurrencies respond primarily to financial uncertainty rather than economic fundamentals. This creates a systematic, priced risk factor driven by institutional activity and cross-market spillovers, making uncertainty a key determinant in crypto asset pricing and portfolio strategies.
III. SOURCE PAPER
Financial Uncertainty and the Cross-Section of Cryptocurrency Returns [Click to Open PDF]
Gonul Colak, Joshua Della Vedova, Sean Foley, Sinh Thoi Mai, University of Sussex ; Hanken School of Economics, The University of San Diego – Knauss School of Business, Macquarie University, Hanken School of Economics
<Abstract>
Our study evaluates the return sensitivity of cryptocurrencies to various measures of uncertainty (uncertainty beta). We identify that crypto returns react primarily to financial uncertainty, which is the unforecastable component of multiple financial indicators. However, crypto returns are not sensitive to other forms of uncertainty such as macro, real, or policy uncertainty, as well as VIX, and inflation. The portfolio analysis yields a significant financial uncertainty premium of around 21% per month, which is driven by the outperformance (underperformance) of cryptocurrencies with a negative (positive) uncertainty beta. The portfolio returns are more potent in coins with speculative, rather than transactional, features such as proof-of-work, non-token, and mineable. Our findings suggest that large investors exhibit a willingness to pay higher premiums for cryptocurrencies with positive uncertainty betas, as these assets can be used as a hedging tool within a larger financial portfolio.


IV. BACKTEST PERFORMANCE
| Annualised Return | 20.7% |
| Volatility | 10.69% |
| Beta | 0.001 |
| Sharpe Ratio | 1.94 |
| Sortino Ratio | N/A |
| Maximum Drawdown | N/A |
| Win Rate | 50% |
V. FULL PYTHON CODE
from AlgorithmImports import *
from collections import deque
from dateutil.relativedelta import relativedelta
from pandas.core.frame import dataframe
import data_tools
import statsmodels.api as sm
# endregionx
class FinancialUncertaintyExplainsCryptocurrencyReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
self.period:int = 24
self.portion:float = 0.1
self.rebalance_hour:int = 0 # rebalance at 00:00
self.portfolio_percentage:float = .1
self.quantile:int = 3
# self.leverage:int = 2
self.cryptos:Dict[str, str] = {
"XLMUSD": "XLM", # Stellar
"XMRUSD": "XMR", # Monero
"XRPUSD": "XRP", # XRP
"ADAUSD": "ADA", # Cardano
"DOTUSD": "DOT", # Polkadot
"UNIUSD": "UNI", # Uniswap
"LINKUSD": "LINK", # Chainlink
"ANTUSD": "ANT", # Aragon
"BATUSD": "BAT", # Basic Attention Token
"BTCUSD": "BTC", # Bitcoin
"BTGUSD": "BTG", # Bitcoin Gold
"DASHUSD": "DASH", # Dash
"DGBUSD": "DGB", # Dogecoin
"ETCUSD": "ETC", # Ethereum Classic
"ETHUSD": "ETH", # Ethereum
# "FUNUSD": "FUN", # FUN Token
"LTCUSD": "LTC", # Litecoin
"MKRUSD": "MKR", # Maker
"NEOUSD": "NEO", # Neo
"PAXUSD": "PAX", # Paxful
"SNTUSD": "SNT", # Status
"TRXUSD": "TRX", # Tron
"XRPUSD": "XRP", # XRP
"XTZUSD": "XTZ", # Tezos
"XVGUSD": "XVG", # Verge
"ZECUSD": "ZEC", # Zcash
"ZRXUSD": "ZRX" # Ox
}
self.data:Dict[Symbol, SymbolData] = {}
# data subscription
for pair, ticker in self.cryptos.items():
data = self.AddCrypto(pair, Resolution.Hour, Market.Bitfinex)
data.SetFeeModel(data_tools.CustomFeeModel())
# data.SetLeverage(self.leverage)
network_data = self.AddData(data_tools.DailyCustomData, ticker, Resolution.Daily).Symbol
self.data[data.Symbol] = data_tools.SymbolData(network_data, self.period)
self.fin_uncertainty:Symbol = self.AddData(data_tools.FinancialUncertaintyIndex, 'FIN_UN', Resolution.Daily).Symbol
self.rebalance_flag:bool = False
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.current_month:int = -1
def OnData(self, data: Slice):
# monthly rebalance
if self.Time.month == self.current_month:
return
if not self.Time.hour == self.rebalance_hour:
return
self.current_month = self.Time.month
# update monthly price
for symbol, symbol_data in self.data.items():
network_symbol:Symbol = symbol_data._network_symbol
if symbol in data and data[symbol]:
self.data[symbol].update(data[symbol].Close, self.Time.date())
if network_symbol in data and data[network_symbol]:
cap_mrkt_cur_usd:float = data[network_symbol].Value
self.data[symbol].update_cap(cap_mrkt_cur_usd, self.Time.date())
network_data_last_update_date:Dict[Symbol, datetime.date] = data_tools.DailyCustomData.get_last_update_date()
financial_uncertainty_last_update_date:datetime.date = data_tools.FinancialUncertaintyIndex.get_last_update_date()
# custom data still comming in
if all([self.Securities[x].GetLastData() for x in list(self.data.keys())]) and any([self.Time.date() >= network_data_last_update_date[x] for x in network_data_last_update_date]) \
or self.Time.date() >= financial_uncertainty_last_update_date:
self.Liquidate()
return
history_financial_uncertainty:dataframe = self.History([self.fin_uncertainty], start=self.Time.date() - relativedelta(months=self.period), end=self.Time.date()).value.unstack(level=0)
crypto_returns_dict:Dict[Symbol, np.ndarray] = {symbol : symbol_data.get_returns() for symbol, symbol_data in self.data.items() if symbol_data.is_ready()}
crypto_returns:List[float] = list(zip(*[[i for i in x] for x in crypto_returns_dict.values()]))
if len(crypto_returns) == 0:
return
# run stock regression
y:np.ndarray = np.array(crypto_returns)
x:np.ndarray = history_financial_uncertainty[1:].values
model = self.multiple_linear_regression(x, y)
beta_values:np.ndarray = model.params[1]
# store betas
beta_by_symbol:Dict[Symbol, float] = {sym : beta_values[n] for n, sym in enumerate(list(crypto_returns_dict.keys()))}
long:List[Symbol] = []
short:List[Symbol] = []
# sort by beta and divide into quantiles
if len(beta_by_symbol) >= self.quantile:
sorted_gamma:List[Symbol] = sorted(beta_by_symbol.items(), key=lambda x:x[1])
quantile:int = int(len(beta_by_symbol) / self.quantile)
long = [symbol for symbol, beta in sorted_gamma][:quantile]
short = [symbol for symbol, beta in sorted_gamma][-quantile:]
# weight:Dict[Symbol, float] = {}
# total_cap_long:float = sum([symbol_data._cap_mrkt_cur_usd for symbol, symbol_data in self.data.items() if symbol in long])
# for symbol in long:
# weight[symbol] = self.data[symbol]._cap_mrkt_cur_usd / total_cap_long
# total_cap_short:float = sum([symbol_data._cap_mrkt_cur_usd for symbol, symbol_data in self.data.items() if symbol in short])
# for symbol in short:
# weight[symbol] = -self.data[symbol]._cap_mrkt_cur_usd / total_cap_short
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
# trade execution
# for symbol, w in weight.items():
# if symbol in data and data[symbol]:
# self.SetHoldings(symbol, self.portfolio_percentage * w)
for symbol in long:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, 1/len(long) * self.portfolio_percentage)
for symbol in short:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, -1/len(short) * self.portfolio_percentage)
def multiple_linear_regression(self, x:np.ndarray, y:np.ndarray):
x = sm.add_constant(x, has_constant='add')
result = sm.OLS(endog=y, exog=x).fit()
return result