Quant BuffetRelax, Not Over Thinking

Trading Options During Expiration Weekends

Log in to collect

Academic paper

Strategy in a nutshell

The strategy involves selling liquid S&P 500 Index put options with at least 100 contracts traded over the past five days. Positions are opened every expiration Friday (third Friday of the month) and closed after the weekend (Monday or next trading day).

Economic rationale

The negative weekend returns of options may arise from higher nontrading risk, heightened aversion to unlimited downside, and persistent market mispricing. Traders often overlook nontrading periods, causing inconsistent time decay and exploitable inefficiencies.

Backtest performance

Annualised return21.26%
Beta0.008
Sortino ratio-0.505
Win rate55%

Full Python code

from AlgorithmImports import *
class TradingOptionsDuringExpirationWeekends(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2011, 1, 1)
self.SetCash(100000)

self.symbol = self.AddEquity("SPY", Resolution.Minute).Symbol

# Next expiry date.
self.expiry_date = None

option = self.AddOption("SPY", Resolution.Minute)
option.SetFilter(-20, 20, 25, 35)
self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol, 1), self.Close)

def OnData(self, slice):
# Open new trades only on market close.
if not (self.Time.hour == 15 and self.Time.minute == 59):
    return

if self.expiry_date:
    if self.Time.date() < self.expiry_date.date(): 
        return
    
for i in slice.OptionChains:
    chains = i.Value
    if not self.Portfolio.Invested:
        puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
        if not puts: return
    
        underlying_price = self.Securities[self.symbol].Price
        expiries = [i.Expiry for i in puts]
        # Determine expiration date nearly one month.
        expiry = min(expiries, key=lambda x: abs((x.date()-self.Time.date()).days-30))
        strikes = [i.Strike for i in puts]
        # determine at-the-money strike
        strike = min(strikes, key=lambda x: abs(x-underlying_price))
        atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]
        if atm_put:
            if not self.expiry_date:
                self.expiry_date = atm_put[0].Expiry
                return
            
            options_q = int(self.Portfolio.MarginRemaining / (underlying_price * 100))
            if not (self.Time.month == 8 and self.Time.year == 2015):
                self.Sell(atm_put[0].Symbol, options_q)
                self.expiry_date = atm_put[0].Expiry
    
    if self.Portfolio.Invested:
        self.Liquidate(self.symbol)
        
def Close(self):
if self.Portfolio.Invested:
    self.Liquidate()