Overall Statistics
Total Trades
6
Average Win
22.55%
Average Loss
-7.71%
Compounding Annual Return
235.347%
Drawdown
8.100%
Expectancy
0.962
Net Profit
26.800%
Sharpe Ratio
7.496
Probabilistic Sharpe Ratio
96.319%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
2.92
Alpha
0.801
Beta
1.867
Annual Standard Deviation
0.232
Annual Variance
0.054
Information Ratio
7.403
Tracking Error
0.167
Treynor Ratio
0.93
Total Fees
$150.00
Estimated Strategy Capacity
$600000.00
Lowest Capacity Asset
AAPL 30IZW3B3L6QH2|AAPL R735QTJ8XC9X
        
'''     Original
    def TradeOptions(self,slice):
        # If there is undelying assets in portfolio at expiration, liquidate the stocks in order to roll into new contracts
        if self.Portfolio["GOOG"].Quantity != 0:
                self.Liquidate()
        
        if not self.Portfolio.Invested and self.Time.hour != 0 and self.Time.minute != 0: 
            for i in slice.OptionChains:
                chain = i.Value
                contract_list = [x for x in chain]
                # if there is no optionchain or no contracts in this optionchain, pass the instance
                if (slice.OptionChains.Count == 0) or (len(contract_list) == 0): 
                    return   
    
                # sorted the optionchain by expiration date and choose the furthest date
                expiry = sorted(chain,key = lambda x: x.Expiry)[-1].Expiry
                # filter the call and put options from the contracts
                call = [i for i in chain if i.Expiry == expiry and i.Right == 0]
                put = [i for i in chain if i.Expiry == expiry and i.Right == 1]
    
                # sorted the contracts according to their strike prices 
                call_contracts = sorted(call,key = lambda x: x.Strike)    
                put_contracts = sorted(put,key = lambda x: x.Strike)    
                if len(call_contracts) == 0 or len(put_contracts) == 0 : continue
    
                otm_put_lower = put_contracts[0]
                otm_put = put_contracts[10]
                otm_call = call_contracts[-10]
                otm_call_higher = call_contracts[-1]
                self.trade_contracts = [otm_call.Symbol,otm_call_higher.Symbol,otm_put.Symbol,otm_put_lower.Symbol]
    
                # if there is no securities in portfolio, trade the options 
                self.Buy(otm_put_lower.Symbol ,1)
                self.Sell(otm_put.Symbol ,1)
                self.Sell(otm_call.Symbol ,1)
                self.Buy(otm_call_higher.Symbol ,1)
'''     
from datetime import timedelta
import QuantConnect as qc
'''
Questions
Portfolio vs Securities usage of Chain / Contract? 
(Heirarchy + Return)

Delta (not seemingly accurate?) -- Pricing Model needed? 

Margin Issue? 

Extras:

Managing Limits Incrementally with OnOrder event? 
'''

class IronCondorAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 2, 1)
        self.SetEndDate(2017, 4, 15)
        self.SetCash(100000) #CHANGE THIS BACK
        
        self.tickers = ['AAPL']
        for i in self.tickers:
            equity = self.AddEquity(i, Resolution.Minute)
            option = self.AddOption(i, Resolution.Minute)
            
            # some of the OptionPriceModels are unreliable
            option.PriceModel = qc.Securities.Option.OptionPriceModels.CrankNicolsonFD()
            option.PriceModel.EnableGreekApproximation = True
            
            
        #THESE only work because it's ONE symbol
        self.symbol = option.Symbol # identifier for the OptionChain for AAPL not any specific contract
        self.ul = equity.Symbol
        option.SetFilter(self.UniverseFunc)
        
        self.SetBenchmark(equity.Symbol)
        
        self.stk_dist = 5 #CHANGED FROM 10
        self.max_delta = .5 #Not used bc not accurate ...? (Need pricingmodel maybe?)
        self.tgt_dte = 40
        
        self.use_limits = False
        
        self.pf_util = .5
        
        self.cts = 100 ## IF DEFINED: Override ^^ Calc w Margin
        
        
        # symbols = self.OptionChainProvider.GetOptionContractList(underlying_symbol, self.Time)
        
        # symbols[0].Underlying
        
    def OnData(self,slice):
        '''
        #How to check for equity holdings / Clear them
        for kvp in self.Securities:       #-- ORIGINALLY this was SECURITIES
            symbol = kvp.Key
            id = symbol.ID
            security_type = id.SecurityType
            if security_type != SecurityType.Option:
                if self.Portfolio[symbol].Invested:
                    self.Liquidate()
        '''
        #Simple version of clearing any Assigned.
        if self.Portfolio[self.ul].Quantity != 0:
            self.Liquidate()
        
        #If holding options
        if self.Portfolio.Invested: 
            self.ExitExpiring() #Check for Expiring options.
            return 
        
        # no positions, every minute
        short_put, long_put = self.GetBullPut(slice, self.max_delta, self.stk_dist, self.tgt_dte)
        
        if not short_put or not long_put: return 

        usd_per_spread = self.GetMarginReq(short_put, long_put)
        if usd_per_spread == 0:
            usd_per_spread = self.stk_distance
        num_cts = self.Portfolio.MarginRemaining * self.pf_util / usd_per_spread
        self.Debug(f'Number Contracts -- {num_cts}')
        self.Debug(f'PF$ {self.Portfolio.MarginRemaining} vs {num_cts * usd_per_spread}')
        if self.cts:
            num_cts = self.cts
        self.ExecuteOptions(short_put, long_put, num_cts, use_lmts = self.use_limits)
        
        #Close Expiring? (Drag in from my example in other script)
        
     
    def GetMarginReq(self, sell, buy):
        ## Accepts FULL OptionChains (from SetFilter)
        #for i in zip(sell, buy): ## For IC?
        
        credit = sell.BidPrice - buy.AskPrice
        distance = sell.Strike - buy.Strike
        self.Debug(f'Credit: {credit}, Distance: {distance}')
        return (distance - credit) * 100
        
        
        
        
        
    '''   Alternate Lookup -- if easier to look up one opt at a time
    def GetOption(self, slice, right,  dte, stk=None, delta=None):
        if isinstance(dte, int):
            tgt_expiry = self.Time + timedelta(dte)
        else:
            tgt_expiry = dte 
          
        for i in slice.OptionChains:
            symbol = i.Key
            chain = i.Value
            if len(x for x in chain) == 0: return
            
            rights = [i for i in chain if i.Right == right]
            
            #Expiry
            sort_by_exp = sorted(rights, key=lambda x: abs(x.Expiry - tgt_expiry), reverse=False) #ASC
            #if not sort_by_exp[0]: return 
            exp = sort_by_exp[0].Expiry
            exp_rts = [i for i in rights if i.Expiry == exp]
            
            if len(exp_rts) == 0: return
        
            if stk:
                return sorted(exp_rts, key=lambda x: abs(x.Strike - stk), reverse=False)[0]
            
            if delta:
                return sorted(exp_rts, key=lambda x: abs(x.Greeks.Delta - delta), reverse=False)[0]
    '''   
            

    ## Easier to write a 'findOption' function, and find EACH component of the spread?
    def GetBullPut(self, slice, max_delta, stk_dist, dte):
        
        inner, outer = None, None
        
        #
        # Slice.OptionChains most recent option chains data, most recent minute
        #
        aapl_option_chain = slice.OptionChains[self.symbol] # pick out aapl option
        
        # if you know the symbol of a contract, you can use this
        # self.AddOptionContract
        
        for i in slice.OptionChains:
            symbol = i.Key
            chain = i.Value
            if len([x for x in chain]) == 0:
                return 

            #Expiry
            tgt_expiry = self.Time + timedelta(dte)
            
            dist_to_expiry = sorted(chain, key=lambda x: abs(tgt_expiry - x.Expiry), reverse=False) #ASCENDING 
            if len(dist_to_expiry) == 0: return
            expiry = dist_to_expiry[0].Expiry
            
            exp_puts = [i for i in chain if i.Right == OptionRight.Put and i.Expiry == expiry]
            
            #clusters = [i for i in exp_puts if i.Strike % 5 == 0]              # HOW TO GET EVEN NUMS
            ## Ignore Greeks Initially -- being wierd?
            
            #Greeks
            
            filter_delta = [i for i in exp_puts if abs(i.Greeks.Delta) < max_delta]
            if len(filter_delta) < 2:
                self.Debug(f'Not enough options meet delta filter.')
                return 
            
            #Get HIGHEST abs delta here
            sort_by_delta = sorted(exp_puts, key=lambda x: abs(x.Greeks.Delta), reverse=True) #DESC
            inner = sort_by_delta[0]
            self.Debug(f'Inner -- {inner.Strike} d: {inner.Greeks.Delta}')
            
            
            #USING SOLELY STK BASED CALC
            ul = chain.Underlying.Price
            
            sort_by_ul_dist = sorted(exp_puts, key=lambda x: abs(x.Strike - ul), reverse=False) #ASC
            #How to get 5 multiples... filter by them early (if x.Strike % 5 == 0) ? 
            inner = sort_by_ul_dist[0]
            
            tgt_outer_stk = inner.Strike - stk_dist                         #HARD CODED TEMPORARILY
            self.Debug(f'Tgt Lower Stk {tgt_outer_stk}')
            outer = sorted(exp_puts, key=lambda x: abs(tgt_outer_stk - x.Strike), reverse = False)[0] #ASC
            self.Debug(f'Lower -- {outer.Strike}')
        return inner, outer
        
        
    #def GetRiskRewardBullPut(self, slice, stk_dist, ) 
    #How to even search this one? Use Margin + Credit to FIND them
            


        
        
    def ExecuteOptions(self, sell, buy, num_cts=1, use_lmts = False):
        if use_lmts:
            ask = buy.AskPrice
            bid = sell.BidPrice
            
            ## THINK this needs symbol BC this is the FULL Security (vs the Symbol object)
            #Do I really need Symbol here? ----------------------------------- TRY W + W/OUT
            self.LimitOrder(buy.Symbol, num_cts, ask)
            self.LimitOrder(sell.Symbol, -num_cts, bid)
            #Could manage these limits with OnOrderEvent ...(Decrease price slowly until filled... if pending)
        else:
            self.MarketOrder(buy.Symbol, num_cts)
            self.MarketOrder(sell.Symbol, -num_cts)
        self.Debug(f'Order Submitted: -{sell.Strike} / +{buy.Strike}')
        
    #This is always showing DTE of 1...?
    def ExitExpiring(self, only_itm=False):
        
        #invested = [kvp for kvp in self.Portfolio if self.Portfolio[kvp.Key].Invested] #?
        # expiries = [x.Key.ID.Date for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
        # self.Debug(f'expiries -- {expiries}')
        # self.Debug(f'Diffs -- {[(i - self.Time) for i in expiries]}')
        # self.Debug(f'DTEs -- {[(i-self.Time).days for i in expiries]}')
        
        # return
        
        
        
        # collection of dictionaries, 1 dictionary for slice.TradeBars (symbol, tradebars), 1 dictionary for
        # OptionChains (symbol, OptionChain)
        
        # for i in myDict.items()
        
        # for kvp in self.CurrentSlice.OptionChains:
        #     chain_symbol = kvp.Key
        #     chain_contracts = kvp.Value
        #     # AAPL chain, QQQ Chain, 
            
        #     # loop through specific contracts of that chain
        #     for contract in chain_contracts:
        
        
        # list_of_symbol_objects = self.OptionChainProvider.GetOptionContractList()
        
        # Symbol.cs
        # Symbol.ID - SecurityIdentifier.cs
        # SecurityIdentifier.cs contains basic information about security
        
        #Shold this be self.Securities? 
        
        
        # iterating through every security, including otions, futures, equities
        
        # Portfolio <keys=Symbol.cs, value=PortfolioHolding.cs>, element for every security invested and not invested, 
        # that has a data subscription
        for kvp in self.Portfolio: 
            symbol = kvp.Key
            holding = kvp.Value
            id = symbol.ID
            if id.SecurityType == SecurityType.Option and self.Portfolio[symbol].Invested: #kvp.Value.Invested
                expiry = symbol.ID.Date                                         #WHY is this always one? 
                dte = (expiry - self.Time).days
                self.Debug(f'DTE -- {dte}')
                if dte > 1: continue
            

                stk = symbol.ID.StrikePrice
                
                if only_itm:
                    
                    
                    ul = symbol.Underlying
                    ul_price = self.Portfolio[ul].Price
                    
                    if symbol.ID.OptionRight == OptionRight.Call:
                        if ul_price > stk:
                            self.Debug(f'Exitting ITM Call')
                            self.Liquidate(symbol)
                    
                    else:
                        if ul_price < stk:
                            self.Debug(f'Exitting ITM Put')
                            self.Liquidate(symbol)

                    
                else:
                    self.Liquidate(symbol)
                    self.Debug(f'Exitting Expiring Symbol')
            
                
                
    def OnOrderEvent(self, orderEvent):
        self.Log(str(orderEvent))

    def UniverseFunc(self, universe):
        return universe.IncludeWeeklys().Strikes(-99, 99).Expiration(timedelta(0), timedelta(self.tgt_dte + 10))