Streaming code has a scheduling problem
Real market data streams are live for six and a half hours a day, five days a week. That leaves most of the week — and all of every weekend — where a streaming client cannot be exercised at all. So the work happens on Tuesday afternoon, and the demo is on Monday morning.
The specific things that go untested:
- Reconnect after a dropped socket, which requires a dropped socket.
- Idle timeouts, which require a stream that goes quiet.
- Parse-error recovery, which requires a malformed frame that does not end the stream.
- Anything at all, on a Sunday.
A stream that is always open
The endpoint is Server-Sent Events rather than a WebSocket, on purpose: SSE is curl-able, which makes it its own documentation.
curl -N 'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO,AAPL'
event: hello
data: {"clock":"demo","symbols":["CUCKOO","AAPL"],"generation":1}
event: tick
data: {"S":"CUCKOO","p":102.34,"t":"2026-08-20T14:31:02Z"}
: hb
event: tick
data: {"S":"AAPL","p":231.08,"t":"2026-08-20T14:31:03Z"}
One hello on connect, then tick events
carrying S (symbol), p (price) and
t (the instant it claims to be). The
: hb lines are SSE comments, not events —
most clients drop them silently, which is exactly their purpose.
| Param | Meaning |
|---|---|
| symbols | Up to 10, comma-separated. Scenario tickers work here too. |
| clock=demo | Default. An always-open synthetic session. Never sleeps. |
| clock=real | Follows the NYSE calendar — silent while the market is closed. |
| seed | Selects an alternate universe, as everywhere else. |
| scenario | Transport faults. See below. |
clock=demo is what a
demo or a dashboard wants: alive regardless of the hour.
clock=real is what you test against when the behavior
under test is “what does my app do when the market is
closed” — a case that is otherwise only reproducible by
waiting.
Limits: 10 symbols, 5 concurrent streams per address, and 15 minutes per connection. That last one is deliberate: a client that cannot handle the server ending a stream is a client with a bug, and this surfaces it within the first quarter hour.
Consuming it
const es = new EventSource(
'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO,CRASH'
)
es.addEventListener('hello', (e) => {
console.log('connected:', JSON.parse(e.data))
})
es.addEventListener('tick', (e) => {
const { S, p, t } = JSON.parse(e.data)
console.log(S, p, t)
})
// EventSource reconnects on its own. Your job is to notice that it did,
// and to decide whether the gap matters.
es.onerror = () => console.warn('stream interrupted; browser will retry')
import json
import httpx
URL = "https://cuckootrade.com/api/v1/stream"
def ticks(symbols, **params):
with httpx.stream("GET", URL, timeout=None,
params={"symbols": symbols, **params}) as r:
event = None
for line in r.iter_lines():
if line.startswith(":"): # heartbeat comment
continue
if line.startswith("event: "):
event = line[7:]
elif line.startswith("data: ") and event == "tick":
yield json.loads(line[6:])
for tick in ticks("CUCKOO"):
print(tick["S"], tick["p"])
CORS is wide open and GET-only, so the browser example works from any origin, including a local file.
Breaking it on purpose
This is the part a real feed cannot give you. Add
scenario= and the transport misbehaves —
deterministically, so the resulting test is not flaky and can live
in CI permanently.
| Effect | What happens | What it catches |
|---|---|---|
| drop:S | Socket closes at T+S, mid-frame, no close event. | Reconnect logic. |
| truncate | One frame cut mid-JSON, connection stays up. | Parser resync — the hard one. |
| garbage:N | N invalid data: payloads among the good ones. | A parse error that must not kill the stream. |
| silent:S | Data and heartbeats stop for S seconds. | Read timeouts, liveness detection. |
| slow:MS | Delays the next frame. | Backpressure, buffering. |
# the socket dies twenty seconds in, mid-frame curl -N 'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO&scenario=drop:20s' # one frame arrives cut in half; the connection stays up curl -N 'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO&scenario=truncate' # three unparseable payloads mixed in with the good ones curl -N 'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO&scenario=garbage:3' # thirty seconds of total silence -- no data, no heartbeats curl -N 'https://cuckootrade.com/api/v1/stream?symbols=CUCKOO&scenario=silent:30s'
drop and truncate look similar and are
not. A dropped socket is the easy case — the connection ends
and something reconnects. A truncated frame on a live
connection is the case that finds bugs, because nothing tells the
client anything went wrong; it has to resynchronise its own parser.
import time
import httpx
def test_client_reconnects_after_a_drop():
started = time.monotonic()
received = 0
try:
with httpx.stream("GET", URL, timeout=None,
params={"symbols": "CUCKOO",
"scenario": "drop:5s"}) as r:
for _ in r.iter_lines():
received += 1
except httpx.RemoteProtocolError:
pass # the drop, as requested
elapsed = time.monotonic() - started
assert received > 0 # it streamed before it died
assert 4 < elapsed < 9 # and died on schedule
The failure that looks like success
A dropped stream is loud. The dangerous one is a stream that keeps delivering punctual, well-formed frames whose values never change — every liveness check green, every number twenty minutes old.
curl -N 'https://cuckootrade.com/api/v1/stream?symbols=STALE'
On the demo clock STALE freezes for twenty seconds out
of every minute, on a fixed schedule, so you see it without waiting.
During the freeze t and p repeat verbatim
and the heartbeats keep flowing — that last detail is
the load-bearing one. The socket is healthy, the ticks are punctual,
the data is dead. If your client cannot tell the difference, neither
can your monitoring.
A note on heartbeats, if you are building one of these
The : hb comment every ~15 seconds is not decoration.
Load balancers close idle connections — 60 seconds is a common
default — and a stream that is legitimately quiet
(HALTS goes silent by design) looks exactly like an
idle one. Without heartbeats, correct quiet behavior gets killed by
infrastructure.
An SSE comment is the right tool because it keeps the connection
warm without emitting an event the client has to know about.
scenario=silent:30s stops the heartbeats too, which is
how you test the timeout that is supposed to catch this.