Overall Statistics
Total Trades
28
Average Win
1.03%
Average Loss
-0.41%
Compounding Annual Return
16.673%
Drawdown
2.300%
Expectancy
0.764
Net Profit
3.859%
Sharpe Ratio
2.692
Probabilistic Sharpe Ratio
79.941%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
2.53
Alpha
0.121
Beta
0.227
Annual Standard Deviation
0.06
Annual Variance
0.004
Information Ratio
-0.157
Tracking Error
0.14
Treynor Ratio
0.719
Total Fees
$136.00
Estimated Strategy Capacity
$30000000.00
Lowest Capacity Asset
SPY WU82X7CDEKDI|SPY R735QTJ8XC9X
# class TradeManagement:
#     '''Manager for trades for each security'''
#     def __init__(self, algorithm, symbol):
        
#         '''
#         stoploss = None
#         takeProfit = None'''
        
        
        
#     def EnterTrade(self, quantity,  takeprofit, stoploss):
        
        
#         '''''
        
        
#         '''''
    
    
#     # do this minutely/hourly
#     def UpdatePosition(self):
        
#         #current_price > takeprofit
#         #liquidate
        
    
    
#     # close out 
#     def timelimits(self):
        
        
#https://www.quantconnect.com/forum/discussion/8399/optionstrategies-limit-orders
import numpy as np

