Type/to search
8
Follow
1394
Followers
FMZ's WebSocket Interface Gets a Major Upgrade: Bringing Strategies Closer to Market Changes
Discussions
Created 2026-11-05 17:20:41  Updated 2026-11-06 09:53:49
 0
 15

img

When writing quantitative strategies, we tend to focus on trading logic: the conditions for entering a position, where to exit, and how to control position size. Once a strategy is running, however, another question comes before the signal: how closely does the market the program sees match what is actually happening? For decisions made on minute or hourly timeframes, this issue may not always be obvious. But once you monitor several exchanges and instruments at once, or start studying brief changes in spreads, the way you obtain market data directly affects the strategy's results.

img

A spread has just widened, but the polling loop has not reached it yet. Quotes from two exchanges have both returned, but they were sampled at different times. After an order is placed, the program is still querying its status while the other leg has already filled. These problems may not originate in the signal formula, yet they affect whether that formula can translate into actual trades. That is why FMZ's latest WebSocket interface upgrade deserves a closer look.

The upgraded interface brings market data subscriptions, event consumption, account updates, and asynchronous trading into a more unified set of calls. A strategy can continuously receive market changes through ctx.subscribe(), process events with ctx.poll(), and submit WebSocket trading requests through ctx.submit() on supported accounts. The change goes beyond reducing the wait for a market data query. More significantly, a strategy can organize its work around changes in the market: what to watch under normal conditions, what to focus on when an opportunity appears, and where to direct processing resources once it holds a position.

img

Markets Do Not Always Move at a Fixed Pace

A traditional REST strategy is easy to understand: the program asks a question, and the exchange returns an answer. Get a quote, make a decision, wait, then query again. This is a clear approach and remains suitable for many tasks. Market information, historical data, and account checks can all be explicitly requested through REST. But observing a continuously changing market at a fixed frequency involves a tradeoff. Query less often and you make fewer requests, but brief changes may slip between two samples. Query more often and you generate more requests, consuming rate-limit capacity and processing resources. The problem becomes more pronounced as the number of instruments grows.

Markets also do not change at an even pace. Sometimes several seconds pass with no new information worth processing; at other times, a burst of quotes and trades arrives almost instantly. Fixed polling follows a predetermined schedule, whereas WebSocket subscriptions let a program update its state as data actually arrives. When volatility suddenly increases, pushed updates can reduce the wait imposed by polling intervals. For strategies monitoring multiple markets, continuously maintaining each market's latest quote is also easier to organize than querying them individually as needed.

img

Here, though, we need to distinguish “more timely” from “guaranteed to be faster.” REST can reuse connections, batch requests, and run them concurrently. WebSocket performance also depends on the exchange's update frequency, the network path, and the load on the FMZ Docker. This upgrade provides an interface better suited to continuous monitoring; the actual reduction in latency still needs to be measured in the target environment. Bringing a strategy closer to the market requires adjusting its data input, decision process, and execution tracking together. Replacing just one function may not produce the same effect.

What This Upgrade Brings Together

FMZ already supported native WebSocket connections through Dial(). Developers could work directly with exchange protocols, which offered considerable flexibility, but subscription formats, fields, order-book updates, and connection management still had to be adapted for each exchange. The upgraded subscription interface shifts more of that work into the platform's connectors. Strategies can use similar calls to receive tickers, best bid and ask quotes, depth, individual trades, and candlesticks. To broaden their coverage, they can subscribe to market-wide tickers on supported exchanges or pass several instruments at once. For strategy authors, this means more effort can go into deciding what to do with the data, instead of repeatedly handling each exchange's message format.

For example, the following read-only example can run with a Binance perpetual futures configuration that supports the relevant channels. It subscribes to two instruments, continuously maintains their latest best bid and ask, and exits after one minute without trading. The array returned by a batch subscription also has an unsubscribe() method, allowing the entire group of subscriptions to be released at once.

javascript
function main() { var subs = ctx.subscribe(exchange, { channel: "bbo", symbol: ["BTC_USDT.swap", "ETH_USDT.swap"], mode: "latest" }); var quotes = {}, started = Date.now(), lastLog = 0; try { while (Date.now() - started < 60000) { var e = ctx.poll([], 200); if (e && e.kind === "status" && e.prefillStatus === 2) { Log("Subscription failed", e.symbol, e.message); } else if (e && e.kind === "ticker" && e.channel === "bbo" && e.bid > 0 && e.ask >= e.bid) { quotes[e.symbol] = { bid: e.bid, ask: e.ask, received: e.ts }; } if (Date.now() - lastLog >= 1000) { Log("Latest quotes", JSON.stringify(quotes)); lastLog = Date.now(); } } } finally { subs.unsubscribe(); } }

