Introducing

Build a LULD Price Band Monitor with Massive's WebSocket API

Jun 18, 2026

TL;DR: Massive's LULD WebSocket channel streams Limit Up/Limit Down price band updates, halts, and resumptions for every NMS stock in real time. In this post, we walk through what LULD is and why it matters, the event schema, and two runnable Python examples: a full firehose that prints every band update, and a smart monitor that narrows the stream down to the Magnificent 7 plus market-wide halt and resumption alerts. Clone the repo, plug in your API key, and start monitoring the market's volatility guardrails in minutes.

In this post, we'll cover what Limit Up/Limit Down price bands are and why they are one of the most useful real-time inputs for a risk system or volatility dashboard, what a LULD WebSocket event looks like field by field, and how to build a practical monitor that separates routine band recalculations from the rare, high-signal halt events.

Who this is for: Quantitative traders, algorithmic trading teams, risk-system engineers, and fintech builders who need real-time awareness of intraday volatility guardrails across U.S. equities.

Plan requirement: LULD data requires a Stocks Advanced plan or the LULD Expansion add-on.

New to WebSockets? If you haven't connected to Massive's WebSocket API before, start with the WebSocket quickstart. It walks through the connect, authenticate, and subscribe pattern, the Python SDK installation, and the shape of a minimal client. The rest of this post assumes familiarity with those basics.

What LULD Is

Limit Up/Limit Down is a regulatory mechanism designed to prevent excessive volatility by establishing dynamic price bands for individual securities. Think of them as guardrails: for every NMS stock, the exchanges calculate an upper and lower price boundary that updates continuously throughout the trading day. When a stock's price approaches or breaches those boundaries, the system responds with escalating intervention. First, the stock enters a limit state. If the price doesn't recover within 15 seconds, a five-minute trading pause is triggered. If volatility persists after the pause, the stock can be halted entirely.

LULD bands are recalculated throughout regular trading hours (9:30 AM to 4:00 PM ET). The width of each band depends on the stock's reference price tier and the time of day. Bands are wider during the first 15 minutes and last 25 minutes of the session, when volatility tends to be highest.

By subscribing to Massive's LULD WebSocket feed, you can monitor these events across NYSE, Nasdaq, and other U.S. exchanges in real time. For quantitative traders and risk systems, LULD bands define the boundary conditions of intraday price movement. Knowing exactly where the ceiling and floor are, and how fast a stock is approaching them, is a direct input to position sizing and risk management. For anyone building monitoring dashboards or alerting tools, LULD events are the earliest signal that a stock is approaching a halt, giving you time to react before the pause hits.

LULD Event Structure

The LULD channel is available on

wss://socket.massive.com/stocks
. You can subscribe to a single ticker (
LULD.TSLA
), a specific set (
LULD.TSLA,LULD.NVDA
), or the full firehose (
LULD.*
).

A sample LULD message looks like this:

{
  "ev": "LULD",
  "T": "MSFT",
  "h": 492.99,
  "l": 446.04,
  "i": [16],
  "z": 3,
  "t": 1764086430905642800,
  "q": 5925769
}

Here is what each field contains:

FieldDescription
evEvent type (always "LULD")
TTicker symbol
hUpper price band (limit up)
lLower price band (limit down)
iArray of indicator codes
zTape (1 = NYSE, 2 = AMEX, 3 = Nasdaq)
tTimestamp (nanoseconds)
qSequence number for message ordering

The indicator codes carry the most signal:

IndicatorMeaning
15, 16Price band update (routine recalculation)
17Trading halt or pause (NASDAQ-listed tickers only)
18Trading resumption or reopening (NASDAQ-listed tickers only)

Indicators 15 and 16 fire continuously as bands are recalculated throughout the day. They are high volume but low urgency. Indicators 17 and 18 are the rare, high-signal events: actual halts and resumptions. A practical monitoring setup watches the routine updates for your specific watchlist while alerting on halts and resumptions across the entire market. For the full list of indicator and condition codes, see the Massive conditions and indicators glossary.

Cloning the Example Repo

Both example scripts are published in Massive's community GitHub repo. You'll need Python 3.10 or later, uv for dependency management, and a Massive API key.

git clone https://github.com/massive-com/community.git
cd community/examples/websocket/luld-monitor
uv sync
cp .env.example .env
# Add your MASSIVE_API_KEY to .env

From there, run any script with

uv run <script>.py
.

Basic Firehose

The simplest approach is to subscribe to

LULD.*
and print everything. The firehose is useful for understanding the volume and shape of the data before layering logic on top.

from massive import WebSocketClient
from massive.websocket.models import Feed, Market
import os
from dotenv import load_dotenv

load_dotenv()
API_KEY = os.getenv("MASSIVE_API_KEY")

client = WebSocketClient(
    api_key=API_KEY,
    feed=Feed.RealTime,
    market=Market.Stocks
)

client.subscribe("LULD.*")

def handle_msg(msgs):
    for m in msgs:
        print(m)

print("LULD Basic Firehose Started")
print("Streaming ALL LULD price-band updates in real time")
print("Press Ctrl+C to stop\n")

