- Personal-use Android client for IBKR, UI inspired by Longbridge.
- Scope: stocks + options + ETFs. Market data + trading.
- Single user. Single device. No multi-tenant.
- Paper account during all dev; switch to live by changing two env vars.
Android (Kotlin + Compose)
│ HTTPS + HMAC signature (X-Timestamp + X-Signature, Keystore-backed)
▼
FastAPI + ib_async (native on Mac, port 8000)
│ TWS binary socket
▼
IB Gateway in Docker (gnzsnz image, port 4002 paper / 4001 live)
│
▼
IBKR servers
The backend on the Mac is the only place holding IBKR credentials.
The phone holds (backend_url, device_id) in DataStore, plus an HMAC key sealed inside AndroidKeyStore (TEE / StrongBox). The bearer token is used only during the one-time pairing.
| Decision |
Why |
| ib_async (not ibapi) |
Sync/async, pandas-friendly, modern repo (ib-api-reloaded org). Official ibapi is callback hell. |
| Docker for Gateway only |
Auto-restart, IBC handles 2FA timeout & weekly Sunday token reset. Easier than babysitting a Java jar. |
| FastAPI native (not in Docker) |
Faster dev iteration. We're a single-host app; container isolation buys nothing. |
| HMAC signature + Keystore (not OAuth) |
Single user. Pairing once uses a bearer token; all subsequent requests are signed by an HMAC key held in TEE / StrongBox. Token rotation does not disturb paired devices. See README §Authentication. |
| Kotlin + Compose |
Native Android only — no React Native / Flutter compromises. Best perf for high-density real-time UI. |
| Ktor (not Retrofit) |
First-class coroutines, kotlinx.serialization, less boilerplate. |
| Material 3 base + custom dark tokens |
M3 default is too airy. Override colorScheme + density to mirror Longbridge's compact dark look. |
- Home Wi-Fi: phone →
http://<mac-lan-ip>:8000
- Outside: Tailscale on Mac + phone →
http://<mac>.<tailnet>.ts.net:8000
- App settings page lets you switch between profiles.
- Future: TLS via Caddy reverse proxy in front of FastAPI (when we go live).
READ_ONLY_API=yes in .env blocks all order placement at the Gateway level.
Only flip to no when explicitly ready to place orders.
- Paper account only until v3+ is tested end-to-end.
clientId=10 reserved for backend; if you want to attach TWS GUI simultaneously, use a different clientId there.
- Two-step confirmation (BiometricPrompt) required for any order placement in app.
- Sunday ~01:00 ET: IBKR resets login tokens. IBC will request 2FA — confirm on IBKR Mobile.
- Daily 23:45 ET (or your configured
AUTO_RESTART_TIME): Gateway restarts itself. Backend should reconnect on next request.
- Historical data pacing: same contract+exchange+barType within 15s = violation. Throttle if you batch-fetch.
- Decimal tickSize (post 2026-02 ib_async): treat numeric ticker fields as
float | Decimal, coerce explicitly.
- Mac sleep: Gateway and FastAPI both stop. Either
caffeinate -i uv run ... or set "Prevent automatic sleeping on power adapter" in System Settings.
| Version |
Scope |
| v0 |
Account summary + positions over REST, Android skeleton |
| v1 |
Watchlist + real-time quote stream via WebSocket |
| v2 |
Stock detail page + K-line chart (TradingView Lightweight Charts in WebView) |
| v3 |
Order placement (limit + market) + biometric confirm |
| v4 |
Option chain + bracket orders |
| v5 |
Push notifications (order fills, price alerts) via FCM |
| v6 |
Home screen widget (Glance API) |