# brokapi for Python: API reference

Made from the docstrings of the package (`pip install brokapi`), one file for agents. Money, prices and quantities
are `decimal.Decimal`. Every method of `Client` is a coroutine. The errors are in `brokapi.errors` and also at the
top level of the package.

## Connecting and working

### `connect(broker: str, *, name: Optional[str] = None, limiter: Optional[Limiter] = None, event_buffer: int = 8192, **settings: Any) -> Client`

Connects to a broker account.

What you do not pass is read from the environment (and a ``.env`` file next to the program), as in the Rust
library:

- ``bcs``: ``refresh_token`` (``BCS_REFRESH_TOKEN``, required), ``write`` (``BCS_IS_WRITE``, ``true``: the token can
  trade).
- ``tbank``: ``token`` (``TBANK_TOKEN``, required), ``sandbox`` (``TBANK_SANDBOX``, without it the address is the real
  one), ``account_id`` (``TBANK_ACCOUNT_ID``, needed when the token has several), ``endpoint`` and ``app_name``
  (``TBANK_ENDPOINT``, ``TBANK_APP_NAME``).
- ``finam``: ``secret`` (``FINAM_SECRET``, required), ``account_id`` (``FINAM_ACCOUNT_ID``, required), ``endpoint``
  (``FINAM_API_URL``).
- ``alor``: ``refresh_token`` (``ALOR_REFRESH_TOKEN``, required), ``portfolio`` (``ALOR_PORTFOLIO``, required when
  the account has several portfolios), ``exchange`` (``ALOR_EXCHANGE``, ``MOEX`` or ``SPBX``, default ``MOEX``),
  ``test`` (``ALOR_TEST``, ``true``: the test contour of the broker, with a test account).
- ``bybit``: ``api_key`` (``BYBIT_API_KEY``, required) and ``api_secret`` (``BYBIT_API_SECRET``, required), ``env``
  (``BYBIT_ENV``: ``"mainnet"`` is the default, ``"demo"`` is the demo account with virtual money, ``"testnet"``). The
  demo account has a key pair of its own, read from ``BYBIT_DEMO_API_KEY`` and ``BYBIT_DEMO_API_SECRET``. Also
  ``category`` (``BYBIT_CATEGORY``, only ``"linear"``: USDT perpetuals and futures), ``settle_coin``
  (``BYBIT_SETTLE_COIN``, ``"USDT"``), ``recv_window_ms`` and ``endpoint``.

``name`` labels the events (``<name>-md``, ``<name>-ud``) and defaults to the broker. Give each client of a
process its own name. ``limiter`` puts this client on the rate limit counter of another one (see
:class:`Limiter`). ``event_buffer`` is how many events wait for the reader before the connector applies the
rule of each subscription.

Raises :class:`~brokapi.Config` for a missing or wrong setting, :class:`~brokapi.Transport` when the broker cannot
be reached or refuses the token. BCS has no sandbox: the account is real. ALOR has a test contour (``test=True``), Bybit a demo account (``env="demo"``).

### `merge(*sources: EventStream | Client) -> AsyncIterator[Envelope]`

One stream out of the streams of several clients: ``async for envelope in brokapi.merge(a, b)``.

``Envelope.source`` still says whose each event is. It ends when all the clients are closed. Stop reading
it only when you are done with those clients: an event that was just fetched when you stop is lost.

### `new_cid() -> uuid.UUID`

A new client order id. The bot makes it, not the library, and writes it to its journal BEFORE ``place``.

### class `Client`

A connection to one broker account: market data, orders, the account, and the stream of events.

Make one with :func:`connect` (the connection to the broker is in this process) or with :func:`connect_gateway`
(the connection is in a gateway, which several bots share). It is the same class with the same methods either way.
Every method is a coroutine. A client is an ``async with`` context: it is closed when the block ends.

The rules of the core hold here as they do in Rust. Trading starts after :meth:`reconcile`. The ``cid`` of an
order is made by the bot (:func:`new_cid`). :class:`~brokapi.Unknown` is never repeated: ask :meth:`order_state`.
A command that you stop waiting for (a cancelled task, a timeout) still runs to its end in the library, so after
that, too, ask the broker what became of it.

#### `Client.close(self, deadline: Optional[float] = 5.0) -> list[ShutdownReport]`

Stops the connectors and ends the event stream. Safe to call twice.

``deadline`` is how many seconds a graceful stop may take, ``None`` stops at once. **Orders at the exchange are
not cancelled**: cancel them yourself before, if the bot should leave none. Returns the reports of the two
connectors (empty if the client was closed already).

A client of a gateway only ends its own connection: the gateway keeps running, and the subscriptions this bot
opened are let go by the gateway when the bot has been away for a while. ``deadline`` has no meaning there and the
list is empty.

#### `Client.caps(self) -> Caps`

What this connection can do: streams, ``replace``, stop orders, how a repeated ``cid`` is treated.

#### `Client.instrument(self, ticker: str, board: Optional[str] = None) -> InstrumentId`

The instrument for a ticker, in the form the broker wants.

``board`` is the class code of BCS, T-Bank and ALOR (default ``TQBR``), the MIC of Finam (default ``MISX``) or the
category of Bybit (``linear``, the only one). ``"SBER@TQBR"`` works as the ticker too, and so does
``"BTCUSDT@linear"``. BCS, Finam, ALOR and Bybit build the id from the text. T-Bank looks it up in its directory
(one request) and raises :class:`~brokapi.Rejected` with code ``not_found`` if there is none.

#### `Client.find(self, query: InstrumentQuery) -> list[Instrument]`

Looks in the directory of the broker.

#### `Client.get_instrument(self, instrument: InstrumentId) -> Instrument`

The directory entry of an instrument.

#### `Client.trading_params(self, instrument: InstrumentId) -> TradingParams`

