该策略涵盖市值超过100万美元的加密货币,数据来自coinmarketcap.com。加密货币的显著性测度(ST)根据与市场平均回报的距离、显著性权重以及回报的协方差计算。将加密货币按ST参数分为五个分位组,策略为价值加权,做多ST最低分位的加密货币,做空ST最高分位的加密货币。投资组合定期调整以反映最新的显著性排序。

策略概述

投资范围包括所有市值超过100万美元的加密货币,数据来自coinmarketcap.com网站。策略根据每种加密货币的显著性测度(Salience Measure, ST)参数进行五分位排序。

要计算某种加密货币的ST参数,首先需要计算该加密货币某一天回报的显著性,这是其与当天市场上所有加密货币平均回报的距离(根据第14页的方程(1)计算)。完成所有加密货币的计算后,按显著性降序排序,并使用该排名在第15页的方程(2)中计算某天该加密货币的显著性权重。最后,使用这些值计算ST,即估算期内显著性权重与加密货币回报的协方差(方程(3))。策略为价值加权,按ST将投资组合分为五分位,做多最低分位的加密货币,做空最高分位的加密货币。

策略合理性

加密货币市场的行为与其他市场存在显著差异,主要由散户投资者主导,导致其他市场上存在的某些效应在这里被放大。显著性理论主要在不确定性高的情况下起作用,而加密货币市场上的许多投资者并非专业人士,因此该理论在解释加密货币市场投资者行为方面具有很强的解释力。由于缺乏足够的客观信息,许多投资者可能会受到社区趋势、广告和欺诈行为的影响,某些加密货币的上涨会变得突出(显著)。当市场回到均衡状态时,这会导致价格反转,而这一策略正是利用这一效应。

论文来源

Salience Theory and Cryptocurrency Returns [点击浏览原文]

<摘要>

我们记录到加密货币的横截面回报可以预测性地遵循风险下选择的显著性理论。投资者会对显著性结果(即在备选项中表现突出的结果)赋予过高的权重,导致显著上涨的加密货币被高估(显著下跌的加密货币被低估),从而在随后一段时间内产生负(正)的预期回报。加密货币市场中的显著性效应比股票市场中的强20倍以上。该效应不同于加密货币市场中已记录的其他回报异常现象,是解释加密货币市场中其他横截面策略回报的一个强有力的风险因子候选者。

回测表现

年化收益率280.1%
波动率45.3%
Beta-0.006
夏普比率6.18
索提诺比率-0.073
最大回撤N/A
胜率47%

完整python代码

from AlgorithmImports import *
from typing import List, Dict
class SalienceTheoryandCryptocurrencyReturns(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        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
            "DAIUSD": "DAI",    # Dai
            "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.symbol_data: Dict[str, data_tools.SymbolData] = {}
        
        self.ranking_period: int = 21
        self.momentum_period: int = self.ranking_period * 2        # need n of daily prices
        self.quantile: int = 5
        self.portfolio_percentage: float = .2
        self.leverage: int = 3
        self.delta: float = .7
        self.theta: float = .1
        self.SetBrokerageModel(BrokerageName.Bitfinex)
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        
        for ticker, crypto in self.cryptos.items():
            data: Crypto = self.AddCrypto(ticker, Resolution.Daily, Market.Bitfinex)
            data.SetFeeModel(data_tools.CustomFeeModel())
            data.SetLeverage(self.leverage)
            
            network_symbol: Symbol = self.AddData(data_tools.CryptoNetworkData, crypto, Resolution.Daily).Symbol
            self.symbol_data[ticker] = data_tools.SymbolData(network_symbol, self.momentum_period)
      
        self.recent_month: int = -1
        
    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.symbol_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.symbol_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.symbol_data[crypto].update_cap(cap_mrkt_cur_usd)
        # monthly rebalance
        if self.recent_month == self.Time.month:
            return
        prepared_symbols: List[Tuple[str, data_tools.CryptoNetworkData]] = [(crypto, symbol_obj) for crypto, symbol_obj in self.symbol_data.items() if symbol_obj.is_ready() and \
                                                                     self.Securities[symbol_obj._network_symbol].GetLastData() and self.Time.date() < crypto_data_last_update_date[symbol_obj._network_symbol] and \
                                                                     crypto in data and data[crypto]]
        ST: Dict[str, float] = {}
        if len(prepared_symbols) != 0:
            self.recent_month = self.Time.month 
            daily_returns: np.ndarray = np.array([symbol_obj.daily_returns() for _, symbol_obj in prepared_symbols])
            # salience
            mean_returns: np.ndarray = np.mean(daily_returns, axis=0)
            salience: np.ndarray = abs(daily_returns - mean_returns) / (abs(daily_returns) + abs(mean_returns) + self.theta)
            
            # salience ranking
            salience_order: np.ndarray = salience.argsort(axis=0)
            salience_rank: np.ndarray = salience_order.argsort(axis=0)
            pi: float = 1. / float(self.ranking_period)
            salience_rank_sum: np.ndarray = salience_rank.sum(axis=0) * pi
            
            # decision weights
            weights: np.ndarray = salience_rank / salience_rank_sum
            # salience effect
            ST = { prepared_symbols[i] : np.cov(stack)[0][1] for i, stack in enumerate(np.stack((weights, daily_returns), axis=1)) }
        long: List[str] = []
        short: List[str] = []
        
        if len(ST) >= self.quantile:
            # sort the portfolio into quintiles, long the lowest quintile, short the highest
            sorted_by_ST: List[Tuple[str, float]] = sorted(ST.items(), key=lambda x:x[1], reverse=True)
            quantile: int = int(len(sorted_by_ST) / self.quantile)
            long = [x[0] for x in sorted_by_ST[-quantile:]]
            short = [x[0] for x in sorted_by_ST[:quantile]]
        
        # value weighting
        weight: Dict[str, float] = {}
        for i, portfolio in enumerate([long, short]):
            mc_sum:float = sum(list(map(lambda x: x[1]._cap_mrkt_cur_usd, portfolio)))
            for ticker, symbol_obj in portfolio:
                weight[ticker] = ((-1)**i) * symbol_obj._cap_mrkt_cur_usd / mc_sum
        
        # trade execution    
        portfolio:List[PortfolioTarget] = [PortfolioTarget(symbol, self.portfolio_percentage * w) for symbol, w in weight.items() if symbol in data and data[symbol]]
        self.SetHoldings(portfolio, True)

Leave a Reply

Discover more from Quant Buffet

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

Continue reading