Overall Statistics
Total Trades
1733
Average Win
1.04%
Average Loss
-1.28%
Compounding Annual Return
322.836%
Drawdown
31.600%
Expectancy
0.418
Net Profit
8325.983%
Sharpe Ratio
1.934
Probabilistic Sharpe Ratio
98.849%
Loss Rate
22%
Win Rate
78%
Profit-Loss Ratio
0.81
Alpha
1.161
Beta
0.137
Annual Standard Deviation
0.608
Annual Variance
0.369
Information Ratio
1.744
Tracking Error
0.614
Treynor Ratio
8.586
Total Fees
$166282.43
import numpy as np

# Custom fee implementation
class CustomFeeModel:
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.002
        return OrderFee(CashAmount(fee, 'USD'))        
        
class BollingerCryptoTrading(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 1, 1)  # Set Start Date
        self.SetCash(10000)            # Set Strategy Cash
        self.lookback = 500            # History length 
        self.symbols = [
            "BTCUSD", 
            "ETHUSD",
            "LTCUSD",
        ]
        for i in self.symbols:
            self.AddCrypto(i, Resolution.Hour, Market.Bitfinex, AccountType.Margin)
            self.Securities[i].SetFeeModel(CustomFeeModel())
        
        self.position = dict.fromkeys(self.symbols, 0)
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 30), Action(self.rebalance))
        self.needs_rebalancing = False
        
    
    def OnData(self, data):
        
        # get price history 
        hist = self.History(self.symbols, self.lookback, Resolution.Hour)
        self.Debug("Processing history of length %i" % len(hist))
        
        for symbol in self.symbols:
            if not symbol in hist.index:
                self.position[symbol] = 0.0
                continue
            mean_price = (hist.loc[symbol]['close']).iloc[-200:].mean()
            std_price = (hist.loc[symbol]['close']).iloc[-400:].std()
            price = (hist.loc[symbol]['close']).iloc[-1]
            if price < 1.0:
                continue
            
            upper = mean_price + 1.0 * std_price
            lower = mean_price - 6.0 * std_price
            
            vol = (hist.loc[symbol]['volume']).iloc[-1]
            mean_vol = (hist.loc[symbol]['volume']).mean()
            volume_aug = (vol / mean_vol) > 1.2
            
            exit_short = price > mean_price and self.position[symbol] < 0.0
            exit_long = price < mean_price and self.position[symbol] > 0.0
            
            if exit_short or exit_long:
                self.position[symbol] = 0
                self.needs_rebalancing = True
            if price > upper and volume_aug and self.position[symbol] != 1.0:
                self.position[symbol] = 1.0 
                self.needs_rebalancing = True
            elif price < lower and volume_aug and self.position[symbol] != -1.0:
                self.position[symbol] = -1.0
                self.needs_rebalancing = True

        if self.needs_rebalancing:
            self.rebalance()
            
            
    def rebalance(self):
        self.Debug("Rebalancing")
        w_sum = 0.2
        for symbol, w in self.position.items():
            w_sum += np.abs(w)
            if w == 0:
                self.Liquidate(symbol)
                
        targets_list = [
            PortfolioTarget(symbol, w/w_sum) 
            for symbol, w in self.position.items()
            if w != 0
        ]
        
        self.SetHoldings(targets_list)
        self.needs_rebalancing = False