TODAY’S EXPECTED MOVEMENT (MMM & IV) OF THE SPX
TOS doesn’t give expected movement (MMM - MarketMakerMove) for the SPX although it does give the Implied Volatility for the day. The ETF SPY, however, does give MMM as well as IV for the day. Since the SPY and the SPX are closely related, I built and indicator for the SPX which would take data from SPY and convert it to correspond with the SPX. In essence it is a SPX “synthetic” which should closely approximate the trading boundaries for the day. When short trading a spread on SPX I would go a few points outside the boundaries to place a trade, or, I would direction trade using my Stop and Reverse Indicator, or my AGAIG AI Intraday Optimizer Indicator.
The boundaries are set a few minutes after open. The MMM is shown as a solid CYAN line and the IV Boundaries as a dashed red line. The do not paint pre-market or after market and are setting probable boundaries for day trading purposes only. This particular indicator is established for the SPX only. Also included is a solid yellow VWAP and dashed lines representing 1SD and 2SD.
This represents a little more trading information for the SPX than is available on the TOS platform?
There are several data labels with this indicator and I usually say no on the “show diagnostics” in Added studies and strategies where you can also change and position the Label Size.
INDICATOR LINK: http://tos.mx/!XJEfJZEq
Code:
# ============================================================
# SPX Synthetic MMM by Ratio — C. Ricks 6/17/26
# Since GetMarketMakerMove() returns 0 on SPX (no single-
# company earnings catalyst to trigger excess volatility),
# this study derives an SPX-equivalent MMM by:
#
# 1. Reading SPY's own live IV-based expected move
# 2. Reading SPY's own live MMM (works fine on SPY)
# 3. Computing the ratio MMM_ratio = SPY_MMM / SPY_IVmove
# 4. Reading SPX's own live IV-based expected move
# (imp_volatility() works fine on SPX, unrestricted)
# 5. SPX synthetic MMM = SPX_IVmove * MMM_ratio
#
# This assumes the SPY MMM/IV relationship (excess vol as a
# fraction of full expected move) transfers reasonably to
# SPX since both track the same underlying index, just
# scaled by share price.
# ============================================================
input tradingDaysPerYear = 252;
input showLabels = yes;
input showDiagnostics = no;
input showLines = yes;
input showOnlyRTH = yes;
input LabelSize = fontsize.medium;
input LabelLocation = location.Bottom_left;
# Detect the first bar of the REGULAR trading session each day,
# using the standard community pattern: a date rollover combined
# with crossing from before-open to inside-open. This is more
# robust through backfill/history than relying on RTH start/end
# alone, since it explicitly anchors to the YYYYMMDD date change.
def todayDate = GetYYYYMMDD();
def isRollover = todayDate != todayDate[1];
def rthStart = RegularTradingStart(todayDate);
def rthEnd = RegularTradingEnd(todayDate);
def beforeRTH = GetTime() < rthStart;
def afterRTH = GetTime() > rthEnd;
def inRTH = !beforeRTH and !afterRTH;
def isFirstBarOfDay = (beforeRTH[1] and !beforeRTH) or (isRollover and !beforeRTH);
# ============================================================
# SECTION 1 — SPY SIDE (the known/reference side)
# ============================================================
def spyClose = close(symbol = "SPY");
def spyOpenPx = open(symbol = "SPY", period = AggregationPeriod.DAY);
# SPY's own blended implied volatility (whole-underlying average)
def spyIVraw = imp_volatility(symbol = "SPY");
def spyIV = if spyIVraw < 3 then spyIVraw * 100 else spyIVraw;
# Time fraction remaining for a 0DTE day — full day fraction used at open
def timeFrac = Sqrt(1) / Sqrt(tradingDaysPerYear);
# SPY IV-based expected move for today's session
def spyIVmove = spyOpenPx * (spyIV / 100) * timeFrac;
# SPY's real Market Maker Move (this DOES populate on SPY when
# excess volatility is present; otherwise reads 0)
def spyMMMraw = GetMarketMakerMove(symbol = "SPY");
# Guard against divide-by-zero / no-MMM days
def spyMMMratioLive = if spyIVmove > 0 and spyMMMraw > 0
then spyMMMraw / spyIVmove
else Double.NaN;
# Lock the ratio at the open as well — MMM itself is computed
# pre-market / at-open by Schwab and shouldn't drift all day
def spyMMMratio;
spyMMMratio = if isFirstBarOfDay then spyMMMratioLive
else if IsNaN(spyMMMratio[1]) then spyMMMratioLive
else spyMMMratio[1];
# ============================================================
# SECTION 2 — SPX SIDE (the side we are actually charting)
# ============================================================
def spxOpenPx = open(symbol = "SPX", period = AggregationPeriod.DAY);
def spxIVraw = imp_volatility(symbol = "SPX");
# imp_volatility() can return either a decimal (0.152) or a
# percentage (15.2) depending on platform/symbol behavior.
# Normalize: if the raw value is less than 3, assume decimal
# form and convert to percentage; otherwise use as-is.
def spxIV = if spxIVraw < 3 then spxIVraw * 100 else spxIVraw;
# Live SPX trade price — used both for the live IV move calc
# and as the anchor source at the moment of locking
def spxPriceNow = close(symbol = "SPX");
def spxIVmoveLive = spxPriceNow * (spxIV / 100) * timeFrac;
# Lock IV move at the open — captured once on first bar of day,
# then held firm for the rest of the session
def spxIVmove;
spxIVmove = if isFirstBarOfDay then spxIVmoveLive
else if IsNaN(spxIVmove[1]) then spxIVmoveLive
else spxIVmove[1];
# Anchor to the actual SPX trade price at the first regular
# session bar, rather than the daily open() aggregation, which
# can occasionally reflect a pre-market or stale print depending
# on how the daily bar resolves mid-session
def spxOpenLocked;
spxOpenLocked = if isFirstBarOfDay then spxPriceNow
else if IsNaN(spxOpenLocked[1]) then spxPriceNow
else spxOpenLocked[1];
# ============================================================
# SECTION 3 — SYNTHETIC SPX MMM
# Apply SPY's measured MMM-to-IV ratio onto SPX's own IV move
# ============================================================
def spxSyntheticMMM = if !IsNaN(spyMMMratio)
then spxIVmove * spyMMMratio
else Double.NaN;
# Cross-check ratio: how SPX's IV move compares to SPY's IV move
# scaled by price (sanity check — should track close to the
# SPX/SPY price ratio, roughly 10–11x, if both are pricing
# the same underlying volatility consistently)
def priceRatio = spxOpenPx / spyOpenPx;
def ivMoveRatio = if spyIVmove > 0 then spxIVmove / spyIVmove else Double.NaN;
# ============================================================
# SECTION 4 — PLOT LEVELS ON SPX CHART
# ============================================================
plot SPX_IV_High = if showLines and (!showOnlyRTH or inRTH) then spxOpenLocked + spxIVmove else Double.NaN;
plot SPX_IV_Low = if showLines and (!showOnlyRTH or inRTH) then spxOpenLocked - spxIVmove else Double.NaN;
SPX_IV_High.SetDefaultColor(Color.RED);
SPX_IV_Low.SetDefaultColor(Color.RED);
SPX_IV_High.SetStyle(Curve.SHORT_DASH);
SPX_IV_Low.SetStyle(Curve.SHORT_DASH);
SPX_IV_High.HideBubble();
SPX_IV_Low.HideBubble();
plot SPX_MMM_High = if showLines and (!showOnlyRTH or inRTH) and !IsNaN(spxSyntheticMMM) then spxOpenLocked + spxSyntheticMMM else Double.NaN;
plot SPX_MMM_Low = if showLines and (!showOnlyRTH or inRTH) and !IsNaN(spxSyntheticMMM) then spxOpenLocked - spxSyntheticMMM else Double.NaN;
SPX_MMM_High.SetDefaultColor(Color.CYAN);
SPX_MMM_Low.SetDefaultColor(Color.CYAN);
SPX_MMM_High.SetStyle(Curve.FIRM);
SPX_MMM_Low.SetStyle(Curve.FIRM);
SPX_MMM_High.HideBubble();
SPX_MMM_Low.HideBubble();
# ============================================================
# SECTION 5 — LABELS
# ============================================================
AddLabel(showLabels and !IsNaN(spxSyntheticMMM),
"SPX Synth MMM: +/-" + Round(spxSyntheticMMM, 2) +
" (" + Round(spxOpenLocked - spxSyntheticMMM, 2) + " / " + Round(spxOpenLocked + spxSyntheticMMM, 2) + ")",
Color.CYAN, LabelLocation, LabelSize
);
AddLabel(showLabels and IsNaN(spxSyntheticMMM),
"SPX Synth MMM: N/A (SPY has no MMM signal today)",
Color.GRAY, LabelLocation, LabelSize
);
AddLabel(showLabels,
"SPX IV Move: +/-" + Round(spxIVmove, 2) +
" (" + Round(spxOpenLocked - spxIVmove, 2) + " / " + Round(spxOpenLocked + spxIVmove, 2) + ")",
Color.RED, LabelLocation, LabelSize
);
# ── Diagnostic labels — off by default, toggle showDiagnostics to debug ──
AddLabel(showDiagnostics,
"SPX Open: " + Round(spxOpenPx, 2) + " IV raw: " + Round(spxIVraw, 4) + " IV used: " + Round(spxIV, 1) + "%",
Color.GRAY, LabelLocation, LabelSize
);
AddLabel(showDiagnostics,
"Locked Anchor: " + Round(spxOpenLocked, 2) + " Live SPX: " + Round(spxPriceNow, 2) +
" FirstBar: " + isFirstBarOfDay,
Color.ORANGE, LabelLocation, LabelSize
);
AddLabel(showDiagnostics,
"SPY MMM/IV Ratio: " + (if !IsNaN(spyMMMratio) then AsPercent(spyMMMratio) else "N/A"),
Color.YELLOW, LabelLocation, LabelSize
);
AddLabel(showDiagnostics,
"Price Ratio SPX/SPY: " + Round(priceRatio, 2) +
"x IV-Move Ratio: " + Round(ivMoveRatio, 2) + "x",
Color.WHITE, LabelLocation, LabelSize
);
# ============================================================
# NOTES
# ============================================================
# - showOnlyRTH (default yes) hides the IV/MMM lines entirely
# outside regular trading hours (9:30am-4:00pm ET), so they
# will not paint through pre-market or after-hours bars even
# if your chart has extended sessions turned on. Set to no
# if you want the locked lines to keep displaying around the
# clock once they're set.
# - The first-bar-of-day detector now uses RegularTradingStart()
# rather than a simple calendar-day check, since calendar-day
# rollover does not align with the actual 9:30am session open
# on charts that include extended-hours data.
# - IV move, MMM ratio, and the open price anchor are all
# captured ONCE on the first bar of each trading day and
# held firm (CompoundValue) for the rest of the session.
# The bands will not drift intraday as live IV fluctuates;
# they reflect conditions exactly as they were at the open.
# - spyMMMraw will read 0 on most ordinary SPY sessions since
# MMM is built to detect single-name earnings-style excess
# volatility, which SPY rarely exhibits. On those days the
# synthetic SPX MMM will show N/A rather than a false number.
# - imp_volatility() returns a blended average across the
# underlying's options, not the exact ATM single-expiration
# number the option chain displays for one specific date.
# - The price ratio and IV-move ratio labels are a sanity
# check: if SPX is genuinely 10-11x SPY's price and both
# IV moves scale consistently, ivMoveRatio should land
# close to priceRatio. A large divergence between the two
# suggests SPX and SPY options are pricing different risk
# that day (term structure, dividend effects, skew, etc).
Last edited by a moderator: