brokapi_bcs/map/
portfolio.rs1use brokapi_core::Decimal;
4use std::time::Instant;
5
6use brokapi_bcs_protocol::http::models::PortfolioPosition as RestPortfolioPosition;
7use brokapi_bcs_protocol::ws::models::WsPortfolioPosition;
8use brokapi_core::{Balance, Event, Money, Portfolio, Position, Price, Qty};
9
10use super::{instrument_id_ticker, timestamps};
11
12pub fn map_rest_portfolio(positions: &[RestPortfolioPosition], now: Instant) -> Portfolio {
18 map_portfolio_positions(positions.iter().map(rest_pos), now)
19}
20
21pub fn map_ws_portfolio(positions: &[WsPortfolioPosition], now: Instant) -> Event {
24 Event::Portfolio(map_portfolio_positions(positions.iter().map(ws_pos), now))
25}
26
27struct PosView<'a> {
28 position_type: &'a str,
29 ticker: Option<&'a str>,
30 board: Option<&'a str>,
31 currency: Option<&'a str>,
32 quantity: Option<Decimal>,
33 locked: Option<Decimal>,
34 balance_price: Option<Decimal>,
35 current_price: Option<Decimal>,
36}
37
38fn rest_pos(p: &RestPortfolioPosition) -> PosView<'_> {
39 PosView {
40 position_type: &p.position_type,
41 ticker: p.ticker.as_deref(),
42 board: p.board.as_deref(),
43 currency: p.currency.as_deref(),
44 quantity: p.quantity,
45 locked: p.locked,
46 balance_price: p.balance_price,
47 current_price: p.current_price,
48 }
49}
50
51fn ws_pos(p: &WsPortfolioPosition) -> PosView<'_> {
52 PosView {
53 position_type: &p.position_type,
54 ticker: p.ticker.as_deref(),
55 board: p.board.as_deref(),
56 currency: p.currency.as_deref(),
57 quantity: p.quantity,
58 locked: p.locked,
59 balance_price: p.balance_price,
60 current_price: p.current_price,
61 }
62}
63
64fn map_portfolio_positions<'a>(
65 positions: impl Iterator<Item = PosView<'a>>,
66 now: Instant,
67) -> Portfolio {
68 let ts = timestamps(now, None);
69 let mut balances = Vec::new();
70 let mut holdings = Vec::new();
71
72 for pos in positions {
73 if pos.position_type.eq_ignore_ascii_case("moneyLimit") {
74 if let Some(currency) = pos.currency {
75 balances.push(Balance {
76 currency: currency.to_string(),
77 available: Money::new(pos.quantity.unwrap_or(Decimal::ZERO)),
78 blocked: Money::new(pos.locked.unwrap_or(Decimal::ZERO)),
79 ts,
80 });
81 }
82 } else if let (Some(ticker), Some(board)) = (pos.ticker, pos.board) {
83 holdings.push(Position {
84 instrument: instrument_id_ticker(ticker, board),
85 qty: Qty::new(pos.quantity.unwrap_or(Decimal::ZERO)),
86 avg_price: pos.balance_price.map(Price::new),
87 current_price: pos.current_price.map(Price::new),
88 ts,
89 });
90 }
91 }
92
93 Portfolio {
94 balances,
95 positions: holdings,
96 ts,
97 }
98}
99
100pub fn map_portfolio(positions: &[WsPortfolioPosition], now: Instant) -> Portfolio {
102 map_portfolio_positions(positions.iter().map(ws_pos), now)
103}