mts1b-brokers — public API surface
Every broker adapter implements mts1b_foundation.protocols.BrokerProtocol. The unified registry lets mts1b-oms swap brokers via config.
Registry
from mts1b_brokers import BROKER_REGISTRY
# Map name → adapter class
{
"paper": PaperClient,
"ibkr": IbkrClient,
"coinbase": CoinbaseClient,
"schwab": SchwabClient,
"moomoo": MoomooClient,
"tradier": TradierClient,
"kraken": KrakenClient,
}
# Instantiate any adapter by name
cls = BROKER_REGISTRY["coinbase"]
async with cls(api_key="...", api_secret="...") as broker:
positions = await broker.get_positions()
PaperClient
from mts1b_brokers.paper import PaperClient
from dataclasses import dataclass
@dataclass
class PaperClient(BrokerProtocol):
name: str = "paper"
fill_model: str = "midpoint" # "midpoint" | "nbbo" | "slippage_model"
fee_bps_per_side: float = 5.0
slippage_bps: float = 2.0
fill_delay_ms: int = 100
In-process fill simulator. Useful for paper trading + tests.
async with PaperClient(fill_model="nbbo", fee_bps_per_side=10.0) as broker:
order = Order(...)
submitted = await broker.submit(order)
async for fill in broker.stream_fills():
print(fill.fill_id, fill.price, fill.fees)
break
IbkrClient
from mts1b_brokers.ibkr import IbkrClient
@dataclass
class IbkrClient(BrokerProtocol):
name: str = "ibkr"
gateway_host: str = "127.0.0.1"
gateway_port: int = 5000
account_id: str = ""
paper: bool = False
extended_hours_default: bool = False
Interactive Brokers via Client Portal API + IB Gateway. Asset classes: equities, options, futures, fx, crypto (via PAXOS).
Quirks (documented + handled)
- PAXOS crypto uses bare 3-char symbols (
BTCnotBTC-USD). Adapter normalizes. - Extended-hours orders need
extended_hours=True+order_type=limit. MARKET is rejected outside RTH. - IBEOS overnight session covers ~10k+ symbols 20:00 ET → 03:50 ET.
async with IbkrClient(account_id="DUM726747", paper=True) as broker:
open_orders = await broker.get_open_orders()
positions = await broker.get_positions()
CoinbaseClient
from mts1b_brokers.coinbase import CoinbaseClient
@dataclass
class CoinbaseClient(BrokerProtocol):
name: str = "coinbase"
api_key: str = ""
api_secret: str = "" # ECDSA private key (PEM)
passphrase: str = ""
sandbox: bool = False # Coinbase Advanced has no sandbox; flag retained
Coinbase Advanced Trade via REST + WebSocket. Asset class: crypto.
Quirks (documented + handled)
- No paper sandbox (deprecated 2023). Use
PaperClientfor paper. - Quote endpoint format:
rates[QUOTE]= QUOTE-per-base. Bug fixed 2026-05-05.
SchwabClient
from mts1b_brokers.schwab import SchwabClient
@dataclass
class SchwabClient(BrokerProtocol):
name: str = "schwab"
client_id: str = ""
client_secret: str = ""
refresh_token: str = ""
account_id: str = ""
Schwab via OAuth2 + REST. Asset classes: equities, options.
MoomooClient
from mts1b_brokers.moomoo import MoomooClient
@dataclass
class MoomooClient(BrokerProtocol):
name: str = "moomoo"
host: str = "127.0.0.1"
port: int = 11111 # OpenD listening port
account_id: str = ""
paper: bool = False
region: str = "US" # "US" | "HK"
Moomoo via OpenD daemon. Asset classes: equities (US, HK).
TradierClient
from mts1b_brokers.tradier import TradierClient
@dataclass
class TradierClient(BrokerProtocol):
name: str = "tradier"
access_token: str = ""
account_id: str = ""
sandbox: bool = False
Tradier via REST. Asset classes: equities, options. Has sandbox for paper.
KrakenClient
from mts1b_brokers.kraken import KrakenClient
@dataclass
class KrakenClient(BrokerProtocol):
name: str = "kraken"
api_key: str = ""
api_secret: str = ""
sandbox: bool = False
Kraken via REST + WebSocket. Asset class: crypto.
The BrokerProtocol contract
Every adapter MUST:
@property
def name(self) -> str: ...
async def submit(self, order: Order) -> Order:
"""Idempotent on Order.idempotency_key. Returns order with submitted_at + broker_order_id."""
async def cancel(self, order_id: str) -> bool:
"""Return True ONLY if cancellation was confirmed (not raced by a fill)."""
async def get_open_orders(self) -> list[Order]: ...
async def get_positions(self) -> list[Position]: ...
async def stream_fills(self) -> AsyncIterator[Fill]:
"""Replay-safe stream of fills. Restart shouldn't lose fills."""
Verify:
from mts1b_foundation.protocols import BrokerProtocol
assert isinstance(my_client, BrokerProtocol)
Adding a new broker
Use mts1b-pluginsdk:
mts1b-plugin new --type broker --name robinhood-crypto
cd mts1b-plugin-robinhood-crypto
pip install -e ".[dev]"
pytest # protocol conformance pre-included
Then register via entry points:
[project.entry-points."mts1b.brokers"]
robinhood-crypto = "mts1b_plugin_robinhood_crypto:RobinhoodCryptoClient"