Overall Statistics
Total Trades
540
Average Win
0.69%
Average Loss
-0.80%
Compounding Annual Return
15.679%
Drawdown
6.700%
Expectancy
0.135
Net Profit
32.207%
Sharpe Ratio
0.937
Probabilistic Sharpe Ratio
68.815%
Loss Rate
39%
Win Rate
61%
Profit-Loss Ratio
0.87
Alpha
0.08
Beta
0.212
Annual Standard Deviation
0.08
Annual Variance
0.006
Information Ratio
0.664
Tracking Error
0.148
Treynor Ratio
0.353
Total Fees
$2550.80
Estimated Strategy Capacity
$850000.00
Lowest Capacity Asset
SPY 32C89ACDSYUZQ|SPY R735QTJ8XC9X
Portfolio Turnover
0.65%
#region imports
from AlgorithmImports import *
#endregion
class VirtualYellowGiraffe(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 1, 1)
        self.SetEndDate(2023, 12, 1)
        self.SetCash(100000) 
        self.equity = self.AddEquity("SPY", Resolution.Minute)
        self.symbol = self.equity.Symbol
        self.SetBenchmark(self.equity.Symbol)
        self.last_time_invested = self.Time
        self.InitOptionsAndGreeks(self.equity)
        self.order_list = {}
        self.rsi = self.RSI(self.symbol, 2)
        self.volatility_target = self.GetParameter("volatility", 0.15)
        self.delta_target = self.GetParameter("delta", 0.5)

    ## 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 least 80 days
        self.SetWarmup(10, 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)

        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 5), self.Liq)

    def OnData(self, data):
        
        ## If we're done warming up, and not invested, Sell a put. 
        if (not self.IsWarmingUp) and (self.Time > self.last_time_invested + timedelta(days = 1) 
            and self.Time.minute > 44  and self.Time.hour < 12 and self.Portfolio.MarginRemaining > 0.5 * self.Portfolio.TotalPortfolioValue): 

            if data.Bars.ContainsKey(self.symbol) and self.rsi.IsReady:# and self.rsi.Current.Value < 20:
                ticket = self.SellAnOTMPut()
                
                if ticket is not None:
                    self.last_time_invested = self.Time
                    self.order_list[ticket.OrderId] = {"id" : ticket.OrderId,"fillPrice" : ticket.AverageFillPrice, "qty": ticket.Quantity,"stopLoss" : 2 * ticket.AverageFillPrice, "takeProfit" : 0.00 * ticket.AverageFillPrice, "symbol" : ticket.Symbol}
                    
                else:
                    return 
                #self.Log(self.order_list[ticket.OrderId])
        
        for key, value in self.order_list.items():
            try: 
                current_symbol = value['symbol']
                current_quantity = value['qty']
                current_stop_loss = value['stopLoss']
                current_take_profit = value['takeProfit']
                current_put_price = data[current_symbol].Close
                #self.Debug(f'the stop loss is {current_stop_loss} while put price is {current_put_price}')
                #self.Debug(f'the take profit is {current_take_profit} while put price is {current_put_price}')

                if current_put_price > current_stop_loss:
                    
                    orderMessageSL = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \
                       f"Buy Stop Loss {current_symbol.Value}, price {current_put_price}"
                    
                    stop_loss_ticket = self.Order(current_symbol.Value, -current_quantity, False, orderMessageSL)
                    self.Log(f"{self.Time} {orderMessageSL}")
                    del self.order_list[key]
                    break
                
                if current_put_price < current_take_profit:
                    orderMessageTP = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \
                       f"Buy Take Profit {current_symbol.Value}, price {current_put_price}"
                    
                    take_profit_ticket = self.Order(current_symbol.Value, -current_quantity, False, orderMessageTP)
                    self.Log(f"{self.Time} {orderMessageTP}")
                    del self.order_list[key]
                    break
            
            except:
                continue 



        # option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]

        # if option_invested:
        #     self.Portfolio[option_invested[0]]
        #     pass
        #     if self.Time + timedelta(28) > option_invested[0].ID.Date:
        #         self.Liquidate(option_invested[0], "Too close to expiration")
        #     return

        if self.Portfolio[self.symbol].HoldStock:
            self.MarketOrder(self.symbol, -self.Portfolio[self.symbol].Quantity)

        if self.Portfolio.MarginRemaining < 0:
            pass

    ## Sell an OTM Put Option.
    ## Use Delta to select a put contract to sell
    ## ==================================================================
    def SellAnOTMPut(self):
        
        ## Sell a 15 delta put expiring in 90 days
        putContract = self.SelectContractByDelta(self.equity.Symbol, self.delta_target, 0, OptionRight.Put) #30 delta

        if putContract.BidPrice != 0 and putContract.BidPrice > 0.60 and putContract.ImpliedVolatility > self.volatility_target:
            self.quantity = max(8,round((self.Portfolio.TotalPortfolioValue * 0.005) / (putContract.BidPrice  * 100),0))
            
            ## construct an order message -- good for debugging and order records
            orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} | " + \
                       f"Sell {putContract.Symbol} "+ \
                       f"({round(putContract.Greeks.Delta,2)} Delta)" + \
                       f"({round(putContract.BidPrice,2)} Ask)" + \
                        f"(Quantity: {self.quantity})"

            self.Log(f"{self.Time} {orderMessage}")
            ticket_p = self.Order(putContract.Symbol, -self.quantity, False, orderMessage)
            return ticket_p
        else:
            return
        
           
   
    ## Get an options contract that matches the specified criteria:
    ## Underlying symbol, delta, days till expiration, Option right (put or call)
    ## ============================================================================
    def SelectContractByDelta(self, symbolArg, strikeDeltaArg, expiryDTE, optionRightArg= OptionRight.Call):

        canonicalSymbol = self.AddOption(symbolArg)
        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 OptionsFilterFunction(self, optionsContractsChain):

        strikeCount  = 35 # no of strikes around underyling price => for universe selection #35
        minExpiryDTE = 0    # min num of days to expiration => for uni selection
        maxExpiryDTE = 5  # max num of days to expiration => for uni selection
        
        return optionsContractsChain.IncludeWeeklys()\
                                    .Strikes(-strikeCount, -0)\
                                    .Expiration(timedelta(minExpiryDTE), timedelta(maxExpiryDTE))
    
    def Liq(self):
        self.Liquidate()
