Option Strategies
Short Box Spread
Introduction
A Short Box Spread is the inverse of a box spread, as well as the combination of a bear call spread and a bull put spread. It consists of buying an OTM call at strike $A$, selling an ITM put at strike $A$, buying an OTM put and strike $B < A$, and selling an ITM call at strike $B$, where all of the contracts have the same expiry date. This strategy serves as an delta-neutral arbitration from Option mispricing. Note that it only attains a true profit when the risk-free return is greater than the risk-free interest rate.
Implementation
Follow these steps to implement the short box spread strategy:
- In the
Initializeinitializemethod, set the start date, set the end date, subscribe to the underlying Equity, and create an Option universe. - In the
OnDataon_datamethod, select the strike and expiry of the contracts in the strategy legs. - In the
OnDataon_datamethod, select the contracts and place the orders.
private Symbol _symbol;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
SetCash(100000);
UniverseSettings.Asynchronous = true;
var option = AddOption("GOOG", Resolution.Minute);
_symbol = option.Symbol;
option.SetFilter(universe => universe.IncludeWeeklys().BoxSpread(30, 5));
} def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
self.set_cash(100000)
self.universe_settings.asynchronous = True
option = self.add_option("GOOG", Resolution.MINUTE)
self._symbol = option.symbol
option.set_filter(lambda universe: universe.include_weeklys().box_spread(30, 5))
The BoxSpreadbox_spread filter narrows the universe down to just the four contracts you need to form a short box spread.
public override void OnData(Slice slice)
{
if (Portfolio.Invested) return;
// Get the OptionChain
if (!slice.OptionChains.TryGetValue(_symbol, out var chain)) return;
// Select an expiry date and ITM & OTM strike prices
var expiry = chain.Max(x => x.Expiry);
var contracts = chain.Where(x => x.Expiry == expiry).ToList();
var higherStrike = contracts.Max(x => x.Strike);
var lowerStrike = contracts.Min(x => x.Strike); def on_data(self, slice: Slice) -> None:
if self.portfolio.invested:
return
# Get the OptionChain
chain = slice.option_chains.get(self._symbol, None)
if not chain:
return
# Select an expiry date and ITM & OTM strike prices
expiry = max([x.expiry for x in chain])
contracts = [x for x in chain if x.expiry == expiry]
lower_strike = min([x.strike for x in contracts])
higher_strike = max([x.strike for x in contracts])
Approach A: Call the OptionStrategies.ShortBoxSpreadOptionStrategies.short_box_spread method with the details of each leg and then pass the result to the Buybuy method.
var shortBoxSpread = OptionStrategies.ShortBoxSpread(_symbol, higherStrike, lowerStrike, expiry); Buy(shortBoxSpread, 1);
short_box_spread = OptionStrategies.short_box_spread(self._symbol, higher_strike, lower_strike, expiry) self.buy(short_box_spread, 1)
Approach B: Create a list of Leg objects and then call the Combo Market Ordercombo_market_order, Combo Limit Ordercombo_limit_order, or Combo Leg Limit Ordercombo_leg_limit_order method.
// Select the call and put contracts
var itmCall = chain.Single(x => x.Expiry == expiry && x.Strike == lowerStrike && x.Right == OptionRight.Call);
var otmCall = chain.Single(x => x.Expiry == expiry && x.Strike == higherStrike && x.Right == OptionRight.Call);
var itmPut = chain.Single(x => x.Expiry == expiry && x.Strike == higherStrike && x.Right == OptionRight.Put);
var otmPut = chain.Single(x => x.Expiry == expiry && x.Strike == lowerStrike && x.Right == OptionRight.Put);
var legs = new List<Leg>()
{
Leg.Create(itmCall.Symbol, -1),
Leg.Create(itmPut.Symbol, -1),
Leg.Create(otmCall.Symbol, 1),
Leg.Create(otmPut.Symbol, 1),
};
ComboMarketOrder(legs, 1); # Select the call and put contracts
itm_call = [x for x in chain if x.right == OptionRight.CALL and x.expiry == expiry and x.strike == lower_strike][0]
otm_call = [x for x in chain if x.right == OptionRight.CALL and x.expiry == expiry and x.strike == higher_strike][0]
itm_put = [x for x in chain if x.right == OptionRight.PUT and x.expiry == expiry and x.strike == higher_strike][0]
otm_put = [x for x in chain if x.right == OptionRight.PUT and x.expiry == expiry and x.strike == lower_strike][0]
legs = [
Leg.create(itm_call.symbol, -1),
Leg.create(itm_put.symbol, -1),
Leg.create(otm_call.symbol, 1),
Leg.create(otm_put.symbol, 1),
]
self.combo_market_order(legs, 1)
Strategy Payoff
This is a fixed payoff, delta-neutral strategy. The payoff is
$$ \begin{array}{rcll} C_T^{ITM} & = & (S_T - K_{-})^{+}\\ C_T^{OTM} & = & (S_T - K_{+})^{+}\\ P_T^{ITM} & = & (K_{+} - S_T)^{+}\\ P_T^{OTM} & = & (K_{-} - S_T)^{+}\\ Payoff_T & = & (C_{T_0}^{ITM} - C_T^{ITM} + P_{T_0}^{ITM} - P_T^{ITM} - C_{T_0}^{OTM} + C_T^{OTM} - P_{T_0}^{OTM} + P_T^{OTM})\times m - fee\\ & = & (K_{-} - K_{+} + C_{T_0}^{ITM} + P_{T_0}^{ITM} - C_{T_0}^{OTM} - P_{T_0}^{OTM})\times m - fee \end{array} $$ $$ \begin{array}{rcll} \textrm{where} & C_T^{ITM} & = & \textrm{ITM Call value at time T}\\ & C_T^{OTM} & = & \textrm{OTM Call value at time T}\\ & P_T^{ITM} & = & \textrm{ITM Put value at time T}\\ & P_T^{OTM} & = & \textrm{OTM Put value at time T}\\ & S_T & = & \textrm{Underlying asset price at time T}\\ & K_{+} & = & \textrm{Higher strike price}\\ & K_{-} & = & \textrm{Lower strike price}\\ & Payoff_T & = & \textrm{Payout total at time T}\\ & C_{T_0}^{ITM} & = & \textrm{ITM Call price when the trade opened (debit paid)}\\ & C_{T_0}^{OTM} & = & \textrm{OTM Call price when the trade opened (credit received)}\\ & P_{T_0}^{ITM} & = & \textrm{ITM Put price when the trade opened (debit paid)}\\ & P_{T_0}^{OTM} & = & \textrm{OTM Put price when the trade opened (credit received)}\\ & m & = & \textrm{Contract multiplier}\\ & T & = & \textrm{Time of expiration} \end{array} $$The following chart shows the payoff at expiration:
The payoff is only dependent on the strike price and the initial asset prices.
If the Option is American Option, there is a risk of early assignment on the contracts you sell.
Example
The following table shows the price details of the assets in the algorithm:
| Asset | Price ($) | Strike ($) |
|---|---|---|
| ITM Call | 23.00 | 810.00 |
| ITM Put | 23.80 | 857.50 |
| OTM Call | 1.85 | 857.50 |
| OTM Put | 2.75 | 810.00 |
| Underlying Equity at expiration | 843.25 | - |
Therefore, the payoff is
$$ \begin{array}{rcll} Payoff_T & = & (K_{-} - K_{+} + C_0^{ITM} + P_0^{ITM} - C_0^{OTM} - P_0^{OTM})\times m - fee\\ & = & (810.00 - 857.50 + 23.00 + 23.80 - 1.85 - 2.75)\times100 - 1.00\times4\\ & = & -534.00\\ \end{array} $$So, the strategy loses $534.
The following algorithm implements a short box spread Option strategy:
public class ProtectiveCollarStrategy : QCAlgorithm
{
private Symbol _equitySymbol;
private Symbol _optionSymbol;
private bool _investEver = false;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
SetCash(100000);
UniverseSettings.Asynchronous = true;
_equitySymbol = AddEquity("GOOG").Symbol;
var option = AddOption("GOOG", Resolution.Minute);
_optionSymbol = option.Symbol;
option.SetFilter(universe => universe.IncludeWeeklys().BoxSpread(30, 5));
}
public override void OnData(Slice slice)
{
if (_investEver) return;
// Get the OptionChain
var chain = slice.OptionChains.get(_optionSymbol, null);
if (chain == null || chain.Count() == 0) return;
// Select an expiry date
var expiry = chain.OrderBy(x => x.Expiry).Last().Expiry;
// Select the strike prices of the contracts
var orderedContracts = chain.OrderBy(x => x.Strike);
var higherStrike = orderedContracts.Last().Strike;
var lowerStrike = orderedContracts.First().Strike;
var boxSpread = OptionStrategies.ShortBoxSpread(_optionSymbol, higherStrike, lowerStrike, expiry);
Buy(boxSpread, 1);
_investEver = true;
}
public override void OnEndOfDay(Symbol symbol)
{
if (symbol == _equitySymbol)
{
Log($"{Time}::{symbol}::{Securities[symbol].Price}");
}
}
} class ProtectiveCollarOptionStrategy(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1);
self.set_end_date(2024, 12, 31);
self.set_cash(100000)
self.universe_settings.asynchronous = True
self.equity_symbol = self.add_equity("GOOG").symbol
option = self.add_option("GOOG", Resolution.Minute)
self.option_symbol = option.symbol
option.set_filter(lambda universe: universe.include_weeklys().box_spread(30, 5))
self.invest_ever = False
def on_data(self, slice: Slice) -> None:
if self.invest_ever: return
# Get the OptionChain
chain = slice.option_chains.get(self.option_symbol, None)
if not chain: return
# Select an expiry date
expiry = sorted(chain, key = lambda x: x.expiry)[-1].expiry
# Select the strike prices of the contracts
ordered_contracts = sorted(chain, key = lambda x: x.strike)
higher_strike = ordered_contracts[-1].strike
lower_strike = ordered_contracts[0].strike
box_spread = OptionStrategies.short_box_spread(self.option_symbol, higher_strike, lower_strike, expiry)
self.buy(box_spread, 1)
self.invest_ever = True
def on_end_of_day(self, symbol: Symbol) -> None:
if symbol == self.equity_symbol:
self.log(f"{self.time}::{symbol}::{self.securities[symbol].price}")