投资宇宙包括138种加密货币。每小时根据前24小时的滞后1小时回报对其排名,做多回报最低的两种,加空回报最高的两种。该策略假设为等权重,并每小时重新平衡。

策略概述

投资宇宙由138种加密货币组成。每小时,投资者根据前24小时的滞后1小时回报对加密货币进行排名,每小时做多回报最低的两种加密货币,并做空回报最高的两种加密货币。该策略假设为等权重,并每小时重新平衡。

策略合理性

此前关于加密货币动量的研究结果不一致,可能是由于样本时间较短以及此类新资产存在的交易工具数量有限。作者提供了一个新的、独特的视角,并创造了“纯动量”这一新术语,他们将其描述为不受基本面影响的对回报变化的纯反应的衡量标准,简单来说就是过去的价格。由于计算回报的参考点不是固定的,而是随着回报窗口的不断移动而持续变化,投资者的感知也随之变化。提出的可能解释包括投资者对风险的反应、他们的偏见和信念,以及对信息的反应不足。

论文来源

Pure Momentum in Cryptocurrency Markets [点击浏览原文]

<摘要>

动量现象是资产定价中最广泛、持久且令人困惑的现象之一。对于动量的普遍解释是,投资者对新信息反应不足,导致资产价格随时间漂移。我们利用了加密货币市场的一个独特特征:它们每天24小时都开放,并报告过去24小时的回报。因此,一天的回报基于滞后信息的消除而出现可预测的波动。我们表明,投资者对与任何新信息发布或资产基本面变化无关的回报变化作出正面反应。我们将这种行为异常称为“纯动量”。

回测表现

年化收益率53.8%
波动率15.28%
Beta0.301
夏普比率3.52
索提诺比率0.949
最大回撤N/A
胜率19%

完整python代码

from AlgorithmImports import *
from data_tools import CustomFeeModel, SymbolData
# endregion

class HourReversalInCryptocurrencies(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2016, 1, 1)
        self.SetCash(100000)
        
        self.cryptos:list[str] = [
            #"ANTUSD", # Aragon
            #"BATUSD", # Basic Attention Token
            "BTCUSD", # Bitcoin
            #"DAIUSD", # Dai
            #"DGBUSD", # Dogecoin
            #"EOSUSD", # EOS
            #"ETCUSD", # Ethereum Classic
            "ETHUSD", # Ethereum
            #"FUNUSD", # FUN Token
            "LTCUSD", # Litecoin
            #"MKRUSD", # Maker
            #"NEOUSD", # Neo
            #"OMGUSD", # OMG Network
            #"SNTUSD", # Status
            #"TRXUSD", # Tron
            #"XLMUSD", # Stellar
            #"XMRUSD", # Monero
            "XRPUSD", # XRP
            #"XTZUSD", # Tezos
            #"XVGUSD", # Verge
            #"ZECUSD", # Zcash
            #"ZRXUSD"  # Ox
        ]
        
        self.data:dict[Symbol, SymbolData] = {}
        
        self.lag_period:int = 24 + 1
        self.max_missing_hours:int = 0

        self.leg_count:int = 2

        self.leverage:int = 5
        self.portfolio_percentage:float = 0.1
        
        self.SetBrokerageModel(BrokerageName.Bitfinex)
        
        for crypto in self.cryptos:
            # GDAX is coinmarket, but it doesn't support this many cryptos, so we choose Bitfinex
            data = self.AddCrypto(crypto, Resolution.Hour, Market.Bitfinex)
            data.SetFeeModel(CustomFeeModel())
            data.SetLeverage(self.leverage)
            
            self.data[data.Symbol] = SymbolData(self.lag_period)
        
    def OnData(self, data):
        curr_time:datetime = self.Time

        performance:dict[Symbol, float] = {}
        
        for symbol, symbol_data in self.data.items():
            if symbol in data and data[symbol]:
                price:float = data.Bars[symbol].Value

                if not symbol_data.data_still_coming(curr_time, self.max_missing_hours):
                    symbol_data.reset_data()

                if symbol_data.prev_price_ready():
                    symbol_data.calculate_perf(price)

                    if symbol_data.performances_ready():
                        performance[symbol] = symbol_data.get_first_perf()

                symbol_data.update_price(curr_time, price)

        if len(performance) < (2 * self.leg_count):
            self.Liquidate()
            return

        sorted_by_perf:list[Symbol] = [x[0] for x in sorted(performance.items(), key=lambda item: item[1])]
        long_leg:list[Symbol] = sorted_by_perf[:self.leg_count]
        short_leg:list[Symbol] = sorted_by_perf[-self.leg_count:]

        # trade execution
        invested:list[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in long_leg + short_leg:
                self.Liquidate(symbol)

        for symbol in long_leg:
            q:float = self.CalculateOrderQuantity(symbol, (1 / self.leg_count) * self.portfolio_percentage)
            if q > self.Securities[symbol].SymbolProperties.MinimumOrderSize:
                self.MarketOrder(symbol, q)

        for symbol in short_leg:
            q:float = self.CalculateOrderQuantity(symbol, (1 / self.leg_count) * self.portfolio_percentage)
            if q > self.Securities[symbol].SymbolProperties.MinimumOrderSize:
                self.MarketOrder(symbol, -q)

Leave a Reply

Discover more from Quant Buffet

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

Continue reading