Overall Statistics
Total Trades
39
Average Win
0.02%
Average Loss
0%
Compounding Annual Return
0.082%
Drawdown
0.000%
Expectancy
0
Net Profit
0.320%
Sharpe Ratio
1.674
Probabilistic Sharpe Ratio
99.562%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0.001
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-0.043
Tracking Error
0.265
Treynor Ratio
306.191
Total Fees
$25.00
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
import pandas as pd

class CalibratedUncoupledRadiator(QCAlgorithm):
    '''
        If SPY is within a certain % of its 52W high, write a call option that is a certain $ above the opening price.
        If the option gets assigned, do a limit order for covering at the assignment price.
        Pre
    '''
    
    # Paramaters
    callOtmAmount = 6
    withinYearlyHighPct = 10
    
    spy = None
    spyOption = None
    spyCoverOrderTicket = None
    yearlyHigh = None
    days = 0
    
    def Initialize(self):
        self.SetStartDate(2007, 1, 1)
        self.SetEndDate(2020, 11, 19)
        self.SetCash(100000)
        
        #self.callOtmAmount = int(self.GetParameter("callOtmAmount"))  # TODO deal with missing parameter
        
        self.spy = self.AddEquity("SPY", Resolution.Minute)
        self.spy.SetDataNormalizationMode(DataNormalizationMode.Raw) # Required for working with options
        self.spy.SetLeverage(4)
        
        self.UniverseSettings.MinimumTimeInUniverse = 10
        
        self.yearlyHigh = self.MAX(self.spy.Symbol, 252, Resolution.Daily)
        history = self.History(self.spy.Symbol, 252, Resolution.Daily)
        for bar in history.itertuples():
            self.yearlyHigh.Update(bar.Index[1], bar.high)
        self.Debug("SPY yearly high warmed up, value = " + str(self.yearlyHigh.Current.Value))

        self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday), self.TimeRules.AfterMarketOpen("SPY", 1), self.OpeningBar)
        self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday), self.TimeRules.BeforeMarketClose("SPY", 0), self.ClosingBar)
        
        self.SetExecution(ImmediateExecutionModel())

    def OpeningBar(self):
        for holding in self.Portfolio.Values:
            if holding.Symbol != self.spy.Symbol and holding.Invested:
                self.Error("Assignment/expiration failed")
                self.Quit()
        
        self.spyOption = None
        if not self.IsMarketOpen(self.spy.Symbol):
            self.Debug("Market not open, returning")
            return

        self.LogPortfolio()
        if self.Portfolio[self.spy.Symbol].Invested:
            self.Liquidate(self.spy.Symbol)
            '''
            if self.spyCoverOrderTicket is None:
                self.Debug("Assigned, placing limit order to cover")
                self.spyCoverOrderTicket = self.LimitOrder(self.spy.Symbol, -1 * self.Portfolio["SPY"].Quantity, self.Portfolio["SPY"].AveragePrice)
            return
            '''
        
        high = self.yearlyHigh.Current.Value
        if 100 * (high - self.Securities[self.spy.Symbol].Close)/high > self.withinYearlyHighPct:
            self.Debug("Too far away from yearly high: " + str(self.Securities[self.spy.Symbol].Close) + " vs " + str(high))
            return
        
        options = self.OptionChainProvider.GetOptionContractList(self.spy.Symbol, self.Time)
        if options is None or len(options) == 0:
            self.Error("No options found")
            return
        callsExpiringToday = [o for o in options if o.ID.Date.date() == self.Time.date() and o.ID.OptionRight == OptionRight.Call]
        sortedByStrike = sorted(callsExpiringToday, key = lambda o: o.ID.StrikePrice)
        
        for option in sortedByStrike:
            id = option.ID
            if id.StrikePrice >= self.Securities[self.spy.Symbol].Open + self.callOtmAmount:
                self.spyOption = self.AddOptionContract(option, Resolution.Minute)
                self.Debug(str(id.Date) + " " + str(id.StrikePrice) + " " + str(id.OptionRight))
                break
        '''
        if self.days > 30:
            self.Quit()
        self.days = self.days+1
        '''
        
    def ClosingBar(self):
        self.spyOption = None
        
    def OnData(self, slice):
        if self.spyOption is not None and self.Securities[self.spyOption.Symbol].Price != 0:
            self.MarketOrder(self.spyOption.Symbol, -5)
            self.spyOption = None
            return
    
    def OnOrderEvent(self, orderEvent):
        self.Debug(str(orderEvent))
        if not self.spyCoverOrderTicket is None and orderEvent.OrderId == self.spyCoverOrderTicket.OrderId and self.spyCoverOrderTicket.Status == OrderStatus.Filled:
            self.Debug("covered!")
            self.spyCoverOrderTicket = None
    
    def LogPortfolio(self):
        for symbol in self.Portfolio.Keys:
            holding = self.Portfolio[symbol]
            if holding.Invested:
                self.Debug(str(symbol) + " " + str(holding.Quantity) + " @ " + str(holding.AveragePrice))