“该策略利用算力增长和市值构建比特币、以太坊等加密货币的因子模拟投资组合,每周重新平衡,以使风险敞口与加密货币动态保持一致。”

I. 策略概要

该策略涉及比特币、以太坊、莱特币、门罗币和达世币,将算力作为基本因素。每日挖矿难度乘以挖矿区块数,这些值的自然对数每周平均。算力增长通过这些每周对数平均值的一阶差分来衡量。

因子模拟投资组合的构建方式是,将每种加密货币对数算力的变化对其他四种加密货币的回报进行回归,使用OLS回归系数作为权重。四种加密货币的加权回报构成了每种货币的因子模拟投资组合。总算力因子计算为这些投资组合的加权平均值,权重基于前一期的相对市值排名。该策略每周重新平衡,使加密货币敞口与算力变化和市场动态保持一致。

II. 策略合理性

算力因子是加密货币定价的基础,因为它确保了高效的交易流和区块链安全。在20周窗口内进行的滚动DLS回归显示,价格和算力之间存在显著的协整关系,表明均值回归并验证了算力的重要性。这种顺周期因子获得了正的风险溢价,并超越了比特币回报和动量因子等传统定价效应,提供了独特的价值。研究证实,加密货币的平均回报与算力等基本因素相关,驱动着长期价格变动,并为理解加密货币定价动态提供了比情绪或基于动量的因素更稳健的框架。

III. 来源论文

Blockchain Characteristics and Cryptocurrency Returns [点击查看论文]

<摘要>

我们研究了区块链特性(如网络规模和算力)是否影响加密货币价格和回报。网络规模反映了区块链的采用和使用情况,而算力则代表了用于保护和确认交易的实际资源消耗。与理论模型一致,我们发现加密货币价格与这两个区块链特性表现出共同变动。此外,总网络和算力至少与包含市场、规模和动量等基于回报的因素的模型一样,能够很好地解释预期加密货币回报的变化。总的来说,我们的结果表明,基于区块链的因素对于解释加密货币价格和回报至关重要。

IV. 回测表现

年化回报15.6%
波动率10.82%
β值0.071
夏普比率1.44
索提诺比率0.74
最大回撤N/A
胜率56%

V. 完整的 Python 代码

from AlgorithmImports import *
from typing import Dict, List
class ComputingPowerFactorinCryptocurrencies(QCAlgorithm):
    def Initialize(self) -> None:
        self.SetStartDate(2015, 1, 1)
        self.SetCash(1_000_000)
        self.period: int = 14
        self.percentage_traded: float = 0.1
        self.segment: int = 7
        
        self.crypto_symbols: Dict[str, str] = {
            'BTC' : 'BTCUSD',
            'ETH' : 'ETHUSD', 
            'LTC' : 'LTCUSD', 
        }
                        
        # Daily hash rate.
        self.hash_rate: Dict[Symbol, RollingWindow] = {}
        
        for crypto, ticker in self.crypto_symbols.items():
            data = self.AddCrypto(ticker, Resolution.Daily, Market.Bitfinex)
            self.AddData(CryptoNetworkData, crypto, Resolution.Daily)
            
            # Crypto data.
            self.hash_rate[crypto] = RollingWindow[float](self.period)
        
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.settings.daily_precise_end_time = False
        self.SetWarmUp(self.period, Resolution.Daily)
    def OnData(self, data: Slice) -> None: 
        crypto_data_last_update_date: Dict[Symbol, datetime.date] = CryptoNetworkData.get_last_update_date()
        # Store daily data.
        for crypto, ticker in self.crypto_symbols.items():
            if crypto in data and data[crypto]:
                hash_rate: float = data[crypto].Value
                if hash_rate != 0:
                    self.hash_rate[crypto].Add(hash_rate)
        if self.Time.date().weekday() != 0 and self.Time.hour == 0:
            return
        growth: Dict[str, float] = {}
        for crypto, ticker in self.crypto_symbols.items():
            if self.Securities[crypto].GetLastData() and self.Time.date() > crypto_data_last_update_date[crypto]:
                self.liquidate(ticker)
                continue
            if self.hash_rate[crypto].IsReady:
                if all(x == 0 for x in list(self.hash_rate[crypto])):
                    continue
                week_t1_hash_rates: List[float] = [np.log(x) for x in self.hash_rate[crypto]][:self.segment]
                week_t2_hash_rates: List[float] = [np.log(x) for x in self.hash_rate[crypto]][self.segment:]
                
                growth[ticker] = np.mean(week_t1_hash_rates) - np.mean(week_t2_hash_rates)
        
        total_growth_abs: float = sum(abs(x[1]) for x in growth.items())
        weight: Dict[str, float] = {x[0] : (x[1] / total_growth_abs) for x in growth.items()}
        
        # trade 
        invested: List[str] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in weight:
                self.Liquidate(symbol)
        portfolio:List[PortfolioTarget] = [PortfolioTarget(ticker, w * self.percentage_traded) for ticker, w in weight.items() if ticker in data and data[ticker] and w > 0]
        self.SetHoldings(portfolio)
        
