Choose a Stock API by Testing Disconnect Recovery, Not Just Streaming Speed

TickDB Research ·

A stock API can reconnect quickly while leaving an application with incomplete subscriptions or an unexplained gap in event history. Choose it by testing when data becomes usable again, not merely when the socket opens. A fresh snapshot can restore current state; only a documented replay or backfill mechanism can establish the required event continuity. The acceptance test below separates those outcomes and includes a small, locally tested recovery gate.

Stock API recovery evaluation cover

Start the comparison at the point where the connection breaks

A fast first message is useful, but it answers a narrow question. After a network interruption, the same client may connect successfully, receive a heartbeat, and still be missing one of the subscriptions its application needs. Alternatively, all subscriptions may return, yet trades that happened during the interruption remain absent.

These are different failures. A price display may recover once it has a valid current quote for each required instrument. A trade-derived calculation may need every event in the missing interval, with duplicates handled correctly. Do not give both applications the same definition of “recovered.”

For an API selection trial, write down two acceptance contracts before testing. The state contract specifies which instruments and fields must be restored, how freshness is judged, and whether partial availability is allowed. The history contract specifies whether missing events must be recovered, which identifier orders them, and how the application handles an interval it cannot reconstruct. An API can pass the first contract and fail the second without contradicting its documentation.

This is an evaluation method, not a ranking or a live vendor benchmark. The code below was tested with synthetic events; no provider recovery timings were measured.

Give recovery more than one finish line

Record the failure injection, the moment the client detects it, the new connection opening, subscription confirmation, reconciliation completion, and the point at which the application releases usable data. Use a monotonic clock for these local durations. Keep provider event timestamps separately; subtracting unrelated clocks can turn clock skew into an apparent latency result.

Measure at least three intervals:

  • Detection delay: failure injection to the client marking the session unavailable.
  • Transport recovery: failure injection to the replacement connection opening.
  • Usable-data recovery: failure injection to the application meeting its state and, where required, history contract.

If you also report recovery after the network is restored, label that start point separately. A forced ten-second outage should not be compared with a one-second outage as though they were the same test. Runs that never recover within the observation window remain failures or censored observations, not missing rows discarded from a latency average.

Five recovery gates from disconnected to usable

Figure 1. An illustrative client recovery model. Opening a connection is only one gate; this is not a vendor protocol state machine.

A successful subscription response is evidence about a subscription request, not proof that its data has arrived. Keep the desired subscription set outside the socket object, then compare it with the evidence available on the new connection. Track this per channel and instrument when the application depends on several streams. If one subscription is rejected, leave that portion unavailable rather than promoting the whole session to healthy.

A heartbeat belongs to the transport check. TickDB's Ping documentation, for example, describes a ping/pong exchange that checks connection availability. Its timestamp is not a quote timestamp, and receiving a pong does not establish that a requested stock subscription is active or that a missed trade has been recovered.

The same distinction prevents a misleading timeout rule: a quiet instrument is not automatically a broken connection. Freshness policy must respect the market session, instrument activity, and the meaning of the channel. A newly received snapshot can legitimately contain an old last-trade timestamp. If the application needs a recent trade, that instrument may remain unusable even though transport and snapshot delivery are working correctly.

Ask exactly what happens to the missing interval

Suppose the client receives event E1, disconnects while E2 and E3 occur, and reconnects before E4. A current snapshot may provide the state after E4. It does not reveal the path through E2 and E3. That distinction matters for volume accumulation, event-driven signals, and audit records, even when the displayed price looks correct.

Snapshot repairs current state while replay repairs missing history

Figure 2. Illustrative recovery semantics. Replay can repair event history only when the source supports the required interval and ordering; a snapshot alone cannot prove continuity.

For snapshot recovery, ask how the client recognizes the complete required state. Is there a completion marker, a stable expected symbol set, or an explicitly documented reconciliation procedure? A batch-size limit is not a completion signal. Nor is “no messages arrived for a moment.”

TickDB's US Full-Market Ticker documentation provides a concrete example of why these questions matter: subscribing to US_Stock sends a batched initial snapshot, then changed tickers, with at most 500 ticker messages per array. Its Best Practices page says to resend subscriptions after reconnect and that a full-market subscription receives another initial snapshot. The reviewed pages do not establish a replay cursor or an explicit snapshot-end marker. That is a documentation boundary to resolve for the intended application, not proof that those features cannot exist.

