Inside Stream's Real-Time Architecture for Trading & Investing Apps

A trade is instant. What happens next (the alert, the discussion, the broadcast) is where most of the engineering work actually happens. This is a look at that work.
Inside Stream's Real-Time Architecture for Trading & Investing Apps

TL;DR

  • Trading apps run two real-time systems: a transaction plane (orders, execution, ledger) and an experience plane (chat, feeds, video). Users judge speed by the second one.
  • Stream Feeds' materialized (hybrid push/pull) architecture holds ~15ms p50 read latency even at 4.5M concurrent users during simulated market-open load.
  • Stream Chat's edge network and offline-first SDKs deliver sub-50ms API responses and scale to 5M people in a single channel.
  • Stream Video's SFU cascading handles a 100K-viewer stream at 225 Gbps with 0% packet loss, comfortably under the 150ms threshold for natural conversation.

Trading has zero tolerance for latency. Exchanges measure their matching engines in microseconds, brokers measure order handling in milliseconds, and firms pay for co-location just to shave a few hundred microseconds off the round-trip to the exchange. Users bring the same expectation to the app on their phone. When a stock moves, the quote has to change immediately.

Stream's Feeds, Chat, and Video are built for the speed of a trading app. They sit alongside the trading system rather than inside it. That same speed has to hold through the bursts, like a market open, an earnings release, or a sharp move in a widely held stock, when every real-time feature gets hit at once.

This piece breaks down the real-time architecture trading and investing apps need. Here's how the three hold together as one system on the other side of a trade.

Two Kinds of Real-Time Inside One App

A trading app contains two kinds of real-time infrastructure:

  • The transaction plane handles market data, order management, risk checks, routing, execution, and the ledger. It cares about ordering, idempotency, auditability, and exact financial state. A duplicated or reordered execution report is a serious bug.
  • The experience plane handles activity feeds, notifications, chat, presence, and live video. It cares about global fanout, low perceived latency, media quality, and clean recovery when a phone drops off the network for a minute. In Stream's terms, that's three products (Feeds, Chat, and Video) that a trading platform has to get working together.

The end user doesn't see that split. Both planes show up in the same interface. A price move updates a quote in the transaction plane, and the same move triggers a price alert, a run of community posts, and an analyst going live in the experience plane.

You can have the best transaction plane in the market, but the experience plane is what puts that data in front of the user in real time. If the alert arrives late or the chat lags, they'll assume your trading is slow too.

Diagram showing domain events flowing from the transaction plane (market data pipeline, trading backend, execution venues, ledger) up to the experience plane (activity feeds and alerts, chat, video)

The backend is authoritative. The experience plane's job is to take data from the backend and put it in front of the user, with enough context to be useful.

Say a user has an open limit order on a stock they follow, and the price hits their limit during a busy morning. In the transaction plane, the sequence looks like this:

  • The market-data path receives the trades that carry the price to the user's limit and updates the cached quote.
  • The venue executes the resting limit order and sends an execution report back to the broker.
  • The OMS moves the order from working to filled, after checking that it hasn't already processed that execution ID.
  • The ledger records the cash movement and the new position, and the portfolio read model is rebuilt from those entries.
  • The backend publishes domain events. OrderFilled for this user, and PriceThresholdCrossed for everyone whose watchlist alert sits at that price.

The transaction plane is tailor-made for this. It is both correct and fast. A duplicated execution report or an out-of-order update is a financial error, so it's built around idempotency and strict ordering. The experience plane then turns those events into what the user sees:

  • The OrderFilled event becomes an activity in the user's portfolio timeline and a notification on their phone, showing the price and quantity recorded in the ledger.
  • The PriceThresholdCrossed event becomes a price alert for every user who has set one at that level, which, at a widely watched price, means thousands of alerts at once.
  • The user opens the stock's community channel. Recent messages load instantly from on-device storage, and new ones arrive via WebSocket as others react to the same move.
  • An analyst on the platform starts a live briefing on the move. That one doesn't come from the trading backend at all. The analyst starts the session from their own tools, a notification goes out to everyone who follows them, and the user joins the video session with the channel's chat alongside it.

From the user's side, this all happened at once. The fill confirmation, the price alert, the discussion, and the briefing appeared within the same minute and looked like a single product.

Underneath, the transaction plane ensured the fill was real and the numbers were right, and the experience plane ensured the user saw it while it still mattered. Neither one can do the other's job. The feed never decides whether the order filled, and the OMS was never built to fan a message out to two million phones.