# Crypto network data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
# Data source: https://coinmetrics.io/community-network-data/
class CryptoNetworkData(PythonData):
    _last_update_date: Dict[Symbol, datetime.date] = {}
    @staticmethod
    def get_last_update_date() -> Dict[Symbol, datetime.date]:
       return CryptoNetworkData._last_update_date
    def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLiveMode: bool) -> SubscriptionDataSource:
        return SubscriptionDataSource(f"data.quantpedia.com/backtesting_data/crypto/{config.Symbol.Value}_network_data.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    # File exmaple:
    # date,AdrActCnt,AdrBal1in100KCnt,AdrBal1in100MCnt,AdrBal1in10BCnt,AdrBal1in10KCnt,AdrBal1in10MCnt,AdrBal1in1BCnt,AdrBal1in1KCnt,AdrBal1in1MCnt,AdrBalCnt,AdrBalNtv0.001Cnt,AdrBalNtv0.01Cnt,AdrBalNtv0.1Cnt,AdrBalNtv100Cnt,AdrBalNtv100KCnt,AdrBalNtv10Cnt,AdrBalNtv10KCnt,AdrBalNtv1Cnt,AdrBalNtv1KCnt,AdrBalNtv1MCnt,AdrBalUSD100Cnt,AdrBalUSD100KCnt,AdrBalUSD10Cnt,AdrBalUSD10KCnt,AdrBalUSD10MCnt,AdrBalUSD1Cnt,AdrBalUSD1KCnt,AdrBalUSD1MCnt,AssetEODCompletionTime,BlkCnt,BlkSizeMeanByte,BlkWghtMean,BlkWghtTot,CapAct1yrUSD,CapMVRVCur,CapMVRVFF,CapMrktCurUSD,CapMrktFFUSD,CapRealUSD,DiffLast,DiffMean,FeeByteMeanNtv,FeeMeanNtv,FeeMeanUSD,FeeMedNtv,FeeMedUSD,FeeTotNtv,FeeTotUSD,FlowInExNtv,FlowInExUSD,FlowOutExNtv,FlowOutExUSD,FlowTfrFromExCnt,HashRate,HashRate30d,IssContNtv,IssContPctAnn,IssContPctDay,IssContUSD,IssTotNtv,IssTotUSD,NDF,NVTAdj,NVTAdj90,NVTAdjFF,NVTAdjFF90,PriceBTC,PriceUSD,ROI1yr,ROI30d,RevAllTimeUSD,RevHashNtv,RevHashRateNtv,RevHashRateUSD,RevHashUSD,RevNtv,RevUSD,SER,SplyAct10yr,SplyAct180d,SplyAct1d,SplyAct1yr,SplyAct2yr,SplyAct30d,SplyAct3yr,SplyAct4yr,SplyAct5yr,SplyAct7d,SplyAct90d,SplyActEver,SplyActPct1yr,SplyAdrBal1in100K,SplyAdrBal1in100M,SplyAdrBal1in10B,SplyAdrBal1in10K,SplyAdrBal1in10M,SplyAdrBal1in1B,SplyAdrBal1in1K,SplyAdrBal1in1M,SplyAdrBalNtv0.001,SplyAdrBalNtv0.01,SplyAdrBalNtv0.1,SplyAdrBalNtv1,SplyAdrBalNtv10,SplyAdrBalNtv100,SplyAdrBalNtv100K,SplyAdrBalNtv10K,SplyAdrBalNtv1K,SplyAdrBalNtv1M,SplyAdrBalUSD1,SplyAdrBalUSD10,SplyAdrBalUSD100,SplyAdrBalUSD100K,SplyAdrBalUSD10K,SplyAdrBalUSD10M,SplyAdrBalUSD1K,SplyAdrBalUSD1M,SplyAdrTop100,SplyAdrTop10Pct,SplyAdrTop1Pct,SplyCur,SplyExpFut10yr,SplyFF,SplyMiner0HopAllNtv,SplyMiner0HopAllUSD,SplyMiner1HopAllNtv,SplyMiner1HopAllUSD,TxCnt,TxCntSec,TxTfrCnt,TxTfrValAdjNtv,TxTfrValAdjUSD,TxTfrValMeanNtv,TxTfrValMeanUSD,TxTfrValMedNtv,TxTfrValMedUSD,VelCur1yr,VtyDayRet180d,VtyDayRet30d
    # 2009-01-09,19,19,19,19,19,19,19,19,19,19,19,19,19,0,0,19,0,19,0,0,0,0,0,0,0,0,0,0,1614334886,19,215,860,16340,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,9.44495122962963E-7,0,950,36500,100,0,950,0,1,0,0,0,0,1,0,0,0,0,11641.53218269,1005828380.584716757433,0,0,950,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,950,950,950,950,950,950,950,950,950,950,950,950,950,0,0,0,0,0,0,0,0,0,0,0,0,0,950,50,50,950,17070250,950,1000,0,1000,0,0,0,0,0,0,0,0,0,0,0,0,0
    def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLiveMode: bool) -> BaseData:
        data: CryptoNetworkData = CryptoNetworkData()
        data.Symbol = config.Symbol
        try:
            cols:str = ['HashRate']
            if not line[0].isdigit():
                header_split = line.split(',')
                self.col_index = [header_split.index(x) for x in cols]
                return None
            split = line.split(',')
            
            data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
            
            for i, col in enumerate(cols):
                data[col] = float(split[self.col_index[i]])
            data.Value = float(split[self.col_index[0]])
            if config.Symbol.Value not in CryptoNetworkData._last_update_date:
                CryptoNetworkData._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()
            if data.Time.date() > CryptoNetworkData._last_update_date[config.Symbol.Value]:
                CryptoNetworkData._last_update_date[config.Symbol.Value] = data.Time.date()
        
        except:
            return None
        return data

发表评论

了解 Quant Buffet 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读