Overall Statistics |
Total Trades 101 Average Win 3.94% Average Loss -1.87% Compounding Annual Return 4729.819% Drawdown 21.100% Expectancy 0.520 Net Profit 45.035% Sharpe Ratio 3.096 Probabilistic Sharpe Ratio 80.916% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 2.10 Alpha 3.381 Beta -2.698 Annual Standard Deviation 1.61 Annual Variance 2.592 Information Ratio 3.068 Tracking Error 1.819 Treynor Ratio -1.848 Total Fees $0.00 |
from trade import * from levels import * class OptionsOvernightContrarian(QCAlgorithm): def Initialize(self): # Settings self.SetStartDate(2020, 1, 31) self.SetEndDate(2020, 3, 5) self.SetCash(10000) self.SetWarmup(timedelta(7)) self.EnableAutomaticIndicatorWarmUp = True self.orderDate = None # Apply Robinhood Fees self.SetSecurityInitializer(lambda security: security.SetFeeModel(ConstantFeeModel(0))) # Select Underlying and Configure self.ticker = "SPY" self.equity = self.AddEquity(self.ticker) self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw) # Select Target Asset to Trade self.option = self.AddOption(self.ticker) self.option.SetFilter(self.OptionFilterUniverse) # Exit before any assignments self.Schedule.On(self.DateRules.EveryDay(self.ticker), self.TimeRules.AfterMarketOpen(self.ticker, 1), self.MarketOpen) self.Schedule.On(self.DateRules.EveryDay(self.ticker), self.TimeRules.BeforeMarketClose(self.ticker, 5), self.BeforeMarketClose) self.Schedule.On(self.DateRules.EveryDay(self.ticker), self.TimeRules.BeforeMarketClose(self.ticker, -1), self.MarketClose) # Trade Tracker self.Trade = TradeManagement(self, self.ticker, self.option.Symbol) # Support Resistance Detection: self.SupportResistance = SupportResistance(self, self.ticker) self.mean = self.EMA(self.ticker, 10, Resolution.Minute) def MarketClose(self): self.SupportResistance.Reset() def CloseTo(self, x, y, delta): return abs(x-y) < delta def OnData(self, data): # Ignore dividends, splits etc if not self.equity.Exchange.ExchangeOpen or self.IsWarmingUp: return # Manage existing positions: self.Trade.ManageOpenPositions() price = self.equity.Price support = self.SupportResistance.NextSupport() resistance = self.SupportResistance.NextResistance() mean = self.mean.Current.Value if self.CloseTo(price, support, 0.15) and mean > support: t = self.Trade.Create(OrderDirection.Buy) self.Log(f"{self.Time} LONG: Price {price} Support {support}") if self.CloseTo(price, resistance, 0.15) and mean < resistance: t = self.Trade.Create(OrderDirection.Sell) self.Log(f"{self.Time} SHORT: Price {price} Resistance {resistance}") ################################################################################################# # SCRATCH ####################################################################################### ################################################################################################# def MarketOpen(self): pass def BeforeMarketClose(self): pass def OptionFilterUniverse(self, universe): # Select puts 2-3 strikes OOM, expiring at least 2 days out; but no more than 7 return universe.IncludeWeeklys().Strikes(-3, 3).Expiration(2, 7) def StrikeVisualization(self, contracts): strikes = f"{self.Time} - {self.equity.Price} :: [[" for x in self.contracts: strikes = strikes + f"{x.Strike}-{x.Expiry} ]] [[" self.Log(strikes) self.Log(f"{self.Time} - Contract: {self.contracts[0]} | Strike: {self.contracts[0].Strike} | Underlying: {self.equity.Price} | Delta: {self.equity.Price - self.contracts[0].Strike}")
import math class SupportResistance: # Get the S&R's for the last 2 weeks. def __init__(self, algorithm, ticker): self.Ticker = ticker self.Algorithm = algorithm self.Daily = RollingWindow[TradeBar](7); self.Min = algorithm.MIN(self.Ticker, 390, Resolution.Minute) # Range of today; breaking out of new highs/lows self.Max = algorithm.MAX(self.Ticker, 390, Resolution.Minute) algorithm.Consolidate(self.Ticker, Resolution.Daily, self.SaveDailyBars) def NextSupport(self): support = [] price = self.Algorithm.Securities[self.Ticker].Price # Rounded Price to $1 support.append( round(price) ) # Rounded Price to $10 support.append( int(math.floor(price / 10.0)) * 10 ) # Yesterday's OHLC Price support.append( self.Daily[0].Close ) support.append( self.Daily[0].Low ) # Append 7 day Low: support.append( min([bar.Low for bar in self.Daily]) ) support = sorted(support, reverse=True) return support[0] def NextResistance(self): resistance = [] price = self.Algorithm.Securities[self.Ticker].Price # Round Price Up to $1 resistance.append( math.ceil(price) ) # Rounded Price Up to $10 resistance.append( int(math.ceil(price / 10.0)) * 10 ) # Yesterday's Close, High resistance.append( self.Daily[0].Close ) resistance.append( self.Daily[0].High ) # Append 7 Day High resistance.append( max([bar.High for bar in self.Daily]) ) # Take the lowest value on the list resistance = sorted(resistance) return resistance[0] # Build a 14 day historical rolling window of underlying prices. def SaveDailyBars(self, bar): self.Daily.Add(bar) # Reset any "daily" indicators def Reset(self): self.Min.Reset() self.Max.Reset()
import random import string import math class TradeManagement: def __init__(self, algorithm, ticker, optionSymbol): self.Trades = {} self.Algorithm = algorithm self.Ticker = ticker self.OptionSymbol = optionSymbol # Volatility indicators for position sizing self.atr = self.Algorithm.ATR(self.Ticker, 3, MovingAverageType.Simple, Resolution.Hour) self.stddev = self.Algorithm.STD(self.Ticker, 6, Resolution.Hour) # Manage Open Positions def ManageOpenPositions(self): for t in self.OpenTrades(): # Scan for Profit-Loss if t.UnrealizedProfit() > t.Target: self.Close(t, "Profit") elif t.UnrealizedProfit() < -t.Target: self.Close(t, "Loss") # Stop Assignment if t.ExpiringSoon(): self.Close(t, "Expiring") # Base target contract count on the number of contracts to hit the profit assuming 2SD move. def ContractSizing(self, targetProfit): expectedDollarMovement = 2 * self.stddev.Current.Value * 100 # At least 1 contract contracts = min(5, math.ceil(targetProfit / expectedDollarMovement)) return int(contracts) # Base target profit per position on the volatility of the last few days. def TargetProfitEstimate(self): return round(self.atr.Current.Value * 100) def OpenTrades(self): return [t for t in self.Trades.values() if t.IsOpen()] # Place a trade in the direction signalled def Create(self, direction): symbol = self.SelectOptionContract(direction) if symbol is None: return # If we already hold; skip alreadyOpen = [c for c in self.OpenTrades() if c.Symbol == symbol] if len(alreadyOpen) > 0: return # If today's the expiry don't trade # if (symbol.ID.Date < self.Algorithm.Time): # return targetProfit = self.TargetProfitEstimate() size = self.ContractSizing(targetProfit) price = self.Algorithm.Securities[symbol].Price asset = self.Algorithm.Securities[self.Ticker].Price tag = f"Asset: {asset} | Opened | Target of ${targetProfit} at ${price}" ticket = self.Algorithm.MarketOrder(symbol, size, False, tag) trade = Trade(self.Algorithm, self.Algorithm.Securities[symbol], ticket, targetProfit) self.Trades[trade.Id] = trade return trade def Close(self, trade, reason): trade.Close(reason) del self.Trades[trade.Id] # Select OOM Option Contract with Given Bias def SelectOptionContract(self, direction): if direction == OrderDirection.Buy: right = OptionRight.Call else: right = OptionRight.Put chain = self.Algorithm.CurrentSlice.OptionChains.GetValue(self.OptionSymbol) if chain is None: self.Algorithm.Log(f"{self.Algorithm.Time} - No option chains with {self.OptionSymbol}. Missing data for Mar-2nd.") return None # Select contracts expirying tomorrow at least. chain = [x for x in chain if (x.Right == right and x.Expiry > self.Algorithm.Time)] reverseSort = right == OptionRight.Call # Reverse sort of a call contracts = sorted(sorted(chain, key = lambda x: abs(x.Strike), reverse=reverseSort), key = lambda x: x.Expiry) # No contracts found if len(contracts) == 0: return None; return contracts[0].Symbol class Trade: def __init__(self, algorithm, contract, ticket, target): self.Id = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) self.Ticket = ticket self.Contract = contract self.Symbol = contract.Symbol self.Algorithm = algorithm self.Open = True self.Target = target def ExpiringSoon(self): expiry = self.Symbol.ID.Date + timedelta(hours=16) if ( (expiry - self.Algorithm.Time) < timedelta(minutes=10)): self.Algorithm.Log(f"{self.Symbol} Close to Expiry: {expiry} - {self.Algorithm.Time} < 10min" ) return True else: return False def UnrealizedProfitPercent(self): return (self.Contract.Holdings.Price - self.Ticket.AverageFillPrice) / self.Contract.Holdings.Price def UnrealizedProfit(self): return 100*(self.Contract.Holdings.Price - self.Ticket.AverageFillPrice) * self.Ticket.Quantity def IsOpen(self): return self.Open def Close(self, reason): self.Open = False tag = f"Close | {reason} | {self.UnrealizedProfit()} Net" self.Algorithm.Liquidate(self.Symbol, tag)