Overall Statistics
Total Trades
2790
Average Win
0.35%
Average Loss
-0.45%
Compounding Annual Return
18.669%
Drawdown
48.500%
Expectancy
0.044
Net Profit
6.072%
Sharpe Ratio
0.966
Probabilistic Sharpe Ratio
37.702%
Loss Rate
41%
Win Rate
59%
Profit-Loss Ratio
0.78
Alpha
0.849
Beta
3.458
Annual Standard Deviation
1.413
Annual Variance
1.997
Information Ratio
0.899
Tracking Error
1.352
Treynor Ratio
0.395
Total Fees
$2790.75
Estimated Strategy Capacity
$3400000.00
Lowest Capacity Asset
SPY Y693XFHRGNAE|SPY R735QTJ8XC9X
Portfolio Turnover
32.46%
# region imports
from AlgorithmImports import *
from datetime import timedelta
#from QuantConnect.Data.Custom.CBOE import *
# endregion

class HyperActiveTanCow(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 10, 22)  # Set Start Date
        self.SetEndDate(2023, 2, 25)
        self.SetCash(10000)  # Set Strategy Cash
        self.equity = self.AddEquity("SPY", Resolution.Minute) # Add the underlying stock: Spy
        self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)

        option = self.AddOption("SPY", Resolution.Minute) # Add the option corresponding to underlying stock
        self.symbol = option.Symbol     #can now symbol back and forth

        self.contract = str()
        self.contractsAdded = set()
        chart = Chart("TEST")
        self.AddChart(chart)
        chart.AddSeries(Series("Liq", SeriesType.Scatter, "$", Color.Red, ScatterMarkerSymbol.Circle))
        chart.AddSeries(Series("buyFlag", SeriesType.Scatter, "$", Color.Green, ScatterMarkerSymbol.Circle))
        chart.AddSeries(Series("buyCall", SeriesType.Scatter, "$", Color.Yellow, ScatterMarkerSymbol.Circle))
        chart.AddSeries(Series("quant", SeriesType.Bar, "$", Color.Blue))
        chart.AddSeries(Series("Chain_None", SeriesType.Scatter, "$", Color.Pink, ScatterMarkerSymbol.Circle))

        #create array to track n amount of 2min ewo bars
        self.ewoTracker = []

        #this is the array iterator to count each entry space/element
        self.i = -1
        self.needToPop = False
        self.haveHoldings = False
        self.buyBar = 0
        self.buyCallFlag = False
        self.inCall = False
        self.sellCall = False

        #Have the algo only look at contracts that are 1 row below the strike price and 0dte
        #option.SetFilter(-1, 1, timedelta(0), timedelta(1))
        option.SetFilter(self.UniverseFunc)
        

        #Create two min consolidator, attach event handler for every 2min, then add consolidator to the engines subscription manager for updates
        twoMinConsolidator = TradeBarConsolidator(timedelta(minutes=10))
        twoMinConsolidator.DataConsolidated += self.TwoMinBarHandler
        self.SubscriptionManager.AddConsolidator("SPY", twoMinConsolidator)

        #Create EMA indicators
        self.ema5 = ExponentialMovingAverage(5, 0.33)
        self.ema35 = ExponentialMovingAverage(35, 0.055)

        #Have indicator work w 2min bars
        self.RegisterIndicator("SPY", self.ema5, twoMinConsolidator)
        self.RegisterIndicator("SPY", self.ema35, twoMinConsolidator)

        #Create a scheduled event that sells all holdings before close of day
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 1), self.ClosePositions)

    def UniverseFunc(self, universe):
        return universe.IncludeWeeklys().Strikes(-1, 0).Expiration(timedelta(0), timedelta(0))

    def OnData(self, data: Slice):
        #Ensure indicators are warmed up
        if not self.ema5.IsReady and self.ema35.IsReady:
            self.Debug("Indicators not ready")
            return
        
        self.Plot("ExponentialMovingAverage", "ema5", self.ema5.Current.Value)
        self.Plot("ExponentialMovingAverage", "ema35", self.ema35.Current.Value)

        #Track for sell
        #check to see if we have any open option positions, check by making a list of invested securities that are option type
        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type == SecurityType.Option]

        if self.buyCallFlag:
            #self.Debug("if buyCallFlag" + str(self.Time))
            chains = str()
            chains = data.OptionChains.get(self.symbol)
            
            
            if chains is None:
                self.Debug("Empty chain")
                self.buyCallFlag = False
                self.contract = str()
                self.Plot("TEST", "Chain_None", 60)
                return
            self.BuyCall(chains)
            
            self.contract = str()
          
            
            #self.Debug("if buyCallFlag3" + str(self.Time))
            
            

        return

    def TwoMinBarHandler(self, sender, consolidated):
        
        
        self.i += 1

    #Deciding Entry (ONLY with CALLS)

        #took out my strategy but feel free to implement some arbitrary buy signal for testing purposes
        if (self.i >= 3):
            self.buyCallFlag = True
            #self.Debug("buyCallFlag True " + str(self.Time))
            self.buyBar = self.i
            self.inCall = True
            #self.MarketOrder("SPY", 100)
            #Call BuyCall function
            self.Plot("TEST", "buyFlag", 40)
            

        if self.inCall:
            if(self.i == 8): #again had to take out the strategy and put something random in there
                #self.Sell(self.call.Symbol, 1)
                #self.Sell(option_invested[0], 1)
                self.sellCall = True
                self.inCall = False
                self.Liquidate()
                #self.buyCallFlag = False