Here, poll([], 200) waits for up to 200 milliseconds and returns sooner if an event arrives. It does not request fresh market data every 200 milliseconds. Once a subscription has been registered, the first poll() call starts the connection and subscription, so receiving a subscription object does not mean valid data has already arrived. Another point must be clear when migrating an older strategy: subscribing through WebSocket does not turn existing queries such as GetTicker() and GetDepth() into cache reads. Those functions still use REST; pushed data must be retrieved through the event interface. To use the new data path, the main loop needs to change accordingly. Official subscription documentation

The upgraded interface also brings in account state. One private subscription per account receives orders, fills, positions, and balances according to what the exchange actually supports. It covers the entire account, so there is no need to subscribe separately for each instrument. This lets a strategy process market changes and its own trading state in the same flow: reassess an opportunity when a quote changes, update trade records when a fill arrives, and check exposure when a position changes. A unified interface does not mean that every exchange pushes exactly the same content, however. Some fields may be absent, and some account changes may not be pushed in real time. After initialization or reconnection, the strategy needs to wait for the account snapshot to be ready; missing information and historical fills still need to be retrieved through queries.

To Adapt to Different Markets, Decide Which Data Must Be Retained

As the volume of market data grows, it is tempting to think: if WebSocket can keep pushing updates, why not subscribe to everything? After running a strategy for a while, it often becomes clear that more data is not necessarily more useful. The real questions are whether the strategy needs to process every update and whether it can keep up with the updates it receives.

A strategy that evaluates spreads from current quotes usually cares most about the latest best bid and ask. Working through a queue of quotes from two seconds ago before reaching the present can leave its decisions behind the market. mode: "latest" allows new market data to replace older updates and is better suited to this use. Research based on individual trades is different. If you calculate order flow from aggressive buys and sells over a short period, overwriting trades in between changes what the statistics mean. Candlesticks likewise require a distinction between historical bars supplied during initialization, the current unfinished bar, and bars that have actually closed.

Another benefit of the upgrade is therefore the ability to organize data by purpose, instead of applying one consumption model to every channel. Below are several typical subscription calls. They show how to bring in different types of information; each still needs to be handled separately in the event loop, with subscriptions released at the end.

javascript
// Order book: after receiving a depth event, read price levels with e.book(n). var depth = ctx.subscribe(exchange, { channel: "depth", symbol: "BTC_USDT.swap", depth: 20 }); // Individual trades: examine trade direction, price, and quantity. var trades = ctx.subscribe(exchange, { channel: "trade", symbol: "BTC_USDT.swap" }); // The candlestick interval is in seconds; also check closed and prefill when identifying closed bars. var bars = ctx.subscribe(exchange, { channel: "kline", symbol: "BTC_USDT.swap", interval: 60 }); // Private stream for the entire account; do not pass symbol. var account = ctx.subscribe(exchange, {channel: "private"});

The FMZ Docker maintains the order book, so a strategy does not need to assemble incremental depth updates separately for each exchange. But book() reads the currently maintained book; it should not be understood as a historical snapshot from the moment an older event occurred. The latest received data also needs to be considered in terms of time. An event's ts is the time when the Docker received the data. Subtracting it from the current time reveals internal processing and event-consumption delay, but excludes upstream network transmission. Quotes from different exchanges must also be checked for their individual ages and the time difference between them. Receiving both through WebSocket does not mean they represent the same instant. Event field documentation

Exploring connection stability over the past couple of days reinforced the same point: having a connection, having received data, and still having usable data are three different states. Restoring updates after a network interruption matters, of course. But if the connection remains open while market updates have gone silent, the strategy still needs its own safeguards. When a target quote becomes stale, the strategy should stop using it to open positions. Once updates resume, it should also reconcile positions and pending orders instead of simply continuing the previous calculation.

Under high volatility or with many instruments, it is also necessary to monitor the dropped-event, backpressure, buffer-capacity, and dropped-response counters exposed by ctx.connectors(). Logging every market update, redrawing a panel, or running slow queries can all slow down the event loop. A strategy that adapts better to market conditions therefore allocates subscriptions and computing resources according to the task at hand. Use lighter data for broad monitoring, add the necessary detail when focusing on a target, and prioritize the relevant quotes and account state while holding positions. When the market gets busy, the program should know which work comes first.

From Seeing a Change to Confirming a Completed Trade

