Skip to main content

ATM Straddle Based on VWAP

This strategy sells an ATM straddle (Call and Put) when the combined premium is below VWAP, signaling potential for theta/time decay. It's designed for sideways or low-volatility days and aims to capture premium erosion throughout the day.

Ideal Conditions:

  • You're expecting a non-trending market.
  • Ideal for expiry days.
  • You want time decay to work in your favor, without directional bias.

Strategy Rules

Entry Conditions

  • No active positions: The system only takes a trade if no trades are currently open.
  • Time filter: Trades are allowed after 9:20 AM once the market has settled a bit.
  • VWAP confirmation: Combined premium < VWAP (on combined-premium candles)
## Entry Condition 
if (self.position == []) and \
(self._triggers == []) and \
(self.candleTime >= datetime.time(9,20,0)) and \
(self.dt_combPrem['close'].iloc[-1] < self.idc_combPrem_vwap.iloc[-1]):
self.act_ac380()

Exit Conditions

  • If Combined Premium > VWAP (vol picking up / regime shift), or
  • Time = 03:15 PM (hard, time-based exit)
## Exit Condition 
if (self.position != []) and \
(self.candleTime >= datetime.time(9,20,0)) and \
(((self.dt_combPrem['close'].iloc[-1] > self.idc_combPrem_vwap.iloc[-1]) or \
(self.candleTime == datetime.time(15,15,0)))):
self.act_ac396()

Imports & Class Shell

import datetime
import pandas as pd
from eventManager import Strategy, runEvents

class st_OptionsStraddle(Strategy):
pass

Strategy is your builder’s base class.

Action Registry

We maintain two actions—enter and exit—and add a minute-level failsafe that squares off around 3:15 PM.

class st_OptionsStraddle(Strategy):
def init(self):
self.actions_all = {
'act_ac380': {'trigger': False, 'legs': []},
'act_ac396': {'trigger': False, 'legs': []}
}

def minTrigger(self):
if (self.currentCandle + datetime.timedelta(minutes=1)).time() == datetime.time(15,15,0):
self.squareoff_all()
self.finish
  • act_ac380 will enter the trade; act_ac396 will exit all.
  • minTrigger runs every minute; we add a safety square-off at 3:15 PM.

Data: Combined Premium

We use the Combined Premium data and store it as dt_combPrem. The asof: 09:20:00 makes ATM selection consistent at that reference time.

def data_init(self):
self.getCombinedPremium(
strikes=[
{'exp': {'expNo': 0, 'expType': 'weekly'}, 'opType': 'CE', 'strike': {'at': 'atm', 'by': 'strike'}},
'ADD',
{'exp': {'expNo': 0, 'expType': 'weekly'}, 'opType': 'PE', 'strike': {'at': 'atm', 'by': 'strike'}}
],
dataLength=1,
candleType='candlestick',
timeFrame=None,
asof='09:20:00',
name='dt_combPrem'
)

Indicator: VWAP on Combined Premium

We apply VWAP directly to the combined-premium dataframe.

def indicator_init(self):
self.applyIndicator(
indicatorName='vwap',
name='idc_combPrem_vwap',
df=lambda: self.dt_combPrem,
)

Entry Action

At entry, sell CE + PE using ATM moneyness at 09:20 PM or after (whenever all conditions are satisfied).

def act_ac380(self):
self.actions_all['act_ac380']['trigger'] = True

# CE leg
leg = self.addLeg(
transactionType='sell',
optionType='CE',
strikeSelection={'strikeBy': 'moneyness', 'strikeVal': 0, 'asof': '09:20:00', 'roundoff': None},
qty=75,
expiry={'expType': 'weekly', 'expNo': 0},
target={'isTarget': False, 'targetOn': 'val', 'targetValue': 1},
SL={'isSL': False, 'SLon': 'val', 'SLvalue': 1},
trailSL={'isTrailSL': False, 'trailSLon': 'val', 'trailSL_X': 1, 'trailSL_Y': 1},
reEntryTg={'isReEntry': False, 'reEntryOn': 'asap', 'reEntryVal': 0, 'reEntryMaxNo': 20},
reEntrySL={'isReEntry': False, 'reEntryOn': '0', 'reEntryVal': None, 'reEntryMaxNo': 20},
waitTrade={'isWT': False, 'wtOn': 'val-up', 'wtVal': 1, 'triggers': []},
segment='OPT',
entryValidity='Day',
remark='Entrying Leg',
squareoff='this',
legName='lg_ac380_1',
webhook={'is': False, 'entryMessage': {}, 'exitMessage': {}}
)
self.actions_all['act_ac380']['legs'].append(leg)

# PE leg
leg = self.addLeg(
transactionType='sell',
optionType='PE',
strikeSelection={'strikeBy': 'moneyness', 'strikeVal': 0, 'asof': '09:20:00', 'roundoff': None},
qty=75,
expiry={'expType': 'weekly', 'expNo': 0},
target={'isTarget': False, 'targetOn': 'val', 'targetValue': 1},
SL={'isSL': False, 'SLon': 'val', 'SLvalue': 1},
trailSL={'isTrailSL': False, 'trailSLon': 'val', 'trailSL_X': 1, 'trailSL_Y': 1},
reEntryTg={'isReEntry': False, 'reEntryOn': 'asap', 'reEntryVal': 0, 'reEntryMaxNo': 20},
reEntrySL={'isReEntry': False, 'reEntryOn': '0', 'reEntryVal': None, 'reEntryMaxNo': 20},
waitTrade={'isWT': False, 'wtOn': 'val-up', 'wtVal': 1, 'triggers': []},
segment='OPT',
entryValidity='Day',
remark='Entrying Leg',
squareoff='this',
legName='lg_ac380_2',
webhook={'is': False, 'entryMessage': {}, 'exitMessage': {}}
)
self.actions_all['act_ac380']['legs'].append(leg)

