Overall Statistics |
Total Trades 96 Average Win 0.07% Average Loss -0.03% Compounding Annual Return -3.785% Drawdown 1.600% Expectancy 0.483 Net Profit -0.369% Sharpe Ratio -0.512 Probabilistic Sharpe Ratio 32.272% Loss Rate 57% Win Rate 43% Profit-Loss Ratio 2.43 Alpha 0.035 Beta 0.12 Annual Standard Deviation 0.071 Annual Variance 0.005 Information Ratio 1.86 Tracking Error 0.3 Treynor Ratio -0.304 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(1000000) 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) # Trade Tracker self.Trade = TradeManagement(self, self.ticker, self.option.Symbol) # Support Resistance Detection: self.SupportResistance = SupportResistance(self, self.ticker) def OnData(self, data): # Ignore dividends, splits etc if not self.equity.Exchange.ExchangeOpen or self.IsWarmingUp: return # Manage existing positions: self.Trade.ManageOpenPositions() # Consider new ones: # if dayRange > 0: self.Trade.Create(OrderDirection.Buy) # else: # self.Trade.Create(OrderDirection.Sell) ################################################################################################# # 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}")
class SupportResistance: # Get the S&R's for the last 2 weeks. def __init__(self, algorithm, ticker): self.ticker = ticker self.daily = RollingWindow[TradeBar](14); 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): pass def NextResistance(self): pass # Build a 14 day historical rolling window of underlying prices. def SaveDailyBars(self, bar): self.daily.Add(bar)
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.Daily) 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 tag = f"Opened for 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)