Overall Statistics
import numpy as np
import pandas as pd
from datetime import timedelta
from decimal import Decimal

# Parameters are global variables (necessitated from indicator custom class)
pOne = 60
pTwo = 780
pThree = 13980
atrLen = 14000

tradeThresh = 15

fOne = 1.25
fTwo = -0.5
fThree = 1

class VXTFAlgo(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2012, 7, 1)   # Set Start Date
        #self.SetEndDate(2012, 12, 30)     # Set End Date
        self.SetCash(100000)          # Set Strategy Cash
        self.Reso = Resolution.Minute
        
        # Add VIX futures contract data 
        #future = self.AddFuture(Futures.Indices.VIX).SetFilter(timedelta(8), timedelta(days=60))
        future = self.AddFuture(Futures.Indices.VIX, self.Reso).SetFilter(lambda x: x.FrontMonth().OnlyApplyFilterAtMarketOpen())
        
        # select the right VX future
        self.frontVX = None
        self.lastBid = 0
        self.lastAsk = 0
        self.orderTktEntry = None
        self.orderTktExit = None
        
        #self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerageModel)
        
        self.quantity = 1
        
        # var for instance of indicators
        self.indicators = None

        
    def OnData(self, data):
        # Manually update indicators with recent values using manual method https://www.quantconnect.com/docs/algorithm-reference/indicators
        # NB need to use lower case 'update' from custom class due to manual updates i.e. customIndicators.updateSMA()
        if data.Bars.ContainsKey(self.frontVX.Symbol):
            self.indicators.updateSMA(data[self.frontVX.Symbol].EndTime, data[self.frontVX.Symbol].Close)
            self.indicators.updateATR(data.Bars[self.frontVX.Symbol])
            
        if not self.indicators.is_ready(): return
            
        # get trend and atr indicator values
        atrAdjOne = self.indicators.atr.Current.Value * pOne ** 0.5
        atrAdjTwo = self.indicators.atr.Current.Value * pTwo ** 0.5
        atrAdjThree = self.indicators.atr.Current.Value * pThree ** 0.5
        
        # value today minus vlaue of SMA @ lookback (windows are currently of IndicatorDataPoint class objects)
        trendOne = 100 * ((self.indicators.smaWinOne[0].Value - self.indicators.smaWinOne[pOne/4].Value)/atrAdjOne) * fOne
        trendTwo = 100 * ((self.indicators.smaWinTwo[0].Value - self.indicators.smaWinTwo[pTwo/4].Value)/atrAdjTwo) * fTwo
        trendThree = 100 * ((self.indicators.smaWinThree[0].Value - self.indicators.smaWinThree[pThree/4].Value)/atrAdjThree) * fThree

        # define indicator
        trend = (trendOne + trendTwo + trendThree) / 3 

        ## Check indicators and generate trading signals
        #holding = None if self.symbol is None else self.Portfolio.get(self.frontVX)
        
        bidPrice = self.frontVX.BidPrice
        askPrice = self.frontVX.AskPrice
        
        
        
        if trend > tradeThresh:
            # cancel open sell orders
            if self.OrderIsPlaced(self.frontVX.Symbol,-self.quantity):
                self.orderTktExit.Cancel("trend value flipped; sell order cancelled")
            
            ## enter if not invested and BUY order not already placed
            if not self.Portfolio.Invested and not self.OrderIsPlaced(self.frontVX.Symbol,self.quantity):
                
                   self.orderTktEntry = self.LimitOrder(self.frontVX.Symbol,self.quantity,bidPrice)
                   #self.Log(f"{self.Time} Trend above thresh: trend {trend} trendOne {trendOne} trendTwo {trendTwo} trendThree {trendThree} atrAdjOne {atrAdjOne} atrAdjTwo {atrAdjTwo} atrAdjThree {atrAdjThree}")

            # order management for entry
            if self.OrderIsPlaced(self.frontVX.Symbol,self.quantity) and self.lastBid != bidPrice:
                updateFields = UpdateOrderFields()
                updateFields.LimitPrice = bidPrice
                self.orderTktEntry.Update(updateFields)
                self.lastBid = bidPrice
        else:
            # cancel open buy orders
            if self.OrderIsPlaced(self.frontVX.Symbol,self.quantity):
                self.orderTktEntry.Cancel("trend value flipped; buy order cancelled")
            
            ## exit if invested with limit orders on the offer, and SELL order not already placed
            if self.Portfolio.Invested and not self.OrderIsPlaced(self.frontVX.Symbol, -self.quantity):
                self.orderTktExit = self.LimitOrder(self.frontVX.Symbol, -self.quantity,askPrice)
                #self.Log(f"{self.Time} Trend below thresh: trend {trend} trendOne {trendOne} trendTwo {trendTwo} trendThree {trendThree} atrAdjOne {atrAdjOne} atrAdjTwo {atrAdjTwo} atrAdjThree {atrAdjThree}")
            
            # order management for exit
            if self.OrderIsPlaced(self.frontVX.Symbol,-self.quantity) and self.lastAsk != askPrice:
                updateFields = UpdateOrderFields()
                updateFields.LimitPrice = askPrice
                self.orderTktExit.Update(updateFields)
                self.lastAsk = askPrice

    def OnSecuritiesChanged(self, changes):
        # https://www.quantconnect.com/forum/discussion/8588/warming-up-an-indicator-rolling-window/p1
        
        # Remove the data consolidator indicators and rolling windows for the previous contract
        if len(changes.RemovedSecurities) > 0:
            # and reset the indicators
            if self.frontVX is not None:
                self.indicators.smaOne.Reset()
                self.indicators.smaTwo.Reset()
                self.indicators.smaThree.Reset()
                self.indicators.atr.Reset()
                
                # Github example doesn't liquidate because auto-liquidates on expiry, but we do
                self.Log(f"Old contract expiring: {self.frontVX.Symbol.Value}")
                self.Liquidate(self.frontVX.Symbol)
                
        
        # Only one security will be added: the new front contract
        # NB assigning the entire security object here, NOT the symbol object
        if len(changes.AddedSecurities) > 0:
            self.frontVX = changes.AddedSecurities[0]
            self.Log(f"Rolling to new contract: {self.frontVX.Symbol.Value}")

            # history call to push through indicator values to the rolling windows
            history = self.History(self.frontVX.Symbol, pThree * 2, self.Reso)
            #self.Debug(history.head())
            
            # initialise instance of customIndicators class and pass history call
            self.indicators = customIndicators(self.frontVX.Symbol,history)



    def OrderIsPlaced(self,symbol,quantity):
        openOrders = self.Transactions.GetOpenOrders()
        for order in openOrders:
            if (order.Symbol == symbol and order.Quantity == quantity):
                return True
        return False    
  
