Alpaca integration — scoped to market data + optional live-broker handoff
Status: Draft (reframed)
Owner: software-architect
Last updated: 2026-04-22
Parent epic: #183
Supersedes: prior version of this doc (PR #184). Prior version's premise — per-user Alpaca OAuth for everything — has been replaced by the MBT-native paper engine. See mbt-paper-trading-engine.md for the replacement.
Related ADRs: 0002, 0013, 0014, 0008 (Superseded), 0009 (Superseded), 0010 (Superseded), 0011 (Superseded)
1. Context + the reframe
The prior version of this doc (committed in PR #184 on the design/multi-tenant-alpaca branch) pointed every user at Alpaca OAuth for both paper and live trading. That premise is now obsolete: Raxx runs its own paper-trading engine (MBT). Alpaca's role in the architecture narrows to two deliberate surfaces:
- Market Data API — one server-side Alpaca account, one key, shared across all users. Source of quotes, bars, options chains, and end-of-day settles for MBT.
- Live-broker handoff — per-user Alpaca OAuth, only when a Pro+ user graduates from paper and explicitly connects a live broker. Minority of users; many will never touch it.
Everything else the old doc specified — per-user OAuth, per-user trade-update streams, mass-revocation as a headline invariant — applies only to the live-handoff subset. The "no stored credentials" invariant is restored for the majority of users (free + most Pro), because without a live broker connection there is no broker token to store.
This doc is now short by design. MBT is the big story (mbt-paper-trading-engine.md). This doc only covers how Alpaca slots in around it.
2. Invariants that apply
Unchanged from docs/architecture/auth.md §2. Specific to this surface:
- No stored credentials for users without a live-broker handoff. The ADR 0009 exception — OAuth access tokens at rest — no longer applies globally. It applies only to the live-handoff subset and is re-scoped in ADR 0014.
- Paper-first gating. Live-handoff is gated by paper-profitable-for-N-cycles on MBT, not on Alpaca paper. Enforcement stays server-side.
- Credentials into infra. The Market Data API key lives in the secret store; so does the OAuth client secret for live-broker registration.
- Audit. Every live-handoff authorization, refresh, use, and revocation writes an
audit_logrow. Market-data key use is logged at the hub level, not per-user.
3. Market Data API (shared, server-side)
Shape
- One Alpaca account owned by Raxx (paid tier determined by product economics — see ADR 0015 or §5 open questions).
- API key + secret in the secret store as
ALPACA_MARKET_DATA_KEY/ALPACA_MARKET_DATA_SECRET. 90-day rotation. Rotatable without redeploy. - Single long-lived REST + WebSocket client in
MarketDataHub(new service inbackend_v2/api/services/). Fans out to in-process pub/sub in v1; Redis pub/sub when we horizontally scale.
Consumers
- MBT order matching — reads NBBO ticks to simulate fills.
- Charts and chains in Antlers — via SSE on
/api/market-data/stream?symbols=.... - Backtesting — historical bars + options chains, pulled REST.
- MQ-A evaluations — bar-close callbacks to MQ-A rules subscribed to a symbol list.
Tier gating
Per docs/marketing/pricing.md:
- Free: 15-min delayed quotes + chains, 5-min cache TTL.
- Pro: real-time quotes + chains + bars.
- Pro+: real-time + historical chains archive + full depth where available.
Enforcement is at the MarketDataHub service boundary, not Antlers. A Free user who calls the raw API gets the delayed path.
Cost + capacity flags
- Alpaca caps WebSocket connections per account (typically 1 concurrent on most tiers). At scale this is a single-point-of-saturation; second key or an account upgrade mitigates.
- Market-data entitlement (SIP vs IEX) drives cost + what "real-time" actually means. Product decision pending; flagged in open questions.
4. Live-broker handoff (Pro+ opt-in)
This is the only surface where per-user Alpaca OAuth still exists. Full treatment in ADR 0014.
Who + when
- Available to Pro+ users only (per pricing matrix: "All supported brokers"). Pro users get "Alpaca live + 1 other"; same handoff pattern.
- Gated by: (a) paper-profitable-for-N-cycles on MBT, or (b) explicit audited override requiring step-up WebAuthn.
- Opt-in. A Pro+ user who stays in paper never enrolls, has no Alpaca token stored, stays under invariant #1 strictly.
Flow
- User clicks "Connect live Alpaca" in Antlers → Alpaca OAuth auth-code flow → Raxx receives access token + (if issued) refresh token.
- Token stored per ADR 0014 (narrower re-statement of ADR 0009's envelope-encryption pattern, applied only to the live-handoff subset).
- MBT remains the primary simulator. Orders can be routed to live Alpaca via an explicit "submit to live broker" action per trade (not a global mirror). Every live submission is a separate audited event.
- Step-up WebAuthn on every live submit in v1. (Later: session-level "live-mode window" with a shorter TTL, behind its own ADR.)
Not included
- Mirror-all-paper-to-live automation. Not in v1. Copy-trading-like behavior hits regulatory questions (§6) that require counsel before product design.
- MQ-A scheduled execution against live broker. Deferred. Hosted strategy execution against user's Alpaca live account crosses into adviser territory — flagged for counsel.
5. What changed from PR #184
For reviewers familiar with the prior version:
| Concern | PR #184 approach | New approach |
|---|---|---|
| Where paper trading runs | Alpaca paper (per-user OAuth) | MBT (our simulator) |
| OAuth tokens stored | For every user | Only for Pro+ users who enroll live-handoff |
| Invariant #1 compliance | Documented exception for all users (ADR 0009) | Restored for most users; exception narrows to live-handoff subset (ADR 0014) |
| Market data | Per-user OAuth (+ shared hub sketched for scale) | One shared server-side key; no user OAuth for data |
| Premium tier compute | Fargate + Firecracker per strategy against user's live Alpaca (ADR 0011) | Deferred; re-scoped as "dedicated compute running MQ-A strategies against MBT + shared market data" — not against user's broker. New ADR when premium-tier spec is written. |
| Retention of paper history | Alpaca's retention policy | Our policy — per tier, our DB (Free 90d / Pro 3yr / Pro+ unlimited) |
| Regulatory posture | Flagged Advisers Act + BD-via-Broker-API | Same flags; MBT stays in user-directed-simulation lane more cleanly |
ADRs 0008, 0009, 0010, 0011 are being marked Superseded by ADRs 0013 + 0014 (see each ADR's updated Status header).
PR #184 will be closed with a redirect to the new PR (design/mbt-paper-trading-engine).
6. Regulatory posture (unchanged from prior, reframed lane)
Flags for securities counsel; do not assume any of these are resolved. The MBT reframe makes the safest-lane story cleaner:
- MBT as education + user-directed simulation → safest. User authors or selects rules; MBT simulates. Not adviser activity.
- Live-broker handoff on user initiation → low risk. User-directed execution; Raxx is not making the trading decision.
- MBT + Raxx-authored strategies the user subscribes to and runs on paper → borderline under Investment Advisers Act §202(a)(11). Even on paper, if the publication is individualized, it can trigger adviser-like obligations. Needs counsel.
- Copy-trading via live-handoff (user's real Alpaca account mirrors Kris's trades) → likely RIA registration required. SEC has been explicit on this. Flagged; no product design against it until counsel engaged.
- Broker API path (Alpaca as clearing) — no longer on our roadmap under the reframe. Removing this line item removes a whole class of regulatory exposure.
v1 stays in the safest lane deliberately. Flags for counsel remain:
7. Security considerations (Alpaca surface only)
- Market data key in secret store, 90-day rotation, scoped to a single read-only data account. Compromise → rotate; upstream impact is "our account is blocked for N minutes."
- OAuth client secret (Raxx's, for the live-handoff) in the secret store, 90-day rotation, separate per env (paper — though we won't use Alpaca paper going forward, keep the registration for disaster fallback — vs live).
- Live-handoff tokens encrypted per ADR 0014 (envelope encryption, KMS-backed, rotatable without re-enrolling users). Same pattern as old ADR 0009 but scoped to far fewer rows.
- Kill switches:
ALPACA_MARKET_DATA_DISABLED=1falls MBT back to last-known-good market-data cache + user banner.ALPACA_LIVE_HANDOFF_DISABLED=1blocks new live authorizations + blocks live order submissions (existing positions untouched). - Audit rows for every live-handoff connection lifecycle event, every live order submit. Market-data use logged at hub level, not per-request (too noisy; aggregated metrics are sufficient).
- Per-user trading mode override — each user's paper/live preference is stored in
customer_trading_mode_overrides(Postgres row keyed byuser_id).resolve_effective_mode()intrading_runtime.pyaccepts auser_idkeyword argument; callers that do not supply one stay on the env-default ("paper"). The old process-globalapp.extensions["trading_mode_override"]has been removed. This eliminates the SEV-1 path where graduating one user would flip all sessions to live.
8. Open questions
- Market-data tier. SIP vs IEX? Drives cost + user expectation. Product + finance call.
- Account capacity ceiling. At what user count does one Alpaca data account become insufficient (WebSocket concurrency, entitlement counts)? Alpaca-partnerships inquiry.
- Handoff broker beyond Alpaca. Pro tier promises "Alpaca live + 1 other." Which broker (IBKR? tastytrade?), and does that broker's OAuth posture match Alpaca's? Flag for follow-up ADR.
- Options chain archive source for Pro+ historical chains — Alpaca's options history is thin. ORATS / Polygon / CBOE DataShop candidates. Blocks Pro+ GA, not MBT v1.
- Backup paper account. Do we keep an Alpaca paper registration alive as a disaster-recovery path (e.g. if MBT's simulator has a bug, route users temporarily back to Alpaca paper)? Operational judgment call.
End of doc. See mbt-paper-trading-engine.md for the primary engine design, ADR 0013 for the reframe decision, and ADR 0014 for the re-scoped Alpaca posture.