Skip to main content

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 (BTC not BTC-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 PaperClient for 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"

See also