Blocked on #564 — the entity decides the endpoints, the auth scheme and the fee table.
Mirrors #369 (keel-broker-alpaca Phase A). The path is well-trodden now: keel-broker-api is the
port, four adapters implement it, and the conformance suite in
keel_broker_api/conformance/suite.py holds every one of them to the same contract.
Scope
A packages/keel-broker-binance/ distribution implementing Broker, registered through the
keel.brokers entry point, with tests/conformance/test_binance_conformance.py green.
The port's full surface, as it stands after #563:
| method |
note |
capabilities() |
venue, asset_classes={"crypto"}, quote_currencies — see #564 on whether that includes USDT |
market_clock() / market_schedule() |
24×7, so both are the trivial declarations test_market_clock_is_open_without_a_call_for_24x7_venues expects |
get_candles() |
/api/v3/klines |
get_balances() |
/api/v3/account, mapped to Balance(currency, available, total) — available is spendable, total includes locked, and the distinction is load-bearing (rail 13; see #562) |
get_instrument() |
/api/v3/exchangeInfo → the LOT_SIZE filter's stepSize is base_increment |
preview_order() |
Binance has no preview endpoint; declare can_preview=False and refuse, as the fake does — never synthesise a quote |
place_order() / get_order() / cancel_order() |
/api/v3/order; cancel_order must return CancelOutcome honestly — CONFIRMED only when the venue states this order is terminal-cancelled |
get_fee_summary() |
/sapi/v1/asset/tradeFee |
The order kinds, and the one that needs a decision
OrderSpec is MarketIOCByQuote | MarketIOCByBase | LimitGTC | StopLimitGTC | BracketGTC. The
first four map cleanly. BracketGTC is the question: Binance spot has OCO (/api/v3/orderList/oco),
which is genuinely the same shape keel means — one order carrying both protective prices, with the
venue owning the race.
If it maps, declare it and let test_the_bracket_declaration_cannot_lie_in_either_direction prove
it. If it does not, refuse it explicitly in to_order_configuration — #521 records that widening
the union broke Alpaca and Robinhood under mypy and each needed an explicit refusal case, "the
better outcome", and every new adapter meets the same wall by construction.
Not in scope
Margin, futures, Earn, lending, staking. Rails 18/19 are the charter — "Spot-only is this agent's
CHARTER, not an operator preference" — and an adapter that could express them would be a shape no
keel path produces.
Acceptance
Blocked on #564 — the entity decides the endpoints, the auth scheme and the fee table.
Mirrors #369 (
keel-broker-alpacaPhase A). The path is well-trodden now:keel-broker-apiis theport, four adapters implement it, and the conformance suite in
keel_broker_api/conformance/suite.pyholds every one of them to the same contract.Scope
A
packages/keel-broker-binance/distribution implementingBroker, registered through thekeel.brokersentry point, withtests/conformance/test_binance_conformance.pygreen.The port's full surface, as it stands after #563:
capabilities()venue,asset_classes={"crypto"},quote_currencies— see #564 on whether that includes USDTmarket_clock()/market_schedule()test_market_clock_is_open_without_a_call_for_24x7_venuesexpectsget_candles()/api/v3/klinesget_balances()/api/v3/account, mapped toBalance(currency, available, total)—availableis spendable,totalincludes locked, and the distinction is load-bearing (rail 13; see #562)get_instrument()/api/v3/exchangeInfo→ theLOT_SIZEfilter'sstepSizeisbase_incrementpreview_order()can_preview=Falseand refuse, as the fake does — never synthesise a quoteplace_order()/get_order()/cancel_order()/api/v3/order;cancel_ordermust returnCancelOutcomehonestly —CONFIRMEDonly when the venue states this order is terminal-cancelledget_fee_summary()/sapi/v1/asset/tradeFeeThe order kinds, and the one that needs a decision
OrderSpecisMarketIOCByQuote | MarketIOCByBase | LimitGTC | StopLimitGTC | BracketGTC. Thefirst four map cleanly.
BracketGTCis the question: Binance spot has OCO (/api/v3/orderList/oco),which is genuinely the same shape keel means — one order carrying both protective prices, with the
venue owning the race.
If it maps, declare it and let
test_the_bracket_declaration_cannot_lie_in_either_directionproveit. If it does not, refuse it explicitly in
to_order_configuration— #521 records that wideningthe union broke Alpaca and Robinhood under mypy and each needed an explicit refusal case, "the
better outcome", and every new adapter meets the same wall by construction.
Not in scope
Margin, futures, Earn, lending, staking. Rails 18/19 are the charter — "Spot-only is this agent's
CHARTER, not an operator preference" — and an adapter that could express them would be a shape no
keel path produces.
Acceptance
tests/conformance/test_binance_conformance.pygreen, no test skipped or specialised for this venue.capabilities()declares only what the adapter actually does; the conformance suite proves each claim.test_get_balances_returns_only_domain_typesand its siblings).get_instrumentreturnsNonefor an unlisted symbol and never a non-positive increment.keel-broker-robinhood, until something constructs it at runtime (Finish the broker-port migration: _build_broker still constructs CoinbaseClient directly #524).