For an event-history contract, ask for the actual replay boundary: what cursor or event identifier can be resumed, how long events are retained, whether limits apply per instrument or connection, and what happens when the requested point is too old. A timestamp alone may not uniquely identify events. Do not invent deduplication using price and time unless the feed guarantees that combination is unique.

Also resolve the join between backfill and the resumed live stream. Buffering new events while fetching history is useful only when a documented ordering or watermark lets the client merge them correctly. Without that contract, a gap or a duplicate at the seam can survive a successful backfill request. Mark continuity as unverified instead of claiming that two successful requests prove it.

Test the gate independently of the network client

The following standard-library Python example demonstrates an application-level gate for a finite set of required instruments. It is deliberately not a WebSocket client or an implementation of replay. The adapter must supply subscription evidence and an explicit reconciliation decision. In production, those decisions need to come from the provider's documented semantics and the application's acceptance policy.

The generation number rejects callbacks left over from an older connection. Every reconnect starts with empty evidence, and a heartbeat cannot make the gate usable. The example uses local receipt age only; provider event-age validation belongs in the adapter before accepting a record.

from dataclasses import dataclass, field

@dataclass
class RecoveryGate:
    required: frozenset[str]
    max_age: float
    generation: int = 0
    connected: bool = False
    subscribed: bool = False
    reconciled: bool = False
    received: dict[str, float] = field(default_factory=dict)

    def begin(self):
        self.generation += 1
        self.connected = True
        self.subscribed = self.reconciled = False
        self.received.clear()
        return self.generation

    def accept(self, generation, kind, now, symbol=None):
        if generation != self.generation or not self.connected:
            return
        if kind == "disconnect":
            self.connected = self.subscribed = self.reconciled = False
            self.received.clear()
        elif kind == "subscription_ok":
            self.subscribed = True
        elif kind == "subscription_error":
            self.subscribed = self.reconciled = False
            self.received.clear()
        elif kind == "record" and self.subscribed:
            if symbol in self.required:
                self.received[symbol] = now
        elif kind == "reconciled" and self.subscribed:
            self.reconciled = True
        # Heartbeats deliberately do not advance recovery.

    def usable(self, now):
        return (
            bool(self.required)
            and self.connected and self.subscribed and self.reconciled
            and all(s in self.received and
                    0 <= now - self.received[s] <= self.max_age
                    for s in self.required)
        )

gate = RecoveryGate(frozenset({"AAA", "BBB"}), max_age=5.0)
old = gate.begin()
gate.accept(old, "disconnect", 0.0)
new = gate.begin()
gate.accept(new, "heartbeat", 1.0)
assert not gate.usable(1.0)
gate.accept(old, "subscription_ok", 1.0)  # Stale callback.
assert not gate.subscribed
gate.accept(new, "subscription_ok", 1.0)
gate.accept(new, "record", 1.0, "AAA")
gate.accept(new, "reconciled", 1.0)
assert not gate.usable(1.0)  # BBB is still missing.
gate.accept(new, "record", 2.0, "BBB")
assert gate.usable(2.0)
assert not gate.usable(7.0)  # AAA has aged out.
gate.accept(new, "subscription_error", 7.0)
assert not gate.usable(7.0)
print("Synthetic recovery-gate checks passed")

Run this as a Python 3.9-or-newer script. AAA and BBB are synthetic instrument labels, and five seconds is an illustrative receipt-age threshold, not a vendor SLA. The expected output is “Synthetic recovery-gate checks passed.”

The small gate assumes one aggregate subscription decision and serialized callbacks. A real adapter may need per-channel decisions, locking, bounded buffers, and schema checks. For an event-dependent application, do not emit “reconciled” until the missing interval and the live/backfill seam satisfy the history contract. For a display, reconciliation may be a narrower current-state decision. Keep those policies explicit rather than embedding them in the reconnect handler.

Run a failure-injection trial that can actually reject a candidate

Use the same required instruments, market session, channel mix, account permissions, client region, and outage durations for each candidate. Record the client version and subscription request so a change in the harness does not masquerade as a provider improvement. Test only connections you control, within service limits; do not generate disruptive load against a provider.

