Overall Statistics
Total Trades
90
Average Win
1.08%
Average Loss
-0.74%
Compounding Annual Return
-1.758%
Drawdown
3.500%
Expectancy
-0.063
Net Profit
-1.474%
Sharpe Ratio
-0.769
Sortino Ratio
-0.637
Probabilistic Sharpe Ratio
4.525%
Loss Rate
62%
Win Rate
38%
Profit-Loss Ratio
1.47
Alpha
-0.015
Beta
-0.022
Annual Standard Deviation
0.023
Annual Variance
0.001
Information Ratio
-0.473
Tracking Error
0.31
Treynor Ratio
0.801
Total Fees
$48.00
Estimated Strategy Capacity
$490000.00
Lowest Capacity Asset
AAPL XJ43QAQNONOM|AAPL R735QTJ8XC9X
Portfolio Turnover
3.74%
#region imports
from AlgorithmImports import *
#endregion
class VirtualYellowGiraffe(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 17)
        self.SetEndDate(2020, 11, 17)
        self.SetCash(100000) 
        self.equity = self.AddEquity("AAPL", Resolution.Minute)
        
        self.InitOptionsAndGreeks(self.equity)
        

    ## Initialize Options settings, chain filters, pricing models, etc
    ## ====================================================================
    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 leasr 30 days
        self.SetWarmup(30, Resolution.Daily)

        ## 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
        theOptionSubscription = self.AddOption(theEquity.Symbol)

        ## 5. set the pricing model, to calculate Greeks and volatility
        theOptionSubscription.PriceModel = OptionPriceModels.CrankNicolsonFD()  # both European & American, automatically
                
        ## 6. Set the function to filter out strikes and expiry dates from the option chain
        theOptionSubscription.SetFilter(self.OptionsFilterFunction)

    def OnData(self, data):
        
        ## If we're done warming up, and not invested, Sell a put. 
        
        if (not self.IsWarmingUp) and (not self.Portfolio.Invested) and ((data.Time.hour == 11) and (data.Time.minute == 00)): 
            
                self.BuyCallSpread()
    ## Use Delta to select a put contract to sell
    ## ==================================================================          
    def BuyCallSpread(self):
        
        ## Sell a 20 delta put expiring in 2 weeks (14 days)
        callContractTuple = self.SelectCallSpreadLegsByDelta(self.equity.Symbol, .70, 'Long', 12, OptionRight.Call)
                                                    
        #putContract = self.SelectContractByDelta(self.equity.Symbol, .30, 10, OptionRight.Put)             
        ## construct an order message -- good for debugging and order rrecords
        if callContractTuple is not None:
            orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} |" + \
                        f"Buying Call Spread {callContractTuple[0].Strike} {callContractTuple[1].Strike} "+ \
                        f"For limit price of {round(callContractTuple[0].AskPrice - callContractTuple[1].AskPrice,2)} " + \
                        f"Underlying price {callContractTuple[0].UnderlyingLastPrice}"
                        
            self.Debug(f"{self.Time} {orderMessage}")
            #self.Order(callContractTuple[0].Symbol, -1, False, orderMessage  )   


            # Create order legs
            legs = []
            quantities = [1, -1]
            for i, contract in enumerate(callContractTuple):
                legs.append(Leg.Create(contract.Symbol, quantities[i]))
                
            # Calculate limit price
            limit_price = callContractTuple[0].AskPrice - callContractTuple[1].AskPrice

            # Place order
            self.ComboLimitOrder(legs, 1, limit_price)

    ## Get an options contract that matches the specified criteria:
    ## Underlying symbol, delta, days till expiration, Option right (put or call)
    ## ============================================================================
    # ============================================================================
    
    def OptionsFilterFunction(self, optionsContractsChain):

        strikeCount  = 1 # no of strikes around underyling price => for universe selection
        minExpiryDTE = 10  # min num of days to expiration => for uni selection
        maxExpiryDTE = 15  # max num of days to expiration => for uni selection
        
        return optionsContractsChain.IncludeWeeklys()\
                                    .Strikes(-strikeCount, strikeCount)\
                                    .Expiration(timedelta(minExpiryDTE), timedelta(maxExpiryDTE))

   
    def SelectCallSpreadLegsByDelta(self, symbolArg, buystrikeDeltaArg, LongOrShortSpreadArg, expiryDTE, optionRightArg= OptionRight.Call):

        canonicalSymbol = self.AddOption(symbolArg)
        if not canonicalSymbol.Symbol in self.CurrentSlice.OptionChains:
            # self.Log('No option chains available at this time')
            return
        theOptionChain  = self.CurrentSlice.OptionChains[canonicalSymbol.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
        applicableContractList = sorted(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-buystrikeDeltaArg), reverse=False)
        
        if len(applicableContractList) >= 2: #Got Enough contracts
            closestBuyContract = applicableContractList[0]
            pairedSellContract = applicableContractList[1]
        
        else:
            return
        
        return [pairedSellContract, closestBuyContract]

    
    
 ##==================================================================================    
 ##==================================================================================  
    
    def SelectContractByDelta(self, symbolArg, strikeDeltaArg, expiryDTE, optionRightArg= OptionRight.Call):

        canonicalSymbol = self.AddOption(symbolArg)
        #if self.CurrentSlice.OptionChains[canonicalSymbol.Symbol]
        theOptionChain  = self.CurrentSlice.OptionChains[canonicalSymbol.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

    ## The options filter function.
    ## Filter the options chain so we only have relevant strikes & expiration dates. 
    ## =============================================================================
    
    
    
    def SellAnOTMPut(self):
        
        ## Sell a 20 delta put expiring in 2 weeks (14 days)
        putContract = self.SelectCallSpreadLegsByDelta(self.equity.Symbol, .30, 5, 'Long', 10, OptionRight.Put)
                                                    
        #putContract = self.SelectContractByDelta(self.equity.Symbol, .30, 10, OptionRight.Put)             
        ## construct an order message -- good for debugging and order rrecords
        if putContract is not None:
            orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} |" + \
                        f"Sell {putContract.Symbol} "+ \
                        f"({round(putContract.Greeks.Delta,2)} Delta)"
                        
            self.Debug(f"{self.Time} {orderMessage}")
            self.Order(putContract.Symbol, -1, False, orderMessage  )