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 .)

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 Return20.7%
Volatility10.69%
Beta0.001
Sharpe Ratio1.94
Sortino RatioN/A
Maximum DrawdownN/A
Win Rate50%

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

Leave a Reply

Discover more from Quant Buffet

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

Continue reading