Effectively, you have two different feeds:

  • A market-data feed carries sequenced quotes, trades, and book updates with gap recovery, and it belongs to the transaction plane.
  • An activity feed carries alerts, posts, research, and portfolio events. The feeds discussed here are the second kind.
Source event What the user sees Real-time feature
PriceThresholdCrossed Watchlist or price alert Notification feed
OrderFilled Portfolio activity item Activity feed
ResearchPublished Personalized research timeline Activity feed
AnalystPosted Community post and discussion Activity feed and chat
AdvisorMessageSent Private client conversation Chat
MarketBriefingStarted Live commentary with chat alongside Video and chat
KYCSessionRequested Identity-review call Video

The trading backend decides what happened, and the experience plane's job is distribution. That means getting the event to the right people, in the right feed, channel, or call, quickly enough that it's still useful when it arrives.

How that holds up under trading-hours load is where the architecture matters.

What Counts as Fast in Each Plane

The user cares that the app keeps up with the market. That is the standard. But under the hood, we have two systems operating on two different latency timescales.

In the transaction plane, the reference point is other machines. The venue matches orders in microseconds, and the quote on the user's screen updates as fast as the market data feed can deliver. The user isn't competing with any of that, and doesn't need to. What they need is to see what happened as soon as possible, so they can decide what to do while the move is still playing out. The quote already gets that treatment. The alert, the chat, and the video have to get it too.

In the experience plane, the question is simpler. Did the alert, feed, chat message, or video frame arrive before the person holding the phone noticed any delay? Two well-established numbers tell you where users start to feel a delay:

  • Though Nielsen Norman Group's response-time limits date from 1993, they are still the reference everyone uses. Around 0.1 seconds feels instantaneous. Around 1 second is noticeable, but the user's thought flow remains intact. They're general usability findings, not trading targets, and they're the right test for feed reads and optimistic UI updates, where the app is trying to look like it responded before it finished the work.
  • ITU-T Recommendation G.114 provides telecom planning guidance on one-way delay in a conversation. It treats 150 ms one-way as acceptable for most conversational voice, and notes that highly interactive tasks can suffer well below that. It's planning guidance, not a threshold where delay suddenly becomes noticeable, and it's the number that matters for an advisor call.

Here's how the layers stack up:

Layer Measured against What fast looks like
Exchange matching and market-data distribution Other exchanges and other participants Microseconds to low milliseconds
Broker order handling The venue and the market-data feed Milliseconds
Feed and chat APIs The person reading the screen Low milliseconds per request
The app's interface The person reading the screen Under 0.1 s to feel instant, under 1 s to keep flow
Voice and video The person in the conversation Under 150 ms one-way, with low jitter and loss

Everything in the experience plane gets judged against the bottom three rows, and it gets judged hardest at market open, when all of those systems are under the most load at the same time.

How Activity Feeds Handle Fanout at Market Open

In a trading app, most of what happens ends up in a feed:

  • Price and watchlist alerts that the trading backend fires.
  • The user's own order and portfolio activity.
  • Research from the analysts and instruments they follow.
  • Posts, comments, and reactions from other investors.
  • Per-ticker, per-sector, or per-topic activity.
  • Ranked "for you" style commentary.

Every one of those is a fanout problem. One event goes in, and it has to come out across many different feeds, each a different mix of what that user follows. That's hard to scale, and you'll only find it fails at market open.

Push Versus Pull Fanout

There are two basic ways to build a feed:

  • With fanout on write, or push, every new activity gets copied into each follower's feed the moment it's created. Reads are quick, because the feed is already sitting there. The cost lands on the write, and it grows with the follower count. If an account has two million followers, every post is two million writes. That's the celebrity problem.
  • With fanout on read, or pull, you store the activity once and build the user's feed when they open it. Writes are trivial. Reads do all the work, pulling and merging activities from everyone the user follows, and that gets slower the more they follow.
Diagram comparing fanout on read, where a user's feed is gathered and merged from everyone they follow at read time, against fanout on write, where an analyst's post is copied into every one of two million follower feeds

An investing app has both problems at once. A well-known analyst, a fund, or the feed for a heavily traded ticker can have millions of followers, so pure push makes every one of their posts expensive. Pure pull moves the cost onto reads instead, and reads arrive all at once at 9:30 when everyone opens the app to see what happened overnight.

