Overall Statistics
Total Orders
11
Average Win
0%
Average Loss
-0.68%
Compounding Annual Return
21.584%
Drawdown
6.800%
Expectancy
-1
Start Equity
100000
End Equity
128000.53
Net Profit
28.001%
Sharpe Ratio
1.176
Sortino Ratio
1.445
Probabilistic Sharpe Ratio
84.226%
Loss Rate
100%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
-0.001
Beta
0.696
Annual Standard Deviation
0.079
Annual Variance
0.006
Information Ratio
-0.922
Tracking Error
0.046
Treynor Ratio
0.134
Total Fees
$14.00
Estimated Strategy Capacity
$130000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
0.21%
# region imports
from AlgorithmImports import *
from datetime import timedelta

# to get pricing data for volatility or VIX
#from QuantConnect.Data.Custom.CBOE import *

# endregion

class SleepyLightBrownGaur(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 8, 6)
        self.set_end_date(2025, 1, 31)
        self.set_cash(100000)
        self.equity = self.add_equity("SPY", Resolution.MINUTE)
        self.equity.set_data_normalization_mode(DataNormalizationMode.RAW)
        self.symbol = self.equity.symbol

        self.vix = self.add_data(CBOE, "VIX").symbol 
        self.rank = 0
        self.contract = str()
        self.contractsAdded = set()

        self.DaysBeforeExp = 2
        self.DTE = 25
        self.OTM = 0.01
        self.lookbackIV = 150
        self.IVlvl = 0.5
        self.percentage = 0.9
        self.options_alloc = 90
        
        self.schedule.On(self.date_rules.every_day(self.symbol), \
            self.time_rules.after_market_open(self.symbol, 30), \
            self.Plotting)
        
        self.schedule.on(self.date_rules.every_day(self.symbol), \
        self.time_rules.after_market_open(self.symbol, 30), \
        self.VIXRank)

    def VIXRank(self):
        history = self.history(CBOE, self.vix, self.lookbackIV, Resolution.DAILY)
        self.rank = ((self.securities[self.vix].price - min(history[::-1]["low"]))/ (max(history[::-1]["high"]) - min(history[::-1]["low"])))
        
        

    def on_data(self, data: Slice):
        if self.is_warming_up:
            return
        
        if not self.portfolio[self.symbol].invested:
            self.set_holdings(self.symbol, self.percentage)

        if self.rank > self.IVlvl:
            self.BuyPut(data)

        if self.contract:
            if (self.contract.ID.date - self.time) <= timedelta(self.DaysBeforeExp):
                self.liquidate(self.contract)
                self.log("Closed: Too close to expiration")
                self.contract = str()



    def BuyPut(self, data):
        if self.contract == str():
            self.contract = self.OptionsFilter(data)
            return
        elif not self.portfolio[self.contract].invested and data.contains_key(self.contract):
            #buy 1 put option for every 90 shares of underlying security that we own
            self.buy(self.contract,round(self.portfolio[self.symbol].quantity / self.options_alloc))
    
    def OptionsFilter(self, data):
        #one way is to use set filter and to iterate over available option chain object
        #this can take a lot of time to backtest
        #instead we use option chan provider which allows you to manually
        #add only those options which you want to trade
        # at the moment option chain provider doesn't allow option greeks and option implied volatilty

        contracts = self.option_chain_provider.get_option_contract_list(self.symbol, self.time)
        self.underlyingprice = self.securities[self.symbol].price

        otm_puts = [i for i in contracts if i.id.OptionRight==OptionRight.PUT and
        self.underlyingprice - i.id.strike_price > self.OTM *self.underlyingprice and
        self.DTE - 8 < (i.id.date - data.time).days < self.DTE + 8
        ]

        if len(otm_puts)> 0:
            contract = sorted(sorted(otm_puts, key = lambda x: abs((x.ID.date - self.time).days - self.DTE)),
            key = lambda x: self.underlyingprice - x.ID.strike_price)[0] 

            if contract not in self.contractsAdded:
                self.contractsAdded.add(contract)
                self.add_option_contract(contract, Resolution.MINUTE)
            return contract
        else:
            return str()
    
    def Plotting(self):
        self.plot("Vol Chart", "Rank", self.rank)
        self.plot("Vol Chart", "lvl", self.IVlvl)
        self.plot("Data Chart", self.symbol, self.securities[self.symbol].close) 

    #     option_invested = [x.key for x in self.portfolio if x.value.invested and x.value.type == self.security_type.option]
    #     if option_invested:
    #         self.plot("Data Chart","Strike", option_invested[0].ID.strike_price)
    
    def on_order_event(self, order_event):
        self.log(str(order_event))