try:
    client.run(handle_msg)
except KeyboardInterrupt:
    pass

This outputs hundreds of messages per minute during market hours. Every price band recalculation for every ticker crosses the wire:

LimitUpLimitDown(event_type='LULD', symbol='NVDA', high_price=200.95, low_price=181.81, indicators=[16], tape=3, ...)
LimitUpLimitDown(event_type='LULD', symbol='ZOOZ', high_price=0.46, low_price=0.16, indicators=[16], tape=3, ...)

The firehose is useful for understanding the volume and shape of the data, but not what you'd run as a monitoring tool. For that, you need filtering.

Smart Monitor: Mag 7 Bands + Market-Wide Halt Alerts

The smart monitor narrows the firehose down to what's actually actionable: price bands for the Magnificent 7 (AAPL, AMZN, GOOGL, META, MSFT, NVDA, TSLA) and any trading halts or resumptions across the full market.

from massive import WebSocketClient
from massive.websocket.models import Feed, Market, LimitUpLimitDown
import os
import time
from dotenv import load_dotenv

load_dotenv()
API_KEY = os.getenv("MASSIVE_API_KEY")

MAG7 = {"AAPL", "AMZN", "GOOGL", "META", "MSFT", "NVDA", "TSLA"}

client = WebSocketClient(
    api_key=API_KEY,
    feed=Feed.RealTime,
    market=Market.Stocks
)
client.subscribe("LULD.*")

def format_ts(ts: int) -> str:
    seconds = ts / 1_000_000_000
    return time.strftime("%H:%M:%S", time.localtime(seconds))

def handle_msg(msgs):
    for m in msgs:
        if not isinstance(m, LimitUpLimitDown):
            continue

        symbol = m.symbol
        indicators = m.indicators or []
        high = getattr(m, "high_price", None)
        low = getattr(m, "low_price", None)
        ts_str = format_ts(m.timestamp)

        alert = ""
        if 17 in indicators:
            alert = " HALT / SUSPENDED PAUSE (Indicator 17)"
        elif 18 in indicators:
            alert = " RESUMPTION / REOPENING (Indicator 18)"

        if symbol in MAG7 or alert:
            print(f"{ts_str} | {symbol:6} | "
                  f"Upper: {high:>8.2f} | Lower: {low:>8.2f} | "
                  f"Ind: {indicators}{alert}")

try:
    client.run(handle_msg)
except KeyboardInterrupt:
    pass

The filtering logic is a two-tier approach. First, it checks whether the ticker is in the Mag 7 set; if so, the routine band update is worth displaying. Second, regardless of ticker, it checks whether the event contains indicator 17 (halt) or 18 (resumption), because those are always worth surfacing no matter the stock.

The result is a clean, focused output:

07:01:00 | NVDA   | Upper:   200.95 | Lower:   181.81 | Ind: [16]
07:01:00 | MSFT   | Upper:   411.48 | Lower:   372.29 | Ind: [16]
07:01:20 | AFJK   | Upper:     0.00 | Lower:     0.00 | Ind: [17] HALT / SUSPENDED PAUSE (Indicator 17)
07:06:20 | AFJK   | Upper:    52.34 | Lower:    42.82 | Ind: [18] RESUMPTION / REOPENING (Indicator 18)

The

MAG7
set is easy to customize. Swap in any tickers you are tracking. The structural pattern (always-watch-halts, selectively-watch-bands) scales to whatever your monitoring needs are.

Running It

cd community/examples/websocket/luld-monitor
uv sync
cp .env.example .env
# Add your MASSIVE_API_KEY to .env

uv run basic_firehose.py    # raw stream, all tickers
uv run luld_monitor.py      # smart monitor

Press Ctrl+C to stop either script.

What You Can Build From Here

The smart monitor is a starting point. A few directions that work well on top of it:

  • Plot LULD upper and lower bounds alongside price throughout the session using Chart.js or Matplotlib, giving you an intuitive view of how the guardrails respond to market conditions. Overlay halt and resumption markers (indicators 17 and 18) on the timeline to highlight moments of extreme volatility.
  • Build an alerting system that triggers when LULD bands narrow on your watchlist, so you can respond to volatility proactively rather than reactively.
  • Automatically adjust position sizing when bands narrow, log events for post-market analysis, or feed the halt and resumption stream into a dashboard your trading desk actually watches.
  • Correlate LULD events with trade and quote data from Massive's
    T
    and
    Q
    channels to study how price behavior compresses and expands relative to the bands.

Get Started

Disclaimer

This content is for educational purposes only. Nothing in this post constitutes investment advice or a recommendation to buy or sell any securities or other financial instruments. Massive is a market data provider, not a broker-dealer, exchange, or investment adviser. Market data accessed through Massive may originate from third-party exchanges and data providers or may be derived or calculated by Massive; in either case, it is subject to the applicable terms of your Massive subscription agreement. The data and code samples provided by Massive are offered on an "as-is" basis without any warranty of accuracy, completeness, or timeliness. You are solely responsible for your use of the data provided by Massive and for compliance with all applicable terms and conditions, laws, and data licensing requirements.

From the blog

See what's happening at Massive