Skip to main content

brokapi_finam/
lib.rs

1//! Finam connectors: market data and user data over the Finam Trade API (gRPC).
2//!
3//! This is the connector layer. It implements the shared traits of `brokapi-core` on top of the protocol
4//! layer (`brokapi-finam-protocol`), the sans-io logic (`brokapi-sansio`) and the runtime pieces (`brokapi-runtime`:
5//! rate limiter, command gate, shutdown).
6//!
7//! | Type | Role | Traits |
8//! |---|---|---|
9//! | [`MarketDataConnector`] | order books, trades, quotes, candles, instrument directory. Not tied to an account | `OrderBookSource`, `MarketDataSource`, `InstrumentSource` |
10//! | [`UserDataConnector`] | orders, stop orders, own trades, portfolio of ONE account | `OrderExecutor`, `StopOrderExecutor`, `ExecutionSource`, `PortfolioSource` |
11//! | [`FinamRaw`] | any other Finam method, with the connector's token and rate limiter | none, see `connector.raw()` |
12//!
13//! Things that are true for the whole crate:
14//!
15//! - **One user data connector is one account.** To trade two accounts, start two connectors. They can
16//!   share one token manager ([`FinamApiConfig::shared_tokens`]) and must share one rate limiter.
17//! - **One rate limiter per Finam user.** Finam documents one request quota for the whole API. The bundled
18//!   limits ([`limits::default_limits`]) are meant to be shared by every connector of the user.
19//! - **A place command that got no answer is never repeated.** A timeout or a broken connection gives
20//!   `Error::Unknown`, because a repeat could create a second order. Ask the order state or call
21//!   `reconcile` to learn what happened.
22//! - **A rejected order is a final answer.** `Error::Rejected` means the broker did not take the order.
23//! - **Quantity is in pieces** (units of the instrument), as everywhere in the Finam API and in this library, not in lots.
24//! - **Instruments are `SYMBOL@MIC`** (for example `SBER@MISX`), carried as `NativeId::SymbolMic`.
25//!
26//! Finam has no sandbox. The examples in this documentation run against an in-process imitation of the
27//! broker (`brokapi-mocks`). The imitation is written from the broker documentation. It does not prove how
28//! the live broker behaves.
29//!
30//! The book with the full guide: <https://brokapi.ru/docs/>
31//!
32//! # Examples
33//!
34//! Read an order book, then place a limit order far from the market and cancel it:
35//!
36//! ```
37//! use brokapi_core::{
38//!     ClientOrderId, InstrumentId, InstrumentKey, NativeId, OrderBook, OrderBookSource, OrderExecutor,
39//!     OrderType, PlaceOrderRequest, Price, Qty, ShutdownMode, Side,
40//! };
41//! use brokapi_finam::limits::default_limits;
42//! use brokapi_finam::{MarketDataConfig, MarketDataConnector, UserDataConfig, UserDataConnector};
43//! use brokapi_runtime::BrokerLimiter;
44//! use tokio::sync::mpsc;
45//! # use brokapi_finam::FinamApiConfig;
46//! # use brokapi_mocks::finam::{MockServer, SECRET};
47//!
48//! # #[tokio::main(flavor = "current_thread")]
49//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
50//! # let server = MockServer::start().await;
51//! # let api = FinamApiConfig::new(SECRET).with_endpoint(server.url.clone());
52//! # let account = "ACC1";
53//! // `api` holds your secret (and the endpoint of the real broker by default).
54//! // One limiter per Finam user, shared by every connector.
55//! let limiter = BrokerLimiter::new(&default_limits());
56//! let (events, _rx) = mpsc::channel(256);
57//!
58//! let md = MarketDataConnector::start(
59//!     MarketDataConfig::new(api.clone(), "finam-md"),
60//!     limiter.clone(),
61//!     events.clone(),
62//! )
63//! .await?;
64//! let sber = InstrumentId::new(
65//!     InstrumentKey::new("SBER", "MISX"),
66//!     NativeId::SymbolMic { symbol: "SBER".into(), mic: "MISX".into() },
67//! );
68//! let book = md.order_book(&sber, 5).await?;
69//! assert!(matches!(book, OrderBook::Snapshot { .. }));
70//!
71//! let ud = UserDataConnector::start(
72//!     UserDataConfig::new(api, account, "finam-ud"),
73//!     limiter,
74//!     events,
75//! )
76//! .await?;
77//! // Orders are refused until the connector is reconciled with the broker (empty ledger here).
78//! ud.reconcile(&[]).await?;
79//! let cid = ClientOrderId::new_v4();
80//! let ack = ud
81//!     .place(PlaceOrderRequest {
82//!         cid,
83//!         instrument: sber,
84//!         side: Side::Buy,
85//!         order_type: OrderType::Limit,
86//!         qty: Qty::from(1),
87//!         price: Some(Price::from(1)),
88//!     })
89//!     .await?;
90//! assert_eq!(ack.cid, cid);
91//! ud.cancel(cid).await?;
92//!
93//! md.shutdown(ShutdownMode::Immediate).await;
94//! ud.shutdown(ShutdownMode::Immediate).await;
95//! # Ok(())
96//! # }
97//! ```
98//!
99//! `connector.raw()` ([`FinamRaw`]) reaches any other method of the API with the connector's token,
100//! endpoint and rate limiter.
101
102#![warn(missing_docs)]
103
104pub mod cid;
105pub mod config;
106pub mod core;
107pub mod error;
108pub mod index;
109pub mod limits;
110pub mod map;
111pub mod md;
112pub mod pump;
113pub mod raw;
114pub mod rpc;
115pub mod stream;
116pub mod subs;
117pub mod ud;
118pub mod util;
119
120pub use cid::FinamCidCodec;
121pub use config::{FinamApiConfig, MarketDataConfig, UserDataConfig, api_config_from_env};
122pub use core::{MdCore, SubStatus, UdCore};
123pub use md::MarketDataConnector;
124pub use raw::FinamRaw;
125pub use ud::UserDataConnector;