Overall Statistics
Total Trades
9490
Average Win
0.59%
Average Loss
-0.35%
Compounding Annual Return
74.827%
Drawdown
91.400%
Expectancy
0.205
Net Profit
1505.965%
Sharpe Ratio
1.265
Probabilistic Sharpe Ratio
39.943%
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
1.68
Alpha
-0.057
Beta
0.763
Annual Standard Deviation
0.708
Annual Variance
0.501
Information Ratio
-0.669
Tracking Error
0.529
Treynor Ratio
1.174
Total Fees
$70266745.95
Estimated Strategy Capacity
$280000.00
Lowest Capacity Asset
BALUSD XJ
import numpy as np
import pandas as pd
from sklearn.linear_model import RidgeClassifier
from AlgorithmImports import *

class MachineLearningAlgo(QCAlgorithm):
    
    def Initialize(self):
        
        self.SetStartDate(2016, 6, 2)  
        self.SetEndDate(2021, 5, 20)  
        self.SetCash(10000000)  
        self.Settings.FreePortfolioValuePercentage = 0.1
        self.SetBrokerageModel(BrokerageName.AlphaStreams)
        self.SetExecution(ImmediateExecutionModel())
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())

        self.lookback = 30
        
        symbol_list = ["BTCUSD","ETHUSD","LTCUSD","BALUSD","DAIUSD","KNCUSD",
                        "OXTUSD","RENUSD","UMAUSD","XRPUSD","ZRXUSD"]
        self.symbols = [self.AddCrypto(symbol, Resolution.Minute, Market.GDAX).Symbol for symbol in symbol_list]
        
        self.SetBenchmark("BTCUSD")

        self.AddAlpha(MachineLearningAlphaModel(self.Time, self.lookback))
                        
class MachineLearningAlphaModel(AlphaModel):
                        
    def __init__(self, Time, lookback):
        self.dataBySymbol = {}
        self.rebalanceTime = Time
        self.lookback = lookback
            
    def GetMLModel(self):
        self.MLModel = RidgeClassifier(random_state=18)   
        
    def Update(self, algorithm, data):
        insights = []
         
        if algorithm.Time < self.rebalanceTime: return []
        
        for symbol, symbolData in self.dataBySymbol.items():
            if data.Bars.ContainsKey(symbol) and not algorithm.IsWarmingUp:
                symbolData.Update(data, symbol)
                
                if symbolData.Close_rolling.IsReady:
                   
                    self.df = pd.DataFrame(symbolData.Close_rolling, columns=["Close"])[::-1].reset_index(drop=True)
                    
                    # calculate daily forward returns to be used to set Target / Signal
                    self.df['Return'] = np.log(self.df["Close"].shift(-1)/self.df["Close"]) 
                    self.df = self.df.dropna()
                    
                    # set Signal / Target
                    self.df["Signal"] = 0
                    self.df['Signal'][self.df["Return"] > 0] = 1
                    self.df['Signal'][self.df["Return"] < 0] = -1
                    
                    # set training data
                    self.X = self.df.drop(["Return", "Signal"], axis=1)
                    self.Y = self.df['Signal']
                    
                    # align feature set & signal 
                    self.Y, self.X = self.Y.align(self.X, axis=0, join='inner')
                    
                    self.X_train = self.X[:-1]
                    self.Y_train = self.Y[:-1]
                    if self.X_train.empty or self.Y_train.empty: return []
                    
                    # fit / train ML model
                    self.GetMLModel()
                    self.MLModel.fit(self.X_train, self.Y_train)
                    
                    # predict next day signal using today's values of feature set
                    self.X_today = self.X.iloc[-1]
                    # self.X_today is Series, so convert to numpy array
                    self.X_today = self.X_today.to_numpy()
                    # reshape self.X_today because it only has 1 day's sample
                    self.X_today = self.X_today.reshape(1,-1)
                    
                    # Y_predict will take predicted signal
                    self.Y_predict = self.MLModel.predict(self.X_today)
                    
                    # set insight based on predicted signal
                    # We cannot short crypto in GDAX
                    # if not all([algorithm.IsMarketOpen(x[symbol]) for x in symbolData.Close_rolling]):
                    #     return []
                    direction = InsightDirection.Up if self.Y_predict == 1 else InsightDirection.Flat
                    insights.append(Insight(symbol, timedelta(days=30), InsightType.Price, direction))
                    
                
        self.rebalanceTime = Expiry.EndOfDay(algorithm.Time)
                
        return insights
    
    def OnSecuritiesChanged(self, algorithm, changes):
        for change in changes.AddedSecurities:
            self.dataBySymbol[change.Symbol] = SymbolData(algorithm, change.Symbol, self.lookback)
            
        for change in changes.RemovedSecurities:
            if change.Symbol in self.dataBySymbol:
                del self.dataBySymbol[change.Symbol]
        
class SymbolData:
    def __init__(self, algorithm, symbol, lookback):
        
        self.lookback = lookback
        algorithm.Consolidate(symbol, Resolution.Daily, lambda x: None)
        
        self.Close_rolling = RollingWindow[float](self.lookback)
        
        history = algorithm.History(symbol, self.lookback, Resolution.Daily)
        if not history.empty:
            for index, bar in history.loc[symbol].iterrows():
                self.Close_rolling.Add(bar.close)
        
    def Update(self, data, symbol):
            self.Close_rolling.Add(data[symbol].Close)