Websocket

Trade ideas, market commentary, session signals, and the 0DTE read pushed as they happen

Instead of polling the REST API, your program can hold one websocket connection to Optionomics and be pushed each item the moment it is made: a trade idea as it opens and again as it resolves, each market commentary as it is published, and—on Developer Pro—every session signal and the minute-by-minute 0DTE read for SPY and SPX.

The websocket carries Optionomics’ own readings. It never carries raw exchange prints.

Availability: Developer includes the trade_ideas and market_commentaries topics. Developer Pro adds readings:signals and readings:zero_dte. See Subscription Plans.

Connecting

   
Address wss://optionomics.ai/socket/websocket
Protocol version add vsn=2.0.0 to the query string
Authentication your API key, sent as the token query parameter
Full URL wss://optionomics.ai/socket/websocket?vsn=2.0.0&token=YOUR_API_KEY

Use the same API key you use for the REST API and the MCP server. You can copy the address from the Where the key goes panel at optionomics.ai/developers/keys.

  • A missing, expired, or revoked key is refused when the connection opens (the handshake fails with HTTP 403).
  • Connect from a program—a server, a script, a bot—not from a web page on another website. Browsers on other sites are refused.
  • Keep the key out of source code: read it from an environment variable, as the examples below do.

Topics

After connecting, you join one or more topics. A topic your plan does not include refuses the join with the reason unauthorized; the connection itself stays open, and the other topics keep working.

Topic Plan Event When it arrives
trade_ideas Developer and Developer Pro trade_idea When a trade idea opens, and again when it resolves
market_commentaries Developer and Developer Pro commentary When a market commentary is published
readings:signals Developer Pro signal, signal_retired During the session (09:00–16:59 ET), as each session signal is published, restated, or retired
readings:zero_dte Developer Pro zero_dte Every minute while the regular session is open

trade_ideas

Each message is event trade_idea, with the same item the trade ideas REST endpoint returns: the idea’s id, symbol, strategy, levels, status, track record, timestamps, and so on. An opening and a resolution use the same event and the same shape—read the idea’s status to tell which one you received.

market_commentaries

Each message is event commentary:

Field Meaning
id The commentary’s id
category The commentary category
severity info, notable, or critical
headline The headline
body The full text
symbols The symbols it mentions (a list, possibly empty)
generated_at When it was written, ISO 8601
return_pct, peak_return_pct Follow-up performance figures, when there are any

readings:signals (Developer Pro)

Session signals are standing statements about a stock or the market—such as a stock touching the edge of its priced move, its priced move repricing far from its norm, or implied volatility’s premium reaching an extreme—each backed by a measurement. They are the same signals the starter alerts fire on. Like every Optionomics reading they are about size, odds, and cost, never direction.

Event signal carries:

Field Meaning
id The signal’s id—stable for the life of the signal
kind What kind of signal it is, for example expected_move_touch or expected_move_repricing
lane Its family: vol, event, flow, execution, or regime
horizon What it bears on: right now, the rest of the session, the next few weeks, or a dated event such as a report (for example intraday)
evidence_class How it is backed, for example measured
symbol The stock it is about, or none for a market-wide signal
score Its strength, 0 to 100
half_life_s How fast its strength fades, in seconds
facts The numbers behind the statement; the contents depend on the kind
revision Starts at 1
session_date The session it belongs to
observed_at, emitted_at, expires_at When it was observed, published, and when it expires

When a signal’s facts change, the same signal is sent again with the same id and a higher revision—replace your copy rather than adding a new one. When a signal expires, event signal_retired arrives with only its id.

readings:zero_dte (Developer Pro)

Event zero_dte arrives every minute of the regular session, once for SPY and once for SPX, in the same shape the zero_dte_read MCP tool returns: product, the time of the read (at), minutes_left to the close, the options-implied forward, the at-the-money straddle and straddle_pct, the cone—the calibrated 50%, 68%, and 90% ranges to the close (each with low, high, and move_pct)—and a strikes list giving each strike’s side, distance in sigmas, bid, ask, the odds of closing beyond it (close_beyond), and the odds of touching it before the close (touch).

The frame format

The server speaks a small JSON protocol that any language can implement. Every frame, in both directions, is a JSON array of five elements:

