ADR 0052 — Broker adapter interface: BrokerAdapter ABC with registry, not extending alpaca_integration.py
Status: Proposed
Date: 2026-05-05
Deciders: software-architect, operator
Context doc: fidelity-broker-integration.md §5
Related ADRs: 0014, 0050
Context
Raxx currently has one broker integration — Alpaca — implemented as a collection of procedural functions in backend_v2/api/services/alpaca_integration.py and trading_runtime.py. There is no interface or base class; the Alpaca-specific implementation is wired directly into the trading routes.
Adding Fidelity as a second broker forces a choice: extend the Alpaca-specific code with conditional branches, or introduce a proper abstraction. This ADR records that choice.
Decision
Introduce a BrokerAdapter abstract base class (ABC) and a BrokerAdapterRegistry service locator.
BrokerAdapter(new, inbackend_v2/api/services/broker/base.py) defines the interface:submit_order,cancel_order,get_order_status,get_positions,get_account,is_market_hours,health_check.AlpacaBrokerAdapterwraps the existing Alpaca functionality and implementsBrokerAdapter.FidelityBrokerAdapterimplementsBrokerAdapterindependently from the start.BrokerAdapterRegistryresolves the correct adapter for a given user at runtime, based on the user's active live connection (fidelity_live_connectionsvsalpaca_live_connections).- Trading routes call
registry.get_adapter_for_user(user_id)— never import an adapter directly.
Do not extend alpaca_integration.py with Fidelity-specific branches. The file stays as the Alpaca implementation. The refactor into AlpacaBrokerAdapter is a migration sub-card (SC-3) that does not break the public API of the trading routes — it moves the internals behind the interface.
Consequences
Positive
- Adding a third broker (IBKR, tastytrade — the "1 other" in Pro tier pricing) requires only a new
XBrokerAdapter(BrokerAdapter)implementation and aBrokerAdapterRegistry.register()call. No changes to trading routes. health_check()method on each adapter feeds the status-page poller without custom-casing each broker.- Testing each adapter is independent: mock
BrokerAdapterin trading-route tests; test each adapter against its own fixtures. broker_idclass constant on each adapter is the stable key inbroker_order_log.broker_id.
Negative
- SC-3 (Alpaca refactor) is a medium-sized migration card. It is not strictly required before Fidelity ships — the two implementations can coexist temporarily — but the registry pattern cannot be fully realized until both adapters are behind the same interface.
- The ABC introduces a new module (
broker/) that feature-developer must understand before making changes to either adapter.
Neutral
alpaca_integration.pyis preserved as theAlpacaBrokerAdapterimplementation file; only the call sites in trading routes change.- The
BrokerAdapterRegistrydoes not use dependency injection or a DI container — it is a simple dict-backed service locator initialized at app startup. This is consistent with the existing Flask app pattern.
Alternatives considered
Extend alpaca_integration.py with Fidelity branches (if broker == 'fidelity': ...)
Rejected. This path produces a file that grows unbounded with each new broker and is impossible to test cleanly. The broker-specific logic for OAuth token management, rate limiting, and order-spec translation is materially different between adapters. Conditional branching would produce an unreadable module.
One file per broker, no ABC (duck typing)
Considered. Python duck typing means the ABC is not strictly enforced at runtime. The ABC is retained for:
1. Explicitness — feature-developer implementing a new adapter gets a clear contract via @abstractmethod.
2. CI enforcement — mypy or pyright can verify all methods are implemented.
3. Documentation — the ABC is the canonical interface specification, not implicit convention.
External broker aggregator (SnapTrade / Plaid Investments) as the abstraction layer
Deferred. The BYOB strategy includes an aggregator slot (existing design). The BrokerAdapter interface is compatible with an aggregator adapter (the aggregator becomes one adapter, routing to multiple downstream brokers). This ADR does not rule out an aggregator adapter — it only establishes the interface that the aggregator adapter would also implement.
Revisit when
- A third broker is ready to implement (triggers review of whether
BrokerAdapterRegistryneeds to support multiple simultaneous live connections per user, e.g., Alpaca live + Fidelity live). - Market data integration expands: if an adapter's
get_market_data()method is needed (currently excluded from the interface because market data is server-side shared, not per-user brokered). - The SC-3 Alpaca refactor reveals constraints that require adjustments to the interface shape.