Overall Statistics
# region imports
from AlgorithmImports import *
from QuantConnect.Securities.Option import OptionPriceModels
# endregion

class CalmVioletWhale(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 11, 1)  # Set Start Date
        self.SetEndDate(datetime.now())
        self.SetCash(100000)  # Set Strategy Cash
        self.equity = self.AddEquity("SPY", Resolution.Minute)
        self.symbol = self.equity.Symbol
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.SetBenchmark(self.symbol)

        option = self.AddOption("SPY", Resolution.Hour)

        # Strikes in the filter need to be made dynamic, a % on current price
        option.SetFilter(-75, 75, timedelta(45), timedelta(65))
        option.PriceModel = OptionPriceModels.BjerksundStensland()
        self.targetdelta = 0.2

        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose("SPY", 10), self.OpenCondor)

    def OnData(self, data: Slice):
        pass
    
    def OpenCondor(self):

        def RoundBaseFive(x, base=5):
            return base * round(x/base)

        longPut = None
        shortPut = None
        shortCall = None
        longCall = None

        for i in self.CurrentSlice.OptionChains:
            chain = [x for x in i.Value]

            # If no option chain, return
            if (self.CurrentSlice.OptionChains.Count == 0):
                return

            # Sort option chain by expiration, choose furthest date
            expiry = sorted(chain, key = lambda x: x.Expiry)[-1].Expiry
            self.Log(str("Chosen expiry") + str(expiry))

            # filter calls and puts
            call = [i for i in chain if i.Right == OptionRight.Call and i.Expiry == expiry]
            put = [i for i in chain if i.Right == OptionRight.Put and i.Expiry == expiry]

            # sort calls by delta
            call_sorted = sorted(call, key = lambda x: x.Greeks.Delta)

            # get strike of contract with target delta
            call_delta = min(call_sorted, key = lambda x: abs(self.targetdelta - x.Greeks.Delta)).Strike
            self.Log(str("Call Strike Chosen: ") + str(call_delta))

            # round to nearest base 5 to get a more liquid strike
            call_strike = RoundBaseFive(call_delta)
            self.Log(str("Call Strike Rounded: ") + str(call_strike))
            self.Log(str("Underlying Price") + str(self.Securities[self.symbol].Price))

            shortCall = sorted(call_sorted, key = lambda x: x.Strike == call_strike)[0]

            self.Sell(shortCall.Symbol, 1)

            # most concerning is incorrect expiration chosen. Some Call strikes are below underlying..