Overall Statistics
Total Trades
343
Average Win
10.66%
Average Loss
-5.20%
Compounding Annual Return
85.255%
Drawdown
78.000%
Expectancy
0.454
Net Profit
622.469%
Sharpe Ratio
1.619
Probabilistic Sharpe Ratio
38.471%
Loss Rate
52%
Win Rate
48%
Profit-Loss Ratio
2.05
Alpha
1.97
Beta
0.615
Annual Standard Deviation
1.265
Annual Variance
1.601
Information Ratio
1.522
Tracking Error
1.262
Treynor Ratio
3.333
Total Fees
$13456.90
Estimated Strategy Capacity
$1900000.00
import clr
clr.AddReference("System")
clr.AddReference("QuantConnect.Algorithm")
clr.AddReference("QuantConnect.Common")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
import datetime
from datetime import timedelta

import numpy as np
from sklearn import preprocessing
from sklearn.linear_model import Ridge, Lasso

import pandas as pd

class ScikitLearnLinearRegressionAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 1, 4)  # Set Start Date 
        self.SetEndDate(2021, 3, 20) # Set End Date
        self.SetCash(1000000)  # Set Strategy Cash
        self.Settings.FreePortfolioValuePercentage = 0.30
        
        self.timestamp = 60*24 #1day
        self.lookback = 30*24*60 # 30days, 1 month
        self.testing = 10*self.timestamp  #testing period table
        
        self.long_quantile = 0.5
        self.short_quantile = 0.05
        self.close_quantile = 0
        self.alpha = 0.1

        self.BTC = self.AddFuture(Futures.Currencies.BTC, Resolution.Minute) 
        #self.SetBenchmark("BTCUSD")
        
        self.BTC.SetFilter(lambda x: x.FrontMonth())
        
        self.Schedule.On(self.DateRules.MonthEnd(),self.TimeRules.At(23, 59) ,self.Regression)
        self.er_rebuild_model = 0
        
        self.Schedule.On(self.DateRules.EveryDay(self.BTC.Symbol), self.TimeRules.Every(timedelta(minutes=self.timestamp)), self.Trade)
        self.run = 0
            
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(23, 59), self.handle_error)    
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(23, 30), self.portfolio_check)    

        
    def OnData(self, slice):
        for chain in slice.FutureChains:
            contracts_list = [contract for contract in chain.Value]
            ideal_contract = sorted(contracts_list, key=lambda k : k.OpenInterest, reverse=True)[0]
            self.contract_symbol = ideal_contract.Symbol
    
    def data_construction(self, look_back_period):
        slices = self.History(look_back_period, Resolution.Minute)
        
        datetime = []
        bidclose = []
        bidsize = []
        askclose = []
        asksize = []
        openprice = []
        close = []

        for s in slices:
            datetime.append(s.Time)
            bidclose.append(s.QuoteBars[self.contract_symbol].Bid.Close)
            bidsize.append(int(s.QuoteBars[self.contract_symbol].LastBidSize))
            askclose.append(s.QuoteBars[self.contract_symbol].Ask.Close)
            asksize.append(int(s.QuoteBars[self.contract_symbol].LastAskSize))
            openprice.append(s.QuoteBars[self.contract_symbol].Open)
            close.append(s.QuoteBars[self.contract_symbol].Close)

        df = pd.DataFrame({"bidclose":bidclose, "bidsize":bidsize, "askclose":askclose, "asksize":asksize, "open":openprice, "close":close}, index=datetime)
        
        if (self.timestamp != 1):
            # Resample the data
            temp_str = str(self.timestamp) + "T"
            df = df.resample(temp_str).last()
            temp_sum = df.resample(temp_str).sum()/self.timestamp
            #temp_sum = temp_sum.astype(int)
            temp_first = df.resample(temp_str).first()
            df[["bidsize", "asksize"]] = temp_sum[["bidsize", "asksize"]]
            df["open"]=temp_first["open"]
        
        df['bidpricechange_lag']=df['bidclose']-df['bidclose'].shift(1)
        df['askpricechange_lag']=df['askclose']-df['askclose'].shift(1)
        df['bidsizediff_lag']=df['bidsize']-df['bidsize'].shift(1)
        df['asksizediff_lag']=df['asksize']-df['asksize'].shift(1)
        df=df.dropna(axis=0)
        
        deltaVolumeBid=[]
        for i in df.index:
            if df.loc[i,'bidpricechange_lag']  > 0:
                deltaVolumeBid.append(df.loc[i,'bidsize'])
            elif df.loc[i,'bidpricechange_lag']  < 0:
                deltaVolumeBid.append(0)
            else:
                deltaVolumeBid.append(df.loc[i,'bidsizediff_lag'])
        
        df['deltaVolumeBid']=deltaVolumeBid
        
        deltaVolumeAsk=[]
        for j in df.index:
            if df.loc[j,'askpricechange_lag']  > 0:
                deltaVolumeAsk.append(0)
            elif df.loc[j,'askpricechange_lag']  < 0:
                deltaVolumeAsk.append(df.loc[j,'asksize'])
            else:
                deltaVolumeAsk.append(df.loc[j,'asksizediff_lag'])
        
        
        df['deltaVolumeAsk']=deltaVolumeAsk
        df['Return']=(df['close'].shift(-1)/df['open'].shift(-1))-1 #open # default trading open?
        df['VOI']=df['deltaVolumeBid']-df['deltaVolumeAsk']
        df['OIR']=(df['bidsize']-df['asksize'])/(df['bidsize']+df['asksize'])
        #df=df.fillna(0)     # As I checked the data and see that bidsize/asksize are 0 in some timestamps
        df['SP']=df['askclose']-df['bidclose']
        #sp_0index = df[df["SP"]==0].index   
        #df.loc[sp_0index, "SP"] = 1       # to ensure that adjusted VOI won't be nan 
        
        df['VOI_SP']=(df['VOI'])/df['SP']
        df['OIR_SP']=(df['OIR'])/df['SP']
        
        df['VOI_SP_lag1']=df['VOI_SP'].shift(1)
        df['VOI_SP_lag2']=df['VOI_SP'].shift(2)
        df['VOI_SP_lag3']=df['VOI_SP'].shift(3)
        df['VOI_SP_lag4']=df['VOI_SP'].shift(4)
        df['VOI_SP_lag5']=df['VOI_SP'].shift(5)
        
        df['OIR_SP_lag1']=df['OIR_SP'].shift(1)
        df['OIR_SP_lag2']=df['OIR_SP'].shift(2)
        df['OIR_SP_lag3']=df['OIR_SP'].shift(3)
        df['OIR_SP_lag4']=df['OIR_SP'].shift(4)
        df['OIR_SP_lag5']=df['OIR_SP'].shift(5)
        df=df.dropna(axis=0)
    
        return df
        
    def Regression(self):
        try:
            df = self.data_construction(self.lookback)
            X = df[["VOI_SP", "VOI_SP_lag1", "VOI_SP_lag2", "VOI_SP_lag3", "VOI_SP_lag4", "VOI_SP_lag5", "OIR_SP", 
                "OIR_SP_lag1", "OIR_SP_lag2", "OIR_SP_lag3", "OIR_SP_lag4", "OIR_SP_lag5"]]
            self.scaler = preprocessing.StandardScaler().fit(X)
            X_scaled = self.scaler.transform(X)
            
            Model = Ridge(alpha = self.alpha).fit(X_scaled, df["Return"])
            df['yhat']= Model.predict(X_scaled)
            
            self.long= df['yhat'].quantile(self.long_quantile)
            self.closelong = df['yhat'].quantile(self.long_quantile-self.close_quantile)
            self.short = df['yhat'].quantile(self.short_quantile)
            self.closeshort = df['yhat'].quantile(self.short_quantile+self.close_quantile)
            self.MLmodel = Model
            self.run=1 
            
            self.Debug("long signal " + str(self.long))
            self.Debug("short signal " + str(self.short))
            self.Debug(self.MLmodel)
            self.Debug(self.Time)
            
            self.er_rebuild_model = 0
        except:
            self.er_rebuild_model = 1
            self.Debug("Model need to be rebuilt in the upcoming day " + str(self.Time))
        
    def handle_error(self):
        if (self.er_rebuild_model == 1):
            self.Regression()

    def Trade(self):
        if self.run == 0:
            self.Regression()
        
        try:
            df = self.data_construction(self.testing)
            X = df[["VOI_SP","VOI_SP_lag1", "VOI_SP_lag2", "VOI_SP_lag3", "VOI_SP_lag4", "VOI_SP_lag5", "OIR_SP", "OIR_SP_lag1", "OIR_SP_lag2", 
                    "OIR_SP_lag3", "OIR_SP_lag4", "OIR_SP_lag5"]]
            X_scaled = self.scaler.transform(X)
            df['yhat'] = self.MLmodel.predict(X_scaled)
            
            predictedReturn = df.iloc[-1]['yhat']
            bid_close = df.iloc[-1]["bidclose"]
            ask_close = df.iloc[-1]["askclose"]
            
            
            
            if self.Portfolio[self.contract_symbol].IsLong:
                if predictedReturn < self.closelong or predictedReturn < 0:
                    self.Liquidate()
            
            if not self.Portfolio[self.contract_symbol].Invested:
                if predictedReturn > self.long and predictedReturn > 0:
                    self.SetHoldings(self.contract_symbol, 1)
            
            
            '''
            # Trading Logic
            if self.Portfolio[self.contract_symbol].IsLong:
                if predictedReturn < self.short:
                    self.Liquidate()
                
                    #self.Debug("Short")
                    
                    self.SetHoldings(self.contract_symbol, -1)
                elif predictedReturn < self.closelong:
                    self.Liquidate()
        
            if self.Portfolio[self.contract_symbol].IsShort:
                if predictedReturn > self.long:
                    self.Liquidate()
                
                    #self.Debug("Long")
                
                
                    self.SetHoldings(self.contract_symbol, 1)
                elif predictedReturn > self.closeshort:
                    self.Liquidate()
            
            #if not self.Portfolio.Invested:
            if not self.Portfolio[self.contract_symbol].Invested:
                if predictedReturn > self.long:
                    self.SetHoldings(self.contract_symbol, 1)
                
                    #self.Debug("Long")
                
                
                if predictedReturn < self.short:
                    self.SetHoldings(self.contract_symbol, -1)
                
                    #self.Debug("Short")
            
            '''
            """
            # Reverse Signal
            if self.Portfolio[self.contract_symbol].IsLong:
                if predictedReturn > self.long:
                    self.Liquidate()
                    #self.LimitOrder(self.contract_symbol, -self.Portfolio[self.contract_symbol].Quantity, ask_close)
                    
                    
                    #self.Debug("Short")
                    
                    quantity = self.CalculateOrderQuantity(self.contract_symbol, -1)
                    self.LimitOrder(self.contract_symbol, quantity, ask_close)
                    
                    #self.SetHoldings(self.contract_symbol, -1)
                elif predictedReturn > self.closeshort:
                    #self.Liquidate()
                    self.LimitOrder(self.contract_symbol, -self.Portfolio[self.contract_symbol].Quantity, ask_close)
                    
        
            if self.Portfolio[self.contract_symbol].IsShort:
                if predictedReturn < self.short:
                    self.Liquidate()
                    #self.LimitOrder(self.contract_symbol, -self.Portfolio[self.contract_symbol].Quantity, bid_close)
                    
                    #self.Debug("Long")
                
                    quantity = self.CalculateOrderQuantity(self.contract_symbol, 1)
                    self.LimitOrder(self.contract_symbol, quantity, bid_close)
                    
                    #self.SetHoldings(self.contract_symbol, 1)
                elif predictedReturn < self.closelong:
                    #self.Liquidate()
                    self.LimitOrder(self.contract_symbol, -self.Portfolio[self.contract_symbol].Quantity, bid_close)
            
            #if not self.Portfolio.Invested:
            if not self.Portfolio[self.contract_symbol].Invested:
                if predictedReturn > self.long:
                    #self.SetHoldings(self.contract_symbol, -1)
                    quantity = self.CalculateOrderQuantity(self.contract_symbol, -1)
                    self.LimitOrder(self.contract_symbol, quantity, ask_close)
                    
                    #self.Debug("Long")
                
                
                if predictedReturn < self.short:
                    #self.SetHoldings(self.contract_symbol, 1)
                    quantity = self.CalculateOrderQuantity(self.contract_symbol, 1)
                    self.LimitOrder(self.contract_symbol, quantity, bid_close)
                    
                    #self.Debug("Short")
            """
            
        except:
            self.Liquidate()
            self.Debug("Pass trading on this timestamp due to data error " + str(self.Time))
            
    def portfolio_check(self):
        self.Debug(str(self.Time) + " Cash " + str(self.Portfolio.Cash))
        self.Debug(self.Portfolio.Keys)
        self.Debug(self.Portfolio.Values)
        self.Debug(self.contract_symbol)