Overall Statistics
Total Trades
72
Average Win
5.48%
Average Loss
-3.78%
Compounding Annual Return
12.866%
Drawdown
34.300%
Expectancy
0.369
Net Profit
125.477%
Sharpe Ratio
0.75
Probabilistic Sharpe Ratio
21.866%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
1.45
Alpha
0.1
Beta
0.395
Annual Standard Deviation
0.203
Annual Variance
0.041
Information Ratio
0.092
Tracking Error
0.219
Treynor Ratio
0.385
Total Fees
$208.06
from QuantConnect.Securities.Option import OptionPriceModels

class CallTailendSetDates(QCAlgorithm):

    #designed so that in backtesting, if there isn't data or it can't find an option, it should bug out. Final product will be human supervised
    def Initialize(self):
        self.SetStartDate(2014, 1, 1)
        self.SetCash(100000)  # Set Strategy Cash
        stock = self.AddEquity("SPY", Resolution.Minute)
        stock.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.stock_symbol = stock.Symbol
        
        bond = self.AddEquity("TLT", Resolution.Minute)
        bond.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.bond_symbol = bond.Symbol
        
        option = self.AddOption("SPY", Resolution.Minute)
        option.SetFilter(lambda universe: universe.Strikes(-30, 30).Expiration(timedelta(366), timedelta(400)))
        option.PriceModel = OptionPriceModels.CrankNicolsonFD()
        self.option_symbol = option.Symbol
        
        self.purchaseDates = [None, None]
        self.callOptions = [None, None]
        self.putOption = None
        
        self.callWeight = .15
        self.putWeight = 0.02
        self.bondWeight = 1 - self.callWeight - self.putWeight
        
        self.month = 0
        self.shouldCheckIndex = -1
        self.shouldRebalance = True
        
        self.SetWarmUp(timedelta(1))

    def OnData(self, data):
        
        if self.IsWarmingUp or self.Time.hour < 9 and self.Time.minute < 30: return
        
        if self.Time.month != self.month:
            self.month = self.Time.month
            if self.month == 6:
                self.shouldCheckIndex = 0
            elif self.month == 12:
                self.shouldCheckIndex = 1
                
        if self.shouldCheckIndex >= 0:
            self.ShouldRebalance(self.shouldCheckIndex)
            if self.shouldRebalance:
                self.Rebalance(self.shouldCheckIndex)
                self.shouldCheckIndex = -1

            
    def GetStockOptions(self, optionType, portfolioPercent):
        optionchain = None
        for kvp in self.CurrentSlice.OptionChains:
            if kvp.Key != self.option_symbol: continue
            optionchain = kvp.Value
        maxValue = self.Portfolio.TotalPortfolioValue * portfolioPercent * (-1 if optionType == OptionRight.Call else 1)
        optionchain = [x for x in optionchain if x.Right == optionType]
        x = 1
        optionchain = sorted(optionchain, key = lambda x: (abs((x.Expiry - self.Time).days - 365), maxValue // (x.AskPrice * 100) * x.Greeks.Delta))
        return optionchain[0]
    
    def ShouldRebalance(self, index):
        self.shouldRebalance = not self.callOptions[index] or self.Portfolio[
            self.callOptions[index].Symbol].UnrealizedProfit < 0 or (self.Time - self.purchaseDates[index]).days > 0
        
        
    def Rebalance(self, index):
        self.Debug(f"Rebalance {self.Time}")
        if self.callOptions[index]:
            self.Liquidate(self.callOptions[index].Symbol)
        if self.putOption:
            self.Liquidate(self.putOption.Symbol)
            
        self.callOptions[index] = self.GetStockOptions(OptionRight.Call, self.callWeight/2)
        self.purchaseDates[index] = self.Time
        self.putOption = self.GetStockOptions(OptionRight.Put, self.putWeight)
            
        if self.callOptions[index - 1]:
            self.SetHoldings(self.callOptions[index - 1].Symbol, self.callWeight / 2)
        self.SetHoldings(self.bond_symbol, self.bondWeight)
        self.SetHoldings(self.putOption.Symbol, self.putWeight)
        self.SetHoldings(self.callOptions[index].Symbol, self.callWeight / 2)
        x = 1
        
    def OnEndOfDay(self):
        for each in self.Portfolio:
            if each.Value.Invested and each.Value.Type == SecurityType.Option:
                self.Debug(f"{each.Key}: {self.Portfolio[each.Key].AbsoluteHoldingsValue}")