#Buy Call Function

    def BuyCall(self, chains):
       
        #If before 1pm, buy 0dte
        if self.Time.hour < 13:
            self.Plot("TEST", "buyCall", 20)
            #sorts available chain by expiration date, and pick the closest expiration date
            expiry = sorted(chains, key = lambda x: x.Expiry, reverse=False)[0].Expiry
            self.Debug("The expiry is " + str(expiry))
            #self.Debug(str(self.Time))
            #Filter out only call options with those expiration dates
            calls = [i for i in chains if i.Expiry == expiry and i.Right == OptionRight.Call]
            self.Plot("TEST", "quant", len(calls))
            #Sort by strike price, closest to the underlyings price.  This is sorted from closest to farthest away from underlying price
            call_contracts = sorted(calls, key = lambda x: abs(x.Strike - x.UnderlyingLastPrice))
            self.buyCallFlag = False
            #self.Debug(str(self.call.UnderlyingLastPrice))
            #df = pd.DataFrame([[x.Right,x.Strike,x.Expiry,x.BidPrice,x.AskPrice] for x in call_contracts],
                           #index=[x.Symbol.Value for x in call_contracts],
                           #columns=['type(call 0, put 1)', 'strike', 'expiry', 'ask price', 'bid price'])
            #self.Log(str(df))

        #IF after 1pm, buy 1dte
        elif self.Time.hour >= 13:
            self.buyCallFlag = False
            return
            #sorts available chain by expiration date, and pick the furthest expiration date
            #expiry = sorted(chains, key = lambda x: x.Expiry, reverse=True)[0].Expiry
            #self.Debug("The expiry is " + str(expiry))
            #self.Debug(str(self.Time))
            #Filter out only call options with those expiration dates
            #calls = [i for i in chains if i.Expiry == expiry and i.Right == OptionRight.Call]
            #Sort by strike price, closest to the underlyings price.  This is sorted from closest to farthest away from underlying price
            #call_contracts = sorted(calls, key = lambda x: abs(x.Strike - x.UnderlyingLastPrice))

            #if list is not empty, save the first element to the call option variable
        if len(call_contracts) == 0:
            self.Debug("No available contracts")
            self.buyCallFlag = False
            return
        self.call = call_contracts[0]

        self.Debug("The call to buy is " + str(self.call) + ". The underlying price is " + str(self.call.UnderlyingLastPrice) + ", and the strike price is " + str(self.call.Strike) + " at " + str(self.Time))

        #calculate order quantity 
        #quantity = self.Portfolio.TotalPortfolioValue / self.call.AskPrice
        #quantity = int(0.05 * quantity / 100)
        #buy the option
        self.Buy(self.call.Symbol, 1)
        self.LimitOrder(self.call.Symbol, -1, (self.call.AskPrice * 1.1))
        self.StopMarketOrder(self.call.Symbol, -1, (self.call.AskPrice * 0.8))
        self.avgPrice = self.call.AskPrice
        self.buyCallFlag = False
        

#Liquidate any holdings before close of day

    def ClosePositions(self):
        self.i = -1
        self.Plot("TEST", "Liq", 1)
        self.ewoTracker = []
        if self.Portfolio.Invested:
            self.Liquidate()

    
    def OnOrderEvent(self, orderEvent):
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        if order.Type == OrderType.OptionExercise:
                self.Debug(f"{orderEvent.Symbol} Profit: {self.Portfolio[orderEvent.Symbol].Profit}, Total Profit: {self.Portfolio.TotalProfit}")
        if order.Status == OrderStatus.Filled:
            if order.Type == OrderType.Limit or order.Type == OrderType.StopMarket:
                self.Transactions.CancelOpenOrders(order.Symbol)