Struct BcsRaw
pub struct BcsRaw { /* private fields */ }Expand description
A handle for calling any BCS API method with a connector’s token and rate limiter.
Get it from raw() on MarketDataConnector or
UserDataConnector. It is cheap to clone, and all clones share the
token and the limiter. See the module docs for the rules of a call.
Implementations§
§impl BcsRaw
impl BcsRaw
pub async fn call<T, F, Fut>(&self, method: &str, f: F) -> Result<T, Error>
pub async fn call<T, F, Fut>(&self, method: &str, f: F) -> Result<T, Error>
Calls a read-only API method: takes the quota for method, then runs f.
method is the name of the quota route, not a URL. Take a name from limits.toml
(for example GetQuotes). An unknown name spends the default route "*". f receives the
connector’s [BcsHttpClient] and returns the future of one API call. Do not use this for
anything that changes the account, use call_command.
§Errors
- [
Error::RateLimited] when the quota is spent. Nothing was sent. Also when the broker answers HTTP 429. - [
Error::Rejected] with codevalidationwhen the broker answers HTTP 400 with a validation error. - [
Error::Transport] for everything else: no connection, a timeout, an unreadable answer, HTTP 404 (not found), a failed token refresh, any other HTTP status. A read can be repeated. - [
Error::Config] when the limits file routes the name to a bucket that does not exist.
The full table is in crate::error::map_err.
§Examples
A quota that is spent refuses the call before anything is sent:
use brokapi_bcs_protocol::http::models::{QuoteInstrument, QuotesRequest};
use brokapi_bcs::{MarketDataConfig, MarketDataConnector};
use brokapi_core::{Error, ShutdownMode};
use brokapi_runtime::{BrokerLimiter, BrokerLimits};
use tokio::sync::mpsc;
// One call per minute for GetQuotes.
let limits = BrokerLimits::from_toml_str(
r#"
[bcs.buckets.one]
rate = 1
per = "60s"
burst = 1
[bcs.route]
"*" = ["one"]
[bcs.conns]
max_conns = 4
max_subs_per_conn = 50
supports_multiplex = false
"#,
)?;
let md = MarketDataConnector::start(
MarketDataConfig::new(cfg, "bcs-md"),
BrokerLimiter::new(&limits),
events_tx,
)
.await?;
let raw = md.raw();
let quotes = || QuotesRequest { instruments: vec![QuoteInstrument::new("SBER", "TQBR")] };
raw.call("GetQuotes", |http| async move { http.get_quotes("es()).await }).await?;
let second = raw.call("GetQuotes", |http| async move { http.get_quotes("es()).await }).await;
assert!(matches!(second, Err(Error::RateLimited { .. })));
md.shutdown(ShutdownMode::Immediate).await;pub async fn call_command<T, F, Fut>(
&self,
method: &str,
f: F,
) -> Result<T, Error>
pub async fn call_command<T, F, Fut>( &self, method: &str, f: F, ) -> Result<T, Error>
Calls an API method that changes the account: takes the quota for method, then runs f.
Like call, but anything that may have reached the broker is
[Error::Unknown]. The library never repeats the call: a retry of a lost place request could
create a second order. Find out what happened (read the order, run reconcile) before you
decide to send it again. The order is not tracked by the connector (see the module docs).
§Errors
- [
Error::Unknown] when there was no answer, a break or a timeout, HTTP 500 or above, or an answer that cannot be read. The command may have been done. - [
Error::Rejected] when the broker answered and said no: codevalidationfor a validation error, codehttp_<status>for another status from 400 to 499 except 429. Nothing was done. - [
Error::RateLimited] when the quota is spent (nothing was sent) or the broker answers HTTP 429. - [
Error::Transport] for a failed token refresh and for HTTP 404 (not found). - [
Error::Config] when the limits file routes the name to a bucket that does not exist.
The full table is in crate::error::map_command_err.
§Examples
An order sent through the raw layer, refused by the broker (the mock refuses a limit order at
REJECT_PRICE). The refusal is a definite [Error::Rejected], not an unknown outcome:
use brokapi_bcs_protocol::http::models::CreateOrderRequest;
use brokapi_bcs_protocol::models::enums::OrderSide;
use brokapi_bcs::limits::default_limits;
use brokapi_bcs::{UserDataConfig, UserDataConnector};
use brokapi_conformance::REJECT_PRICE;
use brokapi_core::{ClientOrderId, Error, ShutdownMode};
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;
let ud = UserDataConnector::start(UserDataConfig::new(cfg, "bcs-ud"), limiter, events_tx).await?;
let order = CreateOrderRequest::limit(
ClientOrderId::new_v4(), "SBER", "TQBR", OrderSide::Buy, 1, REJECT_PRICE.decimal(),
);
let result = ud
.raw()
.call_command("PlaceOrder", |http| async move { http.create_order_sync(&order).await })
.await;
assert!(matches!(result, Err(Error::Rejected { .. })));
ud.shutdown(ShutdownMode::Immediate).await;pub fn connect_ws(
&self,
channel: BcsWsChannel,
event_tx: AsyncSender<IncomingMessage>,
) -> Result<WsSession, Error>
pub fn connect_ws( &self, channel: BcsWsChannel, event_tx: AsyncSender<IncomingMessage>, ) -> Result<WsSession, Error>
Opens a WebSocket channel (Limits, MarginalIndicators, …) with the connector’s token.
The quota name is Connect<Channel> (ConnectLimits). The session reconnects and
resubscribes on its own. Messages arrive on event_tx, parsed by the protocol layer. The
returned [WsSession] connects in the background: it does not wait for the connection.
Read event_tx’s receiver, or the session drops messages when the channel is full (see
WsSession::dropped_events).
§Errors
- [
Error::RateLimited] when the quotaConnect<Channel>is spent. No socket is opened. - [
Error::Config] when the limits file routes the name to a bucket that does not exist.
§Examples
use std::time::Duration;
use brokapi_bcs_protocol::ws::client::BcsWsChannel;
use brokapi_bcs::limits::default_limits;
use brokapi_bcs::{MarketDataConfig, MarketDataConnector};
use brokapi_core::ShutdownMode;
use brokapi_runtime::BrokerLimiter;
use tokio::sync::mpsc;
let md = MarketDataConnector::start(MarketDataConfig::new(cfg, "bcs-md"), limiter, events_tx).await?;
let (tx, _rx) = kanal::bounded_async(64);
let session = md.raw().connect_ws(BcsWsChannel::Limits, tx)?;
// The session connects in the background: wait for it, with a limit.
tokio::time::timeout(Duration::from_secs(5), async {
while !session.is_connected() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await?;
md.shutdown(ShutdownMode::Immediate).await;Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for BcsRaw
impl !UnwindSafe for BcsRaw
impl Freeze for BcsRaw
impl Send for BcsRaw
impl Sync for BcsRaw
impl Unpin for BcsRaw
impl UnsafeUnpin for BcsRaw
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request§impl<L> LayerExt<L> for L
impl<L> LayerExt<L> for L
§fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
Layered].