Once market data is pushed, substantial waits can still remain in the overall process if order placement blocks one order at a time and is followed by repeated fill queries. The new ctx.submit() allows accounts that support WebSocket trading to submit order and cancellation requests asynchronously. It returns a local request ID after sending the request, and the result is then received through ctx.poll().

Below is a function that submits an IOC limit order. Price, quantity, and minimum-order requirements should be checked against the target market's rules before calling it. This illustrates the trading entry point, not a complete order-management system.

javascript
function sendIOC(ex, symbol, side, price, amount, reduceOnly) { var req = { op: "order", symbol: symbol, side: side, price: price, amount: amount, tif: "ioc" }; if (reduceOnly) req.reduceOnly = true; var localId = ctx.submit(ex, req); if (localId instanceof Error) { throw localId; // The request was not sent, so no corresponding response will arrive. } return localId; } // In the main loop, correlate the three stages using localId: // ack.reqId → Request result // order.clientId → Order state, cumulative filled quantity, and average fill price // fill.clientId → Each actual fill and its fee information

Asynchronous trading lets the program continue processing market data while waiting for reports, and makes it easier to send requests for several legs promptly. For trading across exchanges, this is better aligned with practical needs than waiting for one leg to return before handling the other. But a request being sent, receiving a successful acknowledgment, and the order actually filling are different things. A successful ack does not mean a position has been established. Actual quantities and prices still need to be checked against orders, fills, and positions. If the result is unknown, the original request may already have been accepted. The correct approach is to keep it pending and query by client order ID or wait for confirmation from the private stream, rather than simply submitting the order again.

ctx.submit() itself uses only WebSocket. If it is unsupported, it returns an error and does not switch automatically to REST. A strategy can implement its own fallback, but it must confirm that the original request was not sent so that it does not send another request while the outcome remains uncertain. ctx.order() and ctx.cancel() use the REST trading path and should not be confused with it. Asynchronous trading documentation

Batch submissions also require checking each result. Exchanges differ in their batch mechanisms and limits, and a batch may succeed only partially. Across two exchanges, sending requests at the same time certainly does not guarantee that both will fill at the same time. WebSocket improves how execution is organized; the strategy still has to manage unmatched legs, partial fills, and rate limits.

A Practical Application

Consider the classic strategy of trading the convergence of spreads across exchanges. A traditional implementation calls each exchange's market data interface separately and compares spreads after the quotes arrive. As the number of instruments grows, a query cycle becomes longer: the first exchange's quote has arrived while the second is still pending, and by the time the comparison begins, the earlier price may have changed again. After placing orders, the strategy usually continues querying them to check how much each leg has filled and whether any orders remain outstanding.

WebSocket can organize this process differently. At startup, the strategy subscribes to BBO data for the instruments being scanned and uses latest mode to maintain each exchange's latest best bid and ask. As quotes arrive, ctx.poll() updates the corresponding instrument's state, and the strategy checks whether trading conditions are met. It can make decisions as market data arrives, reducing the wait associated with querying instruments one at a time. It must still check the freshness of both quotes to avoid directly comparing prices from different times.

The scope of monitoring does not have to remain equally broad throughout. Once positions are open, the two legs already carrying exposure deserve more attention. The strategy unsubscribes from unrelated market data, retains only the target quotes, and resumes scanning after it exits. The account's private stream stays subscribed throughout, so order and position reports continue to arrive even as the market data focus changes.

img

When it is time to trade, accounts that support WebSocket order placement use ctx.submit() to send requests for both legs promptly, and the program then continues processing events. A new quote may arrive first, or a fill on one leg may arrive first; either way, the corresponding records need to be updated. ack confirms the request result, while order and fill reports establish actual quantities and prices. Positions are then checked to confirm whether the two sides match. Market data continues to enter the same processing flow while the program waits for order results.

After the two requests have been sent, their respective fills still need to be tracked. If an outcome remains uncertain, retain the pending state and wait for a report or query for confirmation. REST still supports initialization, missing-data retrieval, and necessary account checks. Any fallback for order placement must also begin by establishing whether the original request was sent.

Following a single trade makes the relationship between these interfaces clear: the program continuously receives quotes, organizes its work around changes, keeps tracking after submitting orders, and reconciles the actual results. This also makes the next questions more specific: how long did a quote wait inside the program, which leg was confirmed first, and how much did the market change in the meantime? These records provide the basis for judging whether the interface changes have improved actual operation.

Example strategy: Cross-Exchange Crypto and Stock Spread Convergence Arbitrage

Comment
All comments (0)
No data
No data
  • 1
Forums
PINE Language
Get the app
iPhone Download
© 2015 - ∞ INVENTOR PTE LTD (SG)