Skip to main content

InstrumentSource

Trait InstrumentSource 

Source
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§

Source

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
§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"));
Source

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::Config when id.native is 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 as Transport by the BCS connector (message instrument 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");
Source

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
§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));
Source

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
§Examples
use brokapi_core::{InstrumentSource, TradingAvailability};
let status = md.trading_status(&sber).await?;
if status.availability == TradingAvailability::Open {
    // safe to send orders
}
Source

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
§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".

Implementors§