# polyoxide-binance: `usdm`
Rust client for Binance USDⓈ-M futures public market data (`fapi.binance.com`), the `usdm`
module of `polyoxide-binance`: contracts, tickers, the premium index and funding, klines, open
interest, aggregate trades and order book snapshots. With the `usdm-ws` feature it also streams
the market streams on `fstream.binance.com`, described under Streaming below. No credentials are
needed.
Every request is charged against a `WeightBudget`, because Binance limits each
IP by request *weight*, which varies with the route and its parameters, rather
than by request count. The budget follows the server's own count from the
`X-MBX-USED-WEIGHT-1M` header, and a `429` or `418` holds every request until
the server's wait is over.
More information about this module can be found in the [crate documentation](https://docs.rs/polyoxide-binance/latest/polyoxide_binance/usdm/).
## Installation
```toml
[dependencies]
polyoxide-binance = "0.48"
```
`usdm` is on by default. Add `usdm-ws` for the market streams; it turns on `usdm`.
## Usage
```no_run
use polyoxide_binance::usdm::{
types::{Interval, Symbol},
Usdm,
};
# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let usdm = Usdm::new()?;
let btc = Symbol::new("BTCUSDT")?;
let index = usdm.market().premium_index(&btc).send().await?;
println!("mark {} index {}", index.mark_price, index.index_price);
let candles = usdm
.market()
.klines(&btc, Interval::H1)
.limit(24)
.send()
.await?;
println!("{} hourly candles", candles.len());
# Ok(())
# }
```
Clients in one process should share one budget, since Binance counts weight
per IP:
```no_run
use polyoxide_binance::usdm::{Usdm, WeightBudget};
# fn example() -> Result<(), polyoxide_binance::usdm::UsdmError> {
let budget = WeightBudget::new();
let a = Usdm::builder().weight_budget(budget.clone()).build()?;
let b = Usdm::builder().weight_budget(budget).build()?;
# Ok(())
# }
```
Each client may have `max_concurrent` requests in flight (default 4), and the budget's
reserve absorbs in-flight weight only while it stays under 240, so two clients that send
weighted routes should each set `max_concurrent(2)` (2 × 2 × 40 = 160). A client that sends
only the funding routes carries no weight and can keep the default.
## Through the venue-neutral traits
`Usdm` implements `polyoxide-venue`'s `MarketData`, `Instruments`, `Trades`,
`Candles` and `Funding`, keyed `binance.usdm:<SYMBOL>` (`usdm::key`), so code
written against those traits reads USDⓈ-M as it reads any other venue. What
only Binance publishes rides in `usdm::venue`'s `<Record>Ext` types.
A `FundingRate` carries its `interval` from `fundingInfo`, 8 hours for a symbol the list
does not carry, so each funding call sends that request too, on the funding bucket. That bucket
admits 450 requests per 5 minutes, one at a time about 0.67 s apart: `funding_rate` spends one
slot (its `premiumIndex` is weighted, not funding, so only `fundingInfo` counts), and
`funding_history` two (`fundingRate` and `fundingInfo`). Funding calls across N symbols
therefore take about N x 0.67 s (`funding_rate`) or N x 1.3 s (`funding_history`) per IP.
The `usdm::venue` module's documentation has an example.
## Streaming
With the `usdm-ws` feature, the market streams arrive over one connection per routed path
(`/market` for trades, klines, mark prices and tickers; `/public` for depth and book
tickers). `UsdmWsBuilder` keeps each connection alive, replaces a dead one, rotates each
before Binance's 24-hour cutoff, replays its streams at Binance's pace, and reports each
outage: while the client runs, every `Event::Disconnected { path }` is followed by
`Event::Reconnected { path }`, after which anything built from that path's streams should
be rebuilt. Enable it with `polyoxide-binance = { version = "0.48", features = ["usdm-ws"] }`.
```text
use futures_util::StreamExt;
use polyoxide_binance::usdm::{types::Symbol, ws::{Event, StreamName, UsdmWsBuilder, UsdmWsError}};
let btc = Symbol::new("BTCUSDT")?;
let mut feed = UsdmWsBuilder::new()
.streams([StreamName::MarkPrice(btc.clone()), StreamName::BookTicker(btc)])
.connect()
.await?;
while let Some(event) = feed.next().await {
let event = match event {
Ok(event) => event,
// A frame that did not decode is skipped; anything else ends the stream.
Err(UsdmWsError::Frame { .. }) => continue,
Err(err) => return Err(err.into()),
};
match event {
Event::Update(update) => println!("{}", serde_json::to_string(&update)?),
Event::Disconnected { path, reason } => eprintln!("{path} down: {reason}"),
Event::Reconnected { path } => eprintln!("{path} back"),
_ => {}
}
}
```