Hi All,
I have spent the last few months working through the issue dealing with bad performance on option strategies and wanted to give people back the knowledge that i have worked out to increase my options strategy speeds whilst still being able to filter and sort for things other than strikes and expiry.
so firstly the solution that most people will already know is that in order to keep the performance high, you need to keep your universe very small, this means that you need to use get options contract instead of subscribing to all your options chains in the initialize area. as many have pointed out in other topics, this has the drawback of only being able to filter on strikes and expiry's and option types which removes functionality for people.
The solution is to use get options contract to filter for type, strikes and expiry's and then to manually subscribe to the options using add security, then filter and sort the options to find the one you want and then do manually unsubscribe from all others. to illustrate this is have created the code block below which first filters using the standard get options contract, and then uses a custom sorting function to filter on things like bidprice and askprice. using the same workflow, it should be easy for others to run secondary filtration for thing such as option greeks or prices.
My code for reference:
def GetOption(self,slice,underlying,optionright,MinStrike,MaxStrike,MinExpiry,MaxExpiry,sortedfunction,reverse):
contracts = self.OptionChainProvider.GetOptionContractList(underlying, self.Time) ## Get list of Options Contracts for a specific time
if len(contracts) == 0: return
filtered_options = [x for x in contracts if x.ID.OptionRight == optionright and\
x.ID.StrikePrice > MinStrike and\
x.ID.StrikePrice < MaxStrike and\
x.ID.Date > MinExpiry and\
x.ID.Date < MaxExpiry]
if len(filtered_options) == 0: return
added_contracts = []
for x in filtered_options:
option = self.AddOptionContract(x, Resolution.Minute)
optionsymbol = self.Securities[option.Symbol]
added_contracts.append(optionsymbol)
sorted_options = sorted(added_contracts,key = sortedfunction, reverse = reverse)
selected = sorted_options[0]
for x in sorted_options:
if x != selected:
self.RemoveSecurity(x.Symbol)
return selected
.ekz.
Thanks for sharing this.
Note: I'm adding your code here again, formatted, for easier reading.
.ekz.
Derek Melchin
Hi MatteMatto,
Thanks for sharing this with the community! For completeness, the attached backtest demonstrates using the `GetOption` method.
Best,
Derek Melchin
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
.ekz.
On a related note, Derek Melchin, a question: Does RemoveSecurity get called whenever we liquidate an asset? or should we manually be ‘cleaning up’ after ourselves?
Varad Kabade
Hi .ekz.,
The liquidate method does not remove subscriptions to given security.
Best,
Varad Kabade
.ekz.
Ah, noted. Thanks Varad kabade.
Matt Mcwaters
I love this community and the fact that there are people out there like you guys trying to share tips and strategies to help out the rest of us.
I just have a question about the above methodology Varad Kabade MatteMatto. Does the fact that we need to Warm Up for Greek/Volatility data render this strategy useless? I am trying to confirm myself but I am having trouble seeing where I can access Greeks after I've added a warm up period to the algorithm provided by Varad.
MatteMatto
Matt Mcwaters, I had not checked this in ages and honestly, my algo has evolved far past this methodology now as I needed to futher innovate to control universe sizing as my strategy started using muliple tickers as well as wide time frames.
I think what you are asking is how to get to the greeks? and reading this article back again I think i may have implied that as soon as you subscribe you can filter for greeks and then unsubscrribe later. This was wrong so im sorry about that. first you need to add the contracts to the universe, then you wait for the next time step and they will be accessible as option contracts in the chain with the greeks values under .Greeks.Delta for delta)
This has caused a huge issues in my personal live algo as they were still in there too long and when using multiple tickers to trade I would constantly have very high universe numbers causing me to have to buy a large number of IKBR booster packs.
I dont do high frequency trading so this was my solution. basically, when options need to be accessed for a given underlying inside my algo, I load a coarsely filtered list into the universe for a 10 minute window, conduct all the transactions we need in those time steps, then move to the next underlying once the required operations are completed, this means that I can use all my availbile streaming data slots for a single underlying at once which gives my algo more options to filter, sort and select from.
I dont know how useful this is but here is my code below:
latest Universe Updater methods are below, I run this on a 10 minute schedule.
def UpdateOptionUniverse(self):
#only update options when the market is open
if not self.IsMarketOpen(self.symbol): return
# remove uninvested options out of the universe to keep it clean, moved it to trigger on order event as well.
self.remove_uninvested_options_from_universe()
#Stop the system from adding more contracts afer its been completed for 1 underlying
self.add_contracts_to_universe = True
# Add Active Calls to universe:
if not self.stop_selling_calls and self.add_contracts_to_universe:
for Underlying in self.call_underlyings_active:
equity_purchase_price = self.Portfolio[Underlying].AveragePrice
last_price = self.Portfolio[Underlying].Price
MinStrike = max(equity_purchase_price,last_price)
MaxStrike = MinStrike * 1.2
MaxExpiry = self.Time + timedelta(self.call_max_expiry)
self.add_options_to_universe(Underlying,OptionRight.Call,MinStrike,MaxStrike,MaxExpiry)
# Add Active Puts to Universe
if not self.stop_selling_puts and self.add_contracts_to_universe:
for Underlying in self.put_underlyings_active:
MaxStrike = self.Securities[Underlying].Price
MinStrike = MaxStrike * 0.8
MaxExpiry = self.Time + timedelta(self.put_max_expiry)
self.add_options_to_universe(Underlying,OptionRight.Put,MinStrike,MaxStrike,MaxExpiry)
def remove_uninvested_options_from_universe(self):
#First we need to remove all options in the universe that we are not invested in
for x in self.ActiveSecurities:
if x.Value.Type == SecurityType.Option and x.Value.Invested == False:
self.RemoveOptionContract(x.Value.Symbol)
def add_options_to_universe(self, Underlying, OptionRight, MinStrike, MaxStrike, MaxExpiry):
contracts = self.OptionChainProvider.GetOptionContractList(Underlying, self.Time) ## Get list of Options Contracts
if len(contracts) == 0:
#self.Log("add_options_to_universe: OptionChainProvider returned no contracts for :" + str(Underlying))
return
filtered_contracts = [x for x in contracts if x.ID.OptionRight == OptionRight and\
x.ID.StrikePrice > MinStrike and\
x.ID.StrikePrice < MaxStrike and\
x.ID.Date < MaxExpiry and \
x.ID.Date > self.Time]
if len(filtered_contracts) == 0:
#self.Log("add_options_to_universe: No options left after filtering for: " + str(Underlying))
return
else:
self.add_contracts_to_universe = False
for symbol in filtered_contracts:
if self.contracts_added_to_universe == self.Settings.DataSubscriptionLimit - 1: break
option = self.AddOptionContract(symbol, Resolution.Minute)
option.PriceModel = OptionPriceModels.CrankNicolsonFD()
option.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.contracts_added_to_universe += 1
Yayaya
MatteMatto , thanks for your sharing!
A quick question: since you are running option trading live with IBKR via QC. Did you notice Greeks differences between IBKR's live real greeks vs QC system's calculated greeks (e.g. via the function OptionPriceModels.CrankNicolsonFD )? Are the differences between the two significant? Because I read people mentioned the two are not always the same…. thus how to deal with such differences inside strategy? Would love to hear your observations or any comments if possible. Thank you!
.ekz.
MatteMatto
This is great. Thanks for sharing this. I'll be trading options from a universe of underlying and this is something I will have to deal with. Thanks for saving me time and effort!
.ekz.
MatteMatto : have you tried using options on other resolutions? Contract data is now available on hourly and daily resolution.
Matt Mcwaters
MatteMatto Thank you for your response! I'd love to hear more about how you've dealt with runtime/streaming issues as I am facing runtime issues right now. Funny enough, recently I discovered the RemoveOptionContract method myself and put together a nearly identical solution to yours! But I'm not sure if it's doing what I think it's doing. The documentation for it states:
Removes the security with the specified symbol. This will cancel all
open orders and then liquidate any existing holdings
My instinct was that removing the options like you've done would reduce len(self.Portfolio.Securities) or len(self.ActiveSecurities) however those numbers stay the same after we run the method you've labeled : remove_uninvested_options_from_universe.
Do you know what exactly RemoveOptionContract does and how it helps with our runtime?
Thank you!!
MatteMatto
Yayaya I have not checked to be honest. I use greeks within my sorting algorithems so even if they are slightly different, they still produce relative numbers to each of the other contacts when im sorting, so it makes no difference to me.
MatteMatto
.ekz. I have not, and as I now use the time dimension to maximise the use of my streaming data subscriptions, this would not be a good option for me.
MatteMatto
Matt Mcwaters I also noticed the same thing, and this is because you cant test for the len(activeSecurities) in the same slice as it does not change until the next time slice is run. e.g any time you bring securities in or out of the universe the updates are queued for the next time slice. so for this reason, I now run my universe updater every 10 minutes, which gives the algo 9 x 1 minute time slices for my algo to do what It needs to do (I think I could theoretically run it down as tight as every 2 minutes, the system would have 1 slice to run code between universe changes).
Does that help?
Varad Kabade
Hi Matt Mcwaters,
The strategy is not rendered useless. To access the greeks after the underlying volatility is warmed up we recommend going through the following threads[1, 2] for detailed examples regarding the same.
Best,
Varad Kabade
.ekz.
Yayaya were you able to resolve issues with the Greeks differences?
If so, please share.
(regarding your earlier comment, about differences between IBKR's live real greeks and QC system's calculated greeks)
cc: @varad-kabade
Louis Szeto
Hi .ekz.
Our Greeks are calculated with choice of your pricing models and data resolution on QuantLib nuget packages. I think you may reduce this discrepency by using BlackScholes since most sources would use that as reference, as well as a very dense data resolution. I'm not sure what price does IB defaultly use to calculate their Greeks, but LEAN uses the latest quote price. You may want to align with that,
Best
Louis
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
MatteMatto
Louis Szeto ,
Just keep in mind, BlackScholes is only relevant for pricing European options as it does not take into account the option to be exercised before the expiry date.
MatteMatto
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!