Overall Statistics
Total Orders
2
Average Win
0%
Average Loss
0%
Compounding Annual Return
4.844%
Drawdown
0.000%
Expectancy
0
Start Equity
100000
End Equity
100060.5
Net Profit
0.060%
Sharpe Ratio
4.448
Sortino Ratio
0
Probabilistic Sharpe Ratio
75.127%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0.015
Beta
0.021
Annual Standard Deviation
0.003
Annual Variance
0
Information Ratio
0.251
Tracking Error
0.084
Treynor Ratio
0.671
Total Fees
$2.00
Estimated Strategy Capacity
$73000.00
Lowest Capacity Asset
SPY WMDHMEFALEG6|SPY R735QTJ8XC9X
Portfolio Turnover
0.23%
# region imports
from AlgorithmImports import *
# endregion
"""
https://www.quantconnect.com/forum/discussion/3245/using-option-greeks-to-select-option-contracts-to-trade

if you need Greeks:
 A) Filter and B) AddOption 
    more efficient than 
 C) OptionChainProvider and D) AddOptionContract
"""

from QuantConnect.Securities.Option import OptionPriceModels
#from datetime import timedelta
import datetime
import decimal as d

class DeltaHedgedStraddleAlgo(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 4, 17)
        self.SetEndDate(2017, 4, 22)
        self.SetCash(100000) 
        self.equity = self.AddEquity("SPY", Resolution.HOUR)
        self.symbol = self.equity.Symbol

        self._assignedOption = False

        self.CloseOnDTE = timedelta(days=21)
        self.SellGutsDelta = 0.5
        self.OpenDTE = 90
                
        self.InitOptionsAndGreeks(self.equity)

        
        # -----------------------------------------------------------------------------
        # scheduled functions
        # -----------------------------------------------------------------------------

        """ self.Schedule.On(self.DateRules.EveryDay(self.symbol), 
                         self.TimeRules.BeforeMarketClose(self.symbol, 10),      
                         Action(self.close_options)) """

    def InitOptionsAndGreeks(self, theEquity ):

        ## 1. Specify the data normalization mode (must be 'Raw' for options)
        theEquity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        
        ## 2. Set Warmup period of at least 30 days
        self.SetWarmup(30, Resolution.HOUR)

        ## 3. Set the security initializer to call SetMarketPrice
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
        
        ## 4. Subscribe to the option feed for the symbol
        self.theOptionSubscription = self.AddOption(theEquity.Symbol)
        
        ## 5. set the pricing model, to calculate Greeks and volatility
        self.theOptionSubscription.PriceModel = OptionPriceModels.CrankNicolsonFD()  # both European & American, automatically
               
        ## 6. Set the function to filter out strikes and expiry dates from the option chain
        self.theOptionSubscription.SetFilter(self.OptionsFilterFunction)
         
    def on_securities_changed(self, changes):
        for security in [x for x in changes.added_securities if x.type == SecurityType.OPTION]:
            if security.cache.properties.contains_key('iv'):
                continue
            symbol = security.symbol
            right = OptionRight.CALL if symbol.id.option_right == OptionRight.PUT else OptionRight.PUT
            mirror_symbol = Symbol.create_option(symbol.id.underlying.symbol, symbol.id.market, symbol.id.option_style, right, symbol.id.strike_price, symbol.id.date)
            security.iv = self.iv(symbol, mirror_symbol, resolution=Resolution.HOUR)
            security.d = self.d(symbol, mirror_symbol, resolution=Resolution.HOUR)
            security.g = self.g(symbol, mirror_symbol, resolution=Resolution.HOUR)
            security.v = self.v(symbol, mirror_symbol, resolution=Resolution.HOUR)
            security.r = self.r(symbol, mirror_symbol, resolution=Resolution.HOUR)
            security.t = self.t(symbol, mirror_symbol, resolution=Resolution.HOUR)

    
    ## Initialize Options settings, chain filters, pricing models, etc
    ## ====================================================================
      
    def OnData(self, data):

        if self.IsWarmingUp: return
        
        # 1. If we're done warming up, and not invested, Sell a put and a call. 
        if (not self.Portfolio.Invested) and self.HourMinuteIs(11, 00):
            if data.Bars.ContainsKey(self.symbol):
                self.SellAnOTMPut(data)
                self.Debug("invested")
    

    ## ==================================================================
    def SellAnOTMPut(self,data):
        
        ## Sell a 50 delta put expiring in 30 days
        putContract = self.SelectContractByDelta(self.equity.Symbol,data, self.SellGutsDelta, self.OpenDTE, OptionRight.Put)
        callContract = self.SelectContractByDelta(self.equity.Symbol,data, self.SellGutsDelta, self.OpenDTE, OptionRight.Call)
        
        ## construct an order message -- good for debugging and order rrecords
        orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} |" + \
                       f"Sell {putContract.Symbol} "+ \
                       f"({round(putContract.Greeks.Gamma,2)} Gamma)"+ \
                       f"({round(putContract.Greeks.Delta,2)} Delta)"+ \
                       f"Sell {callContract.Symbol} "+ \
                       f"({round(callContract.Greeks.Gamma,2)} Gamma)"+ \
                       f"({round(callContract.Greeks.Delta,2)} Delta)"
                       
        self.Debug(f"{self.Time} {orderMessage}")
        self.Order(putContract.Symbol, -1, False, orderMessage  ) 
        self.Order(callContract.Symbol, -1, False, orderMessage  )
        
        self.GetGreeks(data,callContract.Symbol)
        self.debug("SPY gamma {}" .format(self.gamma))
        self.debug("SPY delta {}" .format(self.delta))
   
    ## Get an options contract that matches the specified criteria:
    ## Underlying symbol, delta, days till expiration, Option right (put or call)
    ## ============================================================================
    def SelectContractByDelta(self, symbolArg,data, strikeDeltaArg, expiryDTE, optionRightArg= OptionRight.Call):

        #canonicalSymbol = self.AddOption(symbolArg)
        theOptionChain  = data.OptionChains[self.theOptionSubscription.Symbol]
        theExpiryDate   = self.Time + timedelta(days=expiryDTE)
        
        ## Filter the Call/Put options contracts
        filteredContracts = [x for x in theOptionChain if x.Right == optionRightArg] 

        ## Sort the contracts according to their closeness to our desired expiry
        contractsSortedByExpiration = sorted(filteredContracts, key=lambda p: abs(p.Expiry - theExpiryDate), reverse=False)
        closestExpirationDate = contractsSortedByExpiration[0].Expiry                                        
                                            
        ## Get all contracts for selected expiration
        contractsMatchingExpiryDTE = [contract for contract in contractsSortedByExpiration if contract.Expiry == closestExpirationDate]
    
        ## Get the contract with the contract with the closest delta
        closestContract = min(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-strikeDeltaArg))      

        return closestContract

    def GetGreeks(self,data,symb):
        chain = data.option_chains.get(self.theOptionSubscription.symbol)
        if chain:
            option = self.securities[symb]
            self.Debug("cont symb {}" .format(option.symbol))
            iv = option.iv.current.value
            self.delta = option.d.current.value
            self.gamma = option.g.current.value
            vega = option.v.current.value
            rho = option.r.current.value
            theta = option.t.current.value

    ## The options filter function.
    ## Filter the options chain so we only have relevant strikes & expiration dates. 
    ## =============================================================================
    def OptionsFilterFunction(self, optionsContractsChain):

        strikeCount  = 10 # no of strikes around underyling price => for universe selection
        minExpiryDTE = 0  # min num of days to expiration => for uni selection
        maxExpiryDTE = self.OpenDTE + 30  # max num of days to expiration => for uni selection
        
        return optionsContractsChain.IncludeWeeklys()\
                                    .Strikes(-strikeCount, strikeCount)\
                                    .Expiration(timedelta(minExpiryDTE), timedelta(maxExpiryDTE))
   
    def HourMinuteIs(self, hour, minute):
        return self.Time.hour == hour and self.Time.minute == minute
 
    def OnAssignmentOrderEvent(self, assignmentEvent):
        self.Log(str(assignmentEvent))
        self._assignedOption = True