该策略投资于618种加密货币,每月根据不确定性贝塔值将其排序为三分位组合。低不确定性贝塔值的加密货币(LOW_UNC)做多,高不确定性贝塔值的加密货币(HIGH_UNC)做空。投资组合按价值加权,每月重构,建议使用不超过10%的交易资本。

策略概述

投资宇宙包括所有加密货币样本(到2021年底数量达到618)。 (基础加密货币的信息是手动收集的;建议的来源包括Brave New Coin(BNC)网站或Coinmarketcap.com。Jurado、Ludvigson和Ng(2015)的不确定性衡量的历史数据、经济金融不确定性(EFU)指数可以通过https://www.sydneyludvigson.com/macro-and-financial-uncertainty-indexes 获取。)

排序规则:每个月,根据个别加密货币的不确定性贝塔值排序形成三分位组合,其中第1三分位(LOW_UNC [在此为LOW_EFU])包含在上个月不确定性贝塔值最低的加密货币,第3三分位(HIGH_UNC [在此为HIGH_EFU])包含不确定性贝塔值最高的加密货币。 交易规则(反向操作):根据EFU(金融不确定性指数),做多不确定性贝塔值低的加密货币,同时做空不确定性贝塔值高的加密货币。

投资组合按价值加权,并每月重构。(建议使用不超过10%的交易资本。)

策略合理性

最近专家的研究报告显示,某些风险因素已被系统地定价在广泛的加密货币中。这些分析发现为加密货币市场中的风险因素提供了新的见解。研究结果表明,加密货币是一类特殊的金融资产,因为它们只对金融不确定性作出反应,而对任何经济基本面没有反应。因此,这些新的交易工具似乎只受其他市场的金融溢出效应影响,而不受任何宏观经济冲击的影响。交易规模分析表明,传统金融机构或加密货币市场中的“大鳄”(whales)是使用这种复杂跨市场对冲的主要参与者,将加密货币作为其大型金融资产组合的一部分。这一发现对文献来说是新颖的,因为它建立了加密货币市场与传统金融市场之间的清晰联系,可以被视为溢出效应的通道。金融不确定性是加密货币横截面中定价的另一个风险因素,因此它应该成为任何“n因子”资产定价模型中的一部分,用于处理此类加密资产。未来的工作可以专注于理解金融不确定性风险因素在已知的加密货币风险因素中的相对重要性,以及我们在加密货币中的不确定性风险因素是否对机构投资者参与加密市场有任何影响。

论文来源

Financial Uncertainty and the Cross-Section of Cryptocurrency Returns [点击浏览原文]

<摘要>

我们的研究评估了加密货币对多种不确定性衡量指标的回报敏感性(不确定性贝塔)。我们发现加密货币回报主要对金融不确定性作出反应,而金融不确定性是多个金融指标中不可预测的成分。然而,加密货币回报对其他形式的不确定性如宏观、实际或政策不确定性、VIX以及通货膨胀并不敏感。投资组合分析显示了大约每月21%的显著金融不确定性溢价,这是由不确定性贝塔为负(表现出色)或正(表现不佳)的加密货币的表现驱动的。投资组合回报在具有投机性特征而非交易性特征的代币(如工作量证明、非代币、可挖矿)中更为强劲。我们的研究发现表明,大型投资者愿意为具有正不确定性贝塔的加密货币支付更高的溢价,因为这些资产可以作为更大金融投资组合中的对冲工具。

回测表现

年化收益率20.7%
波动率10.69%
Beta0.001
夏普比率1.94
索提诺比率N/A
最大回撤N/A
胜率50%

完整python代码

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
# endregion

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