The price step, the smallest quantity and the lot of an instrument.

Also teaches the connector the price step, so that a price off the step is refused at once with
:class:`~brokapi.Rejected` (``price_off_step``), before anything is sent.

#### `Client.trading_status(self, instrument: InstrumentId) -> TradingStatus`

Does the instrument trade now.

#### `Client.schedule(self, exchange: str, start: datetime, end: datetime) -> Schedule`

Trading sessions of an exchange in a period.

What ``exchange`` is depends on the broker. T-Bank: the name of an exchange (``"MOEX"``). BCS: the instrument,
``"TICKER@CLASSCODE"`` (``"SBER@TQBR"``). Finam: the symbol, ``"SBER@MISX"``. ALOR and Bybit have no schedule
(:class:`~brokapi.Unsupported`). A string the broker cannot use is :class:`~brokapi.Config`, nothing is sent.

``start`` and ``end`` are timezone-aware ``datetime`` objects (``datetime(..., tzinfo=timezone.utc)``): a naive
one raises ``ValueError``.

#### `Client.order_book(self, instrument: InstrumentId, depth: int = 10) -> OrderBook`

The order book now, ``depth`` levels on each side (one request).

#### `Client.last_trades(self, instrument: InstrumentId, limit: int = 100) -> list[MarketTrade]`

The last deals of the tape. How deep it goes depends on the broker.

#### `Client.candles(self, instrument: InstrumentId, timeframe: Timeframe, start: datetime, end: datetime) -> list[Candle]`

Bars of a period in one request. A broker accepts only a limited span per request: for a long history use
:meth:`candle_pages` or :meth:`load_candles`, which cut the period into windows.

``start`` and ``end`` must be timezone-aware ``datetime`` objects, for example
``datetime(2026, 1, 1, tzinfo=timezone.utc)``: a naive ``datetime`` raises ``ValueError`` (which zone would it
be?). A period longer than the broker allows is :class:`~brokapi.Rejected`.

#### `Client.candle_pages(self, instrument: InstrumentId, timeframe: Timeframe, start: datetime, end: datetime, *, window: Optional[timedelta] = None, max_rate_waits: Optional[int] = None, transport_retries: Optional[int] = None) -> CandlePager`

A long history, one window per step. ``window`` overrides the span the broker accepts per request.

#### `Client.load_candles(self, instrument: InstrumentId, timeframe: Timeframe, start: datetime, end: datetime) -> list[Candle]`

The whole history in memory. Suits a few thousand bars; for more write page by page.

#### `Client.subscribe_order_book(self, instrument: InstrumentId, *, depth: Optional[int] = None, backpressure: Optional[Backpressure] = None) -> int`

Order book events for an instrument. Returns the subscription id (``Envelope.sub``).

By default ``Backpressure.conflate()``: only the latest book matters, a slow reader just misses old ones.

#### `Client.subscribe_trades(self, instrument: InstrumentId, *, backpressure: Backpressure) -> int`

Deals of the tape. ``backpressure`` has no default: every deal is an event, so ``conflate()`` would lose
deals. Choose ``drop_newest(cap)`` if losing some is acceptable, ``lossless(cap)`` if not.

#### `Client.subscribe_quotes(self, instrument: InstrumentId, *, backpressure: Optional[Backpressure] = None) -> int`

Best bid and ask events.

#### `Client.subscribe_candles(self, instrument: InstrumentId, timeframe: Timeframe, *, backpressure: Optional[Backpressure] = None) -> int`

Candle events.

#### `Client.unsubscribe(self, sub: int) -> None`

Ends a market data subscription.

#### `Client.portfolio(self) -> Portfolio`

Money and holdings.

#### `Client.positions(self) -> list[Position]`

Holdings.

#### `Client.balances(self) -> list[Balance]`

Money by currency.

#### `Client.executions(self, start: datetime, end: datetime) -> list[Execution]`

Your fills in a period.

#### `Client.subscribe_order_state(self) -> int`

Events about your orders (``OrderSnapshot``). Always lossless. Subscribe BEFORE :meth:`reconcile`, so no
event between the snapshot of the reconcile and the subscription is lost.

Returns when the stream is open at the broker, so an order placed right after it cannot be missed. A stream
that does not open within three seconds raises :class:`~brokapi.Transport` and leaves nothing half open:
call again. Finam is the exception: after three seconds the call returns and the stream goes on opening in
the background.

#### `Client.subscribe_executions(self) -> int`

Events about your fills (``Execution``). Returns when the stream is open, like :meth:`subscribe_order_state`.

#### `Client.subscribe_portfolio(self, *, backpressure: Optional[Backpressure] = None) -> int`

Events about money and holdings.

#### `Client.reconcile(self, ledger: Sequence[uuid.UUID | str] = ()) -> ReconcileReport`

Compares your ledger with the account and opens the trading gate.

``ledger`` is the cids your bot believes are alive (an empty list for a new bot). Until a reconcile has
succeeded, ``place`` raises :class:`~brokapi.NotReconciled`. After :class:`~brokapi.Degraded` call it again.

#### `Client.place(self, *, cid: uuid.UUID, instrument: InstrumentId, side: Side, qty: Decimal | str | int, price: Decimal | str | int | None = None, order_type: OrderType = <OrderType.LIMIT: Limit>) -> OrderAck`

Places an order.

``cid`` is made by the bot (:func:`new_cid`) and belongs in its journal before this call. ``qty`` is in
units of the instrument (shares, currency units, contracts), never in lots: a broker that counts in lots
gets the conversion from the connector, and a quantity that is not a whole number of lots is refused with
:class:`~brokapi.Rejected` (``invalid_qty``) before anything is sent. ``qty`` and ``price`` are ``Decimal`` (a ``float`` is refused). A limit order needs a ``price``, else the call raises
:class:`~brokapi.Rejected` (``missing_price``).

