Expand description
Shared behaviour scenarios that every broker adapter must pass against its own mock.
This is the test layer of the connector library. Each adapter (brokapi-bcs, brokapi-tbank,
brokapi-finam, …) implements the capability traits of [brokapi_core], and each runs the same
scenarios from this crate against its own in-process broker imitation (see brokapi-mocks).
So all brokers behave the same through the traits: the same refusal is the same
[brokapi_core::Error], the same stream gives the same envelope. The scenarios use only the
traits, never an adapter’s own types.
A scenario is an async fn that takes the connector (and sometimes the event receiver). It
returns () and panics when the adapter breaks the contract. The panic message starts with
[conformance:<scenario name>] and gives the reason, so the failing scenario and the cause are
visible in the test output. A scenario that passes leaves the connector usable, with its test
orders cancelled.
What a scenario proves is that the adapter agrees with the mock. The mocks are written from the broker documentation. They do not prove how the live broker behaves.
The mock behind the adapter must:
- serve an order book (both sides non-empty) for the instrument passed in;
- stream at least one order book update after subscribe;
- acknowledge a limit order and cancel it;
- refuse a limit order whose price is
REJECT_PRICE(forrejected_order_does_not_block_the_instrument); - serve a portfolio snapshot and push at least one portfolio update after subscribe;
- for
stop_order_far_from_market: accept a stop order, list it while it waits, cancel it; - for
market_data_history: serve the tape, candles for the range, trading status and a schedule; - for
market_data_streams: push a market trade, a quote and a candle after each subscribe; - for
order_state_events: push an order event when an order is placed and when it is cancelled; - for
active_orders_lifecycle/replace_order: keep placed orders inactive_orders; - for
executions: execute an order on request (the adapter’s closure) and keep the trade history.
Trading scenarios need the command gate open, so call reconcile first on a user data
connector. Until the first reconcile every command fails with
[brokapi_core::Error::NotReconciled].
The book is at https://brokapi.ru/docs/.
§Examples
The usual adapter test: start the mock, start the connector on it, run the scenarios, shut down. Here the adapter is the BCS connector.
use brokapi_bcs::limits::default_limits;
use brokapi_bcs::{UserDataConfig, UserDataConnector};
use brokapi_core::{InstrumentId, InstrumentKey, NativeId, ShutdownMode};
use brokapi_runtime::BrokerLimiter;
// `ud` is a connected user data connector, `sber` the test instrument.
brokapi_conformance::reconcile(&ud).await;
brokapi_conformance::place_and_cancel(&ud, &sber, Price::from(1)).await;Constants§
- EVENT_
TIMEOUT - How long a scenario waits for a pushed event, or for one command, before it fails.
- LOT
- One lot of the instruments the mocks serve, in units: 10. The scenarios order whole lots, because an
order of units that is not a whole number of lots is refused (
invalid_qty) by the connectors that know the lot. A connector that does not look the lot up itself (BCS, Finam) is told the lot of the test instrument withprice_grid().set_lot(..)in the test. - REJECT_
PRICE - A limit order at exactly this price is refused by every mock (the broker says no).
Functions§
- active_
orders_ lifecycle active_ordersfollows the order: a placed order is listed with its details, a cancelled one is gone.- duplicate_
place - A
cidis used for one place at most: the second place with it is refused locally and creates nothing. - executions
- Own executions: the history works, and a fill arrives as an
Executionevent and in the history. - executions_
history_ is_ complete - The history of own trades is complete:
expectedtrades come back, none is dropped or doubled. - foreign_
instrument_ id - An instrument id of another broker’s kind is a configuration error, and nothing is sent.
- gate_
checked_ after_ the_ queue - A command that waits in the queue of its instrument is checked against the gate again when its turn comes: a gate that closed meanwhile stops it, and nothing is sent.
- instrument_
lookup - Instrument directory: a search finds the instrument, and its trading parameters are usable for order sizing.
- invalid_
qty - A quantity the broker cannot take is refused before anything is sent, and the cid stays usable.
- lots
nlots of the test instruments, in units.- market_
data_ history - The tape, candles, trading status and schedule of one instrument.
- market_
data_ history_ without_ schedule - The tape, candles and trading status of one instrument:
market_data_historywithout the schedule, for a broker whose API has no timetable of sessions (itsscheduleanswersUnsupported, which this scenario does not call). - market_
data_ streams - Market trades, quotes and candles as streams, each for the right instrument and tagged with our source and subscription id.
- market_
subscribe_ returns_ while_ the_ stream_ opens_ late - The soft variant of
market_subscribe_waits_for_the_stream(seesubscribe_returns_while_the_stream_opens_late):subscribe_order_bookreturnsOkwhen the stream is not open in time, and afterreleasethe books arrive on that subscription. - market_
subscribe_ waits_ for_ the_ stream subscribe_order_bookreturns when the stream is open, and a stream that does not open is an error.- next_
matching - Reads envelopes from
rxuntilpredaccepts one, and returns that one. - order_
book_ resync - The order book stream is void after a break: a [
Event::Resync] follows, then a fresh snapshot. - order_
book_ snapshot - REST order book: the right instrument, the asked depth, sorted best-first, not crossed.
- order_
book_ stream - Order book stream: subscribe, then an order book envelope arrives tagged with our source and subscription id.
- order_
state_ events - The order stream: placing an order gives an event for our cid, cancelling it gives a
Cancelledevent. - order_
state_ unknown_ cid order_stateof acidthe broker has never seen is a refusal: [Error::Rejected] with codenot_found([brokapi_core::NOT_FOUND]), spelled the same at every broker.- place_
and_ cancel - Places a far-from-market limit order, reads its state by cid, cancels it.
- place_
without_ answer_ is_ unknown - A place the broker never answers is
Unknown: not repeated, notRejected, resolved by asking. - portfolio
- Portfolio: the snapshot works, and a portfolio update arrives after subscribe.
- reconcile
- Reconcile: an empty ledger touches nothing, and an unknown cid is reported as closed offline.
- rejected_
order_ does_ not_ block_ the_ instrument - An order the broker refuses must not block the instrument: the next order on the same instrument goes through.
- replace_
order replace: where the broker has it the order moves to a new cid, where it has not,replacesaysUnsupported.- stop_
order_ far_ from_ market - Stop orders, the common path: place, list, cancel by cid, then a stop-limit cancelled by broker id.
- stop_
orders_ unsupported - A connector without stop orders says so: every stop method answers [
brokapi_core::Error::Unsupported]. - stop_
without_ answer_ keeps_ its_ cid - A stop order the broker took, whose answer was lost, comes back in the list with its
cid. - stream_
cut_ degrades_ until_ reconcile - The stream of own orders breaks: the bot is told, the gate closes,
reconcileopens it. - subscribe_
returns_ while_ the_ stream_ opens_ late - The soft variant of
subscribe_waits_for_the_stream, for a broker whose stream may announce itself only with its first message (Finam, gRPC):subscribe_*waits for the stream, and when it does not open in time it still returnsOkand the stream goes on opening in the background. - subscribe_
waits_ for_ the_ stream subscribe_order_stateandsubscribe_executionsreturn when the stream is open, not before.- unknown_
instrument - A read of an instrument the broker does not know is
Rejected(not_found), the same at every broker. - unsubscribe
- Unsubscribing an active subscription succeeds, and doing it again is refused the same way at every broker.
- unsubscribe_
unknown - Unsubscribing an id the connector never gave out is refused: [
Error::Rejected] with codeunknown_subscription. The same answer at every broker, so a bot needs one branch for it.
Type Aliases§
- Subscribe
Call - The future of one
subscribe_*call, as the scenarios of this section hand it to the adapter.