Overall Statistics
Total Trades
19321
Average Win
0.11%
Average Loss
-0.10%
Compounding Annual Return
266.796%
Drawdown
68.400%
Expectancy
0.332
Net Profit
1255.009%
Sharpe Ratio
3.035
Probabilistic Sharpe Ratio
85.668%
Loss Rate
39%
Win Rate
61%
Profit-Loss Ratio
1.17
Alpha
1.403
Beta
0.649
Annual Standard Deviation
0.709
Annual Variance
0.502
Information Ratio
1.603
Tracking Error
0.624
Treynor Ratio
3.312
Total Fees
$32066.91
Estimated Strategy Capacity
$120000.00
Lowest Capacity Asset
MOBUSD 2MN
from AlgorithmImports import *

class RebalancingPremiumsinCryptos(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetEndDate(2022, 1, 1)
        self.SetCash(100000)
        self.SetBrokerageModel(BrokerageName.FTX, AccountType.Cash)
        self.Settings.FreePortfolioValuePercentage = 0.05
        self.week = -1
        self.readyToTrade = False
        self.high = 0
        
        
        # Get crypto USD pairs available to trade on FTX
        self.tickers = []
        url = "https://raw.githubusercontent.com/QuantConnect/Lean/master/Data/symbol-properties/symbol-properties-database.csv"
        spdb = self.Download(url).split('\n')
        # exclude FTX special pairs, stablecoins, and SPELL as it has a data issue on FTX Dec 11th 2021
        matches = ["BULL", "BEAR", "HEDGE", "HALF", "SPELL", "USDCUSD", "USDTUSD", "DAIUSD"]
        for line in spdb:
            csv = line.split(',')
            if csv[0] == 'ftx' and csv[1].endswith('USD') and not any(word in csv[1] for word in matches):
                self.tickers.append(Symbol.Create(csv[1], SecurityType.Crypto, Market.FTX))
        # self.Debug(f'Current FTX Cryptos with USD {len(self.tickers)}')
        
        self.bands_by_ticker = {}
        for symbol in self.tickers:
            self.bands_by_ticker[symbol] = BollingerBands(30, 2)

        self.AddUniverse(SecurityType.Crypto, 'Universe1', Resolution.Daily,
            Market.FTX, self.UniverseSettings, self.Selector)
            
        self.Schedule.On(self.DateRules.EveryDay(),
                         self.TimeRules.At(1, 0),
                         self.DailyRebalance)
                         
        # self.SetRiskManagement(MaximumDrawdownPercentPortfolio(0.3))
                         
                         
    # Build universe: select the top 25 mean-reverting pairs (based on bollinger bands) with highest volume
    def Selector(self, dt):
        current_week = self.Time.isocalendar()[1]
        if current_week == self.week:
            return Universe.Unchanged
        self.week = current_week
        
        # 31 days so the last one is not in the BB when we look at where the price is
        history = self.History(self.tickers, 31, Resolution.Daily)
        volume_by_symbol = {}

        for symbol in self.tickers:
            try:
                if symbol not in history.index: continue
                cur_bands = self.bands_by_ticker[symbol]
                for time, data in history.loc[symbol].iterrows():
                    cur_bands.Update(time, data.close)
                if not cur_bands.IsReady:continue
                df = history.loc[symbol].iloc[-1]
                dollar_volume = df.close * df.volume
                price = df.close
                lower_band = cur_bands.LowerBand.Current.Value
                upper_band = cur_bands.UpperBand.Current.Value
                
                if math.isnan(dollar_volume) or (price < lower_band) or (price > upper_band):
                    continue
                volume_by_symbol[symbol] = dollar_volume
            except:
                continue

        selected = sorted(volume_by_symbol.items(), key=lambda x: x[1], reverse=True)
        universe = [x[0].Value for x in selected][:25]
        # self.Debug(f"My universe: {universe}")
        
        return universe
        
    def OnSecuritiesChanged(self, changes):
        for security in changes.RemovedSecurities:
            self.Liquidate(security.Symbol)
            # self.Debug(f"Removed {security.Symbol} from the the universe")

        for security in changes.AddedSecurities:
            self.readyToTrade = True
            # self.Debug(f"Added {security.Symbol} to the universe")
            
    
    # SetHoldings method applied daily to the symbols in ActiveSecurities        
    def DailyRebalance(self):
        if self.readyToTrade:
            # self.Debug(f"Daily rebalance method triggered at {self.Time}")
            weight = 1.0/len(self.ActiveSecurities)
            
            targets = []
            
            for symbol in self.Portfolio.Keys:
                pnl = self.Securities[symbol].Holdings.UnrealizedProfitPercent
                if pnl < -0.30:
                    self.Liquidate(symbol)
 
                targets.append(PortfolioTarget(symbol, weight))
                    

            if targets:
                self.SetHoldings(targets)