ADR-0139 — Multi-Broker Platform Support for v1 Live Trading
Status: Proposed
Date: 2026-07-13 UTC
Deciders: Kristerpher (operator); software-architect (design)
Scope: Raptor (backend_v2/), Velvet (token lifecycle), Antlers (frontend/raxx-next/),
onboarding wizard (BrokerConnectView)
Supersedes (in part): ADR-0052 (expands interface contract), ADR-0014 (extends
token table pattern to all Tier A brokers)
Extends: ADR-0109 (BYOB tier model), ADR-0052 (adapter interface pattern)
Refs: Epic #495, Research doc #2627 (BLR), parent card driving this design
1. Context
Raxx enters v1 live trading after the MBT paper-beta soak. The operator requirement is a minimum of two active trading platforms at v1-live launch:
- Alpaca — the only real integration today. Approximately 429 code references across
alpaca_integration.py,alpaca_market_data.py, andalpaca_market_data_service.py. All live-execution references are wired directly into trading routes with no adapter interface. ADR-0052 proposed theBrokerAdapterABC pattern but did not fill the full interface contract or provide the migration path. - SnapTrade — intended BYOB aggregator, currently stubbed (five references, none functional). Unlocks IBKR, Schwab, E*TRADE, and dozens of additional brokers via a single aggregator contract; Raxx never holds the downstream broker credential.
- A named second direct broker — required by operator. Tradier is the recommended choice (see §4 for full evaluation; operator may override to tastytrade).
ADR-0109 established the broker tier model (Tier A direct, Tier B aggregator, Tier C read-only), the Velvet token storage extension, and the phased rollout roadmap but deferred the v1-live implementation specifics — the interface contract, the Alpaca migration path, and the platform-selection sub-step in the onboarding wizard — to this ADR.
Paper trading (MBT, ADR-0108) is permanently broker-independent. This ADR governs the live execution path only.
2. Invariants
These constraints are non-negotiable. Any sub-card that conflicts with them must surface the conflict before work begins.
- No stored credentials (ADR-0002): broker OAuth tokens and API keys are the documented exception (ADR-0009/0014). They must be envelope-encrypted in Velvet; no plaintext column exists in any table. Every additional Tier A broker inherits this posture exactly.
- Passkeys only (ADR-0001): step-up WebAuthn is required on every live order submission regardless of broker tier.
- Broker is plumbing (
feedback_no_backend_branding): customer copy never surfaces broker vendor names in generic instructions. "Connect your brokerage account" — not "Connect to Tradier." - Deterministic execution (
feedback_deterministic_execution_ai_augments): the order-firing path is rule-based. Changing the adapter beneath it does not change execution semantics. The adapter translates; it does not decide. - Paper-first gating: live execution requires paper-profitable-for-N-cycles (MBT)
or an explicit audited override via
FLAG_LIVE_TRADING_GATE_OVERRIDE. This gate applies to every broker and every tier. It reads MBT cycle history, not the broker. - Audit trail: every state change touching money, permissions, or data access
writes an
audit_logrow. Every live order submission and every broker connection lifecycle event (connect, disconnect, token-rotate, kill-switch activation) is audited. - Kill-switches exist before the feature ships:
BYOB_LIVE_DISABLED=1(all BYOB live orders) andBYOB_BROKER_<SLUG>_DISABLED=1(per-broker) must be present in Raptor before any non-Alpaca adapter reaches production.
3. Decision
3.1 Three platforms for v1-live
| Platform | Tier | Auth mechanism | Current state |
|---|---|---|---|
| Alpaca | Tier A direct | OAuth 2.0 (ADR-0014) | Exists; needs adapter wrap |
| Tradier | Tier A direct | OAuth 2.0 (3-legged; same pattern as Alpaca) | New |
| SnapTrade | Tier B aggregator | SnapTrade connection ID; downstream broker token held by SnapTrade | Stub → complete (Phase 2) |
Tradier is the recommended second direct broker. See §4 for evaluation against tastytrade. The operator may override; the interface contract below applies unchanged to either choice.
SnapTrade is Phase 2 (post Tradier soak). Sub-cards are filed now so
card-groomer can sequence them against the SnapTrade contract timeline.
3.2 BrokerAdapter interface contract
The interface expands ADR-0052's stub to cover the full live-execution surface, with explicit support for multi-leg options orders required by Raxx's iron-condor and spread workflows.
All adapters implement this contract. Location: backend_v2/api/services/broker/base.py.
# Stub types only — feature-developer chooses concrete library and dataclass shapes.
class BrokerAdapter(ABC):
# ── Identity ─────────────────────────────────────────────────────────────
broker_id: ClassVar[str] # slug: "alpaca" | "tradier" | "snaptrade"
display_name: ClassVar[str] # brand name for Settings detail view only
supports_options: ClassVar[bool] # True: Alpaca, Tradier; check per-broker for SnapTrade
# ── Auth / connection lifecycle ───────────────────────────────────────────
@abstractmethod
def get_auth_url(self, user_id: str, redirect_uri: str) -> str: ...
# Returns the OAuth redirect URL the user follows to authorize Raxx.
# For SnapTrade: returns the SnapTrade-hosted connection widget URL.
@abstractmethod
def exchange_code(self, code: str, state: str) -> ConnectionArtifact: ...
# Exchanges the OAuth authorization code for a token artifact.
# Returns an opaque artifact that Velvet stores.
# Raptor never persists the plaintext token after this call returns.
@abstractmethod
def revoke(self, velvet_token_id: str) -> None: ...
# Calls the upstream revoke endpoint, then marks the Velvet row revoked_at.
# Must be idempotent — safe to call on an already-revoked token.
# ── Account state ─────────────────────────────────────────────────────────
@abstractmethod
def get_account(self, velvet_token_id: str) -> AccountState: ...
# Returns: cash, buying_power, account_id, currency, account_type.
@abstractmethod
def get_positions(self, velvet_token_id: str) -> list[Position]: ...
# Returns open positions: symbol, qty, avg_cost, market_value, asset_class.
# ── Order management ──────────────────────────────────────────────────────
@abstractmethod
def submit_order(self, velvet_token_id: str, order: OrderSpec) -> OrderResult: ...
@abstractmethod
def cancel_order(self, velvet_token_id: str, order_id: str) -> CancelResult: ...
@abstractmethod
def replace_order(
self, velvet_token_id: str, order_id: str, patch: OrderPatch,
) -> OrderResult: ...
@abstractmethod
def get_order_status(self, velvet_token_id: str, order_id: str) -> OrderStatus: ...
# ── Market context ────────────────────────────────────────────────────────
@abstractmethod
def is_market_open(self) -> bool: ...
# Adapter may delegate to the shared MarketDataHub (Alpaca data account)
# for market hours rather than calling the broker API separately.
@abstractmethod
def health_check(self) -> HealthResult: ...
# Returns {ok: bool, latency_ms: int, message: str}.
# Feeds the status-page surface poller ([ADR-0030](https://internal-docs.raxx.app/architecture/adr/0030-status-state-machine.html)/0121) without custom-casing
# each broker.
3.3 Options order support (multi-leg)
OrderSpec is the cross-broker order description. It must support single-leg and
multi-leg options. This is the primary reason Tradier is preferred at Tier A — its
REST API handles multi-leg options natively in a single call.
@dataclass
class Leg:
symbol: str # OCC option symbol (e.g. "SPY230120C00400000") or equity ticker
action: str # "buy_to_open" | "sell_to_open" | "buy_to_close" | "sell_to_close"
qty: int
@dataclass
class OrderSpec:
order_type: str # "market" | "limit" | "net_debit" | "net_credit"
tif: str # "day" | "gtc" | "ioc"
legs: list[Leg] # 1 leg = single; 2 legs = vertical/spread; 4 legs = iron condor
limit_price: Decimal | None
# Each adapter translates this to the broker's native multi-leg format.
# For SnapTrade Tier B: if the downstream broker does not support multi-leg
# via SnapTrade, the adapter raises UnsupportedOrderError — never silently
# splits a multi-leg order into single-leg submissions.
3.4 Alpaca migration path (incremental, no big-bang rewrite)
The 429 Alpaca references must not be broken by a single migration. The approach is adapter-wraps-existing, proven in three passes, each independently shippable:
Pass 1 — Wrap (SC-2): Create AlpacaBrokerAdapter(BrokerAdapter) in
backend_v2/api/services/broker/adapters/alpaca.py. It delegates to the existing
alpaca_integration.py functions unchanged. Register in BrokerAdapterRegistry.
Trading routes that submit orders call registry.get_adapter_for_user(user_id).
No existing function in alpaca_integration.py is deleted or modified.
Pass 2 — Route (SC-2, same card): Replace direct imports of
alpaca_integration.py in live-execution routes (trading.py, options.py) with
registry calls. The underlying functions stay untouched. Paper-trading paths are
untouched (MBT, not Alpaca).
Pass 3 — Internalize (SC-tech-debt, filed separately, not a v1-live blocker):
Move Alpaca-specific logic from alpaca_integration.py into the adapter methods.
Only after Pass 2 has soaked in staging.
BrokerAdapterRegistry is a dict-backed service locator populated at app startup,
consistent with the existing Flask pattern. No DI container is introduced.
3.5 Per-user credential storage
ADR-0014 established alpaca_live_connections for Alpaca. This ADR generalizes it
to a single broker_live_connections table covering all Tier A and Tier B brokers.
Velvet holds the ciphertext; Raptor holds the reference ID (velvet_token_id).
-- Raptor-side reference table (token ciphertext lives in Velvet)
CREATE TABLE broker_live_connections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
broker_slug TEXT NOT NULL,
broker_tier TEXT NOT NULL CHECK (broker_tier IN ('tier_a','tier_b')),
velvet_token_id UUID NOT NULL,
scopes TEXT NOT NULL DEFAULT '',
account_id_at_broker TEXT NULL,
connection_state TEXT NOT NULL DEFAULT 'active'
CHECK (connection_state IN ('active','needs_reauth','revoked','suspended')),
connected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_used_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL
);
CREATE UNIQUE INDEX broker_live_connections_active_uniq
ON broker_live_connections (user_id, broker_slug) WHERE revoked_at IS NULL;
-- One active connection per broker per user in v1.
-- Relaxed in Phase 4+ for multi-broker households ([ADR-0109](https://internal-docs.raxx.app/architecture/adr/0109-byob-roadmap.html) §4).
alpaca_live_connections rows (ADR-0014) are migrated to this table as part of SC-3.
Existing encrypted Alpaca tokens in Velvet are backfilled with broker_slug = 'alpaca'
and broker_tier = 'tier_a' after the Velvet schema extension (SC-4) ships.
3.6 BrokerConnectView extension
BrokerConnectView.tsx currently presents only a paper | live binary choice.
Its BrokerMode type and onNext({ mode }) contract are preserved.
When the user selects live, a new BrokerPlatformSelectView sub-step is rendered.
It presents only the brokers available in the user's subscription tier:
- Free / Pro: Alpaca only.
- Pro+: Alpaca + Tradier (GA); SnapTrade-sourced list post-Phase-2.
Copy rules (invariant): Broker brand names appear in the platform-select list items only. The wizard headline and body copy remain generic ("Connect your brokerage account"). SnapTrade is never mentioned; its downstream brokers appear by their own brand names in the aggregator-powered list. The aggregator is invisible to the user at every UX layer.
3.7 Live-vs-paper gating
The paper-first gate reads MBT cycle history. It is broker-agnostic: a user graduating from MBT paper to live Tradier follows the same logic as a user graduating to live Alpaca. The adapter layer is not involved until the gate clears.
Two paths through the gate:
1. Standard: N profitable MBT cycles (configurable via PAPER_GRAD_CYCLES,
default 5).
2. Override: operator sets FLAG_LIVE_TRADING_GATE_OVERRIDE=true per-user via
the console. Override is audited (actor, reason, timestamp). Requires step-up
WebAuthn from the operator performing the override.
Live order submission call sequence (in this order, no reordering permitted):
1. Kill-switch check (BYOB_LIVE_DISABLED, BYOB_BROKER_<SLUG>_DISABLED).
2. Paper-first gate state verified (live check against DB; not trusted from session).
3. Trade-window compliance check (existing trade_window_compliance_enforcement.py).
4. Step-up WebAuthn token verified.
5. Pre-submit audit_log row written (actor, broker_slug, order_spec, timestamp).
6. Adapter submit_order() called.
7. audit_log row updated with broker-issued order ID and result status.
4. Second Direct Broker: Tradier Recommended
Tradier
| Criterion | Assessment |
|---|---|
| Options | Full multi-leg via REST API (equity + options in one endpoint). Required for iron-condor workflow. |
| Sandbox | Public sandbox at sandbox.tradier.com. No funded account required. Available immediately. |
| OAuth | Standard 3-legged OAuth 2.0 auth-code flow. AlpacaBrokerAdapter is the template. |
| API maturity | Production since 2012. Stable and well-documented. |
| Developer TOS | Public developer program. BLR review required before production (OQ-1). |
| FINRA / SIPC | Yes — options levels 1-4. |
| Integration effort | ~2 engineering weeks (adapter + OAuth + options order mapping + sandbox QA). |
Tastytrade (alternative, not recommended for v1-live)
| Criterion | Assessment |
|---|---|
| Options | Excellent — built for options traders, multi-leg native. |
| Sandbox | Partner program required (api.support@tastytrade.com). Approval adds 2-4 weeks before engineering can start. |
| OAuth | OAuth 2.0 — same once access is granted. |
| Developer TOS | BLR review required before production (same scope as Alpaca #932). |
| Integration effort | ~3 engineering weeks (partner approval + adapter, same methods). |
Recommendation: Tradier. The self-serve sandbox eliminates the 2-4 week partnership-approval gate that blocks tastytrade from starting. The OAuth and options-order API patterns are virtually identical to Alpaca, minimizing novelty. Tastytrade remains a strong Phase 3 Tier A candidate; it is not ruled out.
Hard regulatory dependency (OQ-1): BLR must review Tradier's developer API agreement under the same scope as Alpaca #932 before Tradier orders go to production. The Tradier staging/sandbox sub-cards can proceed; the production-deploy sub-card (SC-7) is blocked on BLR sign-off.
5. Migrations
Migration A — broker_live_connections table
-- File: backend_v2/alembic/versions/0040_broker_live_connections.py
-- POSTGRES-ONLY
CREATE TABLE broker_live_connections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
broker_slug TEXT NOT NULL,
broker_tier TEXT NOT NULL CHECK (broker_tier IN ('tier_a','tier_b')),
velvet_token_id UUID NOT NULL,
scopes TEXT NOT NULL DEFAULT '',
account_id_at_broker TEXT NULL,
connection_state TEXT NOT NULL DEFAULT 'active'
CHECK (connection_state IN ('active','needs_reauth','revoked','suspended')),
connected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_used_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL
);
CREATE UNIQUE INDEX broker_live_connections_active_uniq
ON broker_live_connections (user_id, broker_slug) WHERE revoked_at IS NULL;
CREATE INDEX broker_live_connections_user_id ON broker_live_connections (user_id);
CREATE INDEX broker_live_connections_velvet_id ON broker_live_connections (velvet_token_id);
Rollback: DROP TABLE broker_live_connections; — safe until Alpaca row migration
(Migration B) ships.
Migration B — Alpaca row migration (SC-3, after adapter wrap is stable)
-- File: backend_v2/alembic/versions/0041_alpaca_rows_to_broker_live_connections.py
-- POSTGRES-ONLY
INSERT INTO broker_live_connections (
id, user_id, broker_slug, broker_tier, velvet_token_id,
scopes, connection_state, connected_at, last_used_at, revoked_at
)
SELECT
id, user_id, 'alpaca', 'tier_a', id,
-- velvet_token_id uses the same UUID as the alpaca_live_connections row
-- until Velvet SC-4 backfill adds the canonical Velvet token UUID.
scopes, CASE WHEN revoked_at IS NULL THEN 'active' ELSE 'revoked' END,
issued_at, last_used_at, revoked_at
FROM alpaca_live_connections;
-- alpaca_live_connections retained (not dropped) for 1 sprint verification window.
-- Separate follow-on migration drops the old table.
Rollback: Repoint AlpacaBrokerAdapter to alpaca_live_connections directly
(one config switch); broker_live_connections Alpaca rows can be truncated safely
during the verification window.
Migration C — Velvet schema extension (SC-4, delegated to Velvet sub-card)
Add broker_tier TEXT NOT NULL DEFAULT 'tier_a', broker_slug TEXT NOT NULL DEFAULT
'alpaca', and no_order_write BOOLEAN NOT NULL DEFAULT FALSE to Velvet's tokens
table. Backfill existing rows. no_order_write = TRUE enforces read-only at the Velvet
service layer for future Tier C connections, independent of application logic.
6. Rollout Plan
| Phase | Flag | Scope | Duration |
|---|---|---|---|
| Dark | FLAG_MULTI_BROKER_ADAPTER=off |
SC-1 through SC-4 merged; adapter registry not activated in routes | During remaining beta soak |
| Alpha | FLAG_MULTI_BROKER_ADAPTER=on (operator only) |
Tradier live orders available to operator in staging; Alpaca default unaffected | 2 weeks |
| Beta | FLAG_TRADIER_LIVE=on (selected Pro+ users) |
Tradier live orders to hand-picked beta cohort; Alpaca default unaffected | 2 weeks minimum |
| GA | FLAG_TRADIER_LIVE=on (all eligible users) |
Tradier available to all Pro+ users | — |
| SnapTrade | FLAG_SNAPTRADE_LIVE=on |
SnapTrade integration promoted after GA soak + contract signed (OQ-3) | TBD |
Kill-switch: BYOB_BROKER_TRADIER_DISABLED=1 reverts Tradier orders to an error
without touching Alpaca. BYOB_LIVE_DISABLED=1 disables all BYOB live orders.
MBT paper trading is unaffected by all kill-switches.
7. Security Considerations
All live order submission paths are security-critical surfaces because real money moves.
- Step-up WebAuthn on every live submit. The adapter call is gated; a request
that reaches
submit_order()without a verified step-up token raisesStepUpRequiredupstream and never reaches the adapter. - No credential in Raptor process memory longer than the request.
velvet_token_idis the only credential handle Raptor holds. Velvet decrypts and injects the token per request; Raptor never caches plaintext between requests. - Per-broker rate limit enforcement. Each adapter enforces upstream rate limits
before calling the broker API. Breaches are logged to
audit_logand surface via the adapter'shealth_check()to the status-page poller. - BLR TOS review is a hard pre-production gate for every new broker. No Tier A
adapter reaches production until BLR has signed off on that broker's developer TOS.
This gate is enforced via
blockedlabel on the production-deploy sub-card for each broker. - SnapTrade API key is server-side only. The SnapTrade API key (
SNAPTRADE_API_KEY) is in the secret store, rotatable without redeploy. Users never interact with SnapTrade directly; SnapTrade's OAuth flow for downstream brokers is mediated by SnapTrade's hosted widget, not by Raptor.
8. Security / GDPR Checklist
- PII collected: broker account ID (at broker), connection timestamps, order IDs. Position snapshots are fetched live per request; not cached in Raxx beyond the request scope. Audit log records order intent and broker-issued order ID — not position-level PII beyond what an audit trail requires.
- Retention period:
broker_live_connectionsrows: active connection duration plus 90 days after revocation (for dispute resolution). Audit log: 7 years (ADR-0003). Velvet token rows:revoked_atset on disconnect; purged after 90-day window. - Deletion on DSR: CASCADE DELETE from
users.idpropagates tobroker_live_connections. Corresponding Velvet row is revoked and purged. For SnapTrade connections: Raxx calls the SnapTrade delete-connection API before removing the Velvet row. Audit trail rows retain hashed actor ID (no reversible PII, per ADR-0003). - Audit trail: Every
submit_order,cancel_order,replace_order,revoke, andconnection_statechange writes toaudit_logviacustomer_audit_writer_service.py. Order-affecting rows participate in the KMS HMAC chain (SC-A11/SC-A14). - Stored credentials: All tokens in Velvet only — envelope-encrypted, no plaintext column. SnapTrade connection ID encrypted at rest for consistency (not itself secret). No credential in any file that ships with the repo or in any logged environment variable.
- Breach notification path: Token-exposure breach is financial PII. 72-hour notification to supervisory authority (GDPR Art. 33) and affected users (Art. 34) via the ADR-0003 path. SnapTrade breach: SnapTrade notifies Raxx under their DPA; Raxx re-notifies within 72 hours from notification receipt.
- Secrets location and rotation:
TRADIER_CLIENT_ID,TRADIER_CLIENT_SECRET,SNAPTRADE_API_KEYin Infisical secret store. All rotatable without redeploy. Velvet rotation handler perbroker_slug. - Kill-switch:
BYOB_LIVE_DISABLED=1(all BYOB live orders) andBYOB_BROKER_<SLUG>_DISABLED=1(per-broker). MBT paper trading is unaffected.
9. Language Choice Rationale
This ADR does not introduce a new service. All new modules are additions to existing services:
Service: Raptor (modified, not new) Language tier: - [x] Tier 2 — Python: broker adapter modules, registry, migration.
Broker adapter modules in Raptor do not trigger Tier 1 promotion criteria: broker API
round-trips are 100-500ms regardless of the adapter implementation language (no
sub-5ms p99 budget applies here); no cryptographic key material is handled in the
adapter (Velvet holds all ciphertext); throughput at v1 post-launch scale does not
stress a Python adapter. The credential-handling hot path is already Tier 1 (Velvet,
Rust, operator-designated). Reference: docs/architecture/language-tier-policy.md.
API contract portability (Tier 2): The BrokerAdapter ABC method signatures are
portable to a future Rust/C++ implementation without redesign. The one portability
risk is Python exception types in error paths — these should be codified as enum
error codes in OrderResult rather than exception class names to preserve language
neutrality.
Antlers (BrokerPlatformSelectView): Tier 2 — TypeScript/Next.js, consistent
with all Antlers components.
Velvet (schema extension only, SC-4): Tier 1 — Rust, operator-designated. The
schema migration is an ALTER TABLE; no new Velvet service logic is introduced by this
ADR beyond the no_order_write enforcement already described in ADR-0109.
10. Alternatives Considered
SnapTrade as the only integration (no named direct broker)
Rejected. Operator explicitly required "both — aggregator + a named direct broker." Additionally, multi-leg options support via SnapTrade is downstream-broker-dependent and cannot be guaranteed for the brokers most relevant to iron-condor traders. Tradier Tier A gives Raxx a reliable, Raxx-controlled options order path.
Tastytrade as the second direct broker instead of Tradier
Not rejected as a future target. Deferred for v1-live because the partner-program approval gate (2-4 weeks to obtain sandbox access) delays the start of engineering work. Tradier's self-serve sandbox allows work to start immediately after BLR TOS review clears. Tastytrade is filed as open question OQ-2.
Big-bang refactor: rewrite all 429 Alpaca references at once
Rejected. A single large PR touching trading.py, options.py, trading_runtime.py,
historical_bars_service.py, and related routes simultaneously creates high regression
risk across paper trading, market data, and live-execution paths. The incremental
adapter-wraps-existing approach (§3.4) preserves all existing behavior through each
pass and allows each pass to be reviewed, tested, and soaked independently.
Extend alpaca_integration.py with Tradier branches
Rejected per ADR-0052 — the file grows unbounded with each new broker and cannot be
tested cleanly. Adapter isolation is required for independent testing and for the
health_check() feed to the status page.
Fidelity as the second direct broker
Rejected. Fidelity's direct API (WIX / FDX per ADR-0050/0123) is enterprise-gated and access approval is slow and uncertain. Fidelity is available via SnapTrade (Tier B) once Phase 2 ships. Tradier unblocks work now.
11. Open Questions (operator decisions required)
OQ-1 (hard blocker for SC-7 — Tradier production deploy):
BLR must review Tradier's developer API agreement and third-party platform terms under
the same scope as the Alpaca TOS review (#932) before Tradier orders can reach
production. BLR engagement must be initiated in parallel with this ADR. The SC-7
sub-card carries the blocked label until BLR sign-off is received.
OQ-2 (Phase 3 input, not a v1-live blocker): After Tradier GA soak: does the operator want tastytrade as a second named Tier A direct broker, or defer tastytrade to the SnapTrade Tier B integration (which may route tastytrade accounts naturally via the aggregator)?
OQ-3 (hard blocker for SC-8, SC-9 — SnapTrade integration): SnapTrade contract and pricing tier. SnapTrade sub-cards are blocked until contract is signed. Operator action: initiate SnapTrade contract discussion. Last known pricing from BLR research doc #2627: ~$2/user/month for real-time + trading access.
OQ-4 (Tradier scope, affects SC-5 acceptance criteria): Tradier supports extended-hours orders and certain margin products. For v1-live, are these in scope, or should the adapter restrict to regular-hours equity and options orders only? Recommendation: restrict to regular-hours in v1; extended-hours is a follow-on tech card.
12. References
- [ADR-0002](https://internal-docs.raxx.app/architecture/adr/0002-no-stored-credentials.html) — no stored credentials invariant
- [ADR-0009](https://internal-docs.raxx.app/architecture/adr/0009-oauth-token-posture.html) / [ADR-0014](https://internal-docs.raxx.app/architecture/adr/0014-alpaca-scope-reframe.html) — token exception + Alpaca scope (extended by this ADR)
- [ADR-0013](https://internal-docs.raxx.app/architecture/adr/0013-mbt-paper-trading-engine.html) / [ADR-0108](https://internal-docs.raxx.app/architecture/adr/0108-mbt-engine-design.html) — MBT permanent paper layer
- [ADR-0052](https://internal-docs.raxx.app/architecture/adr/0052-broker-adapter-interface.html) —
BrokerAdapterABC; this ADR expands the interface contract - [ADR-0109](https://internal-docs.raxx.app/architecture/adr/0109-byob-roadmap.html) — BYOB tier model; this ADR selects v1-live platforms from that roadmap
- Epic #495 — Hybrid broker model
- Research doc #2627 — broker inventory + regulatory analysis (BLR)
docs/architecture/multi-tenant-alpaca.md— Alpaca market-data account (Surface 1, shared)- Project memory:
project_byob_hybrid_strategy,feedback_no_backend_branding,feedback_deterministic_execution_ai_augments
Revisit when
- BLR Tradier TOS review completes — OQ-1 resolved, SC-7 unblocked.
- SnapTrade contract signed — OQ-3 resolved, SC-8 and SC-9 unblocked.
- Tradier GA soak completes — assess tastytrade Tier A promotion (OQ-2).
broker_live_connectionsUNIQUE constraint needs relaxing for multi-broker households (Phase 4+ per ADR-0109 §4).- ADR-0052 status updated from Proposed to Accepted once SC-1 ships.
- Any SnapTrade-routed broker reaches >500 active connections — assess Tier A direct promotion cost/benefit.