
Tagging 8-K disclosures with AI: corporate events, labelled by what actually happened
A SEC 8-K API that labels what actually happened. Query filings by event type (CEO departures, cybersecurity incidents, M&A), with source-cited tags.

rian

Introducing
May 13, 2026
TL;DR: Massive's NOI WebSocket channel streams NYSE Net Order Imbalance data in real time, including imbalance quantity, paired shares, and the indicative clearing price. In this post, we walk through how NYSE auctions work, why order imbalances are one of the most actionable pre-auction signals available, what an NOI event looks like field by field, and two runnable Python examples: a firehose that prints every update, and a smart monitor with buy/sell direction labels, convergence tracking, and CLI-based ticker and size filtering.
In this post, we'll cover what Net Order Imbalance data represents and why it matters, the NOI event schema, how to read direction, magnitude, and convergence signals from the stream, and how to build a smart monitor that surfaces only the imbalances you care about.
Who this is for: Quantitative traders, algorithmic trading teams, and fintech builders who want pre-auction order flow intelligence from NYSE in real time.
Plan requirement: NOI data requires a plan with NYSE order imbalance access.
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.
Massive's NOI WebSocket channel gives you real-time visibility into buy and sell pressure ahead of NYSE auctions. By subscribing, you can stream imbalance quantity, paired shares, and indicative clearing prices as they update, giving you a continuous read on auction dynamics before the print happens.
To understand why that matters, it helps to know how auctions differ from regular trading. Most stock transactions happen through continuous trading, where buyers and sellers are matched individually throughout the day as orders hit the book. NYSE auctions work differently. Rather than matching orders one by one, NYSE batches all eligible orders together and determines a single clearing price that maximizes the number of shares traded. NYSE runs these auctions at three specific moments: market open, following a trading halt, and market close. Every participant in that auction transacts at that one price.
In the minutes leading up to each auction, buy and sell orders accumulate at different prices and sizes. The gap between buy-side and sell-side volume is the order imbalance. It signals which direction the auction price may need to move to attract enough liquidity to clear, making it one of the more actionable pre-auction signals available.
Each NOI event exposes three core fields:
NYSE begins disseminating open imbalance data around 9:00 AM ET, roughly 30 minutes before the 9:30 AM open auction. Close imbalance data starts around 3:50 PM ET. These two windows are where the most actionable NOI data is concentrated. Outside of them, events are primarily driven by trading halt auctions, which are less predictable in timing but follow the same imbalance mechanics.
The NOI channel is available on
A sample NOI event looks like this:
{ "ev": "NOI", "T": "JPM", "t": 1601318039223013600, "at": 930, "a": "M", "i": 44, "x": 10, "o": 480, "p": 440, "b": 25.03 }
Each NOI message contains:
| Field | SDK Attribute | Description |
|---|---|---|
| ev | event_type | Always NOI |
| T | symbol | NYSE-listed ticker |
| t | time_stamp | Nanosecond Unix epoch timestamp |
| at | auction_time | Scheduled auction time (HHMM format, Eastern) |
| a | auction_type | M = market open, C = close, H = halt |
| o | imbalance_quantity | Net imbalance in shares (positive = buy-side, negative = sell-side) |
| p | paired_quantity | Shares already matched and ready to clear |
| b | book_clearing_price | Indicative auction clearing price |
| x | exchange_id | Exchange identifier |
| i | symbol_sequence | Sequence number |
Order imbalance direction, magnitude, and convergence all carry signal.
Direction. A positive imbalance quantity (buy-side imbalance) means there are more buy orders than sell orders in the auction queue. To clear, the auction needs additional sell-side liquidity. NYSE's indicative clearing price will typically rise until enough sellers are attracted to close the gap. If the imbalance remains large into the auction, expect the opening or closing print to be above the last traded price. Sell-side imbalance is the inverse. Excess sell orders push the indicative clearing price lower as the auction seeks buy-side liquidity. A persistent sell-side imbalance heading into the close, for example, often results in a closing print below the last traded price.
Magnitude. A large buy imbalance on a thinly traded name carries significantly more weight than the same figure on a high-volume stock. A practical approach is to normalize imbalance quantity against the stock's average daily volume (ADV) or average auction volume. An imbalance representing 5% of ADV is a very different signal than one representing 0.05%.
Convergence. As the auction approaches, a shrinking imbalance means liquidity is arriving on the contra side. This is an important signal: it suggests the market is moving toward equilibrium and the final print is likely to be close to the current indicative clearing price. A large imbalance that is not shrinking as the auction nears suggests the indicative price may still have room to move.
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/noi-imbalance-monitor uv sync cp .env.example .env # Add your MASSIVE_API_KEY to .env
From there, run any script with
Subscribe to
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("NOI.*") def handle_msg(msgs): for m in msgs: print(m) print("NOI Basic Firehose Started") print("Streaming ALL NYSE order imbalance updates in real time") print("Press Ctrl+C to stop\n") try: client.run(handle_msg) except KeyboardInterrupt: pass
Raw output:
Imbalance(event_type='NOI', symbol='JPM', imbalance_quantity=5200, paired_quantity=42800, book_clearing_price=142.5, auction_type='M', ...) Imbalance(event_type='NOI', symbol='BAC', imbalance_quantity=-3100, paired_quantity=28400, book_clearing_price=38.75, auction_type='M', ...)
The firehose is useful for understanding what the stream looks like during the open and close windows. For real monitoring, you'll want filtering and structure.
The smart monitor transforms the raw feed into a structured table with buy/sell direction labels, convergence tracking between updates, and CLI-based filtering by ticker or minimum imbalance size.
import argparse import os from datetime import datetime from zoneinfo import ZoneInfo from dotenv import load_dotenv from massive import WebSocketClient from massive.websocket.models import Feed, Market, Imbalance load_dotenv() API_KEY = os.getenv("MASSIVE_API_KEY") EASTERN = ZoneInfo("America/New_York") AUCTION_LABELS = {"M": "Open", "C": "Close", "H": "Halt"} prev_imbalance: dict[str, int] = {} def fmt_time(ts_ns: int) -> str: return datetime.fromtimestamp( ts_ns / 1_000_000_000, tz=EASTERN ).strftime("%H:%M:%S") def direction(qty: int) -> str: if qty > 0: return "BUY" elif qty < 0: return "SELL" return "FLAT" def convergence(symbol: str, current: int) -> str: previous = prev_imbalance.get(symbol) prev_imbalance[symbol] = current if previous is None: return "" if abs(current) < abs(previous): return "shrinking" elif abs(current) > abs(previous): return "growing" return "" parser = argparse.ArgumentParser() parser.add_argument("--ticker", nargs="+") parser.add_argument("--min-imbalance", type=int, default=0) args = parser.parse_args() if args.ticker: subscriptions = [f"NOI.{t.upper()}" for t in args.ticker] else: subscriptions = ["NOI.*"] client = WebSocketClient( api_key=API_KEY, feed=Feed.RealTime, market=Market.Stocks, subscriptions=subscriptions, ) header = ( f"{'Time':>8} {'Symbol':6} {'Auction':7} {'Dir':4} " f"{'Imbalance':>10} {'Paired':>10} {'Price':>10} Trend" ) print(header) print("-" * len(header)) def handler(msgs): for m in msgs: if not isinstance(m, Imbalance): continue imb = m.imbalance_quantity or 0 if abs(imb) < args.min_imbalance: continue symbol = m.symbol or "" paired = m.paired_quantity or 0 price = m.book_clearing_price or 0.0 auction = AUCTION_LABELS.get(m.auction_type or "", "") ts = fmt_time(m.time_stamp) if m.time_stamp else "--:--:--" trend = convergence(symbol, imb) print( f"{ts:>8} {symbol:6} {auction:7} {direction(imb):4} " f"{imb:>10,} {paired:>10,} ${price:>9.2f} {trend}", flush=True, ) try: client.run(handle_msg=handler) except KeyboardInterrupt: pass
There are two things worth highlighting in this code.
First, the convergence tracker. For each ticker, the script stores the previous absolute imbalance and compares it to the current update. A "shrinking" label means contra-side liquidity is arriving: sellers are stepping in to offset a buy imbalance, or buyers to offset a sell imbalance. This is the signal that the auction is approaching equilibrium and the final print will likely land close to the current indicative price. "Growing" means pressure is still building in one direction.
Second, the subscription-level filtering. When you pass
Sample output watching JPM, BAC, and GS:
NOI Monitor | Watching: JPM, BAC, GS Press Ctrl+C to stop Time Symbol Auction Dir Imbalance Paired Price Trend ------------------------------------------------------------------------ 09:15:22 JPM Open BUY 5,200 42,800 $ 142.50 09:15:22 BAC Open SELL -3,100 28,400 $ 38.75 09:15:22 GS Open BUY 12,400 85,200 $ 412.30 09:15:45 JPM Open BUY 3,800 44,200 $ 142.35 shrinking 09:15:45 BAC Open SELL -4,500 27,000 $ 38.60 growing 09:15:45 GS Open BUY 8,100 89,500 $ 412.15 shrinking
In this snapshot, JPM and GS both show buy-side imbalances that are shrinking between updates, suggesting contra-side liquidity is arriving and the open auction print is likely near the current indicative price. BAC shows a sell-side imbalance that is growing, which means more sell pressure is accumulating and the clearing price may still move lower.
cd community/examples/websocket/noi-imbalance-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 noi_monitor.py # smart monitor, all tickers uv run noi_monitor.py --ticker JPM BAC GS # watch specific tickers uv run noi_monitor.py --min-imbalance 5000 # only large imbalances
Press Ctrl+C to stop.
Coverage: NOI data covers NYSE-listed tickers only. NASDAQ-listed names will not appear.
Timing and throughput: The bulk of NOI event volume is concentrated in two narrow windows: roughly 9:00 to 9:30 AM ET (before the open auction) and 3:50 to 4:00 PM ET (before the close auction). If you are processing selectively, these two windows are where the most actionable data is concentrated. Outside of them, the bulk of regular-hours events are driven by trading halt auctions, which are less predictable in timing but follow the same imbalance mechanics.
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.

Cole Power
cole
See what's happening at Massive

A SEC 8-K API that labels what actually happened. Query filings by event type (CEO departures, cybersecurity incidents, M&A), with source-cited tags.

rian

In this post, we'll walk through how Earnings Watcher built its analytics layer on Massive's options chains and snapshots, why Fair Market Value anchors its scanner, and how the same API pattern extended from options into post-earnings drift analysis on the underlying equity.

alexnovotny

Massive's LULD WebSocket channel streams Limit Up/Limit Down price band updates, halts, and resumptions for every NMS stock in real time.
editor