Overall Statistics |
Total Trades 406 Average Win 0.00% Average Loss 0.00% Compounding Annual Return -1.094% Drawdown 0.200% Expectancy -0.360 Net Profit -0.160% Sharpe Ratio -4.649 Probabilistic Sharpe Ratio 0.484% Loss Rate 59% Win Rate 41% Profit-Loss Ratio 0.55 Alpha -0.006 Beta -0.004 Annual Standard Deviation 0.002 Annual Variance 0 Information Ratio -8.059 Tracking Error 0.095 Treynor Ratio 2.224 Total Fees $409.50 |
####### Session 3 Objectives in order of importance ######## 1.) Rolling Window Indicators on 30 Min bars - for looping through symbols - done ######## 2.) Keeping track of individual 'lots' in order to sell specific # of contracts instead of Liquidate all ######## layer out if needed, timing of multiple strategies as once on a given symbol ####### 3.) Tracking for overlapping bracket orders (muptliple bracket orders on at the same time for a given option ) ###### issue arises when it cancels all open orders ####### 4.) Accessing Bid/Ask and Greeks within the Options selection function - desire is to be a certain % ITM or ATM instead of a fixed $value # bid = self.Securities[option_symbol].BidClose # In order to access greeks, we need to use AddOption(symbol), right now we're using the OptionChainProvider(selectively subscribes to data - efficient backtests) # https://www.quantconnect.com/docs/data-library/options # Example: https://github.com/QuantConnect/Lean/blob/43dba33b8913adacc56da9de299b36c0bbafb022/Algorithm.Python/BasicTemplateOptionsHistoryAlgorithm.py from BracketOrder import * from BracketOrderCCI import * class OptimizedHorizontalCircuit(QCAlgorithm): def Initialize(self): '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' self.SetStartDate(2020,7,10) #Set Start Date self.SetEndDate(2020,8,31) #Set End Date self.SetCash(10000000) # returns a equity object, which has .Symbol property self.spy = self.AddEquity("SPY", Resolution.Minute) # required for dealing with options self.spy.SetDataNormalizationMode(DataNormalizationMode.Raw) # this determines our fees self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage) ###question - Is this doing it correctly?, do I need to do anything in the on data #warmup period of 30 self.SetWarmup(300) # creating a 30 min consolidator spy_thirtyMinuteConsolidator = TradeBarConsolidator(timedelta(minutes=30)) # this is a event handler method, each time there is a new 30 minute bar, "self.ThirtyMinuteBarHandler" will be called spy_thirtyMinuteConsolidator.DataConsolidated += self.ThirtyMinuteBarHandler # our consolidator is registered to consolidate spy data self.SubscriptionManager.AddConsolidator("SPY", spy_thirtyMinuteConsolidator) self.spy_rsi = RelativeStrengthIndex(20, MovingAverageType.Simple) self.RegisterIndicator("SPY", self.spy_rsi, spy_thirtyMinuteConsolidator) #Aroon 13 Added - rolling window setup self.spy_aroon13 = AroonOscillator(13,13) self.RegisterIndicator("SPY", self.spy_aroon13, spy_thirtyMinuteConsolidator) self.spy_aroon13.Updated += self.spy_Aroon13Updated self.spy_aroon13win = RollingWindow[IndicatorDataPoint](10) self.spy__aroon13_AroonUp = RollingWindow[float](10) #Ema 13 Added rolling window setup self.spy_ema13 = ExponentialMovingAverage(13) self.RegisterIndicator("SPY", self.spy_ema13, timedelta(minutes=30)) self.spy_ema13.Updated += self.spy_Ema13Updated self.spy_ema13Window = RollingWindow[IndicatorDataPoint](5) # self.cci = CommodityChannelIndex(20,MovingAverageType.Simple) # self.RegisterIndicator("SPY", self.cci, thirtyMinuteConsolidator) self.underlying_symbol_data = {} #self.underlying_cci[self.spy.Symbol] = self.spy_cci tickers = ["QQQ", "IWM"] #tickers = ["QQQ","SPY","IWM", "GLD", "VXX","EEM","EFA","XHB","SLV","USO"] for ticker in tickers: security = self.AddEquity(ticker, Resolution.Minute) symbol = security.Symbol security.SetDataNormalizationMode(DataNormalizationMode.Raw) symbol_data_object = UnderlyingSymbolData(symbol, self) self.underlying_symbol_data[symbol] = symbol_data_object # self.AddUniverse(self.SelectCoarse) self.outtime = 10080 self.symbolDataBySymbolRSI = {} self.symbolDataBySymbolCCI = {} self.SetSecurityInitializer(self.CustomSecurityInitializer) def OnOrderEvent(self, orderEvent): symbol = orderEvent.Symbol # Is this symbol in our "Option Symbol Data" List if symbol in self.symbolDataBySymbolRSI: bracket_order = self.symbolDataBySymbolRSI[symbol].current_bracket_order # aka we have some entry position if bracket_order is not None: # cancel stop loss if take profit filled and vice versa bracket_order.Update(orderEvent) # if our order is complete after the fill # let's reset it if bracket_order.Closed is True: self.symbolDataBySymbolRSI[symbol].current_bracket_order = None def OnOrderEventCCI(self, orderEvent): symbol = orderEventCCI.Symbol # Is this symbol in our "Option Symbol Data" List if symbol in self.symbolDataBySymbolCCI: bracket_order = self.symbolDataBySymbolCCI[symbol].current_bracket_order # aka we have some entry position if bracket_order is not None: # cancel stop loss if take profit filled and vice versa bracket_order.Update(orderEvent) # if our order is complete after the fill # let's reset it if bracket_order.Closed is True: self.symbolDataBySymbolCCI[symbol].current_bracket_order = None ######Rolling Windows for SPY def spy_Ema13Updated(self, sender, updated): self.spy_ema13Window.Add(updated) def spy_Aroon13Updated(self, sender, updated): '''Adds updated values to rolling window''' self.spy_aroon13win.Add(updated) self.spy__aroon13_AroonUp.Add(self.spy_aroon13.AroonUp.Current.Value) def CustomSecurityInitializer(self, security): bar = self.GetLastKnownPrice(security) security.SetMarketPrice(bar) # Is called each time a new security is added to our universe def OnSecuritiesChanged(self, changes): # for each new underlyingsecurity, we can create an RSI, and save a refernce # in the self.underlying_rsi dictionary pass # ########################################### # We're using the SPY 30 minute consolidator event handler to do our position calculations # Can we replace this with a scheduled event, so that it's not specific to a security? # ############################################## # def ThirtyMinuteBarHandler(self, sender, bar): '''This is our event handler for our 30-minute trade bar defined above in Initialize(). So each time the consolidator produces a new 30-minute bar, this function will be called automatically. The sender parameter will be the instance of the IDataConsolidator that invoked the event ''' for underlying_symbol in self.underlying_symbol_data: symbol_data = self.underlying_symbol_data[underlying_symbol] rsi = symbol_data.rsi cci = symbol_data.cci aroon13 = symbol_data.aroon13 ema13 = symbol_data.ema13 aroon13win = symbol_data.aroon13win ema13win = symbol_data.ema13Window _aroon13_AroonUp = symbol_data._aroon13_AroonUp if not rsi.IsReady: continue holdings = self.Portfolio[underlying_symbol].Quantity if holdings > 10000: # do nothing return #if rsi.Current.Value < 30 and self.spy_ema13Window[0].Value < self.spy_ema13Window[1].Value and self.spy__aroon13_AroonUp[0] < self.spy__aroon13_AroonUp[1]: if rsi.Current.Value < 30 and ema13win[0].Value < ema13win[1].Value and _aroon13_AroonUp[0] < _aroon13_AroonUp[1]: contract_call_symbol = self.OptionsFilter(underlying_symbol, 10, OptionRight.Call) # if there are no contracts do nothing if contract_call_symbol is None: return self.MarketOrder(contract_call_symbol, 3) # latest price for symbol, We can use AddOption to access price data/greek data immediately for every option for secondary filtering current_market_price = self.Securities[contract_call_symbol].Price ask = self.Securities[contract_call_symbol].AskPrice take_profit = current_market_price * 1.10 stop_loss = current_market_price * 0.90 # (algorithm, symbol, quantity, entry, takeProfit, stopLoss) bracket_order = BracketOrder(self, contract_call_symbol, 1, current_market_price, take_profit, stop_loss) self.Log(f"RSI<30 >>") symbolDataObject = SymbolDataRSI(contract_call_symbol, self) self.symbolDataBySymbolRSI[contract_call_symbol] = symbolDataObject # save reference to created bracket order, (is no longer None) symbolDataObject.current_bracket_order = bracket_order symbolDataObject.NewPosition(self.Time, 1) if cci.Current.Value < -100: contract_call_symbol = self.OptionsFilter(underlying_symbol, 10, OptionRight.Call) # if there are no contracts do nothing if contract_call_symbol is None: return # latest price for symbol, We can use AddOption to access price data/greek data immediately for every option for secondary filtering current_market_price = self.Securities[contract_call_symbol].Price ask = self.Securities[contract_call_symbol].AskPrice take_profit = current_market_price * 1.10 stop_loss = current_market_price * 0.90 # (algorithm, symbol, quantity, entry, takeProfit, stopLoss) bracket_order = BracketOrderCCI(self, contract_call_symbol, 1, current_market_price, take_profit, stop_loss) self.Log(f"CCI<30 >>") symbolDataObject = SymbolDataCCI(contract_call_symbol, self) self.symbolDataBySymbolCCI[contract_call_symbol] = symbolDataObject # save reference to created bracket order, (is no longer None) symbolDataObject.current_bracket_order = bracket_order symbolDataObject.NewPosition(self.Time, 1) # SELLING ################ # we have a reference to each option we've traded # every thirty minutes loop over all open option contracts positions for option_symbol in self.symbolDataBySymbolRSI: symbolData = self.symbolDataBySymbolRSI[option_symbol] if not self.Portfolio[option_symbol].Invested: continue # do we have an open position for contract? # if outtime time as passed option_entry_time = symbolData.entry_time current_time = self.Time if (current_time - option_entry_time) >= timedelta(minutes=self.outtime): self.Liquidate(option_symbol) self.Log(f"Sell Call: {option_symbol}, entry_time:{option_entry_time}, exit_time:{current_time}") underlying_symbol = self.Securities[option_symbol].Underlying.Symbol underlying_rsi = self.underlying_symbol_data[underlying_symbol].rsi if underlying_rsi.Current.Value > 70: self.Log(f"Sell RSI>70 >> {option_symbol}") for option_symbol in self.symbolDataBySymbolCCI: symbolData = self.symbolDataBySymbolCCI[option_symbol] if not self.Portfolio[option_symbol].Invested: continue # do we have an open position for contract? # if outtime time as passed option_entry_time = symbolData.entry_time current_time = self.Time if (current_time - option_entry_time) >= timedelta(minutes=self.outtime): self.Liquidate(option_symbol) self.Log(f"Sell Call: {option_symbol}, entry_time:{option_entry_time}, exit_time:{current_time}") underlying_symbol = self.Securities[option_symbol].Underlying.Symbol underlying_cci = self.underlying_symbol_data[underlying_symbol].cci if underlying_cci.Current.Value > 100: self.Log(f"Sell CCI>100 >> {option_symbol}") # Given an underlying, Identify an slightly OTM call contract expiry closest to X days but greater than X days def OptionsFilter(self, underlying_symbol, expiry_days, option_right): # option_right = [OptionRight.Call, OptionRight.Put] contracts = self.OptionChainProvider.GetOptionContractList(underlying_symbol, self.Time) # for symbol in contracts: # cant access price data, we haven't used AddOptionContract yet underlyingPrice = self.Securities[underlying_symbol].Price contracts_filtered_for_right_and_expiry = [i for i in contracts if i.ID.OptionRight == option_right and \ i.ID.Date >= self.Time + timedelta(days = expiry_days)] otm_contracts = None if option_right == OptionRight.Call: otm_contracts = [i for i in contracts_filtered_for_right_and_expiry if i.ID.StrikePrice > underlyingPrice] else: otm_contracts = [i for i in contracts_filtered_for_right_and_expiry if i.ID.StrikePrice < underlyingPrice] # if there are no contrats if otm_contracts is None or len(otm_contracts) == 0: return None sorted_by_strike = sorted(otm_contracts, key=lambda x: abs((underlyingPrice+1) - x.ID.StrikePrice)) closest_strike = sorted_by_strike[0].ID.StrikePrice # all contracts slightly OTM contracts_with_closest_strike = [i for i in sorted_by_strike if i.ID.StrikePrice == closest_strike] # Sort slightly OTM contracts by Expiry and pick sorted_by_expiry = sorted(contracts_with_closest_strike, key=lambda x: x.ID.Date) contract = sorted_by_expiry[0] self.AddOptionContract(contract, Resolution.Minute) return contract class UnderlyingSymbolData: def __init__(self, symbol, algorithm): self.symbol = symbol self.algorithm = algorithm # creating a 30 min consolidator thirtyMinuteConsolidator = TradeBarConsolidator(timedelta(minutes=30)) # our consolidator is registered to consolidate spy data algorithm.SubscriptionManager.AddConsolidator(symbol, thirtyMinuteConsolidator) # create a RSI indicator for that ticke self.rsi = RelativeStrengthIndex(20, MovingAverageType.Simple) algorithm.RegisterIndicator(symbol, self.rsi, thirtyMinuteConsolidator) # save a reference to our RSI indicator for that underlying self.cci = CommodityChannelIndex(20, MovingAverageType.Simple) algorithm.RegisterIndicator(symbol, self.cci, thirtyMinuteConsolidator) #Aroon 13 Added - rolling window setup # creates new AROON indicator and registers it to the algo self.aroon13 = AroonOscillator(13,13) algorithm.RegisterIndicator(symbol, self.aroon13, thirtyMinuteConsolidator) self.aroon13.Updated += self.Aroon13Updated # roll self.aroon13win = RollingWindow[IndicatorDataPoint](10) self._aroon13_AroonUp = RollingWindow[float](10) # self.aroon13win[0].AroonUp.Current.Value #Ema 13 Added rolling window setup self.ema13 = ExponentialMovingAverage(13) algorithm.RegisterIndicator(symbol, self.ema13, timedelta(minutes=30)) self.ema13.Updated += self.Ema13Updated self.ema13Window = RollingWindow[IndicatorDataPoint](5) ######Rolling Windows for non-SPY symbols in list def Ema13Updated(self, sender, updated): self.ema13Window.Add(updated) def Aroon13Updated(self, sender, updated): '''Adds updated values to rolling window''' self.aroon13win.Add(updated) self._aroon13_AroonUp.Add(self.aroon13.AroonUp.Current.Value) # "Option Symbol Data" class SymbolDataRSI: """ Motivation: We want to keep track of properties for each specific security (options, equities) - some symbol """ def __init__(self, symbol, algorithm): self.symbol = symbol self.algorithm = algorithm self.entry_time = algorithm.Time # self.Time self.current_bracket_order = None # idea is we want to add structure within SymbolData so we can keep track # of individual lots of trades for a specific contract # Call June 2020 400 Strike # {"05/12/2020" : +3, 05/17/2020 : +5} # we want to treat them indivudally # record of each individual position for RSI strategy self.positions = {} # key = entrytime , values = size # 1st trade happens when we initialize def NewPosition(self, entry_time, size): self.positions[entry_time] = size # "Option Symbol Data" class SymbolDataCCI: """ Motivation: We want to keep track of properties for each specific security (options, equities) - some symbol """ def __init__(self, symbol, algorithm): self.symbol = symbol self.algorithm = algorithm self.entry_time = algorithm.Time # self.Time self.current_bracket_order = None # idea is we want to add structure within SymbolData so we can keep track # of individual lots of trades for a specific contract # Call June 2020 400 Strike # {"05/12/2020" : +3, 05/17/2020 : +5} # we want to treat them indivudally # record of each individual position for RSI strategy self.positions = {} # key = entrytime , values = size # 1st trade happens when we initialize def NewPosition(self, entry_time, size): self.positions[entry_time] = size
class BracketOrder: def __init__(self, algorithm, symbol, quantity, entry, takeProfit, stopLoss, trailing=False): self.Algorithm = algorithm self.Symbol = symbol self.Quantity = quantity self.Entry = entry self.TakeProfit = takeProfit self.StopLoss = stopLoss self.Trailing = trailing self.Open = True self.Closed = False self.EntryOrderTicket = algorithm.MarketOrder(symbol, quantity) self.TakeProfitOrderTicket = self.Algorithm.LimitOrder(self.Symbol, -self.Quantity, self.TakeProfit) self.StopLossOrderTicket = self.Algorithm.StopMarketOrder(self.Symbol, -self.Quantity, self.StopLoss) self.ID = [self.EntryOrderTicket.OrderId,self.TakeProfitOrderTicket.OrderId, self.StopLossOrderTicket.OrderId] def CancelStopLoss(self): response = self.StopLossOrderTicket.Cancel() self.StopLossOrderTicket = None self.Open = False self.Closed = True def CancelTakeProfit(self): response = self.TakeProfitOrderTicket.Cancel() self.TakeProfitOrderTicket = None self.Open = False self.Closed = True def Update(self, orderEvent): if self.TakeProfitOrderTicket != None and orderEvent.OrderId == self.TakeProfitOrderTicket.OrderId: if orderEvent.Status == OrderStatus.Filled: self.CancelStopLoss() elif self.StopLossOrderTicket != None and orderEvent.OrderId == self.StopLossOrderTicket.OrderId: if orderEvent.Status == OrderStatus.Filled: self.CancelTakeProfit() @property def Statistics(self): exit = self.TakeProfit if self.StopLossOrderTicket is None else self.StopLoss return f"{self.Symbol}, Return: {(self.Entry - exit)/self.Entry} - ${self.Quantity*(self.Entry - exit)} -- Entry Price: {self.Entry} and Exit Price: {exit}"
class BracketOrderCCI: def __init__(self, algorithm, symbol, quantity, entry, takeProfit, stopLoss, trailing=False): self.Algorithm = algorithm self.Symbol = symbol self.Quantity = quantity self.Entry = entry self.TakeProfit = takeProfit self.StopLoss = stopLoss self.Trailing = trailing self.Open = True self.Closed = False self.EntryOrderTicket = algorithm.MarketOrder(symbol, quantity) self.TakeProfitOrderTicket = self.Algorithm.LimitOrder(self.Symbol, -self.Quantity, self.TakeProfit) self.StopLossOrderTicket = self.Algorithm.StopMarketOrder(self.Symbol, -self.Quantity, self.StopLoss) self.ID = [self.EntryOrderTicket.OrderId,self.TakeProfitOrderTicket.OrderId, self.StopLossOrderTicket.OrderId] def CancelStopLoss(self): response = self.StopLossOrderTicket.Cancel() self.StopLossOrderTicket = None self.Open = False self.Closed = True def CancelTakeProfit(self): response = self.TakeProfitOrderTicket.Cancel() self.TakeProfitOrderTicket = None self.Open = False self.Closed = True def Update(self, orderEvent): if self.TakeProfitOrderTicket != None and orderEvent.OrderId == self.TakeProfitOrderTicket.OrderId: if orderEvent.Status == OrderStatus.Filled: self.CancelStopLoss() elif self.StopLossOrderTicket != None and orderEvent.OrderId == self.StopLossOrderTicket.OrderId: if orderEvent.Status == OrderStatus.Filled: self.CancelTakeProfit() @property def Statistics(self): exit = self.TakeProfit if self.StopLossOrderTicket is None else self.StopLoss return f"{self.Symbol}, Return: {(self.Entry - exit)/self.Entry} - ${self.Quantity*(self.Entry - exit)} -- Entry Price: {self.Entry} and Exit Price: {exit}"