Raises :class:`~brokapi.NotReconciled` before the first :meth:`reconcile`, :class:`~brokapi.Rejected` when the
broker or the connector says no (nothing was done), :class:`~brokapi.Unknown` when the outcome is not known:
do not repeat, call :meth:`order_state`.

#### `Client.cancel(self, cid: uuid.UUID) -> None`

Cancels an order. Raises :class:`~brokapi.Unknown` when the outcome is not known.

#### `Client.replace(self, *, cid: uuid.UUID, original_cid: uuid.UUID, qty: Decimal | str | int | None = None, price: Decimal | str | int | None = None) -> OrderAck`

Changes the price or quantity of an order. The new order has its own ``cid``.

Only where the broker can: ``(await client.caps()).ud.supports_replace``. Elsewhere it raises
:class:`~brokapi.Unsupported` and the order is not touched.

#### `Client.place_stop(self, *, cid: uuid.UUID, instrument: InstrumentId, side: Side, kind: StopOrderKind, qty: Decimal | str | int, stop_price: Decimal | str | int, limit_price: Decimal | str | int | None = None, expiry: Optional[StopExpiry] = None) -> OrderAck`

Puts a stop order: it waits at the broker until the price reaches ``stop_price``, then the broker puts an
exchange order (a market order, or a limit order at ``limit_price``). Until then it is not on the exchange.

The rules are those of :meth:`place`: ``cid`` is made by the bot (:func:`new_cid`) and written to its journal
first, it is sent at most once, ``qty`` is in units of the instrument and the gate opens after
:meth:`reconcile`. ``expiry`` defaults to ``StopExpiry.good_till_cancel()``. A ``STOP_LIMIT`` needs a
``limit_price``, else the call raises :class:`~brokapi.Rejected` before anything is sent.

Raises :class:`~brokapi.Unsupported` where the broker has no stop orders (``caps().ud.supports_stop_orders``),
:class:`~brokapi.Rejected` when the request or the broker says no, :class:`~brokapi.Unknown` when the outcome
is not known: do not repeat, look in :meth:`active_stop_orders`. A stop order that has triggered is a plain
order of the broker, and the library does not link it to the stop.

#### `Client.cancel_stop(self, stop: StopOrderRef | uuid.UUID) -> None`

Cancels a stop order that has not triggered.

Name it with ``StopOrderRef.cid(cid)`` (a ``uuid.UUID`` alone means the same) or ``StopOrderRef.broker(id)``.
A ``cid`` is known from what this run has seen: after a restart T-Bank cannot find it, so use the id of the
broker (``OrderAck.broker_id``, ``StopOrderSnapshot.broker_id``). A repeated cancel creates nothing. Raises
:class:`~brokapi.Rejected` when the stop is unknown or has triggered, :class:`~brokapi.Unknown` when the
answer is missing: look in :meth:`active_stop_orders` before you decide.

#### `Client.active_stop_orders(self) -> list[StopOrderSnapshot]`

The stop orders that wait for their trigger, with their parameters and :attr:`StopOrderSnapshot.state`.

Not in :meth:`active_orders` and not in :meth:`reconcile`. This list is the only way to learn that a stop
triggered: there is no event for it.

#### `Client.subscribe_stop_orders(self) -> int`

