pub trait InstrumentSource: Send + Sync {
// Required methods
fn find<'life0, 'async_trait>(
&'life0 self,
q: InstrumentQuery,
) -> Pin<Box<dyn Future<Output = Result<Vec<Instrument>, Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn get<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<Instrument, Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn trading_params<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<TradingParams, Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn trading_status<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<TradingStatus, Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn schedule<'life0, 'life1, 'async_trait>(
&'life0 self,
exchange: &'life1 str,
range: TimeRange,
) -> Pin<Box<dyn Future<Output = Result<Schedule, Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
}Expand description
The instrument directory and the rules for trading an instrument.
Public data, so it lives in the market data connector. The library does NOT check your orders
against it: round quantity and price with TradingParams yourself before
OrderExecutor::place.
§Examples
use brokapi_bcs::limits::default_limits;
use brokapi_bcs::{MarketDataConfig, MarketDataConnector};
use brokapi_core::{
Error, InstrumentId, InstrumentKind, InstrumentQuery, InstrumentSource, TradingAvailability,
ShutdownMode,
};
use brokapi_runtime::BrokerLimiter;
let (events_tx, _events) = tokio::sync::mpsc::channel(256);
let limiter = BrokerLimiter::new(&default_limits());
let md = MarketDataConnector::start(MarketDataConfig::new(cfg, "bcs-md"), limiter, events_tx).await?;
// Find an instrument by ticker. The result carries ids ready to pass to every other method.
let found = md
.find(InstrumentQuery::ByTicker { ticker: "SBER".into(), exchange: None })
.await?;
let sber: InstrumentId = found.first().expect("SBER is in the directory").id.clone();
// How to size and price an order.
let params = md.trading_params(&sber).await?;
assert!(params.lot_size > Qty::from(0) && params.price_step > Price::from(0));
// Is it tradable now?
let status = md.trading_status(&sber).await?;
println!("{}: {:?}", sber.key.display(), status.availability);
// A search by a kind the broker cannot list is refused at once.
let by_kind = md.find(InstrumentQuery::ByKind(InstrumentKind::Option)).await;
assert!(matches!(by_kind, Err(Error::Unsupported { .. })));
md.shutdown(ShutdownMode::Immediate).await;Required Methods§
Sourcefn find<'life0, 'async_trait>(
&'life0 self,
q: InstrumentQuery,
) -> Pin<Box<dyn Future<Output = Result<Vec<Instrument>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn find<'life0, 'async_trait>(
&'life0 self,
q: InstrumentQuery,
) -> Pin<Box<dyn Future<Output = Result<Vec<Instrument>, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Looks instruments up in the directory.
The result can be empty (nothing matches). A query by kind can be a long list, the connector may read it page by page, each page counts against the rate limit.
§Errors
Error::Unsupportedwhen the broker cannot list that kind (BCS:OptionandOther).Error::RateLimited,Error::Transport: wait or repeat later. The BCS connector also answersTransportwhen a kind has more than its page limit, rather than return part of the list.Error::Rejectedwhen the broker refuses the request.
§Examples
use brokapi_core::{InstrumentQuery, InstrumentSource};
let list = md
.find(InstrumentQuery::ByTicker { ticker: "SBER".into(), exchange: None })
.await?;
assert!(list.iter().any(|i| i.id.key.symbol == "SBER"));Sourcefn get<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<Instrument, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<Instrument, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Reads one directory entry.
§Errors
Error::Configwhenid.nativeis of a kind this broker does not use.Error::RateLimited,Error::Transport: wait or repeat later. An instrument the directory does not have is reported asTransportby the BCS connector (messageinstrument not found), there is no separate variant for “not found”.
§Examples
use brokapi_core::InstrumentSource;
let info = md.get(&sber).await?;
assert_eq!(info.id.key.symbol, "SBER");Sourcefn trading_params<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<TradingParams, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn trading_params<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<TradingParams, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Reads lot size, price step and the other numbers needed to size and price an order.
§Errors
Error::Configwhenid.nativeis of a kind this broker does not use.Error::RateLimited,Error::Transport: wait or repeat later. The BCS connector also answersTransportwhen the directory has no trading parameters for the board.
§Examples
use brokapi_core::InstrumentSource;
let params = md.trading_params(&sber).await?;
// The nearest price on the grid of the instrument at or below 250.037. The rounding is exact.
let wanted = Price::new(dec!(250.037));
let price = params.round_price(wanted, RoundMode::Down)?;
assert!(price <= wanted);
assert!(params.is_price_on_grid(price));Sourcefn trading_status<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<TradingStatus, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn trading_status<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 InstrumentId,
) -> Pin<Box<dyn Future<Output = Result<TradingStatus, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Reads whether the instrument can be traded now.
§Errors
Error::Configwhenid.nativeis of a kind this broker does not use.Error::RateLimited,Error::Transport: wait or repeat later.
§Examples
use brokapi_core::{InstrumentSource, TradingAvailability};
let status = md.trading_status(&sber).await?;
if status.availability == TradingAvailability::Open {
// safe to send orders
}Sourcefn schedule<'life0, 'life1, 'async_trait>(
&'life0 self,
exchange: &'life1 str,
range: TimeRange,
) -> Pin<Box<dyn Future<Output = Result<Schedule, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn schedule<'life0, 'life1, 'async_trait>(
&'life0 self,
exchange: &'life1 str,
range: TimeRange,
) -> Pin<Box<dyn Future<Output = Result<Schedule, Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Reads the trading sessions of an exchange over range.
What exchange means depends on the broker: T-Bank takes an exchange name (MOEX), Finam and
BCS take TICKER@MARKET because their schedules are per instrument. The BCS answer is for
one day, the day of range.from.
§Errors
Error::Configwhenexchangeis not in the form the broker needs (BCS: notTICKER@CLASSCODE).Error::RateLimited,Error::Transport: wait or repeat later.
§Examples
use brokapi_core::{Error, InstrumentSource, TimeRange, UnixNanos};
let day = TimeRange::new(
UnixNanos::from_nanos(1_792_368_000_000_000_000), // 2026-10-19 00:00 UTC
UnixNanos::from_nanos(1_792_454_400_000_000_000), // 2026-10-20 00:00 UTC
);
// BCS schedules are per instrument: "TICKER@CLASSCODE".
let schedule = md.schedule("SBER@TQBR", day).await?;
assert!(schedule.sessions.iter().all(|s| s.start < s.end));
// A bare exchange name is not enough for BCS.
assert!(matches!(md.schedule("MOEX", day).await, Err(Error::Config(_))));Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".