Overall Statistics |
Total Trades 825 Average Win 1.34% Average Loss -2.92% Compounding Annual Return 10.105% Drawdown 74.600% Expectancy 0.078 Net Profit 94.048% Sharpe Ratio 0.43 Probabilistic Sharpe Ratio 4.245% Loss Rate 26% Win Rate 74% Profit-Loss Ratio 0.46 Alpha 0.012 Beta 1.228 Annual Standard Deviation 0.348 Annual Variance 0.121 Information Ratio 0.13 Tracking Error 0.291 Treynor Ratio 0.122 Total Fees $18745.91 |
### 2020_11_13 v3 ### ---------------------------------------------------------------------------- ### Added a cash instrument to invest the margin remaining from the two futures contracts ### ---------------------------------------------------------------------------- import numpy as np class VixFuturesStrategyAlgorithm(QCAlgorithm): def Initialize(self): self.SetTimeZone("America/Chicago") self.SetStartDate(2014, 1, 1) #self.SetEndDate(2020, 11, 5) self.SetCash(1000000) # notional target as a percentage over total portfolio value self.notionalOverPortfolio = -0.5 # allocations to roll every week for different expiration groups (keys are the weeks left to fully roll) self.allocationsToRoll = {1: [1], 2: [0.25, 0.75], 3: [0.25, 0.25, 0.5], 4: [0.2, 0.2, 0.2, 0.4]} # add a ticker (cashSymbol) for the margin remaining self.cashTicker = 'TLT' # add futures data and always get fron month contract self.vix = self.AddFuture(Futures.Indices.VIX, Resolution.Minute) self.vix.SetFilter(3, 90) # add data for cash symbol if self.cashTicker is not None: self.cashSymbol = self.AddEquity(self.cashTicker, Resolution.Minute).Symbol self.contractsSortedByExpiry = [] self.previousFrontMonthContract = None self.allocationToRoll = 0 self.day = 0 # plot notional exposure by contract and total notionalExposurePlot = Chart('Chart Notional Exposure') self.AddChart(notionalExposurePlot) def OnData(self, data): # skip if the cash ticker has price of zero if self.cashTicker is not None and self.Securities[self.cashSymbol].Price == 0: return if self.Time.day == self.day: return # get relevant contracts once a day -------------------------------------------------------------- for chain in data.FutureChains: contracts = [contract for contract in chain.Value] self.contractsSortedByExpiry = sorted(contracts, key = lambda x: x.Expiry, reverse = False) if len(self.contractsSortedByExpiry) < 2: return # define contracts frontContract = self.contractsSortedByExpiry[0] secondContract = self.contractsSortedByExpiry[1] self.day = self.Time.day # skip if not monday if self.Time.date().weekday() != 0: return # trade first front month contracts ----------------------------------------------------------- if not self.Portfolio.Invested: self.RollFuturesContracts(frontContract, 0, 'front contract') # invest the margin remaining in the cash symbol if self.cashTicker is not None: self.RebalanceCashSymbol() # print a few logs self.PrintRelevantLogs(frontContract, secondContract) # when we have a new front month contract, # calculate the number of weeks left to expiration --------------------------------------------- if self.previousFrontMonthContract != frontContract.Symbol.Value or self.weeksToFrontContractExpiry == 0: self.weeksToFrontContractExpiry = self.WeekdaysBetweenDates(self.Time.date(), frontContract.Expiry.date(), 0) - 2 self.Log(frontContract.Symbol.Value + '; ' + str(self.weeksToFrontContractExpiry) + ' weeks of rolling') self.previousFrontMonthContract = frontContract.Symbol.Value self.allocationToRoll = 0 self.weeksCount = 0 return if self.weeksToFrontContractExpiry == 0: return # rolling ---------------------------------------------------------------------------------------- # update the allocation to roll ---------------------------------------- self.allocationToRoll += self.allocationsToRoll[self.weeksToFrontContractExpiry][self.weeksCount] self.weeksCount += 1 self.Log('allocationToRoll: ' + str(self.allocationToRoll)) # trade front month contracts ------------------------------------------ self.RollFuturesContracts(frontContract, self.allocationToRoll, 'front contract') # trade front month contracts ------------------------------------------ self.RollFuturesContracts(secondContract, 1 - self.allocationToRoll, 'second contract') # invest the margin remaining in the cash symbol ----------------------- if self.cashTicker is not None: self.RebalanceCashSymbol() # print a few logs ----------------------------------------------------- self.PrintRelevantLogs(frontContract, secondContract) # plot notional exposure by contract and total ------------------------- self.PlotNotionalExposure(frontContract, secondContract) def GetTargetNumberOfContracts(self, contract, adjustment): ''' Calculate the number of contracts to hold to achieve notional target ''' numberOfContracts = 0 notionalTarget = self.Portfolio.TotalPortfolioValue * self.notionalOverPortfolio * (1 - adjustment) #contractPrice = self.Securities[contract.Symbol].BidPrice if self.notionalOverPortfolio < 0 else self.Securities[contract.Symbol].AskPrice contractPrice = contract.BidPrice if self.notionalOverPortfolio < 0 else contract.AskPrice contractNotionalValue = contractPrice * self.vix.SymbolProperties.ContractMultiplier if contractNotionalValue == 0: return numberOfContracts numberOfContracts = int(notionalTarget / contractNotionalValue) return numberOfContracts def RollFuturesContracts(self, contract, adjustment, message): ''' Roll futures contracts ''' targetNumberOfContracts = self.GetTargetNumberOfContracts(contract, adjustment) currentNumberOfContracts = self.Portfolio[contract.Symbol].Quantity quantityContracts = targetNumberOfContracts - currentNumberOfContracts self.MarketOrder(contract.Symbol, quantityContracts, False, message) self.Log('target contracts for ' + message + ': ' + str(targetNumberOfContracts)) def RebalanceCashSymbol(self): ''' Rebalance cash symbol to use all the remaining margin ''' targetCashShares = int(self.Portfolio.MarginRemaining / self.Securities[self.cashSymbol].Price) currentCashShares = self.Portfolio[self.cashSymbol].Quantity cashShares = targetCashShares - currentCashShares self.MarketOrder(self.cashSymbol, cashShares, False, 'cash rebalancing') def GetCurrentNotionalExposure(self, contract): ''' Calculate the current notional exposure of a given contract ''' #contractPrice = self.Securities[contract.Symbol].BidPrice if self.notionalOverPortfolio < 0 else self.Securities[contract.Symbol].AskPrice contractPrice = contract.BidPrice if self.notionalOverPortfolio < 0 else contract.AskPrice currentNotionalValue = contractPrice * self.vix.SymbolProperties.ContractMultiplier * self.Portfolio[contract.Symbol].Quantity currentNotionalExposure = currentNotionalValue / self.Portfolio.TotalPortfolioValue return currentNotionalExposure def PlotNotionalExposure(self, frontContract, secondContract): ''' Plot the notional exposure by contract and in total ''' notionalExposureFrontContract = self.GetCurrentNotionalExposure(frontContract) self.Plot('Chart Notional Exposure', 'Front Month Notional Exposure', notionalExposureFrontContract) notionalExposureSecondContract = self.GetCurrentNotionalExposure(secondContract) self.Plot('Chart Notional Exposure', 'Second Month Notional Exposure', notionalExposureSecondContract) totalNotionalExposure = notionalExposureFrontContract + notionalExposureSecondContract self.Plot('Chart Notional Exposure', 'Total Notional Exposure', totalNotionalExposure) def WeekdaysBetweenDates(self, fromDate, toDate, weekdayNumber): ''' Calculate the number of given weekday between two dates ''' daysBetween = (toDate - fromDate).days weekdaysBetween = sum((fromDate + timedelta(i)).weekday() == weekdayNumber for i in range(0, daysBetween)) return weekdaysBetween def PrintRelevantLogs(self, frontContract, secondContract): ''' Print a few relevant logs ''' self.Log('current contracts: ' + str({frontContract.Symbol.Value: [frontContract.Expiry.date(), self.Portfolio[frontContract.Symbol].Quantity, self.Securities[frontContract.Symbol].BidPrice], secondContract.Symbol.Value: [secondContract.Expiry.date(), self.Portfolio[secondContract.Symbol].Quantity, self.Securities[secondContract.Symbol].BidPrice]})) if self.cashTicker is not None: self.Log('TotalPortfolioValue: ' + str(self.Portfolio.TotalPortfolioValue) + '; Cash: ' + str(self.Portfolio.Cash) + '; MarginRemaining: ' + str(self.Portfolio.MarginRemaining) + '; cashSymbol: ' + str(self.Portfolio[self.cashSymbol].Quantity)) else: self.Log('TotalPortfolioValue: ' + str(self.Portfolio.TotalPortfolioValue) + '; Cash: ' + str(self.Portfolio.Cash) + '; MarginRemaining: ' + str(self.Portfolio.MarginRemaining)) def OnOrderEvent(self, orderEvent): ticket = self.Transactions.GetOrderTicket(orderEvent.OrderId) if ticket.Tag == 'Liquidate from delisting': self.Log('liquidate due to contract delisting') def OnMarginCall(self, requests): self.Log('liquidate all holdings due to margin call') self.Liquidate() return requests