Ready to integrate? Our team is standing by to help you. Contact us today and launch tomorrow!

How the Materialized Feed Works in Stream

The Stream Feeds API uses a hybrid, called a materialized feed. Store each activity once, then decide per feed whether it's worth updating, based on how people use the app.

Here's how it works:

  • Fanout goes to recently active feeds first. Most feeds in most apps are idle at any given moment, so Stream skips them rather than writing to feeds nobody's about to open.
  • When someone opens a feed that was skipped, the API fetches only the new activities from the feeds they follow and merges them into what's already stored.
  • While it's building the top N items, it keeps track of which followed feeds have nothing recent enough to make the cut, so it stops checking them.
  • The fields used for ranking and aggregation are copied onto the activity record, so a feed can be ordered before the full activity objects are loaded.
  • Aggregation keys get computed on write, so aggregated feeds open quickly. Ranked feeds can't be fully precomputed, so a fast expression parser re-ranks them at read time.
  • Underneath, storage is TiKV, which handles the multi-Raft clustering and rebalancing we used to run ourselves.
Fanout on
write
Fanout on
read
Materialized
feed
Write cost High, one copy
per follower
Low, a
single write
Low, a single write
with selective updates
Read cost Low, the feed
is precomputed
High, assembled
at read time
Low, precomputed
with delta fetching
Celebrity accounts Millions of writes
per activity
No problem Handled by prioritizing
active feeds
Inactive users Wasted writes to
feeds nobody reads
Nothing wasted Skipped
Read latency ~1 to 5 ms ~100 to 500 ms
or more
~10 ms

Here's the flow for a single activity:

Diagram showing a new activity stored once, then a selective fanout that updates materialized feeds for recently active followers immediately while skipping inactive followers until they return

Reads then go through a few layers of caching before they touch storage:

  • Redis client-side caching keeps hot data in the API process itself, which saves a round trip to Redis on busy feeds.
  • Ristretto and singleflight handle cache stampedes. When a hot key expires, one request rebuilds it, and everyone else waits for that result instead of piling onto the database.
  • For shared ranked feeds with very high read volume, there's an opt-in hot feed cache that serves one cached ordering to every reader. A "most discussed tickers today" feed at market open is exactly what it's for.
  • On the WebSocket side, servers use cuckoo filters to cheaply check whether a user is even connected before doing any work to push an update.

What the Feeds Benchmark Measured

Say we have an app with 100 million users, and 4.5 million of them are online at the same time, reading and writing feeds at up to 1,500 requests per second. That's a market-open crowd for a large retail broker. We ran that test against a dataset of 500 million activities and 200 million follow relationships:

  • Feed reads came back in about 15 ms at p50.
  • Feed writes took about 31 ms at p50.
  • p99 stayed under 140 ms at the heaviest setting.
  • Every request succeeded.
Chart showing GetOrCreateFeed latency over time, with p95 near 47-57ms, p90 declining to around 38ms, and p50 stable near 11ms

So the alert or the post is in the user's feed well inside the 0.1 seconds where a person starts to notice a delay, and it stays that way when everyone opens the app at once.

How Chat Stays Fast at the Edge

In a trading app, chat is where everyone ends up at the same moment. The channel next to an earnings call, the ticker community during a sharp move, or the support inbox at market open. A message has to get from one person's phone to everyone in the room before the market moves on.

This means:

  • The message has to reach a server that can act on it without the sender having to pay for intercontinental round trips first.
  • One message has to reach millions of people in the same channel within milliseconds, at the moment they all joined.

A user in Singapore talking to an origin in Virginia pays around 200 ms for a round trip, and a new TLS connection needs several of those before the first message goes anywhere. So a Stream client connects to the nearest edge server instead. The handshake, authentication, and rate limiting all occur within a few milliseconds, and traffic flows from the edge to the origin over already-open encrypted HTTP/2 connections.

Diagram showing a trader's phone connecting over HTTP/3 or HTTP/2 to the nearest edge server for auth and rate limiting, which then connects to the origin API over a long-lived encrypted HTTP/2 connection, backed by Redis clusters, in-process caches, and a database

The API itself often responds in under 50 ms. On the device, SDKs keep recent channels and messages in local storage, so the app opens on its last state, and a sent message appears immediately while the request finishes in the background.

