Skip to main content

brokapi_finam/core/
mod.rs

1//! Sans-io cores: frames in, normalized events out. No sockets, timers or tasks here.
2//!
3//! A stream task reads gRPC messages and wraps them into frames ([`MdFrame`], [`UdFrame`]). The core
4//! turns a frame into events of `brokapi-core` (order books, quotes, orders...) and keeps the state that
5//! needs memory: the local order book, the M10 candle being built, the id of seen trades, the
6//! order lifecycle. Because the core does no input and output, tests drive it with plain values and a
7//! fixed clock.
8//!
9//! # Examples
10//!
11//! A quote subscription walked through by hand: no network, no clock besides `Instant::now()`.
12//!
13//! ```
14//! use std::time::Instant;
15//! use brokapi_core::{Event, SubscriptionId};
16//! use brokapi_finam::core::{MdCore, MdEvent, MdFrame, SubStatus};
17//! use brokapi_finam::util::instrument_from_symbol;
18//! use brokapi_sansio::{BrokerCore, DesiredSubscription, EventSink, SubscriptionKind, SubscriptionSet};
19//! use brokapi_finam_protocol::pb::google::r#type::Decimal;
20//! use brokapi_finam_protocol::pb::grpc::tradeapi::v1::marketdata as pb;
21//!
22//! let sub = SubscriptionId::new(1);
23//! let sber = instrument_from_symbol("SBER@MISX");
24//! let mut core = MdCore::new();
25//! core.apply(&SubscriptionSet::with_subscriptions(vec![DesiredSubscription {
26//!     id: sub,
27//!     kind: SubscriptionKind::Quotes { instrument: sber },
28//! }]));
29//! assert_eq!(core.status(sub), Some(SubStatus::Connecting));
30//!
31//! let mut out = EventSink::new();
32//! core.on_frame(MdFrame { sub, event: MdEvent::Connected }, Instant::now(), &mut out);
33//! assert_eq!(core.status(sub), Some(SubStatus::Active));
34//!
35//! let price = |v: &str| Some(Decimal { value: v.into() });
36//! let quote = pb::Quote { symbol: "SBER@MISX".into(), bid: price("250.1"), ask: price("250.2"), ..Default::default() };
37//! let message = pb::SubscribeQuoteResponse { quote: vec![quote], error: None };
38//! core.on_frame(MdFrame { sub, event: MdEvent::Quotes(message) }, Instant::now(), &mut out);
39//!
40//! let events = out.drain();
41//! assert!(matches!(events[0].payload, Event::Quote(_)));
42//! ```
43
44pub mod md;
45pub mod ud;
46
47pub use md::{MdCore, MdEvent, MdFrame};
48pub use ud::{UdCore, UdEvent, UdFrame};
49
50/// State of one subscription as seen by the core.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum SubStatus {
53    /// Registered, stream not open yet.
54    Connecting,
55    /// Stream open.
56    Active,
57    /// Stream dropped, being reopened with backoff.
58    Reconnecting(String),
59    /// Server refuses the subscription (bad symbol, no permission...); retried slowly.
60    Failed(String),
61    /// Stream is open but carried an in-band error message.
62    Erroring(String),
63}
64
65/// What a stream task reports about its stream (before it is wrapped into a core frame).
66#[derive(Debug, Clone)]
67pub enum StreamEvent<M> {
68    /// The stream was opened (also after a reopen).
69    Connected,
70    /// One message of the stream.
71    Msg(M),
72    /// The stream broke or ended. It is being reopened. The text says why.
73    Disconnected(String),
74    /// The broker refuses the stream (bad symbol, no permission). It is retried slowly. The text says why.
75    Failed(String),
76}