Below an example of a delta-hedged straddle on the SPY.
Strategy in a nutshell:
- sell 1-month At-The-Money call and put options on the SPY,
- delta-hedge once a day (if delta above a threshold),
- close other option in case one is assigned (or both on their last trading day),
- Repeat.
Nate Wine
Alex - Thank you for sharing this!! Excellent work
Alex Muci
You're welcome ;-)
Jing Wu
Hi Alex, thanks for posting this excellent straddle algorithm.
LEAN has this method to get all American public holidays.
self.TradingCalendar.GetDaysByType(TradingDayType.PublicHoliday, startDate, EndDate)
Here is an example to demonstrate this method
Jay Sisodiya
Newbie here. I cloned this algo to understand the code (as I am learning). Why does backtest take so long on this? It has been running (successfully) for last 10+ minutes. Is it because the total time period for the backtest is 1 year and it is using minute resolution?
JordanBaucke
Alex Muci
I've converted your code to C#, and added Jing Wu's suggestion of US Holidays:
if (TradingCalendar
.GetDaysByType(TradingDayType.PublicHoliday, expiry, expiry)
.ToList().Count != 0)
{
Debug("Expiry on Holiday");
return expiry.AddDays(-1);
}
Helped me get into writing something in C# as I'm better with it than Python- just need to buy some data so I can actually test locally (don't like the web-IDE that much).
Still not 100% sure I've got it selling both the call / put or that if my days are "off by 1".
Josh Davidson
My first comment is excellent work! I am new to QuantConnect and have been using this project to learn. I have a question however: What is the utility of the variable _assignedOption?
It is being used only 3 times.
1x in the class declaration:
private bool _assignedOption = false;
And 2x in the OnData function:
if (_assignedOption) { // can't figure out Linq Portfolio.Where(x => x.Value.Invested).Foreach(x => {}) ?? foreach (Securities.SecurityHolding holding in Portfolio.Values) { if (holding.Invested) { Liquidate(holding.Symbol); } } _assignedOption = false; }
So, my question is, how is the code segment in OnData that is executed when _assignedOption is true ever triggered?
It seems this is a bug. And what is happening is that instead of liquidating the symbol here, all of the stock position is being liquidated in the close_options:
if (Portfolio[this.equity_symbol].Invested) { Liquidate(equity.Symbol); }
I am not sure of the effect, but I don't think this is the behavior that is expected.
Alex Muci
Hi Josh, look at my Python code.
self._assignedOption is true in case one of the two options gets assigned (event fire up automatically in the OnAssignmentOrderEvent function - such assignment is simulated by QuantConnect to recreate live trading situations). My choice was to closing all legs of the trade once one of the options got assigned (NB: buyers can exercise their American options before expiry).
Hope this helps.
Andrew Kan
Total noob here. Great algorithm and thanks for sharing. Quick questions:
1. What happens if I changed Resolution.Minute to Resolution.Hour? When I tried to run it, it didnt seem to do anything.
2. When I changed the symbol to, say, "FB" instead of "SPY", it would complain about something non-tradable. Why is that? Which line is it complaining about, specifically?Â
Thanks.
Halldor Andersen
Hi Andrew.
Welcome! There are useful examples and information on options basics in this documentation section on QuantConnect Options APIs.
The option data is only available in minute data resolution. You can use consolidators to construct an hourly bar. Here is an example of how to use consolidators for options.
TedVZ
Not sure what's changed, but sometime in the last month or so my clone of this algorithm stopped working. I was using it as an example, but when I re-clone the C# project referenced above and run the backtest no trades are executed. :(
Jared Broad
needed a hour or two downtime.
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.
TedVZ
Ah, no worries, thanks for the update. I'll try again later.
Josh M
Just checking this out; why is 'premium' added but never called? Best wishes, Josh
Rahul Chowdhury
Hey Josh,
It seems to just be an unused parameter. The best practice is to remove the parameter if it is not in use. However, if we wish to put the parameter into use instead. We could use Premium to define the upper bound on price for the options we want to look at. Perhaps something like
calls = [i for i in sorted_contracts \ if i.Right == OptionRight.Call and i.Strike >= spot_price and i.LastPrice <= self.PREMIUM * spot_price]
Â
Josh M
I'm wondering now if it is possible to modify this strategy to trade calendar spreads 'the tasty way'
Essentially we would buy the 60DTE option at the 40 delta and sell the 30DTE option for the same strike, only taking trades when IVR is <25 and take profit at 10-25%.
It already has the ability to screen for delta and select dates; but I'm wondering how to manage multi leg options based on profit targets?
Rahul Chowdhury
Hey Josh,
It's certainly possible to do so. Here are the ways one might retrieve contracts with 60DTE and 30DTE, and also calculate IVR.We would need to first change the max expiry to 60 days and then in get_contracts, filter our contracts for the contracts with approximately 30DTE and 60DTE.
30_DTE = [contract for contract in chain if contract.Expiry <= self.Time + timedelta(30)] sorted_30_DTE = sorted(30_DTE, key=lambda k:k.Expiry, reverse=True) 30_DTE_approx = sorted_30_DTE[0].Expiry # Contracts expiring approximately 30 days away 30_DTE_contracts = [contract for contract in sorted_30_DTE if contract.Expiry == 30_DTE_approx] # Similar process for 60_DTE
IVR is calculated by finding the one year range of IV and checking where our current IV fits in that range. We can keep track of daily IV by using a scheduled event which stores the today's IV in a rolling window.
def OnEndOfDay(self): for kvp in self.CurrentSlice.OptionChains: if kvp.Key != self.option_symbol: continue spot_price = chain.Underlying.Price chain = kvp.Value # Sort to find ATM contracts sorted_strikes = sorted(chain, key=lambda k:abs(k.Strike - spot_price), reverse=False) # IV of ATM contract curr_IV = sorted_strikes[0].ImpliedVolatility self.IV_window.add(curr_IV)
Then we can use that rolling window to find the range of IVs and calculate the current IVR.
sorted_IVs = sorted(list(self.IV_window)), reverse=True) max_IV = sorted_IVs[0] min_IV = sorted_IVs[-1] IV_range = max_IV - min_IV IVR = current_IV / IV_range
It will be much faster to implement the strategy using the OptionChainProvider rather than using options universe. But I'll leave the full implementation of the strategy to you or anyone else who wants to take a shot at it.Hope that helped!
Best
Rahul
Ernest Shaggleford
Hi Rahul,
re: "It will be much faster to implement the strategy using the OptionChainProvider rather than using options universe."
If using OptionChainProvider, isn't it the case that the option greeks are not available using this method and so the algorithm wouldn't be able to select contracts using Delta ?
Thanks,
ES.
Ernest Shaggleford
Hi Josh M,
I've backtested a number of "tasty way" option strategies and my results haven't been that great, and in some cases can result in blowups depending on position sizing. The concept of trade often and trade small suits a particular statistical probability approach, but in the real world "anything can happen", which option price models do not account for. In fact, there was a particular educational episode on the Tasty network where Tom was discussing some historical test results for put selling, and he made a point that major market sell-offs result in a significant hit to the profitability of this type of strategy.
I'd suggest keeping a healthy dose of skepticism on hand w.r.t to the "tasty" approach, as they are, at the end of the day, a brokerage with a specific agenda.
I'd suggest to always evaluate a strategy using your own backtesting and make your own assessment on the viability and risks.
The QC Lean platform now allows anyone to backtest option strategies, which was previously near impossible for a retail trader, due to the cost, data and infrastructure involved. I tip my hat to Jared and his team for building this.
Best,
ES.
Jared Broad
<3 Thank you ES.
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.
Apollos Hill
this algo doesn't seem to work anymore. i've given it a shot .Â
Runtime Error: Trying to perform a summation, subtraction, multiplication or division between 'float' and 'decimal.Decimal' objects throws a TypeError exception. To prevent the exception, ensure that both values share the same type.
at OnData
unit_price = self.Securities[self.equity_symbol].Price * d.Decimal(100.0) # share price x 100
===
at Python.Runtime.PyObject.Invoke (Python.Runtime.PyTuple args in main.py:line 107
TypeError : unsupported operand type(s) for *: 'float' and 'decimal.Decimal' (Open Stacktrace)
Alex Muci
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!