# Custom class for manually handling indicator warm up and updates: https://www.quantconnect.com/forum/discussion/7972/using-atr-and-other-039-complex-039-indicators-with-history/p1           
class customIndicators():
    def __init__(self, symbol, history):
        self.frontVX = symbol
                
        ## Indicators - taken from https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/EmaCrossFuturesFrontMonthAlgorithm.py
        # SMAs
        self.smaOne = SimpleMovingAverage(pOne)
        self.smaTwo = SimpleMovingAverage(pTwo)
        self.smaThree = SimpleMovingAverage(pThree)
        
        #ATR
        self.atr = AverageTrueRange(self.frontVX, atrLen)
        
        # create rollingwindows and bind their updates to an event handler called on Updated - equivalent to:
            #self.smaOne.Update(time,close)
            #self.smaWinOne.Add(self.smaOne.Current)
            # see https://docs.microsoft.com/en-us/dotnet/standard/events/#event-handlers. I think lambda s,e refer to 'IsReady' and 'Current.Value'?
        self.smaWinOne = RollingWindow[IndicatorDataPoint](pOne)
        self.smaOne.Updated += lambda s,e: self.smaWinOne.Add(e)
        self.smaWinTwo = RollingWindow[IndicatorDataPoint](pTwo)
        self.smaTwo.Updated += lambda s,e: self.smaWinTwo.Add(e)
        self.smaWinThree = RollingWindow[IndicatorDataPoint](pThree)
        self.smaThree.Updated += lambda s,e: self.smaWinThree.Add(e)
        
        # Loop over the history data and update the indicators
        for tuple in history.itertuples():
            #self.Debug(f"tuple.Index {tuple.Index[2]} tuple.close {tuple.close}")
            bar = TradeBar(tuple.Index[2], self.frontVX, tuple.open, tuple.high, tuple.low, tuple.close, tuple.volume)   
            self.updateSMA(tuple.Index[2],tuple.close)
            self.updateATR(bar)

    # Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
    def is_ready(self):
        # taken from https://www.quantconnect.com/forum/discussion/3303/how-to-use-rollingwindow-on-multiple-equities-and-indicators/p1
        return (self.smaOne.IsReady and
                    self.smaTwo.IsReady and
                    self.smaThree.IsReady and
                    self.atr.IsReady and
                    self.smaWinOne.IsReady and 
                    self.smaWinTwo.IsReady and 
                    self.smaWinThree.IsReady)
    
    def updateSMA(self,time,close):
        self.smaOne.Update(time,close)
        self.smaTwo.Update(time,close)
        self.smaThree.Update(time,close)
        
    def updateATR(self,bar):
        self.atr.Update(bar)