from AlgorithmImports import *
from datetime import timedelta

class PutCalendarSpreadStrategy(QCAlgorithm): 
    def Initialize(self):
        self.SetStartDate(2022, 12, 29)
        self.SetEndDate(2023, 2, 20)
        self.SetCash(500000)

        self.underlying = self.AddEquity("SPY", Resolution.Minute)
        self.options = self.AddOption(self.underlying.Symbol, Resolution.Minute)
        self.options.SetFilter(lambda u: u.WeeklysOnly().Strikes(-2,0).Expiration(0,1))
        self.symbol = self.options.Symbol
        self.action_taken = False
    
    def OnData(self, data):
        # avoid extra orders
        if self.Portfolio.Invested: 
            self.holdings = [x.Symbol for x in self.Portfolio.Values if x.Invested]
            self.positions_debug = [x for x in self.Portfolio.Values if x.Invested]
            
            for item in self.holdings:

                if item.HasUnderlying == False:
                    self.MarketOrder(self.underlying.Symbol, 100)
                    self.Debug(f'ho comprato 100 SPY al {self.Time}')

                if item.HasUnderlying:
                    chain_invested_opt = data.OptionChains.get(item.Canonical, None)
                    if chain_invested_opt:
                        self.contract = chain_invested_opt.Contracts.get(item)
                        if self.contract:
                            delta = self.contract.Greeks.Delta
                            if delta <= -0.8 and self.action_taken and self.Time.date() == self.last_action_date:
                                return

                            self.MarketOrder(self.underlying.Symbol, -100)
                            self.Debug(f'Delta contratto era {delta} e quindi ho venduto 100 SPY al {self.Time}')
                            self.action_taken = True
                            self.last_action_date = self.Time.date()
            return

            
        # Get the OptionChain of the self.symbol
        chain = data.OptionChains.get(self.options.Symbol, None)
        if not chain: return

        # get at-the-money strike
        atm_strike = sorted(chain, key=lambda x: abs(x.Strike - chain.Underlying.Price))[0].Strike

        # filter the put options from the contracts which is ATM in the option chain.
        puts = [i for i in chain if i.Strike == atm_strike and i.Right == OptionRight.Put]
        if len(puts) == 0: return

        # sorted the optionchain by expiration date
        expiries = sorted([x.Expiry for x in puts], key = lambda x: x)
        
        # select the farest expiry as far-leg expiry, and the nearest expiry as near-leg expiry
        near_expiry = expiries[0]
        #far_expiry = expiries[-1]
        
        naked_put = OptionStrategies.NakedPut(self.symbol, atm_strike, near_expiry)

        #option_strategy = OptionStrategies.PutCalendarSpread(self.symbol, atm_strike, near_expiry, far_expiry)
        # We open a position with 1 unit of the option strategy
        self.Buy(naked_put, 1)
        # self.Sell(option_strategy, 1) if short put calendar spreadSecurities[holdings[0].Value].Price
        
    def OnEndOfAlgorithm(self):
        for symbol, sec in self.Securities.items():
            self.Log(f"{symbol} :: {sec.Price}")

    def OnEndOfDay(self):
        self.action_taken = False