The broker door for Brue trade-entry scripts and for any platform. Brue is for writing trade entries (buy, sell, close) and connecting them to brokers; Brue Connect is the connecting half. A platform (a terminal, a strategy runner, a notebook, a Brue script) talks to a broker through an ADAPTER: a local process that speaks one line-delimited JSON protocol on stdio. Write that adapter once and your broker works in every platform built on this contract.
Any broker becomes one adapter. The reference paper simulator is an adapter with no special access, and nothing anywhere in the platform may special-case a broker by name.
Who this package is for. Two audiences, one contract:
- Trading through a broker? You want a platform built on this contract
(the LSE Terminal, or
pip install brue-languagefor scripts), not this package directly; it comes along as a dependency and stays out of sight. - Integrating your broker or venue? You are in the right place. Read
SPEC.md (the law), answer ONBOARDING.md, write one adapter, then prove it:
pip install brue-connectand run the conformance suite against your adapter command. Nothing FAILs = your broker works in every platform on this contract.
- SPEC.md is the contract, and it is normative. It is complete enough to implement an adapter in any language that can read and write lines of JSON: envelope, handshake, every method and event, the closed error set, reconciliation, and the safety requirements.
- ONBOARDING.md is the questionnaire to send a broker before anyone writes code. Every question in it exists because a real integration got that fact wrong quietly.
- This file is the practical part: the SDK, the environment, how to prove you are conformant, and how to get listed.
pip install brue-connectThe SDK is a normal Python package with no dependencies. From a checkout, pip install -e /path/to/brue-connect gives the same result. Either way import brueconnect must resolve from anywhere, including the adapter you write in your own directory; if it does not, the conformance command below spawns an adapter that dies on its first line.
Python adapters subclass Adapter and implement handle_<method> (dots
become underscores: order.place is handle_order_place). The base class
owns the stdio loop, dispatch, event sequencing, the handshake and the
idempotency store, so you write broker logic and nothing else.
from brueconnect import Adapter, AdapterError
class MyBroker(Adapter):
name, version, broker = "mybroker", "0.1", "My Broker"
mode = "paper" # "live" means real money (SPEC 8.2)
account_currency, account_model = "USD", "netting"
# capabilities() is a METHOD you override, not a class attribute. It
# returns the SPEC section 3 capabilities object; anything you leave out
# is treated as a feature you do not have.
def capabilities(self):
return {"orders": {"market": True, "partial_close": False},
"data": {"quotes": False, "bars": False, "history": False},
"flatten_all": True}
def on_first_call(self):
"""Log in HERE, never in your startup path: `hello` must stay
answerable cold so a picker can learn your name for free."""
def handle_catalog_list(self, params):
return {"instruments": [...]} # InstrumentSpec, SPEC section 4
def handle_account_get(self, params):
return {"balance": 0.0, "equity": 0.0, "margin_used": 0.0,
"margin_free": 0.0, "currency": "USD"}
def handle_positions_list(self, params):
return {"positions": []}
def handle_order_place(self, params):
prior = self.idempotent_replay(params["client_id"])
if prior is not None:
return prior # a replay places nothing
# Conformance REQUIRES you to reject bad orders (SPEC 8.5), so a real
# handler validates before it deals:
# unknown symbol -> AdapterError("unknown_symbol", ...)
# qty off lot_step, or <min_qty / >max_qty -> "invalid_qty"
# cannot afford margin -> "insufficient_margin"
... # deal it at your broker
self.emit("order.filled", {...})
return self.idempotent_store(params["client_id"], {"order_id": "1"})
def handle_flatten_all(self, params):
return {"closed": 0} # REQUIRED of every adapter
def handle_history_fills(self, params):
return {"fills": []} # REQUIRED: the ledger, oldest first
if __name__ == "__main__":
MyBroker().run()This is a skeleton, not a conformant adapter: it leaves out validation, a real catalog, and event handling. It shows the SHAPE. The conformance suite is what tells you when you are actually done.
idempotent_replay(client_id) returns the stored result of a client_id you
have already seen (or None), and idempotent_store(client_id, result)
records one; together they make a replayed order.place return its original
outcome and place nothing. Raise AdapterError(code, message, retryable)
inside a handler to return a typed error; code must come from the closed
set in SPEC section 6, or carry an x_ prefix if it is yours. Use
self.emit(name, data) for events; it is thread-safe. Guard your own state
with your own locks (any name is fine; the SDK's own lock is private and
cannot collide), and do not hold a lock across emit() longer than you must.
Working examples in this repo, smallest first:
| Adapter | Lines | Why read it |
|---|---|---|
tests/mini_adapter.py |
84 | the whole contract, one instrument |
brueconnect/adapters/novafx/ |
164 | foreign vocabulary on a shared engine |
brueconnect/adapters/paper/ |
662 | the reference adapter |
brueconnect/adapters/meridian/ |
~2000 | a full integration: OAuth login, account selection, an at-least-once stream, REST as the backstop |
brueconnect/adapters/ holds eight reference adapters for simulated venues
(atlas, helix, meridian, northgate, pemberton, quantex, sablepoint, vertex),
each speaking a genuinely different wire protocol (FIX, HMAC REST, a binary
link, JSON-RPC over websocket, XML polling, STOMP, gRPC, OAuth2 REST), plus
three more: paper (the reference simulator), novafx (foreign vocabulary
on the paper engine) and lse_sim (the LSE demo account as a broker). All
are pure standard library on purpose: an adapter is spawned by whatever
Python the user's machine has, so a pip dependency makes that broker
unconnectable for people who do not have it.
Credentials never travel in a protocol message (SPEC 8.5). They reach you as environment variables at spawn:
| Variable | Meaning |
|---|---|
BRUE_BROKER_STATE |
your private directory: credentials, tokens, caches. Yours alone, 0700, and it survives restarts |
BRUE_CRED_<FIELD> |
what the user typed into your credential form, upper-cased (api_key becomes BRUE_CRED_API_KEY). Treat as optional |
BRUE_ACCOUNT_ID |
which of the login's accounts this process deals on (SPEC 1.7). A platform sets it once the user has chosen; if it is empty your adapter falls back to the login's primary. This is your fallback, not the platform's default: a platform must never auto-pick an account for the user (SPEC 1.7), but your adapter must still do something sensible when spawned bare, e.g. in conformance |
Anything else your adapter needs (an endpoint, a mode switch) is your own variable, set from the broker's directory row.
An adapter is conformant when the suite reports zero failures. Run it against your adapter exactly as a platform would spawn it:
python -m brueconnect.conformance.run --adapter "python3 path/to/my_adapter.py"Useful flags:
--partial-hint SYMBOL:QTYa size known to fill partially, so the partial fill scenario can run instead of skipping.--restartableyour adapter persists its state, so the reconnect scenario may kill and relaunch it.--liveyou accept that the suite will place REAL orders. It refuses to certify a non-paper account without this.--auth-driver "cmd"for a broker with an interactive login: a command that plays the human and the browser, given the login URL as its last argument. Without it, an adapter that needs a login cannot be certified and the suite says so rather than failing noisily. Seebrueconnect/conformance/auth_drivers/meridian_login.py.
Give each run its own state directory, the way a real profile gets one:
BRUE_BROKER_STATE=/tmp/mybroker-state \
python -m brueconnect.conformance.run --adapter "python3 path/to/my_adapter.py"Read the SKIPs. A skip is the suite honestly reporting a capability you did not declare; a skip you caused by declaring a capability false to dodge a failing scenario is not conformance, it is hiding.
Being conformant makes you connectable; a directory row makes you visible. The platform reads a runtime directory, so a broker is added or delisted without anyone updating an installed app. Your row carries:
| Field | What it is |
|---|---|
display_name, tagline |
your name and one line about you |
logo_svg, website |
your mark, rendered in the panel's ink |
transport |
how you speak (rest_sse, fix, grpc, ...), display only |
endpoint_host/port/path/tls |
where your adapter dials |
credential_form |
the fields to collect from the user before connecting; empty when you need nothing typed |
Two presentation contracts in SPEC say what your fields become on screen, so you can see the result before you send them: section 3.1 for your broker identity, and section 1.7 for your accounts.
These are the ones integrations get wrong quietly, so they are worth stating twice:
helloanswers COLD. No login, no account read, no stream in your startup path. A picker handshakes you just to learn your name.modemust be truthful, and it is per ACCOUNT once a login reaches several. Anything notpaperis treated as real money and requires arming.- Anything absent from
capabilitiesis absent from you. The platform refuses the feature loudly rather than emulating it silently. - Every money figure you report is already in the account's currency, at the rate you actually charge. The platform holds no exchange rates.
- Idempotency on
client_idfor every mutating call: a replay returns the original outcome and places nothing. flatten_allmust work while everything else is failing or rate-limited.- Persist refresh tokens. A broker that keeps them in memory signs every client out each time it deploys.
SPEC.md the protocol (normative)
ONBOARDING.md the questionnaire for a new broker
brueconnect/ the SDK: Adapter, Connector, protocol, registry
brueconnect/adapters/ eight simulated-venue adapters plus paper, novafx and lse_sim
brueconnect/conformance/ the suite that decides conformance
auth_drivers/ stand-ins for a human at a login page
tests/ SDK unit tests (python3 -m unittest discover -s tests)