| 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] = sizeclass 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}"