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