class QuantumParticleAtmosphericScrubbers(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 2, 8)  # Set Start Date #2019, 12,8
        self.SetEndDate(2018,5,8)
        self.SetCash(100000)  # Set Strategy Cash
        
        self.spy_symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
        self.Securities[self.spy_symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)
        
        self.SetSecurityInitializer(self.CustomSecurityInitializer) #TO WARM SHIT UP WHEN ADDED!!
        
        self.straddleTickets = {}
        
        self.max_util = .5
        self.lmts = False
        
        self.last_option_exp = None
        
        # credit/max_risk

    def OnData(self, data):
        
        if not data.Bars.ContainsKey(self.spy_symbol):
            return
        
        ## To handle any early assignment (Could loop through univ tickers here.)
        #if self.Portfolio[self.spy_symbol].Invested:
        #    self.Liquidate(self.spy_symbol)
        
        ''' Original (Straddle)
        if not self.Portfolio.Invested:
            
            price = data.Bars[self.spy_symbol].Close;
            expiry = self.Time + timedelta(days = 14)
            
            call_symbol, put_symbol = self.GetStraddleContracts(self.spy_symbol, price, expiry)
            
            #Why made into a call / put here (vs call / put symbol?) ***************
            call = self.AddOptionContract(call_symbol, Resolution.Minute)
            put = self.AddOptionContract(put_symbol, Resolution.Minute)
            
            call_tickets, put_tickets = self.TradeStraddle(call, put, 2) #(Tgt, Stop) format
        
            self.straddleTickets[self.spy_symbol] = [call_tickets, put_tickets] #[(Tgt, Stp), (Put Tgt, Put Stp)]
            #Addit
            self.last_option_exp = call_symbol.ID.Date
        '''
        
        if not self.Portfolio.Invested:
            #HERE is where you would loop (thorugh securities)
            
            price = data.Bars[self.spy_symbol].Close
            expiry = self.Time + timedelta(days = 30)
            
            #Just using 10% strikes for IC here -- NEXT UP: greeks
            icp = price * 1.10
            ocp = price * 1.20
            
            ipp = price * .9
            opp = price * .8
            
            #Lookup Option Contracts
            o_call, call, put, o_put = self.IronCondor(self.spy_symbol, expiry, icp, ocp, ipp, opp)
            
            # #Add Respective Symbols (To be Traded) -- DO THIS inside Execute
            # oc_sym = self.AddOptionContract(o_call, Resolution.Minute)
            # c_sym = self.AddOptionContract(call, Resolution.Minute)
            if o_call and call and put and o_put:
                to_buy =  [o_call, o_put]
                to_sell = [call,     put]
                
                self.ExecuteSpread(to_buy, to_sell, use_limits = self.lmts)
                
            
        if self.Portfolio.Invested:
            self.CloseExpiring(data, False)
                
            
            
            
        ''' 
        SAMPLE (Getting Options data from Securities or Portfolio -- once added/held):
            ## https://www.quantconnect.com/forum/discussion/3549/getting-option-contract-info-from-portfolio-holding-objects-in-python/p1
            ## So the OPTION uses the KEY aspect, really KEY.ID !! (CHECK if Option first.)
            for kvp in self.Securities:
                symbol = kvp.Key
                id = symbol.ID
                security_type = id.SecurityType
                if security_type = SecurityType.Option
                    expiry = id.Date
                    strike_price = id.StrikePrice
        
        #ZO Initial Attempt (Cleaned up Below in CloseExpiring)
        if self.last_option_exp and self.Portfolio.Invested:
            
            days_to_exp = (self.last_option_exp - self.Time).days
            self.Debug(f'DTE -- {days_to_exp}')
            
            if days_to_exp < 1:
                
                self.Debug(f'Closing Prior to Exit.') #Should we check for ITM? 
                option_posits = [symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested and \
                                            symbol.SecurityType == SecurityType.Option]
                                            
                for symbol in option_posits:
                    self.Liquidate(symbol)
        '''
    
    def GetICMargin(self, buys, sells):
        #calls = [i for i in buys + sells if i.Right == OptionRight.Call]
        #puts = [i for i in buys + sells if i.Right == OptionRight.Put]
        #self.GetVerticalMargin() #Weird to now sort, etc. Faster to just use abs diff
        
        #MUST BE ADDED (in Securities) PRIOR TO CALLING THIS!
    
        credits = np.sum([self.Securities[symbol].BidPrice for symbol in sells])
        debits = np.sum([self.Securities[symbol].AskPrice for symbol in buys]) #[symbol.ID.AskPrice
        
        credit = credits - debits
        
        call_stks = [i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Call]
        put_stks = [i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Put]
        

        self.Debug(f'Calls: {[i for i in call_stks]}')
        self.Debug(f'Puts: {[i for i in put_stks]}')
        call_dist = abs(max(call_stks) - min(call_stks))
        put_dist = abs(max(put_stks) - min(put_stks))
        
        #call_dist = abs(np.diff([i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Call]))
        #put_dist = abs(np.diff([i.ID.StrikePrice for i in buys + sells if i.ID.OptionRight == OptionRight.Put]))
        
        distance = max(call_dist, put_dist)
        self.Debug(f'credit {credit} -- dist {distance}')
        return (distance - credit) * 100
        
                
            
                
    #Not going to be used here... Easier to track buys / sells  ^^          
    def GetVerticalMargin(self, buy, sell):
        credit = sell.BidPrice - buy.AskPrice #Ex: $1.50
        distance = abs(buy.Strike - sell.Strike) #Ex: 5 
        
        return (distance - credit) * 100 #100 shares per contract
        
        
        
    def ExecuteSpread(self, buys, sells, use_limits = False):

        added = [self.AddOptionContract(c) for c in buys + sells]
        
        if buys[0] not in self.Securities: return 
        #if not set(buys+sells).issubset(self.Securities): return  #More accurate, confusing.
    
        #usd_per_ct = self.GetICMargin(buys, sells)
        #self.Debug(f'USD per ct -- {usd_per_ct}')
        #num_cts = int(self.Portfolio.MarginRemaining / usd_per_ct * self.max_util)
        pf = (self.Portfolio.MarginRemaining * self.max_util)
        
        num_cts = 2 #max(num_cts,5)
        
        ## ---------------- ADDIT: APPROX of Margin
        
        call_dist = abs(buys[0].ID.StrikePrice - sells[0].ID.StrikePrice)
        put_dist = abs(buys[1].ID.StrikePrice - sells[1].ID.StrikePrice)

        distance = max(call_dist, put_dist)
        credit = abs(self.Securities[sells[0]].BidPrice - self.Securities[buys[0]].AskPrice) + \
                abs(self.Securities[sells[1]].BidPrice - self.Securities[buys[1]].AskPrice)
        
        self.Debug(f'Distance - credit * 100 --- {distance} - {credit} * 100 -- > {abs(distance - credit) * 100} ')
        if max(call_dist, put_dist) == 0:
            #Approximate
            ul = self.Securities[sells[0]].Underlying.Price * .2 
            k = sells[0].ID.StrikePrice
            m1 = .2 * ul - abs(k - ul)
            usd_per_cts = (m1 - credit) * 100
            self.Debug(f'Strangle Calc ----  per_ct: {m1}')
        else:
            usd_per_ct = (distance - credit) * 100
            
        num_cts = int(pf / usd_per_ct)
        
        ##  ---------------------- End Addit 
        self.Debug(f'Num Contracts -- {num_cts}')
        for buy in buys:
            if use_limits:
                ask = self.Securities[buy].AskPrice #Does this need to be buy.Symbol ? 
                self.LimitOrder(buy, num_cts, ask)
            else:
                self.MarketOrder(buy, num_cts)
        
        for sell in sells:
            if use_limits:
                bid = self.Securities[sell].BidPrice
                self.LimitOrder(sell, -num_cts, bid)
            else:
                self.MarketOrder(sell, -num_cts)
                
                
            
    def IronCondor(self, symbol, expiry, call, otm_call, put, otm_put):
        #Returns oc, ic, ip, op
        inner_call, outer_call = self.GetVertical(symbol, OptionRight.Call, call, otm_call, expiry)
        inner_put, outer_put = self.GetVertical(symbol, OptionRight.Put, put, otm_put, expiry)
        
        return outer_call, inner_call, inner_put, outer_put
        

        ''' Original -- Cleaned this up
        if inner_call and outer_call and inner_put and outer_put:
            
            ## For Limits ***
            #Selling inners (need bid)
            # call_price = self.Securities[inner_call].BidPrice
            # put_price = self.Securities[inner_put].BidPrice
            
            # #Buying Outers (need ask)
            # otm_call_p = self.Securities[outer_call].AskPrice
            # otm_put_p = self.Securities[outer_put].AskPrice
            
            self.MarketOrder(outer_call, 1)
            self.MarketOrder(inner_call, -1)
            
            self.MarketOrder(inner_put, -1)
            self.MarketOrder(outer_put, 1)
        '''
            
        
        
    def GetVertical(self, symbol, right, inner, outer, expiry):
        #Get Contracts (OCP, bc no greeks, iv)
        contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
        
        #Get Expiry
        expiry_sorted = sorted(contracts, key=lambda k: abs(k.ID.Date - expiry), reverse=False) #Asc
        closest = expiry_sorted[0].ID.Date
        correct_exp = [symbol for symbol in contracts if symbol.ID.Date == closest]
        
        #Select Side (Call / Put)
        rights = [symbol for symbol in correct_exp if symbol.ID.OptionRight == right]
        
        if len(rights) == 0: return None, None
        
        #self.Debug(f'Inner: {inner} ----- Outer: {outer}') #THIS is correct... 
        #Select Strike
        inner_ct = sorted(rights, key=lambda k: abs(k.ID.StrikePrice - inner), reverse=False)[0]
        outer_ct = sorted(rights,key=lambda k: abs(k.ID.StrikePrice - outer), reverse=False)[0]
        
        #Not sure if I need to split this? (Maybe Just SORT by strike, and take FIRST, then NEXT?)
        
        #self.Debug(f' (EOVertical) -- Inner: {inner_ct.ID.StrikePrice} ---- Outer: {outer_ct.ID.StrikePrice}')
        return inner_ct, outer_ct
            
        
        
        
    def CloseExpiring(self, data, only_itm=False):
        ## QUESTION -- how to get to OCP type contract from PORTFOLIO, or SECURITIES
        #open_pos_exps = [symbol.ID.Date for symbol, val in self.Portfolio.items() \
        #                if val.Invested and symbol.SecurityType == SecurityType.Option ][0]

        #dte = (open_pos_exps - self.Time).days
        
        #ul_price = data.Bars[self.spy_symbol].Close
        
        to_close = []
        for kvp in self.Portfolio:
            symbol = kvp.Key #OPTION symbol I assume? -- HOW to get Underlying?
            value = kvp.Value
            if symbol.ID.SecurityType == SecurityType.Option: 
                
                #Check if Expiring (If not, Ignore)
                expiry = symbol.ID.Date
                dte = (expiry - self.Time).days
                if dte > 1: continue
                
                #Close ALL expiring
                if not only_itm:
                    self.Liquidate(symbol)
                    continue
                
                #Close expiring IF ITM
                elif only_itm:
                    
                    strike = symbol.ID.StrikePrice #Can also check ITM here!!
                    
                    ##How to get Underlying Price from OPTION?
                    ul = symbol.Underlying
                    ul_price = data.Bars[ul].Close
                    
                    if symbol.ID.OptionRight == OptionRight.Call:
                        if strike > ul_price:
                            self.Liquidate(symbol)

                    else:
                        if strike < ul_price:
                            self.Liquidate(symbol)
                


    def GetStraddleContracts(self, symbol, strike, expiry):
        
        contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
        
        expiry_sorted = sorted(contracts, key=lambda k: abs(k.ID.Date - expiry), reverse=False)
        closest_expiry = expiry_sorted[0].ID.Date
        
        contracts_with_desired_expiry = [symbol for symbol in contracts if symbol.ID.Date == closest_expiry]
        
        calls = [symbol for symbol in contracts_with_desired_expiry if symbol.ID.OptionRight == OptionRight.Call]
        
        puts = [symbol for symbol in contracts_with_desired_expiry if symbol not in calls]
        
        sorted_calls = sorted(calls, key=lambda k: abs(k.ID.StrikePrice - strike), reverse=False)
        
        sorted_puts = sorted(puts, key=lambda k: abs(k.ID.StrikePrice - strike), reverse=False)
        
        return sorted_calls[0], sorted_puts[0]
        
    ## Interesting use of Stops / Targets (With OnOrder to make them OCO!!) VERY clever...
    def TradeStraddle(self, call, put, quantity):
        
        call_entry= call.AskPrice
        call_entry_ticket = self.MarketOrder(call.Symbol, quantity)
        
        call_take_profit = call_entry * 1.50
        call_stop_loss = call_entry * 0.90
        
        call_profit_ticket = self.LimitOrder(call.Symbol, -quantity, call_take_profit)
        call_loss_ticket = self.StopMarketOrder(call.Symbol, -quantity, call_stop_loss)
        
        put_entry = put.AskPrice
        put_entry_ticket = self.MarketOrder(put.Symbol, quantity)
        
        put_take_profit = put_entry * 1.50
        put_stop_loss = put_entry * 0.90
        
        put_profit_ticket = self.LimitOrder(put.Symbol, -quantity, put_take_profit)
        put_loss_ticket = self.StopMarketOrder(put.Symbol, -quantity, put_stop_loss)
        
        return (call_profit_ticket, call_loss_ticket), (put_profit_ticket, put_loss_ticket)
        
    
    def CustomSecurityInitializer(self, security):
        bar = self.GetLastKnownPrice(security)
        security.SetMarketPrice(bar)
        
    # OCO Logic (For Exits) --- CAN we get FILL price in here? I suppose it's in PF object anyway, but still.
    #Make the Stop / Tgt OCO's basically.
    def OnOrderEvent(self, orderevent):
        
        if orderevent.Status != OrderStatus.Filled:
            return
        
        #FILLED orderID
        orderId = orderevent.OrderId
        
        for underlying, order_pairs in self.straddleTickets.items():
            for order_pair in order_pairs:
                order_one = order_pair[0]
                order_two = order_pair[1]
                #IF tgt filled, cancel Stop (Vice Versa)
                if orderId == order_one.OrderId:
                    order_two.Cancel()
                elif orderId == order_two.OrderId:
                    order_one.Cancel()