brokapi_bcs_protocol/ws/models.rs
1//! Frames of the WebSocket channels: the commands you send and the data the broker sends.
2//!
3//! Market data frames are told apart by their `responseType` ([`MarketDataMessage`]). The other
4//! channels send a bare object or array, and the channel decides the type. All times from the broker
5//! are UTC. Quantities are in pieces.
6
7use crate::models::enums::{ExecutionType, OrderSide, OrderStatus, OrderType};
8use crate::models::instrument::InstrumentKey;
9use crate::utils::date_format;
10use chrono::{DateTime, Utc};
11use rust_decimal::Decimal;
12use serde::{Deserialize, Serialize};
13
14// ─── common ──────────────────────────────────────────────────────────────────
15
16/// An error frame of the broker (`"type": "error"`), the server to client direction.
17///
18/// A frame like this is parsed into [`IncomingMessage::Error`](crate::models::messages::IncomingMessage::Error).
19///
20/// # Examples
21///
22/// ```
23/// use brokapi_bcs_protocol::ws::models::WsError;
24///
25/// let e: WsError = serde_json::from_str(r#"{"type":"error","code":"UNAUTHORIZED","message":"no token"}"#)?;
26/// assert_eq!(e.code, "UNAUTHORIZED");
27/// # Ok::<(), serde_json::Error>(())
28/// ```
29#[derive(Debug, Clone, Deserialize)]
30pub struct WsError {
31 /// Kind of the frame (JSON field `type`), `error` for this type.
32 #[serde(rename = "type")]
33 pub msg_type: String,
34 /// Machine code of the error.
35 pub code: String,
36 /// Text of the error.
37 pub message: String,
38}
39
40/// The two fields of a frame that the parser looks at first to decide what kind of frame it is.
41///
42/// Every other field is ignored here. The parser then reads the whole frame into the matching type.
43#[derive(Debug, Clone, Deserialize)]
44pub struct WsRawMessage {
45 /// The `type` of the frame, if there is one. `error` marks an error frame.
46 #[serde(default, rename = "type")]
47 pub msg_type: Option<String>,
48 /// The `responseType` of the frame, if there is one. Market data frames have it.
49 #[serde(default, rename = "responseType")]
50 pub response_type: Option<String>,
51}
52
53// ─── subscription ─────────────────────────────────────────────────────────────
54
55/// A subscribe or unsubscribe command for the market data channel (client to server).
56///
57/// Build it with the constructors: [`MarketDataSubscribe::quotes`], [`MarketDataSubscribe::order_book`],
58/// [`MarketDataSubscribe::last_trades`], [`MarketDataSubscribe::last_candle`] and the `unsubscribe_*` forms.
59/// `subscribe_type` and `data_type` are numbers on the wire, as in the broker docs. The broker allows
60/// 100 instruments per connection and 10 client messages per second.
61///
62/// # Examples
63///
64/// ```
65/// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
66/// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
67///
68/// let sub = MarketDataSubscribe::order_book(vec![InstrumentKey::new("SBER", "TQBR")], 20);
69/// assert_eq!(
70/// serde_json::to_value(&sub)?,
71/// serde_json::json!({
72/// "subscribeType": 0,
73/// "dataType": 0,
74/// "instruments": [{ "ticker": "SBER", "classCode": "TQBR" }],
75/// "depth": 20
76/// })
77/// );
78/// # Ok::<(), serde_json::Error>(())
79/// ```
80#[derive(Debug, Clone, PartialEq, Serialize)]
81#[serde(rename_all = "camelCase")]
82pub struct MarketDataSubscribe {
83 /// `0` subscribe, `1` unsubscribe.
84 pub subscribe_type: u8,
85 /// What to subscribe to: `0` order book, `1` last candle, `2` anonymous trades, `3` quotes.
86 pub data_type: u8,
87 /// The instruments the command is about.
88 pub instruments: Vec<InstrumentKey>,
89 /// Order book depth, 1 to 20 by the broker docs (default 20). Sent only for the order book. Not checked here.
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub depth: Option<u32>,
92 /// Candle time frame: `M1 M5 M15 M30 H1 H4 D W MN`. Sent only for the last candle. Not checked here.
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub time_frame: Option<String>,
95}
96
97impl MarketDataSubscribe {
98 /// Turns a subscribe command into the matching unsubscribe: the same data type, instruments, depth
99 /// and time frame, with `subscribe_type` set to `1`.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
105 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
106 ///
107 /// let sub = MarketDataSubscribe::order_book(vec![InstrumentKey::new("SBER", "TQBR")], 10);
108 /// let unsub = sub.clone().into_unsubscribe();
109 /// assert_eq!((sub.subscribe_type, unsub.subscribe_type), (0, 1));
110 /// assert_eq!(unsub.depth, Some(10));
111 /// ```
112 pub fn into_unsubscribe(self) -> Self {
113 Self {
114 subscribe_type: 1,
115 ..self
116 }
117 }
118
119 /// Subscribes to quotes (`dataType` 3).
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
125 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
126 ///
127 /// let sub = MarketDataSubscribe::quotes(vec![InstrumentKey::new("SBER", "TQBR")]);
128 /// assert_eq!((sub.subscribe_type, sub.data_type), (0, 3));
129 /// ```
130 pub fn quotes(instruments: Vec<InstrumentKey>) -> Self {
131 Self {
132 subscribe_type: 0,
133 data_type: 3,
134 instruments,
135 depth: None,
136 time_frame: None,
137 }
138 }
139
140 /// Unsubscribes from quotes (`dataType` 3, `subscribeType` 1).
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
146 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
147 ///
148 /// let cmd = MarketDataSubscribe::unsubscribe_quotes(vec![InstrumentKey::new("SBER", "TQBR")]);
149 /// assert_eq!((cmd.subscribe_type, cmd.data_type), (1, 3));
150 /// ```
151 pub fn unsubscribe_quotes(instruments: Vec<InstrumentKey>) -> Self {
152 Self {
153 subscribe_type: 1,
154 data_type: 3,
155 instruments,
156 depth: None,
157 time_frame: None,
158 }
159 }
160
161 /// Subscribes to the order book (`dataType` 0) with `depth` levels per side.
162 ///
163 /// The broker docs allow 1 to 20 and default to 20. The depth is not checked here. The broker
164 /// computes the volumes in the answers over the levels within `depth`.
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
170 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
171 ///
172 /// let sub = MarketDataSubscribe::order_book(vec![InstrumentKey::new("SBER", "TQBR")], 5);
173 /// assert_eq!((sub.data_type, sub.depth), (0, Some(5)));
174 /// ```
175 pub fn order_book(instruments: Vec<InstrumentKey>, depth: u32) -> Self {
176 Self {
177 subscribe_type: 0,
178 data_type: 0,
179 instruments,
180 depth: Some(depth),
181 time_frame: None,
182 }
183 }
184
185 /// Unsubscribes from the order book (`dataType` 0, `subscribeType` 1). Pass the same `depth` as
186 /// at subscribe if you compare it with the restore list of a session.
187 ///
188 /// # Examples
189 ///
190 /// ```
191 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
192 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
193 ///
194 /// let cmd = MarketDataSubscribe::unsubscribe_order_book(vec![InstrumentKey::new("SBER", "TQBR")], 5);
195 /// assert_eq!((cmd.subscribe_type, cmd.data_type), (1, 0));
196 /// ```
197 pub fn unsubscribe_order_book(instruments: Vec<InstrumentKey>, depth: u32) -> Self {
198 Self {
199 subscribe_type: 1,
200 data_type: 0,
201 instruments,
202 depth: Some(depth),
203 time_frame: None,
204 }
205 }
206
207 /// Subscribes to the anonymous trades tape (`dataType` 2).
208 ///
209 /// # Examples
210 ///
211 /// ```
212 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
213 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
214 ///
215 /// let sub = MarketDataSubscribe::last_trades(vec![InstrumentKey::new("SBER", "TQBR")]);
216 /// assert_eq!(sub.data_type, 2);
217 /// ```
218 pub fn last_trades(instruments: Vec<InstrumentKey>) -> Self {
219 Self {
220 subscribe_type: 0,
221 data_type: 2,
222 instruments,
223 depth: None,
224 time_frame: None,
225 }
226 }
227
228 /// Unsubscribes from the anonymous trades tape (`dataType` 2, `subscribeType` 1).
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
234 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
235 ///
236 /// let cmd = MarketDataSubscribe::unsubscribe_last_trades(vec![InstrumentKey::new("SBER", "TQBR")]);
237 /// assert_eq!((cmd.subscribe_type, cmd.data_type), (1, 2));
238 /// ```
239 pub fn unsubscribe_last_trades(instruments: Vec<InstrumentKey>) -> Self {
240 Self {
241 subscribe_type: 1,
242 data_type: 2,
243 instruments,
244 depth: None,
245 time_frame: None,
246 }
247 }
248
249 /// Subscribes to the last candle (`dataType` 1) for a time frame.
250 ///
251 /// `time_frame` is one of `M1 M5 M15 M30 H1 H4 D W MN` (broker docs). It is not checked here.
252 ///
253 /// # Examples
254 ///
255 /// ```
256 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
257 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
258 ///
259 /// let sub = MarketDataSubscribe::last_candle(vec![InstrumentKey::new("SBER", "TQBR")], "M5");
260 /// assert_eq!((sub.data_type, sub.time_frame.as_deref()), (1, Some("M5")));
261 /// ```
262 pub fn last_candle(instruments: Vec<InstrumentKey>, time_frame: &str) -> Self {
263 Self {
264 subscribe_type: 0,
265 data_type: 1,
266 instruments,
267 depth: None,
268 time_frame: Some(time_frame.to_string()),
269 }
270 }
271
272 /// Unsubscribes from the last candle (`dataType` 1, `subscribeType` 1) for a time frame.
273 ///
274 /// # Examples
275 ///
276 /// ```
277 /// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
278 /// use brokapi_bcs_protocol::ws::models::MarketDataSubscribe;
279 ///
280 /// let cmd = MarketDataSubscribe::unsubscribe_last_candle(vec![InstrumentKey::new("SBER", "TQBR")], "M5");
281 /// assert_eq!(cmd.subscribe_type, 1);
282 /// ```
283 pub fn unsubscribe_last_candle(instruments: Vec<InstrumentKey>, time_frame: &str) -> Self {
284 Self {
285 subscribe_type: 1,
286 data_type: 1,
287 instruments,
288 depth: None,
289 time_frame: Some(time_frame.to_string()),
290 }
291 }
292}
293
294/// A subscription confirmation in the flat form (server to client).
295///
296/// The parser does not produce this type: confirmations inside [`MarketDataMessage`] use
297/// [`SubscribeSuccessPayload`]. It is kept for code that parses a frame itself.
298#[derive(Debug, Clone, Deserialize)]
299#[serde(rename_all = "camelCase")]
300pub struct SubscribeSuccess {
301 /// Kind of the confirmation, for example `OrderBookSuccess`.
302 pub response_type: String,
303 /// The instruments the subscription covers, if the broker lists them.
304 #[serde(default)]
305 pub instruments: Vec<InstrumentKey>,
306 /// Order book depth, for an order book subscription.
307 #[serde(default)]
308 pub depth: Option<u32>,
309 /// Candle time frame, for a candle subscription.
310 #[serde(default)]
311 pub time_frame: Option<String>,
312}
313
314// ─── outgoing ─────────────────────────────────────────────────────────────────
315
316/// A command a session sends to the broker. It serializes as the plain command, without a wrapper.
317///
318/// The two variants hold the same type: the difference is only the purpose, and the session turns an
319/// unsubscribe into a command with `subscribeType = 1`.
320///
321/// # Examples
322///
323/// ```
324/// use brokapi_bcs_protocol::models::instrument::InstrumentKey;
325/// use brokapi_bcs_protocol::ws::models::{MarketDataSubscribe, WsOutgoing};
326///
327/// let keys = vec![InstrumentKey::new("SBER", "TQBR")];
328/// let msg = WsOutgoing::Subscribe(MarketDataSubscribe::quotes(keys));
329/// let json = serde_json::to_value(&msg)?;
330/// assert_eq!(json["dataType"], 3);
331/// assert_eq!(json["subscribeType"], 0);
332/// # Ok::<(), serde_json::Error>(())
333/// ```
334#[derive(Debug, Clone, PartialEq, Serialize)]
335#[serde(untagged)]
336pub enum WsOutgoing {
337 /// A subscribe command.
338 Subscribe(MarketDataSubscribe),
339 /// An unsubscribe command.
340 Unsubscribe(MarketDataSubscribe),
341}
342
343// ─── market_data ──────────────────────────────────────────────────────────────
344
345/// A frame of the market data channel, told apart by its `responseType`.
346///
347/// The success variants confirm a subscription. The data variants carry the data. A value of
348/// `responseType` this library does not know reads as [`MarketDataMessage::Undefined`].
349///
350/// **Error frames of the documented shape do not arrive as `ErrorResponse`.** The docs show an error as
351/// a frame with the `responseType` of the data (for example `OrderBook`) and an `errors` list. Such a frame
352/// does not fit the data type, so the parser returns it as
353/// [`IncomingMessage::Unknown`](crate::models::messages::IncomingMessage::Unknown) with the raw text. The
354/// `ErrorResponse` variant is for a frame that really has `responseType = "ErrorResponse"`.
355///
356/// # Examples
357///
358/// ```
359/// # use rust_decimal::Decimal;
360/// # use rust_decimal_macros::dec;
361/// use brokapi_bcs_protocol::ws::models::MarketDataMessage;
362///
363/// let frame = r#"{"responseType":"LastTrades","ticker":"SBER","classCode":"TQBR","side":"BUY","price":300.15,"quantity":100}"#;
364/// match serde_json::from_str::<MarketDataMessage>(frame)? {
365/// MarketDataMessage::LastTrades(t) => assert_eq!(t.quantity, dec!(100.0)),
366/// other => panic!("unexpected: {other:?}"),
367/// }
368/// # Ok::<(), serde_json::Error>(())
369/// ```
370#[derive(Debug, Clone, Deserialize)]
371#[serde(tag = "responseType")]
372pub enum MarketDataMessage {
373 /// The subscription to quotes was accepted.
374 QuotesSuccess(SubscribeSuccessPayload),
375 /// A quote update.
376 Quotes(QuotesData),
377 /// The subscription to the order book was accepted.
378 OrderBookSuccess(SubscribeSuccessPayload),
379 /// An order book update.
380 OrderBook(OrderBookData),
381 /// The subscription to the trades tape was accepted.
382 LastTradesSuccess(SubscribeSuccessPayload),
383 /// One trade of the tape.
384 LastTrades(LastTradesData),
385 /// The subscription to the last candle was accepted (`responseType` is `CandleStickSuccess`).
386 #[serde(rename = "CandleStickSuccess")]
387 LastCandleSuccess(SubscribeSuccessPayload),
388 /// An update of the last candle (`responseType` is `CandleStick`).
389 #[serde(rename = "CandleStick")]
390 LastCandle(LastCandleData),
391 /// An error frame with `responseType = "ErrorResponse"`. See the note on the type.
392 ErrorResponse(MarketDataError),
393 /// A `responseType` this library does not know.
394 #[serde(other)]
395 Undefined,
396}
397
398/// The body of a subscription confirmation.
399///
400/// The docs show the instrument as flat `ticker` and `classCode` fields in the confirmation. This type
401/// reads an `instruments` list. If the broker sends the flat form, `instruments` stays empty. Not verified
402/// against the live API which form the broker uses.
403#[derive(Debug, Clone, Deserialize)]
404#[serde(rename_all = "camelCase")]
405pub struct SubscribeSuccessPayload {
406 /// The instruments the subscription covers, if the broker lists them. Empty otherwise.
407 #[serde(default)]
408 pub instruments: Vec<InstrumentKey>,
409 /// Order book depth, for an order book subscription.
410 #[serde(default)]
411 pub depth: Option<u32>,
412 /// Candle time frame, for a candle subscription.
413 #[serde(default)]
414 pub time_frame: Option<String>,
415}
416
417/// A quote update of one instrument.
418///
419/// The current docs name the best sell price `offer` and the last price `last`. Older samples used `ask`
420/// and `lastPrice`. Both spellings are read into [`QuotesData::ask`] and [`QuotesData::last_price`].
421/// The docs give no quantity at the best prices.
422///
423/// # Examples
424///
425/// ```
426/// # use rust_decimal::Decimal;
427/// # use rust_decimal_macros::dec;
428/// use brokapi_bcs_protocol::ws::models::QuotesData;
429///
430/// // The current docs form.
431/// let q: QuotesData = serde_json::from_str(
432/// r#"{"ticker":"SBER","classCode":"TQBR","bid":305.86,"offer":305.96,"last":305.97}"#,
433/// )?;
434/// assert_eq!((q.bid, q.ask, q.last_price), (Some(dec!(305.86)), Some(dec!(305.96)), Some(dec!(305.97))));
435///
436/// // The older form.
437/// let old: QuotesData = serde_json::from_str(r#"{"ticker":"SBER","classCode":"TQBR","ask":305.96,"lastPrice":305.97}"#)?;
438/// assert_eq!(old.ask, Some(dec!(305.96)));
439/// # Ok::<(), serde_json::Error>(())
440/// ```
441#[derive(Debug, Clone, Deserialize)]
442#[serde(rename_all = "camelCase")]
443pub struct QuotesData {
444 /// Ticker.
445 pub ticker: String,
446 /// Class code (board).
447 pub class_code: String,
448 /// Time the data was updated (UTC), kept as the text the broker sends.
449 #[serde(default)]
450 pub date_time: Option<String>,
451 /// Best buy price.
452 #[serde(default)]
453 pub bid: Option<Decimal>,
454 /// Best sell price. The broker docs call the field `offer`, older samples `ask`. Both are read.
455 #[serde(default, alias = "offer")]
456 pub ask: Option<Decimal>,
457 /// Price of the last trade. The broker docs call the field `last`, older samples `lastPrice`. Both are read.
458 #[serde(default, alias = "last")]
459 pub last_price: Option<Decimal>,
460 /// Opening price of the session.
461 #[serde(default)]
462 pub open: Option<Decimal>,
463 /// Closing price of the previous session.
464 #[serde(default)]
465 pub close: Option<Decimal>,
466 /// Highest price of the day.
467 #[serde(default)]
468 pub high: Option<Decimal>,
469 /// Lowest price of the day.
470 #[serde(default)]
471 pub low: Option<Decimal>,
472 /// Price change over the session, in the price currency.
473 #[serde(default)]
474 pub change: Option<Decimal>,
475 /// Price change over the session, in percent.
476 #[serde(default)]
477 pub change_rate: Option<f64>,
478 /// Traded value. Not in the current docs, read if the broker sends it.
479 #[serde(default)]
480 pub value: Option<Decimal>,
481 /// Traded volume. Not in the current docs, read if the broker sends it.
482 #[serde(default)]
483 pub volume: Option<Decimal>,
484 /// Number of trades. Not in the current docs, read if the broker sends it.
485 #[serde(default)]
486 pub trade_num: Option<u64>,
487}
488
489/// One price level of the order book: a price and the quantity at it.
490#[derive(Debug, Clone, Deserialize)]
491pub struct OrderBookLevel {
492 /// Price of the level.
493 pub price: Decimal,
494 /// Quantity at the level, in pieces.
495 pub quantity: Decimal,
496}
497
498/// An order book update. It is a full snapshot of the levels within `depth`, not a delta.
499///
500/// `bids` and `asks` come best price first, as the broker sends them. The code does not sort them.
501///
502/// # Examples
503///
504/// ```
505/// # use rust_decimal::Decimal;
506/// # use rust_decimal_macros::dec;
507/// use brokapi_bcs_protocol::ws::models::OrderBookData;
508///
509/// let book: OrderBookData = serde_json::from_str(
510/// r#"{"ticker":"SBER","classCode":"TQBR","depth":2,
511/// "bids":[{"price":244.30,"quantity":100}],"asks":[{"price":244.35,"quantity":120}]}"#,
512/// )?;
513/// assert_eq!(book.bids[0].price, dec!(244.30));
514/// # Ok::<(), serde_json::Error>(())
515/// ```
516#[derive(Debug, Clone, Deserialize)]
517#[serde(rename_all = "camelCase")]
518pub struct OrderBookData {
519 /// Ticker.
520 pub ticker: String,
521 /// Class code (board).
522 pub class_code: String,
523 /// Depth of the book.
524 #[serde(default)]
525 pub depth: Option<u32>,
526 /// Time the book was updated (UTC), kept as the text the broker sends.
527 #[serde(default)]
528 pub date_time: Option<String>,
529 /// Buy side, best price first.
530 #[serde(default)]
531 pub bids: Vec<OrderBookLevel>,
532 /// Sell side, best price first.
533 #[serde(default)]
534 pub asks: Vec<OrderBookLevel>,
535}
536
537/// One trade of the anonymous trades tape: one trade per frame.
538///
539/// # Examples
540///
541/// ```
542/// use brokapi_bcs_protocol::ws::models::LastTradesData;
543///
544/// let t: LastTradesData = serde_json::from_str(
545/// r#"{"ticker":"SBER","classCode":"TQBR","side":"SELL","volume":30015.0,"price":300.15,"quantity":100}"#,
546/// )?;
547/// assert_eq!(t.side.as_deref(), Some("SELL"));
548/// # Ok::<(), serde_json::Error>(())
549/// ```
550#[derive(Debug, Clone, Deserialize)]
551#[serde(rename_all = "camelCase")]
552pub struct LastTradesData {
553 /// Ticker.
554 pub ticker: String,
555 /// Class code (board).
556 pub class_code: String,
557 /// Side of the trade as text: `BUY` or `SELL` (the REST tape uses `1` and `2`).
558 #[serde(default)]
559 pub side: Option<String>,
560 /// Trade volume in the instrument currency.
561 #[serde(default)]
562 pub volume: Option<Decimal>,
563 /// Trade price.
564 pub price: Decimal,
565 /// Quantity in pieces.
566 pub quantity: Decimal,
567 /// Time of the trade (UTC), kept as the text the broker sends.
568 #[serde(default)]
569 pub date_time: Option<String>,
570}
571
572/// An update of the last candle for a time frame.
573///
574/// # Examples
575///
576/// ```
577/// # use rust_decimal::Decimal;
578/// # use rust_decimal_macros::dec;
579/// use brokapi_bcs_protocol::ws::models::LastCandleData;
580///
581/// let c: LastCandleData = serde_json::from_str(
582/// r#"{"ticker":"SBER","classCode":"TQBR","timeFrame":"M1","open":244.2,"close":244.5,
583/// "high":244.7,"low":243.9,"volume":3200,"dateTime":"2024-11-10T10:30:00.000Z"}"#,
584/// )?;
585/// assert_eq!(c.close, Some(dec!(244.5)));
586/// assert_eq!(c.timestamp.as_deref(), Some("2024-11-10T10:30:00.000Z"));
587/// # Ok::<(), serde_json::Error>(())
588/// ```
589#[derive(Debug, Clone, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct LastCandleData {
592 /// Ticker.
593 #[serde(default)]
594 pub ticker: Option<String>,
595 /// Class code (board).
596 #[serde(default)]
597 pub class_code: Option<String>,
598 /// Time frame of the candle: `M1 M5 M15 M30 H1 H4 D W MN`.
599 #[serde(default)]
600 pub time_frame: Option<String>,
601 /// Time of the candle (UTC), kept as text (JSON field `dateTime`).
602 #[serde(default, rename = "dateTime")]
603 pub timestamp: Option<String>,
604 /// Opening price.
605 #[serde(default)]
606 pub open: Option<Decimal>,
607 /// Closing price.
608 #[serde(default)]
609 pub close: Option<Decimal>,
610 /// Highest price.
611 #[serde(default)]
612 pub high: Option<Decimal>,
613 /// Lowest price.
614 #[serde(default)]
615 pub low: Option<Decimal>,
616 /// Traded volume of the candle, in the currency.
617 #[serde(default)]
618 pub volume: Option<Decimal>,
619}
620
621/// The error part of a market data frame.
622///
623/// Either `error` (one) or `errors` (a list) can be set. The broker docs show the list.
624#[derive(Debug, Clone, Deserialize)]
625pub struct MarketDataError {
626 /// A single error, if the frame has one.
627 #[serde(default)]
628 pub error: Option<MarketDataErrorDetail>,
629 /// The errors of the frame. Empty if there are none.
630 #[serde(default)]
631 pub errors: Vec<MarketDataErrorDetail>,
632}
633
634/// One error of a market data frame.
635///
636/// The docs list these codes: `NO_DATE` (no data), `NOT_FOUND` (instrument not found), `INCORRECT_JSON`
637/// (invalid JSON), `BAD_REQUEST` (execution error), `UNAUTHORIZED` (client not authorized).
638#[derive(Debug, Clone, Deserialize)]
639pub struct MarketDataErrorDetail {
640 /// Error code, see the type docs.
641 pub code: String,
642 /// Text of the error.
643 pub message: String,
644}
645
646// ─── orders ───────────────────────────────────────────────────────────────────
647
648fn unknown_side() -> OrderSide {
649 OrderSide::Unknown
650}
651
652/// A message about an order from the order channels.
653///
654/// It comes from `orders/events/ws` (the current channel) and from the deprecated execution and transaction
655/// channels. It carries the report in [`OrderMessage::data`].
656///
657/// # Examples
658///
659/// ```
660/// # use rust_decimal::Decimal;
661/// # use rust_decimal_macros::dec;
662/// use brokapi_bcs_protocol::models::enums::{ExecutionType, OrderStatus};
663/// use brokapi_bcs_protocol::ws::models::OrderMessage;
664///
665/// let m: OrderMessage = serde_json::from_str(
666/// r#"{"originalClientOrderId":"o-1","clientOrderId":"",
667/// "data":{"orderStatus":"1","executionType":"1","ticker":"SBER","classCode":"TQBR",
668/// "side":"1","orderType":"2","orderQuantity":10,"executedQuantity":4,
669/// "lastQuantity":4,"remainedQuantity":6,"price":300.0}}"#,
670/// )?;
671/// let data = m.data.unwrap();
672/// assert_eq!(data.order_status, OrderStatus::PartiallyFilled);
673/// assert_eq!(data.execution_type, Some(ExecutionType::PartialFill));
674/// assert_eq!(data.last_quantity, dec!(4.0));
675/// # Ok::<(), serde_json::Error>(())
676/// ```
677#[derive(Debug, Clone, Deserialize)]
678#[serde(rename_all = "camelCase")]
679pub struct OrderMessage {
680 /// The `clientOrderId` you gave when you placed the order. For an edit it is the id of the changed order, as the broker docs word it.
681 #[serde(default)]
682 pub original_client_order_id: Option<String>,
683 /// Id of the request that caused the message. Empty text if the broker sent none.
684 #[serde(default)]
685 pub client_order_id: String,
686 /// The report. `None` if the message has none.
687 #[serde(default)]
688 pub data: Option<OrderExecutionData>,
689}
690
691/// The report inside an [`OrderMessage`]: the state of the order and the last fill.
692///
693/// It is the WebSocket form of [`crate::http::models::OrderExecutionData`], with typed enums. Quantities
694/// are in pieces. Fields the broker leaves out in some messages read as `0` or `None`, noted per field.
695///
696/// * `execution_type` says why the message was sent. A fill has `last_quantity` above 0 and a trade
697/// id in `execution_id`.
698/// * This struct has no field for a reject reason. The REST report has `rejectReason`, but the events
699/// docs do not list it among the fields of `data`.
700#[derive(Debug, Clone, Deserialize)]
701#[serde(rename_all = "camelCase")]
702pub struct OrderExecutionData {
703 /// Type of the message, as text.
704 #[serde(default)]
705 pub message_type: Option<String>,
706 /// State of the order. Required: a message without it does not parse.
707 pub order_status: OrderStatus,
708 /// Why the message was sent.
709 #[serde(default)]
710 pub execution_type: Option<ExecutionType>,
711 /// Quantity of the order, in pieces.
712 #[serde(default)]
713 pub order_quantity: Option<Decimal>,
714 /// Executed quantity so far, in pieces.
715 #[serde(default)]
716 pub executed_quantity: Option<Decimal>,
717 /// Absent in pure status messages of the current `orders/events/ws` channel (no fill): `0`.
718 #[serde(default)]
719 pub last_quantity: Decimal,
720 /// Remaining quantity, in pieces. `0` if the message has none.
721 #[serde(default)]
722 pub remained_quantity: Decimal,
723 /// Ticker. Required.
724 pub ticker: String,
725 /// Class code (board). Empty text if absent.
726 #[serde(default)]
727 pub class_code: String,
728 /// Buy or sell. [`OrderSide::Unknown`] if absent.
729 #[serde(default = "unknown_side")]
730 pub side: OrderSide,
731 /// Type of the order. Types other than market and limit read as [`OrderType::Unknown`].
732 #[serde(default)]
733 pub order_type: Option<OrderType>,
734 /// Average execution price. For repo and spread deals, the price of the first part.
735 #[serde(default)]
736 pub average_price: Option<Decimal>,
737 /// Id of the order at the exchange.
738 #[serde(default)]
739 pub order_id: Option<String>,
740 /// Id of the trade (fill). Set when the message reports a fill.
741 #[serde(default)]
742 pub execution_id: Option<String>,
743 /// Absent in some cancel / reject messages: `0`.
744 #[serde(default)]
745 pub price: Decimal,
746 /// Currency.
747 #[serde(default)]
748 pub currency: Option<String>,
749 /// Client code.
750 #[serde(default)]
751 pub client_code: Option<String>,
752 /// Time of the transaction (UTC). `None` if absent or the epoch.
753 #[serde(default, with = "date_format::option")]
754 pub transaction_time: Option<DateTime<Utc>>,
755 /// Trade date, kept as text. For the evening session of the derivatives market it is the date of the next session.
756 #[serde(default)]
757 pub trade_date: Option<String>,
758 /// Order number.
759 #[serde(default)]
760 pub order_number: Option<String>,
761 /// Accrued coupon income.
762 #[serde(default)]
763 pub accrued_coupon: Option<Decimal>,
764 /// Volume of the trade.
765 #[serde(default)]
766 pub execution_value: Option<Decimal>,
767 /// Commission.
768 #[serde(default)]
769 pub commission: Option<Decimal>,
770 /// Exchange id.
771 #[serde(default)]
772 pub security_exchange: Option<String>,
773}
774
775// ─── portfolio ────────────────────────────────────────────────────────────────
776
777/// A portfolio frame: an array of positions, the same records as the REST portfolio.
778pub type WsPortfolioData = Vec<WsPortfolioPosition>;
779
780/// One position or balance of a portfolio frame.
781///
782/// The same fields as [`crate::http::models::PortfolioPosition`], without the deprecated ones the docs
783/// mark (sub-account, agreement, logo). All but `type` are optional.
784///
785/// # Examples
786///
787/// ```
788/// # use rust_decimal::Decimal;
789/// # use rust_decimal_macros::dec;
790/// use brokapi_bcs_protocol::ws::models::WsPortfolioPosition;
791///
792/// let p: WsPortfolioPosition = serde_json::from_str(
793/// r#"{"type":"depoLimit","ticker":"SBER","quantity":10,"unrealizedPL":40.0}"#,
794/// )?;
795/// assert_eq!(p.unrealized_pl, Some(dec!(40.0)));
796/// # Ok::<(), serde_json::Error>(())
797/// ```
798#[derive(Debug, Clone, Deserialize)]
799#[serde(rename_all = "camelCase")]
800pub struct WsPortfolioPosition {
801 /// Kind of the record (JSON field `type`), for example `depoLimit` for a securities position and `moneyLimit` for a currency balance.
802 #[serde(rename = "type")]
803 pub position_type: String,
804 /// Ticker.
805 #[serde(default)]
806 pub ticker: Option<String>,
807 /// Name of the instrument for display.
808 #[serde(default)]
809 pub display_name: Option<String>,
810 /// Ticker of the base asset, for derivatives.
811 #[serde(default)]
812 pub base_asset_ticker: Option<String>,
813 /// Price currency.
814 #[serde(default)]
815 pub currency: Option<String>,
816 /// Exchange code.
817 #[serde(default)]
818 pub exchange: Option<String>,
819 /// Trading account.
820 #[serde(default)]
821 pub account: Option<String>,
822 /// Top-level instrument type, as text. The broker docs list no values.
823 #[serde(default)]
824 pub upper_type: Option<String>,
825 /// Instrument type, as text.
826 #[serde(default)]
827 pub instrument_type: Option<String>,
828 /// Settlement term code.
829 #[serde(default)]
830 pub term: Option<String>,
831 /// Quantity in pieces.
832 #[serde(default)]
833 pub quantity: Option<Decimal>,
834 /// Quantity held by active orders or by collateral (initial margin).
835 #[serde(default)]
836 pub locked: Option<Decimal>,
837 /// Balance price (the price the position was opened at), in the price currency.
838 #[serde(default)]
839 pub balance_price: Option<Decimal>,
840 /// Current price.
841 #[serde(default)]
842 pub current_price: Option<Decimal>,
843 /// Balance value (at the opening price), in the price currency.
844 #[serde(default)]
845 pub balance_value: Option<Decimal>,
846 /// Balance value in roubles.
847 #[serde(default)]
848 pub balance_value_rub: Option<Decimal>,
849 /// Balance value in dollars.
850 #[serde(default)]
851 pub balance_value_usd: Option<Decimal>,
852 /// Balance value in euros.
853 #[serde(default)]
854 pub balance_value_eur: Option<Decimal>,
855 /// Current value in the price currency.
856 #[serde(default)]
857 pub current_value: Option<Decimal>,
858 /// Current value in roubles.
859 #[serde(default)]
860 pub current_value_rub: Option<Decimal>,
861 /// Current value in dollars.
862 #[serde(default)]
863 pub current_value_usd: Option<Decimal>,
864 /// Current value in euros.
865 #[serde(default)]
866 pub current_value_eur: Option<Decimal>,
867 /// Price change since the position was opened (JSON field `unrealizedPL`, with capital letters).
868 #[serde(default, rename = "unrealizedPL", alias = "unrealizedPl")]
869 pub unrealized_pl: Option<Decimal>,
870 /// The same change in percent (JSON field `unrealizedPercentPL`).
871 #[serde(default, rename = "unrealizedPercentPL", alias = "unrealizedPercentPl")]
872 pub unrealized_percent_pl: Option<f64>,
873 /// Price change over the day (JSON field `dailyPL`). The broker docs word it as "daily change of the price since the position was opened".
874 #[serde(default, rename = "dailyPL", alias = "dailyPl")]
875 pub daily_pl: Option<Decimal>,
876 /// The same change in percent (JSON field `dailyPercentPL`).
877 #[serde(default, rename = "dailyPercentPL", alias = "dailyPercentPl")]
878 pub daily_percent_pl: Option<f64>,
879 /// Share of the portfolio in percent, of its value in roubles.
880 #[serde(default)]
881 pub portfolio_share: Option<f64>,
882 /// Number of decimal places of the price.
883 #[serde(default)]
884 pub scale: Option<u32>,
885 /// Minimum price step.
886 #[serde(default)]
887 pub minimum_step: Option<Decimal>,
888 /// Class code (board) of the instrument.
889 #[serde(default)]
890 pub board: Option<String>,
891 /// Unit of the price.
892 #[serde(default)]
893 pub price_unit: Option<String>,
894 /// Face value, for bonds.
895 #[serde(default)]
896 pub face_value: Option<Decimal>,
897 /// Accrued coupon income, for bonds.
898 #[serde(default)]
899 pub accrued_income: Option<Decimal>,
900 /// `true` if the class is blocked.
901 #[serde(default)]
902 pub is_blocked: Option<bool>,
903 /// `true` if the trading account is blocked.
904 #[serde(default)]
905 pub is_blocked_trade_account: Option<bool>,
906 /// Amount reserved as collateral (initial margin) on futures.
907 #[serde(default)]
908 pub locked_for_futures: Option<Decimal>,
909 /// Quantity in a lot.
910 #[serde(default)]
911 pub ratio_quantity: Option<Decimal>,
912 /// Expiration date, kept as text.
913 #[serde(default)]
914 pub expire_date: Option<String>,
915}
916
917// ─── limits ───────────────────────────────────────────────────────────────────
918
919/// A limits frame: one object with the current limits of the account.
920///
921/// The four lists are optional, because the broker may leave out a list that is empty.
922///
923/// # Examples
924///
925/// ```
926/// # use rust_decimal::Decimal;
927/// # use rust_decimal_macros::dec;
928/// use brokapi_bcs_protocol::ws::models::LimitsData;
929///
930/// let l: LimitsData = serde_json::from_str(
931/// r#"{"moneyLimits":[{"currencyCode":"RUB","locked":10.0,"quantity":{"type":"T0","value":500.0}}]}"#,
932/// )?;
933/// assert!(l.depo_limit.is_none());
934/// assert_eq!(l.money_limits.unwrap()[0].quantity.as_ref().unwrap().value, dec!(500.0));
935/// # Ok::<(), serde_json::Error>(())
936/// ```
937#[derive(Debug, Clone, Deserialize)]
938#[serde(rename_all = "camelCase")]
939pub struct LimitsData {
940 /// Securities positions.
941 #[serde(default)]
942 pub depo_limit: Option<Vec<DepoLimitEntry>>,
943 /// Futures positions.
944 #[serde(default)]
945 pub future_holding: Option<Vec<FutureHoldingEntry>>,
946 /// Money balances.
947 #[serde(default)]
948 pub money_limits: Option<Vec<MoneyLimitEntry>>,
949 /// Limits of the derivatives market.
950 #[serde(default)]
951 pub futures_limits: Option<Vec<FuturesLimitEntry>>,
952}
953
954/// A securities position in a limits frame (`depoLimit`).
955#[derive(Debug, Clone, Deserialize)]
956#[serde(rename_all = "camelCase")]
957pub struct DepoLimitEntry {
958 /// Ticker.
959 #[serde(default)]
960 pub ticker: Option<String>,
961 /// Class code (board).
962 #[serde(default)]
963 pub class_code: Option<String>,
964 /// Exchange code.
965 #[serde(default)]
966 pub exchange: Option<String>,
967 /// Weighted average price at which the position was opened.
968 #[serde(default)]
969 pub average_price: Option<Decimal>,
970 /// Quantity in pieces, with the settlement mode.
971 #[serde(default)]
972 pub quantity: Option<Quantity>,
973 /// Quantity in lots, with the settlement mode.
974 #[serde(default)]
975 pub quantity_batch: Option<Quantity>,
976 /// Instrument type as the broker names it.
977 #[serde(default)]
978 pub instrument_type: Option<String>,
979 /// Time the broker updated the data, kept as text.
980 #[serde(default)]
981 pub load_date: Option<String>,
982 /// Money held by active buy orders for this security.
983 #[serde(default)]
984 pub locked_buy_value: Option<Decimal>,
985 /// Money held by active sell orders for this security. The docs word it the same way as for buy.
986 #[serde(default)]
987 pub locked_sell_value: Option<Decimal>,
988}
989
990/// A money balance in a limits frame (`moneyLimits`).
991#[derive(Debug, Clone, Deserialize)]
992#[serde(rename_all = "camelCase")]
993pub struct MoneyLimitEntry {
994 /// Exchange code.
995 #[serde(default)]
996 pub exchange: Option<String>,
997 /// Currency code (JSON field `currencyCode`).
998 #[serde(default, rename = "currencyCode")]
999 pub currency: Option<String>,
1000 /// Money held by active orders.
1001 #[serde(default)]
1002 pub locked: Option<Decimal>,
1003 /// Weighted average opening price.
1004 #[serde(default)]
1005 pub average_price: Option<Decimal>,
1006 /// Instrument type as the broker names it.
1007 #[serde(default)]
1008 pub instrument_type: Option<String>,
1009 /// Amount of money, with the settlement mode.
1010 #[serde(default)]
1011 pub quantity: Option<Quantity>,
1012 /// Time the broker updated the data, kept as text.
1013 #[serde(default)]
1014 pub load_date: Option<String>,
1015}
1016
1017/// A futures position in a limits frame (`futureHolding`).
1018#[derive(Debug, Clone, Deserialize)]
1019#[serde(rename_all = "camelCase")]
1020pub struct FutureHoldingEntry {
1021 /// Ticker.
1022 #[serde(default)]
1023 pub ticker: Option<String>,
1024 /// Class code (board).
1025 #[serde(default)]
1026 pub class_code: Option<String>,
1027 /// Exchange code.
1028 #[serde(default)]
1029 pub exchange: Option<String>,
1030 /// Estimated value of the planned positions after the nearest clearing.
1031 #[serde(default)]
1032 pub cbpl_planned: Option<Decimal>,
1033 /// Estimate of the variation margin on the positions.
1034 #[serde(default)]
1035 pub var_margin: Option<Decimal>,
1036 /// Value of the derivatives market positions.
1037 #[serde(default)]
1038 pub position_value: Option<Decimal>,
1039 /// Current net positions.
1040 #[serde(default)]
1041 pub total_net: Option<Decimal>,
1042 /// Execution date, kept as text.
1043 #[serde(default)]
1044 pub execution_date: Option<String>,
1045 /// Total variation margin of the main clearing, credited on all positions.
1046 #[serde(default)]
1047 pub total_var_margin: Option<Decimal>,
1048 /// Variation margin credited during clearing.
1049 #[serde(default)]
1050 pub real_var_margin: Option<Decimal>,
1051 /// Weighted average opening price.
1052 #[serde(default)]
1053 pub average_price: Option<Decimal>,
1054 /// Instrument type as the broker names it.
1055 #[serde(default)]
1056 pub instrument_type: Option<String>,
1057 /// Date of the trading session, kept as text.
1058 #[serde(default)]
1059 pub trade_date: Option<String>,
1060}
1061
1062/// A limit of the derivatives market in a limits frame (`futuresLimits`).
1063#[derive(Debug, Clone, Deserialize)]
1064#[serde(rename_all = "camelCase")]
1065pub struct FuturesLimitEntry {
1066 /// Currency code (JSON field `currencyCode`).
1067 #[serde(default, rename = "currencyCode")]
1068 pub currency: Option<String>,
1069 /// Exchange code.
1070 #[serde(default)]
1071 pub exchange: Option<String>,
1072 /// Variation margin for the current day (the JSON field is spelled `accruedint`).
1073 #[serde(default, rename = "accruedint")]
1074 pub accrued_int: Option<Decimal>,
1075 /// Current limit of open positions.
1076 #[serde(default)]
1077 pub cbp_limit: Option<Decimal>,
1078 /// Positions after the last clearing.
1079 #[serde(default)]
1080 pub cbpl_used: Option<Decimal>,
1081 /// Estimated value of the planned positions after the nearest clearing.
1082 #[serde(default)]
1083 pub cbpl_planned: Option<Decimal>,
1084 /// Collateral (initial margin) reserved for the client orders.
1085 #[serde(default)]
1086 pub cbpl_used_for_orders: Option<Decimal>,
1087 /// Collateral (initial margin) reserved for the open client positions.
1088 #[serde(default)]
1089 pub cbpl_used_for_positions: Option<Decimal>,
1090 /// Premiums on options.
1091 #[serde(default)]
1092 pub options_premium: Option<Decimal>,
1093 /// Instrument type as the broker names it.
1094 #[serde(default)]
1095 pub instrument_type: Option<String>,
1096 /// Time the broker updated the data, kept as text.
1097 #[serde(default)]
1098 pub load_date: Option<String>,
1099 /// Estimate of the variation margin on the positions.
1100 #[serde(default)]
1101 pub var_margin: Option<Decimal>,
1102 /// Variation margin credited during clearing.
1103 #[serde(default)]
1104 pub real_var_margin: Option<Decimal>,
1105}
1106
1107/// An amount with its settlement mode (`T0`, `T1`, `T365` and so on), in limits frames.
1108#[derive(Debug, Clone, Deserialize)]
1109#[serde(rename_all = "camelCase")]
1110pub struct Quantity {
1111 /// Settlement mode, such as `T0` (JSON field `type`).
1112 #[serde(rename = "type")]
1113 pub quantity_type: String,
1114 /// The amount: pieces, lots or money, depending on the field that holds it.
1115 pub value: Decimal,
1116}
1117
1118// ─── marginal_indicators ──────────────────────────────────────────────────────
1119
1120/// A margin indicators frame: the margin picture of the account in one object.
1121///
1122/// The broker docs describe nine parts. Every part is optional here. **Some parts of this library do not
1123/// match the docs:** the docs name the fields of `marginParameters` `marginValue`, `collateral`,
1124/// `marginCall` and `forcedClosure`, and give `futuresParameters` as `futuresValue` and a
1125/// `futuresPortfolio` list. [`MarginParameters`] and [`FuturesParameters`] read other names, so they stay
1126/// `None` against a broker that follows the docs. The docs list `fortsMarginCall`, `fortsForcedClosure`
1127/// and `riskLevel` for the derivatives stability, which [`FortsStability`] does not read either. Not
1128/// verified against the live API.
1129///
1130/// # Examples
1131///
1132/// ```
1133/// # use rust_decimal::Decimal;
1134/// # use rust_decimal_macros::dec;
1135/// use brokapi_bcs_protocol::ws::models::MarginalIndicatorsData;
1136///
1137/// let m: MarginalIndicatorsData = serde_json::from_str(
1138/// r#"{"portfolioCurrentValue":{"currentValueRub":512000.0},"cashByCurrency":[{"currency":"RUB","sum":50000.0}]}"#,
1139/// )?;
1140/// assert_eq!(m.portfolio_current_value.unwrap().current_value_rub, Some(dec!(512000.0)));
1141/// assert_eq!(m.cash_by_currency.len(), 1);
1142/// # Ok::<(), serde_json::Error>(())
1143/// ```
1144#[derive(Debug, Clone, Deserialize)]
1145#[serde(rename_all = "camelCase")]
1146pub struct MarginalIndicatorsData {
1147 /// Data about the account.
1148 #[serde(default)]
1149 pub agreement_data: Option<AgreementData>,
1150 /// Current value of the portfolio and its changes.
1151 #[serde(default)]
1152 pub portfolio_current_value: Option<PortfolioCurrentValue>,
1153 /// Stability (margin level) of the portfolio.
1154 #[serde(default)]
1155 pub portfolio_stability: Option<PortfolioStability>,
1156 /// Stability of the derivatives market account.
1157 #[serde(default)]
1158 pub forts_stability: Option<FortsStability>,
1159 /// Parameters of the margin portfolio. See the note on the type.
1160 #[serde(default)]
1161 pub margin_parameters: Option<MarginParameters>,
1162 /// Free money by currency. Empty if the broker sent none.
1163 #[serde(default)]
1164 pub cash_by_currency: Vec<CashByCurrency>,
1165 /// Debts by currency.
1166 #[serde(default)]
1167 pub money_debt: Option<MoneyDebtWrapper>,
1168 /// Margin positions.
1169 #[serde(default)]
1170 pub margin_securities: Option<MarginSecuritiesWrapper>,
1171 /// Futures positions. See the note on the type.
1172 #[serde(default)]
1173 pub futures_parameters: Option<FuturesParameters>,
1174}
1175
1176/// Data about the brokerage account in a margin indicators frame (`agreementData`).
1177#[derive(Debug, Clone, Deserialize)]
1178#[serde(rename_all = "camelCase")]
1179pub struct AgreementData {
1180 /// Account number.
1181 #[serde(default)]
1182 pub number: Option<String>,
1183 /// `true` if the account is an IIA (individual investment account).
1184 #[serde(default)]
1185 pub is_iia: Option<bool>,
1186 /// Name for display.
1187 #[serde(default)]
1188 pub display_name: Option<String>,
1189 /// Id of the sub-account.
1190 #[serde(default)]
1191 pub subaccount_id: Option<String>,
1192 /// Colour of the account as a HEX code.
1193 #[serde(default)]
1194 pub color: Option<String>,
1195}
1196
1197/// Free money in one currency (`cashByCurrency`).
1198#[derive(Debug, Clone, Deserialize)]
1199#[serde(rename_all = "camelCase")]
1200pub struct CashByCurrency {
1201 /// Currency.
1202 #[serde(default)]
1203 pub currency: Option<String>,
1204 /// Amount.
1205 #[serde(default)]
1206 pub sum: Option<Decimal>,
1207}
1208
1209/// Debts of the account (`moneyDebt`): the total and the debt per currency.
1210#[derive(Debug, Clone, Deserialize)]
1211#[serde(rename_all = "camelCase")]
1212pub struct MoneyDebtWrapper {
1213 /// Total debt.
1214 #[serde(default)]
1215 pub total_money_debt: Option<Decimal>,
1216 /// Debt per currency.
1217 #[serde(default)]
1218 pub debt_by_currency: Vec<DebtByCurrency>,
1219}
1220
1221/// Debt in one currency.
1222#[derive(Debug, Clone, Deserialize)]
1223#[serde(rename_all = "camelCase")]
1224pub struct DebtByCurrency {
1225 /// Currency.
1226 #[serde(default)]
1227 pub currency: Option<String>,
1228 /// Amount of the debt.
1229 #[serde(default)]
1230 pub sum: Option<Decimal>,
1231}
1232
1233/// Margin positions (`marginSecurities`): the total and the list.
1234#[derive(Debug, Clone, Deserialize)]
1235#[serde(rename_all = "camelCase")]
1236pub struct MarginSecuritiesWrapper {
1237 /// Total. The docs label this field "total debt".
1238 #[serde(default)]
1239 pub total_margin_security: Option<Decimal>,
1240 /// The margin positions.
1241 #[serde(default)]
1242 pub margin_by_security: Vec<MarginBySecurity>,
1243}
1244
1245/// One margin position.
1246#[derive(Debug, Clone, Deserialize)]
1247#[serde(rename_all = "camelCase")]
1248pub struct MarginBySecurity {
1249 /// Ticker.
1250 #[serde(default)]
1251 pub ticker: Option<String>,
1252 /// Trading mode (board).
1253 #[serde(default)]
1254 pub board: Option<String>,
1255 /// Name for display.
1256 #[serde(default)]
1257 pub display_name: Option<String>,
1258 /// Link to a logo. Deprecated by the broker.
1259 #[serde(default)]
1260 pub logo_link: Option<String>,
1261 /// Quantity.
1262 #[serde(default)]
1263 pub quantity: Option<Decimal>,
1264 /// Current value.
1265 #[serde(default)]
1266 pub current_value: Option<Decimal>,
1267 /// Currency.
1268 #[serde(default)]
1269 pub currency: Option<String>,
1270 /// Discount in percent.
1271 #[serde(default)]
1272 pub discount: Option<f64>,
1273}
1274
1275/// Current value of the portfolio and its changes (`portfolioCurrentValue`).
1276#[derive(Debug, Clone, Deserialize)]
1277#[serde(rename_all = "camelCase")]
1278pub struct PortfolioCurrentValue {
1279 /// Current value in roubles.
1280 #[serde(default)]
1281 pub current_value_rub: Option<Decimal>,
1282 /// Change over the day, in roubles.
1283 #[serde(default)]
1284 pub daily_portfolio_change_rub: Option<Decimal>,
1285 /// Change over the day, in percent.
1286 #[serde(default)]
1287 pub daily_portfolio_change_perc: Option<f64>,
1288 /// Change over all time, in roubles.
1289 #[serde(default)]
1290 pub total_portfolio_change_rub: Option<Decimal>,
1291 /// Change over all time, in percent.
1292 #[serde(default)]
1293 pub total_portfolio_change_perc: Option<f64>,
1294 /// Collateral available.
1295 #[serde(default)]
1296 pub collateral_available: Option<Decimal>,
1297 /// Variation margin.
1298 #[serde(default)]
1299 pub var_margin: Option<Decimal>,
1300}
1301
1302/// Stability of the portfolio (`portfolioStability`).
1303#[derive(Debug, Clone, Deserialize)]
1304#[serde(rename_all = "camelCase")]
1305pub struct PortfolioStability {
1306 /// `true` if margin trading is on.
1307 #[serde(default)]
1308 pub is_margin_on: Option<bool>,
1309 /// All debts, in roubles.
1310 #[serde(default)]
1311 pub borrowed_funds: Option<Decimal>,
1312 /// Id of the status, 0 to 5 by the broker docs.
1313 #[serde(default)]
1314 pub stability_status_id: Option<u32>,
1315 /// Text of the status.
1316 #[serde(default)]
1317 pub stability_status_name: Option<String>,
1318 /// Amount needed to get out of a margin call.
1319 #[serde(default)]
1320 pub refill_sum: Option<Decimal>,
1321}
1322
1323/// Stability of the derivatives market account (`fortsStability`).
1324#[derive(Debug, Clone, Deserialize)]
1325#[serde(rename_all = "camelCase")]
1326pub struct FortsStability {
1327 /// Own funds.
1328 #[serde(default)]
1329 pub own_funds: Option<Decimal>,
1330 /// Free funds.
1331 #[serde(default)]
1332 pub free_cash: Option<Decimal>,
1333 /// Id of the status.
1334 #[serde(default)]
1335 pub stability_status_id: Option<u32>,
1336 /// Text of the status.
1337 #[serde(default)]
1338 pub stability_status_name: Option<String>,
1339}
1340
1341/// Parameters of the margin portfolio (`marginParameters`).
1342///
1343/// The fields here are the names this library reads. The current docs name the fields `marginValue`,
1344/// `collateral`, `marginCall` and `forcedClosure` instead. See the note on [`MarginalIndicatorsData`].
1345#[derive(Debug, Clone, Deserialize)]
1346#[serde(rename_all = "camelCase")]
1347pub struct MarginParameters {
1348 /// Portfolio value.
1349 #[serde(default)]
1350 pub portfolio_value: Option<Decimal>,
1351 /// Initial margin (collateral).
1352 #[serde(default)]
1353 pub initial_margin: Option<Decimal>,
1354 /// Minimal margin.
1355 #[serde(default)]
1356 pub minimal_margin: Option<Decimal>,
1357 /// Corrected margin.
1358 #[serde(default)]
1359 pub corrected_margin: Option<Decimal>,
1360 /// Corrected portfolio value.
1361 #[serde(default)]
1362 pub corrected_portfolio_value: Option<Decimal>,
1363 /// Liquid portfolio value.
1364 #[serde(default)]
1365 pub liquid_portfolio_value: Option<Decimal>,
1366 /// Assets without a discount.
1367 #[serde(default)]
1368 pub assets_without_discount: Option<Decimal>,
1369}
1370
1371/// Futures parameters (`futuresParameters`).
1372///
1373/// The fields here are the names this library reads. The current docs give `futuresValue` and a
1374/// `futuresPortfolio` list instead. See the note on [`MarginalIndicatorsData`].
1375#[derive(Debug, Clone, Deserialize)]
1376#[serde(rename_all = "camelCase")]
1377pub struct FuturesParameters {
1378 /// Initial margin (collateral).
1379 #[serde(default)]
1380 pub initial_margin: Option<Decimal>,
1381 /// Minimal margin.
1382 #[serde(default)]
1383 pub minimal_margin: Option<Decimal>,
1384 /// Variation margin.
1385 #[serde(default)]
1386 pub var_margin: Option<Decimal>,
1387 /// Free funds.
1388 #[serde(default)]
1389 pub free_cash: Option<Decimal>,
1390}