How to isolate broker-specific authentication, symbols and order formats behind one internal interface.
In plain English
This guide treats the broker as an external API with its own authentication, symbols, limits and failure states. A successful HTTP request is not always the same as a confirmed final action.
Why adapters matter
Broker APIs differ in authentication, symbol names, order parameters, response structures and session behavior. If strategy code calls a broker SDK directly everywhere, changing broker becomes a rewrite.
Define your own small interface
from typing import Protocol
class Broker(Protocol):
def get_price(self, symbol: str) -> float: ...
def get_positions(self) -> list[dict]: ...
def place_order(self, symbol: str, side: str, size: float) -> dict: ...
def close_position(self, position_id: str) -> dict: ...
Then implement CapitalBroker, OandaBroker, IBKRBroker and so on. Strategy code only sees the protocol.
Normalize symbols
Create a mapping layer. Your internal name might be BTCUSD, while different providers may use an epic, conid, instrument identifier or another symbol format.
Normalize results too
{
"ok": true,
"broker": "example",
"order_id": "123",
"status": "accepted",
"raw": {...}
}
Keep the raw response for diagnostics, but let the rest of the application use your normalized fields.
Before you rely on this in production
- Demo/live environments are clearly separated.
- Authentication renewal is handled.
- Position/order state is checked before retrying.
- Rate limits and final confirmations are logged.
