Roadmap
v0.1 — alpha ✅ shipped (2026-04-21)
End-to-end rebalancing-bot use case.
-
Vendor + normalise IBKR OpenAPI spec (
bezant-spec) -
Codegen all 154 CPAPI endpoints via oas3-gen (
bezant-api) -
Ergonomic facade: Client, auth, keepalive, health (
bezant-core) -
HTTP sidecar exposing the facade over REST (
bezant-server) - Docker image bundling IBKR Gateway + bezant-server
- WebSocket client with cookie auth + typed subscribe helpers
- Pagination helpers + symbol → conid cache
- Tracing instrumentation across the facade
-
CLI (
bezant-cli) + MCP server (bezant-mcp) + TypeScript client - Snapshot tests driven by spec example payloads
- GitHub Actions CI (fmt, clippy, test, MSRV, audit, multi-arch Docker)
- Dual MIT / Apache-2.0 license
v0.2 — production hardening ✅ shipped (2026-05-03)
Goal: deployable to a real production trading bot, not just localhost dev.
- Cloudflare Zero Trust + residential-Pi deploy guide — bypasses IBKR’s Akamai datacenter-IP rejection, the silent killer of cloud- hosted CPAPI deploys
-
NameKeyedJarcookie store — replaces reqwest’s path-aware jar to fix duplicateJSESSIONIDaccumulation that CPGateway rejects -
Edge-cookie filter — drops
CF_Authorization/CF_AppSession/ AWS ALB / OAuth2 Proxy / Pomerium / Vercel cookies before they poison the upstream call (Akamai 401s on unrecognised cookies) -
/debug/probe+/debug/jardiagnostics, gated byBEZANT_DEBUG_TOKEN(constant-time compare, names+lengths only, never raw values) -
Strip
Authorization/X-Forwarded-*/Forwarded/X-Real-IPat the proxy boundary - Multi-arch Docker builds on native arm64 GitHub runners (~5 min vs ~20 min QEMU)
v0.3 — typed surface + observability ✅ shipped (2026-05-03)
Goal: library-quality ergonomics + production-debuggable runtime.
-
11 typed
Errorvariants replacingError::Other(String)—UpstreamStatus,Unknown,UrlNotABase,MissingQuery,Header,SymbolNotFound,BadConid,WsHandshake,WsTransport,WsProtocol,ResponseBuild -
Error::is_retryable()for backoff loops -
bezant::preludefor the typical bot use case -
#[non_exhaustive]onAuthStatus+TickleResponseso future fields aren’t SemVer breaks -
Per-request correlation IDs (
SetRequestIdLayer+PropagateRequestIdLayer) + handler#[tracing::instrument]+ keepalive task span -
Graceful shutdown (SIGTERM/SIGINT drain + awaited
keepalive.stop()) +ConcurrencyLimitLayer(256)+ reqwest pool tuning (pool_max_idle_per_host,tcp_keepalive,connect_timeout,pool_idle_timeout) -
KeepaliveHandle::Dropsends shutdown signal so a forgotten handle doesn’t keep tickling -
WebSocket
Subscriptionhandle — RAII cancel viaSubscription::cancel(&mut ws).awaitinstead of caller-tracked conids;WsClient::splitreturns concreteWsSink/WsRecv;WsMessage::topic()/as_value()accessors -
/debug/probeper-step timeout (5s) + body-preview redaction (session/token/secretkeys) + non-destructive ssodh skip -
bezant-cli --output {json,table}+quote SYMBOL+orders ACCOUNT+ cap warning onMAX_POSITION_PAGES - 14 spec-normaliser invariant tests + CI drift-check job
- Published to crates.io at v0.3.0
post-0.3 (unreleased) — events observability ✅ shipped (2026-05-06)
Goal: capture every order, fill, rejection, PnL update, and (per-conid) market-data tick the upstream WebSocket sees, and expose them via cursor-paginated REST so polling consumers don’t lose events between strategy ticks.
-
bezant-serverevents module — internalbezant::WsClientconsumer with reconnect + heartbeat-timeout, per-topic ring buffers (orders,pnl,marketdata:<conid>,gap),reset_epoch/cursor wire semantics so clients can detect gaps -
/events/*REST surface —orders,pnl,marketdata,gap,_statusendpoints with 200 / 204 / 412 cursor outcomes -
Lazy market-data subs —
/events/marketdata?conid=Nref-counts upstreamsmd+<conid>+{}subscribes; re-establishes across WS reconnects -
Optional sqlite history —
BEZANT_EVENTS_DB_PATHmirrors every captured event intoevents(...), served via/events/{topic}/history?since_ts=…. Per-topic retention with hourly prune (orders/pnl 90d, marketdata 14d, gap 365d) -
WsClient::connecthonoursaccept_invalid_certs— fixes reconnect loop against the Gateway’s expired self-signed cert -
pump_until_ready— waits for CPAPI’ssystem+successframe before subscribing; CPAPI silently drops pre-ready subscribes
v0.4 — feature flags + auto-reconnect 🔭 planned
Goal: smooth out remaining rough edges; expand for non-Rust ecosystems.
Library
-
Feature flags on
bezant-core(ws,keepalive-tokio) so callers don’t pay for tokio-tungstenite if they only want REST -
Async runtime decoupling —
spawn_keepaliveaccepts a runtime handle so async-std / smol consumers can use the crate -
bezant::ws::TickerManager— auto-reconnect on disconnect, re-subscribes existing topics, exposed as a background actor -
Retry middleware with exponential backoff on
is_retryable() - Typed error variants for common IBKR failure modes (insufficient funds, market closed, restricted account)
MCP + ecosystem
-
bezant-mcpmarket data + orders tools (currently read-only), gated behind--allow-ordersso registration itself is opt-in - MCP resources for accounts/positions so Claude can include state in context without explicit tool calls
-
Python bindings via pyo3 —
pip install bezantfor quant scripts
Robustness
- Live-account integration tests gated behind a feature flag, opt-in via env var
- OAuth 1.0a / 2.0 auth when IBKR opens it to retail accounts
-
Anyhow-free
bezant-core— redrivehelpers.rs/auth.rsoff the generated client’s typed Result so anyhow can become optional
v1.0 — stable
- Stable public API. SemVer discipline.
- Production-grade docs + examples for every surface.
- Reference rebalancing bot as a published companion crate.
- Options / futures / forex / fixed income convenience builders.
Contributing
PRs welcome. If you hit a new spec quirk that isn’t in
scripts/normalize-spec.py, please open an issue with the failing operation
ID or schema name and ideally the minimal reproducer — that lets us expand
both the normaliser and the upstream bug report against IBKR.