A stream of stop order statuses. **No broker has one** (BCS does not document it, T-Bank's API has none,
Finam has none, ALOR's stream has no event of the common interface): this raises
:class:`~brokapi.Unsupported` for all of them. Poll :meth:`active_stop_orders`; the library does not poll for
you.

#### `Client.raw(self, method: str, args: Optional[Mapping[str, Any]] = None, *, command: bool = False) -> Any`

Calls any method of the broker's API, outside the common interface. Returns the reply as ``dict``.

The call goes through the same token, address and rate limit counter as the client: the quota is taken by the
method name, and a call that finds none left is :class:`~brokapi.RateLimited` and is not sent.
Errors of the broker are the usual ones. **What ``raw`` does not do:** it does not know the orders of the
client. An order placed through it is not in the events, not in :meth:`reconcile` and not in the order gate,
and the library checks nothing in it (a price step, a lot, a stop-limit price). Place orders with
:meth:`place`. Use ``raw`` for what the common interface has no word for: reports, coupons and dividends,
favourites, new methods of the broker.

``command=False`` is for reading: a network failure is :class:`~brokapi.Transport`, and the call may be
repeated. ``command=True`` is for anything that **changes the account**: a timeout or a lost reply is
:class:`~brokapi.Unknown` and the call is never repeated, and it runs to its end even if you stop waiting. When in
doubt whether a method changes the account, pass ``command=True``.

- **T-Bank, Finam** (gRPC): ``method`` is the name of the method in the description of the API
  (``"GetLastPrices"``, ``"Clock"``), or ``"Service/Method"`` where two services have the same name.
  ``args`` is the request message as JSON with the field names of the ``.proto``, 64 bit integers and money
  as text. The reply is the message as a ``dict`` in the same form. The account is not added: put it in the
  request. Streaming methods raise :class:`~brokapi.Unsupported` (``raw_streaming``).
- **BCS** (REST): ``method`` is a name from the rate limit file of the library (``"GetQuotes"``,
  ``"GetPortfolio"``, ``"GetOperationHistory"``, ...) or ``"GET /path"`` / ``"POST /path"`` for any other
  method. ``args`` has ``query`` (URL parameters), ``body`` (the JSON of a ``POST``) and, for
  ``GetOrderStatus``, ``path`` (``{"id": ...}``). A name without a route in the rate limit file spends the
  default quota.
- **ALOR** (REST): ``method`` is ``"VERB /path"`` with the verb ``GET``, ``POST``, ``PUT`` or ``DELETE`` and the
  path of the API description (``"GET /md/v2/time"``, ``"GET /md/v2/Clients/MOEX/D12345/summary"``). ``args``
  has ``query`` (URL parameters), ``body`` (the JSON of a ``POST`` or ``PUT``) and ``request_id`` (the
  ``X-REQID`` of an order command: the broker answers a repeated id with a copy of its first answer; a new one is
  made if you give none). Every call spends the default quota.

Raises :class:`~brokapi.Config` for a method the broker does not have or arguments that do not fit it
(nothing was sent and no quota was spent).

#### `Client.order_state(self, cid: uuid.UUID) -> OrderSnapshot`

What the broker says about an order. The way out of :class:`~brokapi.Unknown`.

#### `Client.active_orders(self) -> list[OrderSnapshot]`

Orders that are alive at the broker.

### class `EventStream`

The events of one client, in order: ``async for envelope in client.events``.

    One stream per client, for market data and for the account together. :attr:`Envelope.source` says which
    connector sent an event, :attr:`Envelope.sub` which subscription. The stream ends when the client is closed:
the events sent before :meth:`Client.close` are still delivered, then the iteration stops.

    Read it as fast as you can. Do slow work (a database, a model) in another task and hand it the events through
    your own queue: when this stream is not read, the connector applies the rule of each subscription, and a lossless
    one closes trading after it overflows (see ``Overflow``).

#### `EventStream.get(self, timeout: Optional[float] = None) -> Envelope`

The next event. Raises ``asyncio.TimeoutError`` after ``timeout`` seconds, ``EOFError`` at the end.

#### `EventStream.drain(self, limit: int = 10000) -> list[Envelope]`

The events that are already waiting, without waiting for more.

### class `CandlePager`

A long candle history, one window per step: ``async for bars in client.candle_pages(...)``.

A window is as long as the broker accepts, reads wait out ``RateLimited`` and repeat after a network error
(reading is safe to repeat). Bars come in time order, each one once. A page can be empty (a weekend): that is
not the end. After an error, :attr:`resume_from` is where to start again, and nothing before it is read twice.

### class `Limiter`

The rate limit counter of one broker area.

A broker counts its limits per token or per user, so everything that talks to the broker with that token must
share one counter: two counters together exceed the quota and the broker starts to refuse. Every
:func:`connect` makes its own. To put several clients on one counter, pass ``limiter=first.limiter`` to the
next ones. A limiter is shared only inside one broker.

## brokapi.testing: imitations of the brokers

### class `MockBroker`

A running imitation of one broker. Use it as ``async with``: it stops when the block ends.

#### `MockBroker.connect(self, **overrides: Any) -> Client`

A client connected to the imitation. ``overrides`` are the arguments of :func:`brokapi.connect`
(``name``, ``limiter``, ``account_id`` ...). The client is closed when the imitation is.

#### `MockBroker.start_gateway(self, name: str = 'mock-gateway') -> MockGateway`

Starts a gateway on this imitation, ready for bots. The gateway stops when the imitation does.

#### `MockBroker.fill(self, cid: uuid.UUID, *, qty: Decimal | str | int | None = None, price: Decimal | str | int | None = None, account: Optional[str] = None) -> None`

Executes the order ``cid``: it becomes filled, and the fill event goes to the account.

BCS, Finam, ALOR and Bybit need ``qty`` and ``price`` (``qty`` less than the order makes a partial fill on BCS). T-Bank
fills the whole order at its price and ignores both. ``account`` picks the account of T-Bank
(``acc-a`` or ``acc-b``) or Finam (``ACC1`` or ``ACC2``).

#### `MockBroker.lose_stop_answers(self, lose: bool = True) -> None`

While on, the broker takes every stop order and the answer is lost.

The stop order is at the broker, and ``place_stop`` ends with :class:`~brokapi.Unknown`. This is the failure
a bot must survive without placing a second stop: look the stop up (``active_stop_orders``) and never send the
same ``cid`` again. ``lose_stop_answers(False)`` brings the usual answers back.

#### `MockBroker.close(self) -> None`

Closes the clients that were connected through it, the gateways started on it, then stops the server.

### class `MockGateway`

A gateway that runs in this process on an imitation of a broker: the way to test a bot that talks to a gateway.

Make one with :meth:`MockBroker.start_gateway`. ``await gateway.connect(bot="my-bot")`` gives a
:class:`~brokapi.Client` of the gateway, the one :func:`brokapi.connect_gateway` makes. :meth:`restart` is the failure
a bot behind a gateway must survive: the gateway process dies and a new one starts. It stops with the imitation it
runs on.

#### `MockGateway.connect(self, bot: str = 'test-bot', **options: Any) -> Client`

A client of the gateway. ``options`` are the arguments of :func:`brokapi.connect_gateway`
(``own_orders_only``, ``event_buffer`` ...). It is closed when the gateway is.

#### `MockGateway.restart(self) -> None`

Ends the gateway as a crash would and starts a new one in the same directory.

The clients stay connected: they find the new process, and are told of the loss with a :class:`~brokapi.Gap`
(``new_epoch``). The orders at the imitation are untouched, and the new gateway compares its journal with them
before it takes a command.

#### `MockGateway.close(self) -> None`

Closes the clients that were connected through it, then stops the gateway.

### `mock_alor(*, synthetic_candles: bool = False) -> MockBroker`

An imitation of ALOR (HTTP and WebSocket). One portfolio, ``D00001``, and one exchange, ``MOEX``.

``synthetic_candles`` has no effect on ALOR: it always answers three bars from the start of the period.

### `mock_bcs(*, synthetic_candles: bool = False) -> MockBroker`

An imitation of BCS (HTTP and WebSocket). Its token can trade.

``synthetic_candles=True`` makes ``candles`` answer a series for the period asked, one bar per step of the
timeframe, and refuse a period longer than 1440 bars as BCS does. Off, it answers three fixed bars.

### `mock_bybit(*, synthetic_candles: bool = False) -> MockBroker`

An imitation of Bybit (HTTP and WebSocket), category ``linear``. The test instrument is ``MOCKUSDT`` (a perpetual,
the quantity step and the smallest order 10, the price step 0.01); ``BTCUSDT`` has the figures of the real contract.

``synthetic_candles`` has no effect on Bybit: it always answers a series for the period asked, one bar per step
of the timeframe.

### `mock_finam(*, synthetic_candles: bool = False) -> MockBroker`

An imitation of Finam (gRPC). Two accounts: ``ACC1`` (the default) and ``ACC2``.

``synthetic_candles=True``: a series for the period asked, one bar per step of the timeframe.

### `mock_tbank(*, synthetic_candles: bool = False) -> MockBroker`

An imitation of T-Bank (gRPC). Two accounts: ``acc-a`` (the default) and ``acc-b``.

``synthetic_candles=True``: a series for the period asked, refused when longer than T-Invest allows.

## brokapi.record: the event log

### class `EnvelopeReader`

Reads the envelopes of a log back, in the order they were written: ``for envelope in EnvelopeReader(path)``.

Opening the file reads and checks the header: a file that is not a log, an empty one, or one written by a newer
version raises :class:`~brokapi.LogError` at once. After the first bad line the iteration raises it and ends: what
follows a bad line cannot be trusted to be in order.

### class `EnvelopeWriter`

Appends envelopes to a new log file, one line each. Use it as ``with``: the file is flushed and closed at the end.

The file is created and the header written when the writer is made. An existing file is an error
(``LogError`` of kind ``io``) unless ``overwrite=True``: a log is a record of one run, and a second run must not
silently replace the first. Writes are buffered: :meth:`flush` or :meth:`close` after the last one, so the tail
is not left in a buffer when the program ends. After a failed write do not append to the same file, start a new
one.

#### `EnvelopeWriter.write(self, envelope: Envelope) -> None`

Appends one envelope as one line. Raises :class:`~brokapi.LogError` when the write fails.

#### `EnvelopeWriter.flush(self) -> None`

Flushes the buffer to the file.

#### `EnvelopeWriter.close(self) -> None`

Flushes and closes the file. Safe to call twice.

### class `ReplaySpeed`

How fast :func:`replay` plays. Build it with :meth:`instant` or :meth:`paced`.

Fields:

- `factor`: `Optional[float]`
- `max_gap`: `timedelta`

#### `ReplaySpeed.instant(cls) -> "'ReplaySpeed'"`

As fast as the consumer reads. For backtests and tests. The default.

#### `ReplaySpeed.paced(cls, factor: float = 1.0, max_gap: timedelta = datetime.timedelta(seconds=1)) -> "'ReplaySpeed'"`

At the recorded pace divided by ``factor``. The pace is the difference of ``rx_ts`` of neighbouring
envelopes, a pause is never longer than ``max_gap``.

### `record_events(events: AsyncIterator[Envelope], path: _PathLike, *, overwrite: bool = False) -> AsyncIterator[Envelope]`

Passes the events of a stream on and writes each one to a log first:
``async for envelope in record_events(client.events, "run.jsonl")``.

It records what your code reads, which is the stream as the bot saw it. Each envelope is flushed to the file as
it is written, so a program that stops in the middle of a run (a crash, ``break``) leaves a log that is complete up
to the last envelope it read. The file is closed when the stream ends or the generator is closed
(``contextlib.aclosing``), not at the ``break`` itself.

A :class:`~brokapi.Gap` (the stream of a client made by :func:`~brokapi.connect_gateway`) is passed on and not written:
it is not an event of a connector, and the file has the format of those.

### `replay(path: _PathLike, speed: Optional[ReplaySpeed] = None, *, buffer: int = 256) -> AsyncIterator[Envelope]`

Plays a log as a stream of envelopes, in order, like ``client.events``: ``async for envelope in replay(path)``.

The envelopes keep their ``source`` and ``sub``, so the code that reads them routes them as it did live. The
difference from a live connector is the one that matters for research: a replay **waits** for the reader, so a
slow reader slows it down and no event is lost. A live connector cannot wait and applies the loss policy of the
subscription instead. Code that handles ``Overflow`` therefore needs its own test: a replay never produces one.

``buffer`` is how many envelopes are read ahead. A bad line raises :class:`~brokapi.LogError` after the envelopes
before it. The file is opened (and its header checked) at the call, not at the first ``await``.

## brokapi.errors

### exception `BrokapiError`

Base class of every error that brokapi raises about a broker or a command.

### exception `Config`

The setup is wrong: a missing token, a bad value, an input that cannot work with this broker.

Nothing was sent. The call fails the same way until the setting is fixed.

### exception `Degraded`

The trading gate is closed because the connector lost events it could not afford to lose.

A lossless subscription overflowed (an :class:`~brokapi.Overflow` event was sent). Read the event stream to the
end, then call ``reconcile()``: a successful one opens the gate again. Nothing was sent.

### enum `ErrorClass`

The coarse answer to "may I try this call again" (``Error::class`` in Rust).

Members: `RETRYABLE`, `FATAL`, `UNKNOWN`

### exception `LogError`

The event log could not be written or read (``LogError`` of the Rust library).

``kind`` says why: ``io`` (the file or the disk failed: check the path and the space), ``format`` (a line is not
what the format says, the file is damaged or is not a log: ``line`` is its number, the header is line 1) or
``schema`` (the file was written by a newer version: update the package to read it). A reader never skips a bad
line silently: it stops with this error.

### exception `NotReconciled`

The trading gate is closed: ``reconcile()`` has not run successfully yet.

Call ``reconcile`` with your ledger (an empty list if you have none), then repeat the command. Nothing was sent.

### exception `RateLimited`

A rate limit stopped the call: the local counter of the connector or the broker's own answer.

Wait ``retry_after``, then repeat. A command refused by the local counter was not sent. The library does not
wait and repeat for you.

### exception `Rejected`

The broker (or the connector, before sending) said no to this request, and nothing was done.

The request is wrong or not allowed: a price outside the band, no money, an unknown order. Change the request.
Do not repeat it as it is. ``code`` is a short label set by the connector (``validation``, ``missing_price``,
``not_found``, ``price_off_step``, ...), safe to compare with ``==``. Do not parse ``message``.

### exception `ShuttingDown`

The connector is shutting down and takes no new trading commands.

Final for this client. Orders already at the exchange are not touched by a shutdown. Connect again to trade.

### exception `Transport`

The network or the broker's endpoint failed in a way that is not an answer (connect, TLS, timeout, a cut stream).

For a reading call, repeat it later. A command that might have reached the broker raises :class:`Unknown`
instead, so this error on a command means the connector has no sign that it did.

### exception `Unknown`

A command was sent and nothing says whether the broker did it: a timeout, a cut connection, an HTTP 5xx.

The order may exist, may be cancelled, or may never have arrived. The library never repeats the command and
neither should you:

1. Do not send the command again.
2. Ask the broker: ``order_state(cid)``, ``active_orders()`` or the executions.
3. Only when the broker has no such order, place a new one under a NEW ``cid``.

### exception `Unsupported`

This connector or this broker does not have the capability, so the call was not made.

Raised at once, without a network call. ``capability`` is a short name of what is missing. The library does not
emulate a missing stream by polling. ``client.caps`` says in advance what a connector can do.

## brokapi.numbers

### enum `RoundMode`

Where to go when the value is between two steps.

Members: `DOWN`, `UP`, `NEAREST`

### `is_on_step(value: Decimal, step: Decimal) -> bool`

Is ``value`` a whole number of ``step``? A step of zero or below means "no grid" and answers ``True``.

### `round_to_step(value: Decimal, step: Decimal, mode: RoundMode = <RoundMode.DOWN: Down>) -> Decimal`

Rounds ``value`` to a multiple of ``step``. Exact: a value that already is a multiple is returned as it is.

## brokapi.models: data

### class `Backpressure`

What a subscription does when the reader is slow. Build it with the class methods.

Fields:

- `kind`: `str`
- `cap`: `int`

#### `Backpressure.conflate(cls) -> "'Backpressure'"`

Keep only the latest value. For books, quotes and candles, where the old value is useless.

#### `Backpressure.lossless(cls, cap: int) -> "'Backpressure'"`

Never drop. At ``cap`` events in the buffer an :class:`Overflow` is sent and trading closes.

#### `Backpressure.drop_newest(cls, cap: int) -> "'Backpressure'"`

Keep up to ``cap`` events and drop the newest beyond that (and count them).

### class `Balance`

Money of one currency.

Fields:

- `currency`: `str`
- `available`: `Decimal`
- `blocked`: `Decimal`
- `ts`: `Timestamps`

### class `BalancesUpdate`

The balances of the account changed.

Fields:

- `balances`: `list[Balance]`

### class `BookLevel`

One price level of an order book.

Fields:

- `price`: `Decimal`
- `qty`: `Decimal`

### class `Caps`

What a connection can do (``client.caps``).

Fields:

- `md`: `MarketDataCaps`
- `ud`: `UserDataCaps`

### class `Candle`

One bar.

Fields:

- `instrument`: `InstrumentId`
- `timeframe`: `Timeframe`
- `open`: `Decimal`
- `high`: `Decimal`
- `low`: `Decimal`
- `close`: `Decimal`
- `volume`: `Decimal`
- `open_ts`: `datetime`
- `close_ts`: `datetime`
- `ts`: `Timestamps`

### class `Envelope`

One event with its address.

Fields:

- `source`: `str`
- `sub`: `Optional[int]`
- `ts`: `Timestamps`
- `payload`: `Event`
- `schema`: `int`
- `_text`: `Optional[str]`

### class `Execution`

A fill of one of your orders.

Fields:

- `broker_order_id`: `str`
- `instrument`: `InstrumentId`
- `side`: `Side`
- `price`: `Decimal`
- `qty`: `Decimal`
- `commission`: `Decimal`
- `trade_id`: `str`
- `ts`: `Timestamps`
- `cid`: `Optional[uuid.UUID]`

### class `Gap`

Events were lost between the gateway and this client: the gateway no longer has what came after the last event the
client was given. Only a client made by :func:`~brokapi.connect_gateway` gets it (``source`` is ``<gateway>-gateway``).

It is the gateway's counterpart of :class:`Overflow` and :class:`Reconnected`, and the rule is the same: read the state
from the broker (``active_orders``, ``order_state``, ``executions``) and call ``reconcile``. Trading is closed
(:class:`~brokapi.Degraded`) until you have. The stream goes on from the present moment, nothing needs to be reopened.

``reason`` is ``"too_old"`` (the gateway keeps a limited number of recent events and the client was away longer),
``"new_epoch"`` (the gateway was restarted: it is a new process, and the numbers of events begin again) or ``"ahead"``
(the client's number is ahead of the gateway's, which does not happen unless the gateway's data was replaced).
``requested_after`` is the number the client asked to continue from, ``first_available`` the oldest event the
gateway still has.

Fields:

- `reason`: `str`
- `requested_after`: `int`
- `first_available`: `int`

### class `Instrument`

A directory entry.

Fields:

- `id`: `InstrumentId`
- `name`: `str`
- `kind`: `InstrumentKind`
- `currency`: `str`
- `exchange`: `str`
- `isin`: `Optional[str]`

### class `InstrumentId`

An instrument: the common key and the broker's own id.

Fields:

- `key`: `InstrumentKey`
- `native`: `NativeId`

### class `InstrumentKey`

The same for every broker: a symbol on a venue.

Fields:

- `symbol`: `str`
- `mic`: `str`

### enum `InstrumentKind`

Kind of an instrument.

Members: `STOCK`, `BOND`, `OPTION`, `FUTURE`, `PERPETUAL`, `ETF`, `MUTUAL_FUND`, `CURRENCY`, `OTHER`

### class `InstrumentQuery`

What to look for in the directory of the broker. Build it with the class methods.

Fields:

- `_json`: `Any`

#### `InstrumentQuery.by_ticker(cls, ticker: str, exchange: Optional[str] = None) -> "'InstrumentQuery'"`

A ticker, optionally on one exchange or board.

#### `InstrumentQuery.by_isin(cls, isin: str) -> "'InstrumentQuery'"`

An ISIN.

#### `InstrumentQuery.by_kind(cls, kind: InstrumentKind) -> "'InstrumentQuery'"`

All instruments of one kind (can be a long list).

### class `MarketDataCaps`

What the market data side can do.

Fields:

- `max_order_book_depth`: `int`
- `incremental_order_book`: `bool`
- `streaming`: `StreamingCaps`

### class `MarketTrade`

A deal on the exchange tape.

Fields:

- `instrument`: `InstrumentId`
- `price`: `Decimal`
- `qty`: `Decimal`
- `side`: `Side`
- `ts`: `Timestamps`
- `trade_id`: `Optional[str]`

### class `NativeId`

How the broker itself names an instrument. Opaque: take it from ``client.instrument`` and pass it back.

Fields:

- `kind`: `str`
- `values`: `tuple[tuple[str, str], ...]`

### class `OrderAck`

The broker accepted an order.

Fields:

- `cid`: `uuid.UUID`
- `broker_id`: `str`
- `ts`: `Timestamps`

### class `OrderBookIncrement`

A change to apply to the book. A level with a zero ``qty`` is removed. ``seq`` grows by one.

Fields:

- `instrument`: `InstrumentId`
- `seq`: `int`
- `bids`: `list[BookLevel]`
- `asks`: `list[BookLevel]`
- `ts`: `Timestamps`

### class `OrderBookSnapshot`

The whole book (the levels asked for). Replaces what you had.

Fields:

- `instrument`: `InstrumentId`
- `bids`: `list[BookLevel]`
- `asks`: `list[BookLevel]`
- `ts`: `Timestamps`

### class `OrderSnapshot`

An order as the broker has it. Also the event for a change of an order.

Fields:

- `cid`: `uuid.UUID`
- `instrument`: `InstrumentId`
- `state`: `OrderState`
- `side`: `Side`
- `order_type`: `OrderType`
- `qty`: `Decimal`
- `filled`: `Decimal`
- `ts`: `Timestamps`
- `broker_id`: `Optional[str]`
- `price`: `Optional[Decimal]`

### class `OrderState`

Where an order is. The fields that do not apply to ``kind`` are ``None``.

Fields:

- `kind`: `OrderStateKind`
- `cid`: `uuid.UUID`
- `broker_id`: `Optional[str]`
- `filled`: `Optional[Decimal]`
- `left`: `Optional[Decimal]`
- `reason`: `Optional[str]`

### enum `OrderStateKind`

Where an order is in its life. See the orders page of the book for the diagram.

Members: `IN_FLIGHT`, `UNKNOWN`, `ACKED`, `PARTIALLY_FILLED`, `FILLED`, `CANCELLED`, `REJECTED`

### enum `OrderType`

Limit order (needs a price) or market order.

Members: `LIMIT`, `MARKET`

### class `Overflow`

The connector lost events of a lossless subscription since ``lost_from``.

Trading is closed (:class:`~brokapi.Degraded`) until you read the stream to the end and call ``reconcile``.

Fields:

- `sub`: `int`
- `lost_from`: `datetime`

### class `Reconnected`

The stream of your orders or of your fills was cut and opened again: events of the gap never reached you.

``sub`` is the subscription whose stream was cut (one event for the order state subscription and one for the
executions subscription). ``gap_from`` is when the connector noticed the cut: the hole starts at or before it.

Trading is closed (:class:`~brokapi.Degraded`) until you read the stream to the end, read the state from the broker
(``active_orders``, ``order_state``, ``executions`` from a little before ``gap_from``) and call ``reconcile``.

Fields:

- `sub`: `int`
- `gap_from`: `datetime`

### class `PlaceIdempotency`

What the broker does with a second ``place`` that has the same ``cid``.

Fields:

- `kind`: `str`
- `window`: `Optional[timedelta]`

### class `Portfolio`

Money and holdings of the account.

Fields:

- `balances`: `list[Balance]`
- `positions`: `list[Position]`
- `ts`: `Timestamps`

### class `Position`

A holding.

Fields:

- `instrument`: `InstrumentId`
- `qty`: `Decimal`
- `ts`: `Timestamps`
- `avg_price`: `Optional[Decimal]`
- `current_price`: `Optional[Decimal]`

### class `PositionsUpdate`

The positions of the account changed.

Fields:

- `positions`: `list[Position]`

### class `Quote`

Best bid and best ask.

Fields:

- `instrument`: `InstrumentId`
- `ts`: `Timestamps`
- `bid`: `Optional[BookLevel]`
- `ask`: `Optional[BookLevel]`

### class `ReconcileReport`

What ``reconcile`` found.

Fields:

- `confirmed`: `list[OrderSnapshot]`
- `closed_offline`: `list[uuid.UUID]`
- `foreign`: `list[OrderSnapshot]`

### class `Resync`

The order book of this instrument must be taken again (take a snapshot, then follow increments).

Fields:

- `sub`: `int`
- `instrument`: `InstrumentId`
- `reason`: `ResyncReason`

### enum `ResyncReason`

Why an order book must be taken again.

Members: `SEQUENCE_GAP`, `SUBSCRIPTION_RESET`, `MANUAL`

### class `Schedule`

Trading sessions of an exchange.

Fields:

- `exchange`: `str`
- `sessions`: `list[SessionInterval]`
- `ts`: `Timestamps`

### class `SessionInterval`

One trading session.

Fields:

- `start`: `datetime`
- `end`: `datetime`
- `kind`: `SessionKind`

### enum `SessionKind`

Kind of a trading session.

Members: `PRE_MARKET`, `MAIN`, `POST_MARKET`, `AUCTION`, `OTHER`

### class `ShutdownReport`

What a stop of one connector did.

Fields:

- `unsubscribed`: `int`
- `unsubscribe_failed`: `int`
- `inflight_abandoned`: `list[uuid.UUID]`

### enum `Side`

Buy or sell.

Members: `BUY`, `SELL`

### class `StopExpiry`

How long a stop order lives. Build it with the class methods.

Fields:

- `kind`: `str`
- `until`: `Optional[datetime]`

#### `StopExpiry.good_till_cancel(cls) -> "'StopExpiry'"`

Until cancelled.

#### `StopExpiry.end_of_day(cls) -> "'StopExpiry'"`

Until the end of the trading day (not every broker has it).

#### `StopExpiry.good_till_date(cls, moment: datetime) -> "'StopExpiry'"`

Until the given moment (an aware ``datetime``). Not every broker has it for every kind.

### enum `StopOrderKind`

What a stop order is for. With the side it fixes the direction of the trigger.

Members: `STOP_LOSS`, `TAKE_PROFIT`, `STOP_LIMIT`

#### `StopOrderKind.triggers_on_rise(self, side: "'Side'") -> bool`

``True`` when the stop triggers on a RISING price for this side. Falling otherwise.

### class `StopOrderRef`

Which stop order a command is about. Build it with :meth:`cid` or :meth:`broker`.

Fields:

- `kind`: `str`
- `value`: `str`

#### `StopOrderRef.cid(cls, cid: uuid.UUID | str) -> "'StopOrderRef'"`

The ``cid`` given to ``place_stop``. The connector knows it only for stops it saw in this run: after a
restart T-Bank refuses it with ``Rejected`` (``unknown_stop_order``), use :meth:`broker` then.

#### `StopOrderRef.broker(cls, broker_id: str) -> "'StopOrderRef'"`

The id of the broker, from ``OrderAck.broker_id`` or ``StopOrderSnapshot.broker_id``.

### class `StopOrderSnapshot`

A stop order as the broker shows it (``active_stop_orders``).

Fields:

- `broker_id`: `str`
- `instrument`: `InstrumentId`
- `side`: `Side`
- `kind`: `StopOrderKind`
- `qty`: `Decimal`
- `stop_price`: `Decimal`
- `expiry`: `StopExpiry`
- `state`: `StopOrderState`
- `ts`: `Timestamps`
- `cid`: `Optional[uuid.UUID]`
- `limit_price`: `Optional[Decimal]`

### class `StopOrderState`

Where a stop order is: :attr:`kind`, and the broker's own text when it is ``OTHER``.

Fields:

- `kind`: `StopOrderStateKind`
- `text`: `Optional[str]`

### enum `StopOrderStateKind`

Where a stop order is in its life.

Members: `ACTIVE`, `TRIGGERED`, `CANCELLED`, `EXPIRED`, `OTHER`

### class `StreamingCaps`

Which market data streams the broker has.

Fields:

- `order_book`: `bool`
- `market_trades`: `bool`
- `quotes`: `bool`
- `candles`: `bool`

### enum `Timeframe`

Candle length.

Members: `M1`, `M5`, `M10`, `M15`, `M30`, `H1`, `H4`, `D1`, `W1`, `MN`

### class `Timestamps`

When something happened, in nanoseconds since the Unix epoch (exact).

Fields:

- `rx_ns`: `int`
- `broker_ns`: `Optional[int]`

### enum `TradingAvailability`

Whether an instrument trades right now.

Members: `OPEN`, `CLOSED`, `BREAK`, `UNKNOWN`

### class `TradingParams`

What an order must respect: the step of the price, the smallest quantity, the lot.

Fields:

- `instrument`: `InstrumentId`
- `lot_size`: `Decimal`
- `min_qty`: `Decimal`
- `price_step`: `Decimal`
- `price_step_value`: `Decimal`
- `currency`: `str`

### class `TradingStatus`

Whether an instrument trades now.

Fields:

- `instrument`: `InstrumentId`
- `availability`: `TradingAvailability`
- `reason`: `Optional[str]`
- `ts`: `Optional[datetime]`

### class `UnknownEvent`

An event of a kind this version of the Python package does not know (the core is newer than the package).

The stream does not break on it: ``kind`` is the name the core gave it and ``data`` its content as it came
(JSON values, money as text). Update the package to get a typed class. An event of a kind you do not know may be
about the loss of data (the way :class:`Overflow` and :class:`Reconnected` are): when in doubt, ``reconcile`` before
trading further.

Fields:

- `kind`: `str`
- `data`: `Any`

### class `UserDataCaps`

What the account side can do.

Fields:

- `place_idempotency`: `PlaceIdempotency`
- `ws_rpc`: `bool`
- `ordered_command_channel`: `bool`
- `supports_replace`: `bool`
- `supports_stop_orders`: `bool`
- `streams_executions`: `bool`
- `sandbox`: `bool`