Note: In this version we’ve disabled SL/targets to keep logic simple.

Exit Action

Square-off all positions.

def act_ac396(self):
self.actions_all['act_ac396']['trigger'] = True
self.squareoff_all(remark='Square off all')

Daily Setup — Load & Init

Load data, apply indicators, reset per-day counters.

def onNewDay(self):
self.data_init()
self.indicator_init()
self.entries = {'cnd_ct1': {'max': 0, 'cur': 0}}

Complete Code

import datetime
import pandas as pd
from eventManager import Strategy, runEvents

class st_OptionsStraddle(Strategy):
def init(self):
self.actions_all = {'act_ac380': {'trigger': False, 'legs': []}, 'act_ac396': {'trigger': False, 'legs': []}}

def minTrigger(self):

if (self.currentCandle + datetime.timedelta(minutes=1)).time() == datetime.time(15,15,0):
self.squareoff_all()
self.finish

def data_init(self):
self.getCombinedPremium(strikes=[{'exp': {'expNo': 0, 'expType': 'weekly'}, 'opType': 'CE', 'strike': {'at': 'atm', 'by': 'strike'}}, 'ADD', {'exp': {'expNo': 0, 'expType': 'weekly'}, 'opType': 'PE', 'strike': {'at': 'atm', 'by': 'strike'}}],
dataLength=1, candleType='candlestick',
timeFrame=None, asof = '09:20:00', name = 'dt_combPrem')

def indicator_init(self):
self.applyIndicator(indicatorName = 'vwap', name='idc_combPrem_vwap', df = lambda : self.dt_combPrem, )

def act_ac380(self):
self.actions_all['act_ac380']['trigger'] = True
## ATM Straddle
leg = self.addLeg(transactionType='sell', optionType='CE',
strikeSelection={'strikeBy' : 'moneyness', 'strikeVal':0, 'asof':'09:20:00', 'roundoff' : None},
qty=75, expiry={'expType':'weekly','expNo':0},
target={'isTarget':False,'targetOn':'val','targetValue':1},
SL={'isSL' : False, 'SLon': 'val', 'SLvalue' : 1},
trailSL = {'isTrailSL':False, 'trailSLon':'val', 'trailSL_X': 1, 'trailSL_Y': 1},
reEntryTg = {'isReEntry' : False, 'reEntryOn' : 'asap', 'reEntryVal' : 0, 'reEntryMaxNo':20},
reEntrySL = {'isReEntry' : False, 'reEntryOn' : '0', 'reEntryVal' : None, 'reEntryMaxNo':20},
waitTrade = {'isWT' : False, 'wtOn' : 'val-up', 'wtVal' : 1, 'triggers': []},
segment = 'OPT', entryValidity = 'Day',
remark = 'Entrying Leg',
squareoff = 'this', legName = 'lg_ac380_1',
webhook = {'is' : False, 'entryMessage' : {}, 'exitMessage' : {}})
self.actions_all['act_ac380']['legs'].append(leg)
leg = self.addLeg(transactionType='sell', optionType='PE',
strikeSelection={'strikeBy' : 'moneyness', 'strikeVal':0, 'asof':'09:20:00', 'roundoff' : None},
qty=75, expiry={'expType':'weekly','expNo':0},
target={'isTarget':False,'targetOn':'val','targetValue':1},
SL={'isSL' : False, 'SLon': 'val', 'SLvalue' : 1},
trailSL = {'isTrailSL':False, 'trailSLon':'val', 'trailSL_X': 1, 'trailSL_Y': 1},
reEntryTg = {'isReEntry' : False, 'reEntryOn' : 'asap', 'reEntryVal' : 0, 'reEntryMaxNo':20},
reEntrySL = {'isReEntry' : False, 'reEntryOn' : '0', 'reEntryVal' : None, 'reEntryMaxNo':20},
waitTrade = {'isWT' : False, 'wtOn' : 'val-up', 'wtVal' : 1, 'triggers': []},
segment = 'OPT', entryValidity = 'Day',
remark = 'Entrying Leg',
squareoff = 'this', legName = 'lg_ac380_2',
webhook = {'is' : False, 'entryMessage' : {}, 'exitMessage' : {}})
self.actions_all['act_ac380']['legs'].append(leg)

def act_ac396(self):
self.actions_all['act_ac396']['trigger'] = True
## Square off
self.squareoff_all(remark='Square off all')

def onNewDay(self):
self.data_init()
self.indicator_init()
self.entries = {'cnd_ct1': {'max': 0, 'cur': 0}}

def onCandleClose(self):

## Entry Condition
if (self.position == []) and \
(self._triggers == []) and \
(self.candleTime >= datetime.time(9,20,0)) and \
(self.dt_combPrem['close'].iloc[-1] < self.idc_combPrem_vwap.iloc[-1]):
self.act_ac380()

## Exit Condition
if (self.position != []) and \
(self.candleTime >= datetime.time(9,20,0)) and \
(((self.dt_combPrem['close'].iloc[-1] > self.idc_combPrem_vwap.iloc[-1]) or \
(self.candleTime == datetime.time(15,15,0)))):
self.act_ac396()

def onEnd(self):
pass