Overall Statistics
Total Trades
8472
Average Win
0.74%
Average Loss
-0.40%
Compounding Annual Return
-11.712%
Drawdown
97.100%
Expectancy
0.044
Net Profit
-47.222%
Sharpe Ratio
0.314
Probabilistic Sharpe Ratio
2.216%
Loss Rate
64%
Win Rate
36%
Profit-Loss Ratio
1.86
Alpha
-0.493
Beta
0.646
Annual Standard Deviation
0.817
Annual Variance
0.667
Information Ratio
-1.227
Tracking Error
0.736
Treynor Ratio
0.397
Total Fees
$2566393.68
Estimated Strategy Capacity
$30000.00
Lowest Capacity Asset
BTCUSD 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, 7, 18)  
        self.SetCash(1000000)  
        # self.AddEquity("SPY", Resolution.Daily)  
        # self.SetBenchmark("SPY")
        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.SetWarmup(self.lookback)

        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 = 0
        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:
                   
                    df1 = pd.DataFrame(symbolData.Close_rolling, columns=["Close"]).reset_index(drop=True)
                    
                    self.df = pd.concat([df1], axis=1)
                    
                    # 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.loc[self.df["Return"] > 0, "Signal"] = 1
                    self.df.loc[self.df["Return"] < 0, "Signal"] = -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]
                    self.X_train.replace([np.inf, -np.inf], np.nan, inplace=True)
                    self.Y_train.replace([np.inf, -np.inf], np.nan, inplace=True)
                    
                    drops = []
                    [drops.append(i) for i in range(self.X_train.shape[0]) if self.X_train.iloc[i].isnull().any()]
                    [drops.append(i) for i in range(self.Y_train.shape[0]) if self.Y_train.iloc[i] == np.nan and i not in drops]
                    self.X_train.drop(index=self.X_train.index[drops], inplace=True)
                    self.Y_train.drop(index=self.Y_train.index[drops], inplace=True)
                    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.Y.iloc[-1]
                    try:
                        self.Y_predict = self.MLModel.predict(self.X_today)
                    except: return []
                    
                    # set insight based on predicted signal
                    if self.Y_predict == 1:
                        insights.append(Insight(symbol, timedelta(days=30), InsightType.Price, InsightDirection.Up))
                    elif self.Y_predict == -1:
                        insights.append(Insight(symbol, timedelta(days=30), InsightType.Price, InsightDirection.Flat))
                    else:
                        insights.append(Insight(symbol, timedelta(days=30), InsightType.Price, InsightDirection.Flat))
                
        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, self.DailyBarHandler)
        
        self.Close_rolling = RollingWindow[float](self.lookback)

        # self.fast = algorithm.SMA(symbol, 5, Resolution.Daily, Field.Low)
        # self.slow = algorithm.SMA(symbol, 60, Resolution.Daily, Field.High)
        
        history = algorithm.History(symbol, self.lookback, Resolution.Daily)
        
        if not history.empty:
            # for index, tradebar in history.loc[symbol].iterrows():
            #     self.fast.Update(index, tradebar.low)
            #     self.slow.Update(index, tradebar.high)
            
            last_row = history.loc[symbol].iloc[-1]
            self.open = last_row.open
            self.close = last_row.close
            self.high = last_row.high
            self.low = last_row.low
        
    def DailyBarHandler(self, consolidated):
        self.open = consolidated.Open
        self.close = consolidated.Close
        self.high = consolidated.High
        self.low = consolidated.Low
        
    def Update(self, data, symbol):
            self.Close_rolling.Add(data[symbol].Close)
            
    # def IsReady(self):
    #     return self.fast.IsReady and self.slow.IsReady