| Overall Statistics |
|
Total Trades 155 Average Win 40.55% Average Loss -0.16% Compounding Annual Return 4.831% Drawdown 9.700% Expectancy 5.438 Net Profit 60.360% Sharpe Ratio 0.229 Probabilistic Sharpe Ratio 0.000% Loss Rate 97% Win Rate 3% Profit-Loss Ratio 246.85 Alpha 0.068 Beta -0.151 Annual Standard Deviation 0.215 Annual Variance 0.046 Information Ratio -0.276 Tracking Error 0.266 Treynor Ratio -0.325 Total Fees $2375.50 Estimated Strategy Capacity $3400000.00 Lowest Capacity Asset QQQ 31R15TVANMZ8M|QQQ RIWIV7K5Z9LX |
# Watch my Tutorial: https://youtu.be/Lq-Ri7YU5fU
# Options: Options perform best during periods of short sharp drops when volatility spikes.
# Options held till expiry do a great job of reducing drawdown severity but won't boost returns unless the drawdown is
# protracted and still exists at time of Expiry.
from datetime import timedelta
class OptionChainProviderPutProtection(QCAlgorithm):
def Initialize(self):
# set start/end date for backtest
self.SetStartDate(2011, 10, 1)
self.SetEndDate(2021, 10, 1)
# set starting balance for backtest
self.SetCash(400000)
# add the underlying asset
self.equity = self.AddEquity("QQQ", Resolution.Minute)
self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.underlyingSymbol = self.equity.Symbol
# initialize the option contract with empty string
self.contract = str()
self.contractsAdded = set()
# parameters ------------------------------------------------------------
self.OTM = 0.20 # target percentage OTM of put
self.StaggerStrike = 0.05 + self.OTM # Stagger strikes at X% levels OTM
self.DTE = 60 # target 'Calendar' days till expiration
self.DTEBuffer = 15
self.DaysBeforeExp = 2 # number of days before expiry to exit
self.ProfitTarget = 0.5 # OptionValue as a Percentage of Portfolio
self.percentage = 0.97 # percentage of portfolio for underlying asset
self.options_weight = 0.02 * 30 / 365 # This allows roughly X% per year for StrikeStaggeredPuts
# ------------------------------------------------------------------------
# schedule Plotting function 30 minutes after every market open
self.Schedule.On(self.DateRules.EveryDay(self.underlyingSymbol), self.TimeRules.AfterMarketOpen(self.underlyingSymbol, 30), self.Plotting)
def OnData(self, data):
if not data.ContainsKey(self.underlyingSymbol) or data[self.underlyingSymbol] is None or data[self.underlyingSymbol].IsFillForward:
return
# OPTIONS -------------------------------------------
# Close Options Positions.
for contract in self.contractsAdded:
if self.Portfolio[contract].Invested:
if not data.ContainsKey(contract):
continue
# Do nothing if data is NoneType.
if data[contract] is None:
continue
# 2) Close put if Target achieved (Rebalance proceeds?).
optionValue = self.Portfolio[contract].Quantity * 100 * self.Securities[contract].BidPrice
optionExposure = optionValue / self.Portfolio.TotalPortfolioValue
if optionExposure > self.ProfitTarget:
self.SellPut(contract, data)
self.Log(f'Put Target achieved @ {self.Time}')
self.Log(f'BidPrice: {self.Securities[contract].BidPrice}, AskPrice: {self.Securities[contract].AskPrice}')
# 3) Close put before it expires
if self.Time.minute % 5 == 1 and (contract.ID.Date - self.Time) <= timedelta(self.DaysBeforeExp):
self.SellPut(contract, data)
# Initiate our Options position
option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
option_invested = sorted(option_invested, key=lambda x: x.ID.StrikePrice, reverse=True)
buyOption = False
# 1) If no options, BUY.
if not option_invested:
buyOption = True
# 2) If price has exceeded our current strike by X%, BUY
elif option_invested[0]:
buyOption = bool( (1 - option_invested[0].ID.StrikePrice / self.Securities[self.underlyingSymbol].Price) > self.StaggerStrike)
# Don't try to buy on each data point. Wait a few minutes to search for our contract and initiate a position.
if buyOption:
if self.Time.minute % 5 == 0:
self.contract = self.OptionsFilter(data)
if self.Time.minute % 5 == 3 and self.contract and not self.Portfolio[self.contract].Invested:
self.BuyPut(data)
def BuyPut(self, data):
# Do nothing if data is NoneType or data is stale.
if data.ContainsKey(self.contract) and not self.Portfolio[self.contract].Invested:
if data[self.contract] is None or data[self.contract].IsFillForward:
return
quantity = self.CalculateOrderQuantity(self.contract, self.options_weight)
if quantity < 1:
self.Log('Cannot buy Options... too expensive!')
self.SetHoldings(self.contract, self.options_weight)
def SellPut(self, contract, data):
self.SetHoldings(contract, 0) #, ([PortfolioTarget(self.underlyingSymbol, self.percentage)])
self.contract = str()
def OptionsFilter(self, data):
''' OptionChainProvider gets a list of option contracts for an underlying symbol at requested date.
Then you can manually filter the contract list returned by GetOptionContractList.
The manual filtering will be limited to the information included in the Symbol
(strike, expiration, type, style) and/or prices from a History call '''
contracts = self.OptionChainProvider.GetOptionContractList(self.underlyingSymbol, data.Time)
underlyingPrice = self.Securities[self.underlyingSymbol].Price
# filter the out-of-money put options from the contract list which expire close to self.DTE num of days from now
otm_puts = [i for i in contracts if i.ID.OptionRight == OptionRight.Put and
underlyingPrice - i.ID.StrikePrice > self.OTM * underlyingPrice and
(self.DTE - self.DTEBuffer) < (i.ID.Date - data.Time).days < (self.DTE + self.DTEBuffer)]
if len(otm_puts) > 0:
# sort options by closest to self.DTE days from now and desired strike, and pick first
contract = sorted( sorted(otm_puts, key = lambda x: abs((x.ID.Date - self.Time).days - self.DTE)),
key = lambda x: underlyingPrice - x.ID.StrikePrice)[0]
if contract not in self.contractsAdded:
self.contractsAdded.add(contract)
self.AddOptionContract(contract, Resolution.Minute)
return contract
else:
#self.Log('No Options at this time...')
return str()
def Plotting(self):
self.Plot("Data Chart", self.underlyingSymbol, self.Securities[self.underlyingSymbol].Close)
# plot strike of put option
option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
option_invested = sorted(option_invested, key=lambda x: x.ID.StrikePrice)
if option_invested:
self.Plot("Opton Invested", "Options", len(option_invested))
self.Plot("Data Chart", "Strike1", option_invested[0].ID.StrikePrice)
def OnOrderEvent(self, orderEvent):
# log order events
pass
#self.Log(str(orderEvent))