Overall Statistics
Total Trades
8
Average Win
2.88%
Average Loss
0%
Compounding Annual Return
10.557%
Drawdown
2.000%
Expectancy
0
Net Profit
11.985%
Sharpe Ratio
2.027
Probabilistic Sharpe Ratio
91.890%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0.069
Beta
-0.084
Annual Standard Deviation
0.036
Annual Variance
0.001
Information Ratio
0.542
Tracking Error
0.214
Treynor Ratio
-0.862
Total Fees
$20.00
Estimated Strategy Capacity
$24000000.00
Lowest Capacity Asset
SPY 323UYHQN3M1YE|SPY R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion

class VirtualRedDogfish(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2021, 12, 1)
        # self.SetEndDate(2022, 10, 1)
        self.SetCash(1000000)

        self.vix = self.AddIndex('VIX', Resolution.Minute)

        self.stock = self.AddEquity("SPY", Resolution.Minute)
        self.stock.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.stock_symbol = self.stock.Symbol
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
        
        self.contract = None    #Initialize the option contract
        self.trade = False   #Setup flag to trade
        self.option_wt = 0.05   #Target portfolio weight

       
        #Set parameters for optimization
        self.option_premium  = float(self.GetParameter("option_premium"))
        # self.option_premium = 0.95   # % Strike price premium/discount to underlying
        self.option_dte = float(self.GetParameter("option_dte"))
        # self.option_dte = 90    #Target days-to-expiration for option contract

        #Schedule trade #1
        self.Schedule.On(self.DateRules.On(2021, 12, 21), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupBuyTrade)

        self.Schedule.On(self.DateRules.On(2022, 1, 24), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupSellTrade)
       
        #Schedule trade #2
        self.Schedule.On(self.DateRules.On(2022, 2, 9), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupBuyTrade)

        self.Schedule.On(self.DateRules.On(2022, 2, 24), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupSellTrade)
        
        #Schedule trade #3
        self.Schedule.On(self.DateRules.On(2022, 4, 12), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupBuyTrade)

        self.Schedule.On(self.DateRules.On(2022, 4, 29), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupSellTrade)
            
        #Schedule trade #4
        self.Schedule.On(self.DateRules.On(2022, 8, 8), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupBuyTrade)

        self.Schedule.On(self.DateRules.On(2022, 9, 23), \
                        self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
                        self.SetupSellTrade)

        # #Schedule trade #5
        # self.Schedule.On(self.DateRules.On(2023, 1, 13), \
        #                 self.TimeRules.AfterMarketOpen(self.stock_symbol, 30), \
        #                 self.SetupBuyTrade)


    def OnData(self, slice):
        #Liquidate any options that are within 1.5 days of expiration    
        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
        if option_invested:
            expirydate = self.contract.ID.Date
            if (expirydate - self.Time) < timedelta(1.5):
                # self.Debug("Sell option, less than 2 days before expiry")
                self.Liquidate(self.contract)
                self.contract = None
                
        if self.trade:
            # self.Log("Buy trade fired at : {0}".format(self.Time))
            self.InitiateTrade()
            
            
    def SetupBuyTrade(self):
        #Check to see if already invested in option
        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]

        if not option_invested:
            self.contract = self.GetPut()
            self.trade = True

    def SetupSellTrade(self):
        #Check to see if already invested in option
        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
    
        if option_invested:
            # self.Log("Exit trade fired at : {0}".format(self.Time))
            self.Liquidate(self.contract)
            self.contract = None
            self.trade = False
    
      
    def GetPut(self):
        targetStrike = self.Securities[self.stock_symbol].Price * self.option_premium
        contracts = self.OptionChainProvider.GetOptionContractList(self.stock_symbol, self.Time)
        # self.Debug(f"SPY total contracts found: {len(contracts)}")
        
        puts = [x for x in contracts if x.ID.OptionRight == OptionRight.Put]
        puts = sorted(sorted(puts, key = lambda x: x.ID.Date),
                       key = lambda x: x.ID.StrikePrice, reverse = False)
        # self.Debug(f"SPY puts found: {len(puts)}")

        puts = [x for x in puts if (x.ID.StrikePrice - targetStrike) >= 0
            and (x.ID.StrikePrice - targetStrike) < 100]
        puts = [x for x in puts if self.option_dte < (x.ID.Date - self.Time).days <= self.option_dte+100]   #self.option_dte+100 sets dte buffer of 100 days
        if len(puts) == 0:
            # self.Debug(f"!!! No puts available")
            return None
        # else: self.Debug(f"SPY puts w/ good strikes found: {len(puts)}")
        
        self.Debug(f"Selected option contract: {puts[0]} &, expiration: {puts[0].ID.Date} \
            &, strike: {puts[0].ID.StrikePrice} &, SPY: {self.stock.Price} & , VIX: {self.vix.Price}")  
        self.AddOptionContract(puts[0], Resolution.Minute)
        return puts[0]
        
        
    def InitiateTrade(self):
        if self.contract is not None:
            self.SetHoldings(self.contract, self.option_wt)
        
        self.trade = False    #Reset trading flag