Start with four cases, because each challenges a different claim:

  1. Clean disconnect. Close the socket, reconnect, and confirm that the desired subscriptions are restored. The pass condition is usable data under the declared contract, not a successful open callback.
  2. Silent interruption. In a test harness or permitted local network proxy, stop delivery without a clean close. Verify that the client eventually detects the loss and stops presenting the old session as usable. Keep a fixture that delivers heartbeats but withholds records to test the separate data gate.
  3. Partial recovery. Reject one requested subscription or withhold one required instrument in the fixture. The application must report the missing coverage and remain wholly or partially unavailable according to its predeclared policy.
  4. Recovery race. Deliver a delayed callback from the previous connection, repeat an event, and interrupt again during reconciliation. Confirm that old-generation callbacks cannot update the new session and that any event deduplication uses a documented identity rule.

Use local fixtures to prove that the client handles these conditions; use permitted live trials to learn how a candidate behaves. A synthetic rejection is evidence about the harness and client, not evidence that a provider produces that rejection. Record those two evidence types separately.

Include a configuration failure as a separate check. For example, TickDB documents mandatory permessage-deflate negotiation for full-market subscriptions, with error 2008 when it is missing, and permission error 4005 when the key lacks access. Retrying the same invalid configuration does not demonstrate resilience. The trial should verify that the client exposes an actionable error instead of looping indefinitely. Follow the selected provider's documented retry policy and stop conditions rather than applying one universal schedule.

Make the selection from acceptance results, then compare speed

For each candidate, keep one record per injected failure: scenario, session and instruments, detection delay, transport recovery, usable-data recovery, missing subscriptions, unresolved history interval, and final outcome. Label every result as documentation-based, synthetic-test evidence, live observation, or unknown. Report the observation window and failures alongside any latency summary. With a small sample, show the individual runs instead of presenting a percentile as a stable service property.

A current-state application can accept snapshot recovery if it meets its coverage and freshness requirements. An event-dependent application needs a credible way to reconstruct the missing interval, or an explicitly accepted degraded mode that stops the affected calculation. Neither decision should be hidden inside a green connection indicator.

Reject a candidate that cannot satisfy the application's recovery contract, even if its first message arrives sooner. Among candidates that pass, compare usable-data recovery time, operating effort, and the rest of the commercial requirements. That ordering makes streaming speed useful without allowing it to conceal a broken recovery path.

Documentation basis: TickDB Best Practices, Ping, and US Full-Market Ticker, reviewed for this article. Product statements are documentation-based; the executable example uses synthetic data only.

Explore related data tools

Related articles

Choose a Stock API by Testing Requirements Before Comparing Price

A stock API can return valid JSON and still be unsuitable for your application. Choose candidates that meet your required data coverage, permitted use and operating capacity, then compare their total cost under the same workload. Alpaca’s documented IEX and SIP feed distinction shows why matching ticker symbols is not enough. A small acceptance test, with unresolved requirements kept visible, produces a more defensible shortlist than a feature count.

TickDB Research ·

Stock API Watchlists Can Share Requests Without Hiding Stale Prices

A stock watchlist can exhaust an API budget even when every user follows the same symbols. Where data permissions allow it, batching those symbols and sharing retrieval across viewers reduces duplicate upstream requests while preserving each record's timestamp. In a hypothetical case with 20 identical 120-symbol watchlists, batches of 50, and a five-second polling interval, independent polling needs 720 requests per minute; one shared collector needs 36, before retries and other traffic.

TickDB Research ·

Stock API Charts Need Complete Downloads and Explicit Data Gaps

A stock chart can look convincing even when its history is incomplete. A stock API supplies records under specific retrieval and aggregation rules; your application must finish the download and preserve missing observations. This article explains pagination, absent candles, and a small Python example that keeps an unknown price from becoming a false market move.

TickDB Research ·

Stock API Price Alerts Need State as Well as a Price Threshold

A stock API can supply prices for an alert, but a successful subscription does not define when a notification should fire. A useful alert needs an observation type, a trigger rule, remembered state, and a policy for missing data. In a synthetic five-update example, checking whether the price exceeds $100 produces three matches, while detecting an observed upward crossing produces two events. The distinction prevents repeated updates above the threshold from becoming repeated crossing alerts.

TickDB Research ·

Choose a US Stocks API That Keeps Extended Hours Data Distinct

A US stocks API can return a valid price while leaving an extended-hours screen misleading. For a product that shows pre-market, post-market or overnight activity, choose a feed whose coverage and session semantics you can verify, then keep each observation tied to its session and timestamp. TickDB documents separate extended-session snapshot objects; Alpaca documents separate stock feeds. Those are different integration choices, and neither removes the need to test missing data and session transitions.

TickDB Research ·