The chat next to a widely watched earnings call can have millions of people in it, and every message has to reach them all. Servers use cuckoo filters to skip users who aren't connected to them, serialize each message once and share it across recipients, and write to many sockets in a single syscall. Clients on networks that block WebSockets fall back to HTTP long polling.

What the Chat Benchmark Measured

Say we have 5 million people in one channel, joining at about 4,000 per second, with three messages a second going out to all of them. That's the chat for a stock everyone owns at the moment the results come out. We ran that test on a single channel:

  • Around 15 million message deliveries per second at peak.
  • Average message send latency remained well under 40 ms.
  • No loss of stability for the whole run.
Chart showing connected users ramping to 5 million while API latency stays steady between roughly 28 and 45 milliseconds

So a message posted into the biggest room in the app reaches everyone in it before they'd notice a delay, and the room keeps working when the whole user base joins it at once.

How Video Handles a Whole Audience Joining at Once

Video in a trading app is either a small call or a large broadcast. One-to-one advisor consultations and KYC calls need to stay under 150 ms one-way with reliable audio. Earnings commentary and analyst briefings have a few publishers and the whole user base watching, usually with chat alongside.

Serving the broadcast involves:

  • Pushing more bandwidth than a single server can handle to viewers on three continents.
  • Handling thousands of joins in the same few seconds, because a briefing starts the moment the results come out.

Forwarding One Stream to 100,000 Viewers

WebRTC was designed for peer-to-peer communication, where each publisher sends a separate copy of their stream to each other participant. That stops working past a handful of people. With a selective forwarding unit (SFU), each publisher uploads once, and the SFU forwards streams to whoever needs them, so each phone's connection count drops from O(n²) to O(n).

A 100,000-viewer 1080p stream needs more than 200 Gbps, which is more than one SFU can serve, so we cascade. The publisher's video goes to one SFU, which then forwards it to other SFUs in other regions, each serving around a thousand viewers in their area. Viewers only subscribe to the streams visible on screen, and simulcast lets each viewer receive the quality their connection can handle.

Diagram showing an analyst publishing a 1080p stream to an origin SFU, which forwards it to SFUs in Frankfurt, Oregon, and Virginia, each serving around 1,000 viewers

SFU capacity scales in steps based on how quickly it's needed, so a sharp rise in load can double or triple the number of SFUs running. Join-time thundering-herd protection prevents a burst of simultaneous joins from overwhelming signaling. For advisors and institutional users on locked-down corporate networks, TURN runs on our SFU servers over TCP on port 443, with a dedicated TURN network behind it and static IPs.

What the Video Benchmark Measured

Say we have 100,000 people watching one analyst's 1080p stream, joining at 10,000 per minute, with peaks of 600 per second, spread across six regions in the US and Europe. We ran that test:

  • 225 Gbps of peak traffic across 132 cascading SFUs.
  • A stable 30 fps for the whole run, with 0% packet loss and 4 ms jitter.
  • Zero API failures.
Chart showing connected users climbing to over 100,000 while frame rate holds steady at 30fps for the duration of the stream

So the briefing starts the moment the results come out, the whole audience is in within seconds, and everyone's watching at full quality.

Where This Leaves the Build Decision

None of this changes who owns the trading system. Orders, positions, balances, and execution remain with your platform or brokerage infrastructure provider, and nothing in the experience plane can assert financial state.

The question is whether you build the experience plane yourself. Running it in-house involves:

  • Global WebSocket fanout with edge termination and regional failover.
  • Feed materialization and cache-stampede protection that holds at market open.
  • Offline storage and delta sync across iOS, Android, and web.
  • SFU cascading, simulcast, TURN, and autoscaling sized for join spikes you can't schedule.
  • Capacity in several regions for load that arrives in bursts a few times a day.

Every one of those is a general problem, and we've already solved each of them at a scale most trading apps won't reach. Your team keeps its time for the parts of the app that carry financial risk, and the alerts, chat, and video run on infrastructure that's been tested with 4.5 million users online, 5 million people in a single channel, and 100,000 people watching a single stream.

The user judges your app by whether it keeps up with the market, and the experience plane is the part they see. If the alert arrives late or the chat lags, they'll assume the trading is slow too, however fast the order path underneath it is.

If you're building or replacing the feeds, chat, or video in a trading app, start with our Feeds, Chat, and Video architecture pages and read the benchmarks against your own peak concurrency. Or talk to our team about what your market open looks like.