| 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)