Skip to main content

brokapi_bcs/
config.rs

1//! Configuration of the two connectors: the BCS client settings plus the choices of the connector
2//! itself (source label, reconnect pauses, buffer size).
3//!
4//! Both configs wrap a `BcsConfig` from the protocol crate. It holds the refresh token, the
5//! timeouts and the write switch. Build it with `BcsConfig::from_env()` for a real account, or with
6//! `BcsConfig::for_mock(token, url)` for tests against `brokapi-mocks`.
7
8use std::sync::Arc;
9use std::time::Duration;
10
11use brokapi_bcs_protocol::config::BcsConfig;
12use brokapi_bcs_protocol::ws::session::{DEFAULT_RECONNECT_MAX, DEFAULT_RECONNECT_MIN, WsOptions};
13use brokapi_runtime::PriceGrid;
14
15/// Settings of [`MarketDataConnector`](crate::MarketDataConnector).
16///
17/// # Examples
18///
19/// ```
20/// use std::time::Duration;
21/// use brokapi_bcs_protocol::config::BcsConfig;
22/// use brokapi_bcs::MarketDataConfig;
23///
24/// let mut cfg = MarketDataConfig::new(BcsConfig::new("refresh-token".into()), "bcs-md");
25/// assert_eq!(&*cfg.source, "bcs-md");
26/// // Wait at most 10 seconds between reconnects instead of the default 30.
27/// cfg.reconnect_max = Duration::from_secs(10);
28/// ```
29#[derive(Debug, Clone)]
30pub struct MarketDataConfig {
31    /// Client settings of the BCS API: refresh token, timeouts, endpoints, write switch.
32    pub bcs: BcsConfig,
33    /// Envelope source label, e.g. `"bcs-md"`.
34    pub source: Arc<str>,
35    /// Pause before the first reconnect of the WebSocket, doubling up to `reconnect_max`
36    /// (default 0.5 s, as at T-Bank and Finam).
37    pub reconnect_min: Duration,
38    /// Upper bound of the pause (default 30 s). The pause returns to `reconnect_min` once a connection
39    /// has lived 10 seconds.
40    pub reconnect_max: Duration,
41    /// How long a `subscribe_*` call waits for the broker to confirm the subscription (default 3 s). The call
42    /// returns when the broker has confirmed: what happens after that is delivered. A refusal is returned as
43    /// `Rejected` (`subscription_refused`). If nothing comes in time, the call fails with `Transport` and the
44    /// subscription is taken back, so the call can be repeated.
45    pub subscribe_ack_timeout: Duration,
46}
47
48impl MarketDataConfig {
49    /// Creates a config with the default reconnect pauses (0.5 s up to 30 s) and a 3 s wait for the
50    /// confirmation of a subscription.
51    ///
52    /// `source` is the label put in every [`Envelope`](brokapi_core::Envelope) this connector emits,
53    /// so a bot with several connectors can tell them apart.
54    pub fn new(bcs: BcsConfig, source: impl Into<Arc<str>>) -> Self {
55        Self {
56            bcs,
57            source: source.into(),
58            reconnect_min: DEFAULT_RECONNECT_MIN,
59            reconnect_max: DEFAULT_RECONNECT_MAX,
60            subscribe_ack_timeout: Duration::from_secs(3),
61        }
62    }
63}
64
65/// Settings of [`UserDataConnector`](crate::UserDataConnector).
66///
67/// For a connector that sends orders the `bcs` config must be built with `with_write(true)`.
68///
69/// # Examples
70///
71/// ```
72/// use brokapi_bcs_protocol::config::BcsConfig;
73/// use brokapi_bcs::UserDataConfig;
74///
75/// let bcs = BcsConfig::new("refresh-token".into()).with_write(true);
76/// let mut cfg = UserDataConfig::new(bcs, "bcs-ud");
77/// assert_eq!(cfg.order_state_buffer, 1024);
78/// // A bigger buffer gives a slow bot more room before an overflow closes the trading gate.
79/// cfg.order_state_buffer = 4096;
80/// ```
81#[derive(Debug, Clone)]
82pub struct UserDataConfig {
83    /// Client settings of the BCS API: refresh token, timeouts, endpoints, write switch.
84    pub bcs: BcsConfig,
85    /// Envelope source label, e.g. `"bcs-ud"`.
86    pub source: Arc<str>,
87    /// Capacity of the lossless buffer of each order state and execution subscription (default
88    /// 1024 events). When it is full, the subscription reports an overflow and the connector
89    /// becomes degraded: commands are refused until the next successful `reconcile`.
90    pub order_state_buffer: usize,
91    /// Pause before the first reconnect of the WebSocket, doubling up to `reconnect_max`
92    /// (default 0.5 s, as at T-Bank and Finam).
93    pub reconnect_min: Duration,
94    /// Upper bound of the pause (default 30 s). The pause returns to `reconnect_min` once a connection
95    /// has lived 10 seconds.
96    pub reconnect_max: Duration,
97    /// How long a `subscribe_*` call waits for the WebSocket of its channel to be open (default 3 s). The
98    /// call returns when the socket is open: an order placed right after it is not missed. If the socket does
99    /// not open in time, the call fails with `Transport` and the subscription is taken back, so the call can
100    /// be repeated.
101    pub subscribe_ack_timeout: Duration,
102    /// Price steps the connector checks order prices against (default: empty, nothing is checked).
103    /// Fill it with [`PriceGrid::learn`] from the trading parameters of the instruments you trade. A
104    /// price off the step is then refused with `Rejected` (`price_off_step`) before anything is sent.
105    pub price_grid: PriceGrid,
106}
107
108impl UserDataConfig {
109    /// Creates a config with the default buffer (1024 events), the default reconnect pauses
110    /// (0.5 s up to 30 s) and a 3 s wait for the socket in `subscribe_*`.
111    ///
112    /// `source` is the label put in every [`Envelope`](brokapi_core::Envelope) this connector emits.
113    pub fn new(bcs: BcsConfig, source: impl Into<Arc<str>>) -> Self {
114        Self {
115            bcs,
116            source: source.into(),
117            order_state_buffer: 1024,
118            reconnect_min: DEFAULT_RECONNECT_MIN,
119            reconnect_max: DEFAULT_RECONNECT_MAX,
120            subscribe_ack_timeout: Duration::from_secs(3),
121            price_grid: PriceGrid::default(),
122        }
123    }
124}
125
126impl MarketDataConfig {
127    pub(crate) fn ws_options(&self) -> WsOptions {
128        // The core is told of every connect: a reconnect voids the order books (`Resync`).
129        WsOptions {
130            announce_connects: true,
131            reconnect_min: self.reconnect_min,
132            reconnect_max: self.reconnect_max,
133        }
134    }
135}
136
137impl UserDataConfig {
138    /// Options of the order events session: it announces every connect, so the core sees a lost
139    /// connection ([`brokapi_core::Event::Reconnected`]). The portfolio session does not (every message of
140    /// it is a whole state).
141    pub(crate) fn ws_options(&self) -> WsOptions {
142        WsOptions {
143            announce_connects: true,
144            reconnect_min: self.reconnect_min,
145            reconnect_max: self.reconnect_max,
146        }
147    }
148
149    /// Options of the portfolio session: no announcements.
150    pub(crate) fn portfolio_ws_options(&self) -> WsOptions {
151        WsOptions {
152            announce_connects: false,
153            ..self.ws_options()
154        }
155    }
156}