Christopher Renshaw Notes

Field note · Android · iOS · Fintech

Real-time trading on Android: lessons from Schwab Mobile.

A trade ticket looks simple. A symbol field, a quantity, a buy/sell toggle, a Submit button. Everything that makes it actually work — live quotes, multiple order types, market-hours logic, encrypted transport, biometric auth, and the assumption that the user is on a flaky 4G connection in a parking lot — happens behind that surface. Here's the shape of what we shipped at Schwab.

The latency budget you don't get to negotiate

A trader expects the displayed bid/ask to update at least once a second on an active symbol, and they expect the order confirmation to land in under 1.5 seconds after they tap Submit. Both are non-negotiable: traders will leave a brokerage over either one. That gives you a hard budget — pick any one of (network round-trip, JSON parse, view recomposition, animation frame) and assume it has 100 ms.

Two consequences shape the whole stack:

The streaming layer

Real-time quotes don't come over REST. The wire is a long-lived persistent connection — historically WebSockets, occasionally a server-sent stream — and the server pushes deltas. The client subscribes to a watchlist and the server confirms with a snapshot, then sends partial updates per tick.

Two patterns that matter on mobile:

// Coalesced quote feed — emits at most one update per symbol per frame window.
class QuoteFeed(private val ws: QuoteSocket) {
    val quotes: SharedFlow<Map<Symbol, Quote>> = ws.ticks
        .scan(emptyMap<Symbol, Quote>()) { acc, tick ->
            acc + (tick.symbol to acc[tick.symbol]?.apply(tick) ?: Quote(tick))
        }
        .sample(100.milliseconds)
        .flowOn(Dispatchers.Default)
        .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)

    suspend fun subscribe(symbols: Set<Symbol>) = ws.subscribe(symbols)
}

The order ticket is a state machine

"Buy 100 AAPL at market" hides a small state machine. Validations are layered: client-side (positive quantity, valid symbol, market-hours awareness for stop orders), server-side (account funds, day-trade-pattern check, regulatory pre-trade), and broker-side (acceptance, partial fill, full fill, reject).

The mistake to avoid: trying to model "submitting" as a single boolean. The actual states a ticket flows through are roughly:

  1. Drafting — user can edit fields freely.
  2. Validating — client check passes, awaiting server pre-trade.
  3. Confirming — server accepted; user sees the disclosure / confirm screen.
  4. Submitted — order acknowledged, awaiting broker.
  5. Working / partial / filled / rejected — broker terminal states.

Every screen that interacts with the ticket binds to that state, not to ad-hoc booleans. The reward is no double submissions, no impossible UI states, and a much easier QA story.

Biometric auth — and what it doesn't do

Face / fingerprint unlock is table stakes in fintech. It's also frequently misimplemented: developers treat the biometric prompt as authentication, when it's actually authorization to release a stored secret. The authentication still happens between your stored token and the server.

The pattern that holds up to a security review:

Network — the layer that bites you in production

Three small things saved us repeatedly:

  1. Idempotency keys on order submission. The user double-tapped Submit because they didn't see the spinner. Without an idempotency key, you book two orders. With it, the second is a no-op.
  2. Aggressive client-side timeouts on order paths. 5 seconds, then surface "we don't know the status of your order" and link to Order Status. Never sit on a spinner for 30 seconds during a fast market.
  3. Always reconcile on resume. When the app comes back to foreground, fetch live order status before showing any cached state. Stale order data in fintech is worse than blank.

Bottom line

A real-time trading app is fundamentally a UI bound to a streaming backend, with a state machine for orders and a security model that assumes the device is hostile. None of the individual pieces are exotic — but the combination is unforgiving, and the patterns above are the ones I'd reach for first if I were building it again tomorrow.

By Christopher Renshaw — Senior Mobile Software Engineer based in Miami, FL. Built core features for Schwab Mobile (Android & iOS, 1.8M+ Android installs) including the trade ticket, real-time quote streaming, charting, and biometric auth — plus iOS feature work on Ameriprise Financial's wealth management app.

Hire me for senior mobile engineering ↗ All notes ↗