Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-1.329
Tracking Error
0.618
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
#region imports
from AlgorithmImports import *
#endregion


#region imports
from AlgorithmImports import *
#endregion
class SymbolData():
    def __init__(self, network_symbol, period):
        self.network_symbol = network_symbol
        self.cap_mrkt_cur_usd = None
        self.closes = RollingWindow[float](period)
        self.last_update_cap = None
        self.last_update_price = None
            
    def update(self, close, date):
        self.closes.Add(close)
        self.last_update_price = date
        
    def update_cap(self, cap_mrkt_cur_usd, date):
        self.cap_mrkt_cur_usd = cap_mrkt_cur_usd
        self.last_update_cap = date
            
    def is_ready(self):
        return self.closes.IsReady and self.cap_mrkt_cur_usd
        
    def are_data_still_comming(self, curr_date, max_days_pause):
        return (self.last_update_price - curr_date).days <= max_days_pause and  (self.last_update_cap - curr_date).days <= max_days_pause
        
    def performance(self):
        closes = [x for x in self.closes]
        return -(closes[0] - closes[-1]) / closes[-1]

# Crypto network data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
# Data source: https://coinmetrics.io/community-network-data/
class CryptoNetworkData(PythonData):
    def GetSource(self, config, date, isLiveMode):
        self.cap_mrkt_cur_usd_index = None
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/crypto/{0}_network_data.csv".format(config.Symbol.Value), 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, line, date, isLiveMode):
        data = CryptoNetworkData()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit():
            self.cap_mrkt_cur_usd_index = None
            
            # on header line try to set index of CapMrktCurUSD
            split = line.split(',')
            
            for index in range(len(split)):
                if split[index] == 'CapMrktCurUSD':
                    # set index and break out of the cycle
                    self.cap_mrkt_cur_usd_index = index
                    
                    break
            
            return None
         
        # check if csv file has CapMrktCurUSD in columns
        if not self.cap_mrkt_cur_usd_index:
            return None
        
        split = line.split(',')
        
        data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
        data['CapMrktCurUSD'] = float(split[self.cap_mrkt_cur_usd_index])
        data.Value = float(split[self.cap_mrkt_cur_usd_index])
        
        return data     

# Custom fee model.
class CustomFeeModel(FeeModel):
    def __init__(self, algo):
        self.algorithm = algo
        
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))
# region imports
from AlgorithmImports import *
# endregion

# https://quantpedia.com/strategies/price-based-value-in-cryptocurrencies/
#
# The investment universe consists of cryptocurrencies from CoinMarketCap.com. Stablecoins, coins with zero prices, zero market capitalization, or
# zero trading volumes in all periods are omitted. The value measure is constructed as the negative of the past 52-week return. Sort the cryptos
# into decile portfolios based on their value. Long the top decile and short the bottom decile. The strategy is rebalanced weekly and value-weighted.
#
# QC Implementation:
#   - The investment universe consists of 23 cryptocurrencies with market cap data available.
#   - Top and bottom deciles are treaded.
#   - Portfolio is equally weighted.

class PricebasedValueinCryptocurrencies(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetCash(100000)
        
       
        self.data = {}
        self.period = 52*7 
        self.max_days_pause = 5 # max days, which crypto can stop receiving data and won't be removed from trade selection 
        self.quantiles = 5
        self.portfolio_percentage = 1
        self.SetBrokerageModel(BrokerageName.Binance)
        
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(CryptoCoarseFundamentalUniverse(Market.Binance, self.UniverseSettings, self.UniverseSelectionFilter))
                
        self.Schedule.On(self.DateRules.WeekStart(), self.TimeRules.At(10,10), self.Rebalance)

    def OnSecuritiesChanged(self, changes):
        pass

    def OnData(self, data):
        curr_date = self.Time.date()
        
        # daily updating of crypto prices and market capitalization(CapMrktCurUSD)
        for crypto, symbol_obj in self.data.items():
            network_symbol = symbol_obj.network_symbol
            
            if crypto in data.Bars and data[crypto]:
                # get crypto price
                price = data.Bars[crypto].Value
                self.data[crypto].update(price, curr_date)
            
            if network_symbol in data and data[network_symbol]:
                # get market capitalization
                cap_mrkt_cur_usd = data[network_symbol].Value
                self.data[crypto].update_cap(cap_mrkt_cur_usd, curr_date)
        
    def Rebalance(self):
        curr_date = self.Time.date()
        
        performance = {}
        
        for crypto, symbol_obj in self.data.items():
            # crypto doesn't have enough data
            if not symbol_obj.is_ready() or not symbol_obj.are_data_still_comming(curr_date, self.max_days_pause):
                continue
            
            # calculate performance for current crypto
            performance[crypto] = symbol_obj.performance()
        
        # not enough cryptos for selection    
        if len(performance) < self.quantiles:
            self.Liquidate()    
            return
        
        # perform selection
        quantile = int(len(performance) / self.quantiles)
        sorted_by_perf = [x[0] for x in sorted(performance.items(), key=lambda item: item[1])]
        
        # long top quantile
        long = sorted_by_perf[-quantile:]
        # short bottom quantile
        short = sorted_by_perf[:quantile]
        
        # trade execution
        invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        for crypto in invested:
            if crypto not in long + short:
                self.Liquidate(crypto)

        long_c = len(long)
        short_c = len(short)
        
        # trade long
        for crypto in long:
            self.SetHoldings(crypto, 1/long_c)
        
        # trade short
        for crypto in short:
            self.SetHoldings(crypto, -1/short_c)
        
    def UniverseSelectionFilter(self, data):
        filtered = [datnum for datnum in data
                if datnum.VolumeInUsd and datnum.VolumeInUsd > 5000000]
        filteredtwice = [datnum for datnum in filtered if datnum.Symbol.Value.find("USDT") != -1]
        sorted_by_volume = sorted(filtered, key = lambda datnum: datnum.VolumeInUsd, reverse=True)[:50]
        return [datnum.Symbol for datnum in sorted_by_volume]