[join_ref, ref, topic, event, payload]
Position Name What it is
1 join_ref A string you choose when you join a topic, repeated on that topic’s frames. null for heartbeats and for server pushes.
2 ref A string you choose for each message you send, unique on the connection (a counter works). The server’s reply carries the same ref. Server pushes have null.
3 topic The topic, for example trade_ideas. Heartbeats use the topic phoenix.
4 event What the frame is: phx_join to join, heartbeat, phx_reply for the server’s answer, or a data event such as trade_idea or signal.
5 payload A JSON object: {} for a join or heartbeat, the data for a push.

The rules:

  1. Join a topic by sending [join_ref, ref, topic, "phx_join", {}].
  2. The server answers with event phx_reply and a payload of {"status": "ok", "response": {}}, or {"status": "error", "response": {"reason": "unauthorized"}} when your plan does not include the topic.
  3. Data arrives as [null, null, topic, event, payload].
  4. Heartbeat every 30 seconds by sending [null, ref, "phoenix", "heartbeat", {}]. If the server hears nothing from you for 60 seconds, it closes the connection.
  5. If the connection drops, reconnect and join your topics again. Items published while you were disconnected are not replayed—use the REST API to catch up.

Node.js example

Node.js 22 and later have WebSocket built in, so this needs no packages:

// Node.js 22 or later: WebSocket is built in, no packages needed.
const url =
  "wss://optionomics.ai/socket/websocket?vsn=2.0.0&token=" + process.env.OPTIONOMICS_API_KEY
const ws = new WebSocket(url)

let ref = 0
const send = (topic, event, payload = {}, joinRef = null) =>
  ws.send(JSON.stringify([joinRef, String(++ref), topic, event, payload]))

ws.onopen = () => {
  for (const topic of ["trade_ideas", "market_commentaries"]) send(topic, "phx_join", {}, topic)
  setInterval(() => send("phoenix", "heartbeat"), 30_000)
}

ws.onmessage = ({ data }) => {
  const [, , topic, event, payload] = JSON.parse(data)
  if (event === "phx_reply") {
    if (payload.status !== "ok") console.error("join refused:", topic, payload.response)
    return
  }
  console.log(topic, event, payload)
}

Run it with OPTIONOMICS_API_KEY=your-key node stream.js. On Developer Pro, add "readings:signals" and "readings:zero_dte" to the list of topics.

Python example

This uses the websockets package (pip install websockets):

import asyncio
import itertools
import json
import os
from urllib.parse import urlencode

import websockets

URL = "wss://optionomics.ai/socket/websocket?" + urlencode(
    {"vsn": "2.0.0", "token": os.environ["OPTIONOMICS_API_KEY"]}
)
# On Developer Pro, add "readings:signals" and "readings:zero_dte".
TOPICS = ["trade_ideas", "market_commentaries"]


async def main():
    refs = itertools.count(1)

    async with websockets.connect(URL) as ws:

        async def send(topic, event, payload=None, join_ref=None):
            frame = [join_ref, str(next(refs)), topic, event, payload or {}]
            await ws.send(json.dumps(frame))

        async def heartbeat():
            while True:
                await asyncio.sleep(30)
                await send("phoenix", "heartbeat")

        for topic in TOPICS:
            await send(topic, "phx_join", join_ref=topic)

        beating = asyncio.create_task(heartbeat())
        try:
            async for raw in ws:
                _join_ref, _ref, topic, event, payload = json.loads(raw)
                if event == "phx_reply":
                    if payload["status"] != "ok":
                        print("join refused:", topic, payload["response"])
                    continue
                print(topic, event, payload)
        finally:
            beating.cancel()


asyncio.run(main())

Run it with OPTIONOMICS_API_KEY=your-key python stream.py.

Tips

  • Signals are updated in place. Keep signals in a dictionary keyed by id; overwrite on each signal and delete on signal_retired.
  • The 0DTE read is a snapshot. Each zero_dte message replaces the previous one for that product.
  • Plan changes take effect on the next connection. If your plan changes, reconnect so the server checks your topics again.
  • Requests made over the websocket do not count against your REST/MCP per-minute allowance; only the REST API and MCP server are rate limited.

Optionomics Documentation

Getting Started
Workspaces
Live Data
AI, Alerts, and Research
Daily Analytics
Historical Research
Mobile and Integrations

Optionomics Documentation