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)