1use brokapi_core::{
4 Instrument, InstrumentId, InstrumentKind, Money, Price, Qty, TradingAvailability,
5 TradingParams, TradingStatus,
6};
7use brokapi_tbank_protocol::v1 as pb;
8
9use crate::ids::{id_from_parts, price_of_quotation, wall_nanos};
10
11pub fn kind(k: pb::InstrumentType) -> InstrumentKind {
27 match k {
28 pb::InstrumentType::Bond => InstrumentKind::Bond,
29 pb::InstrumentType::Share => InstrumentKind::Stock,
30 pb::InstrumentType::Currency => InstrumentKind::Currency,
31 pb::InstrumentType::Etf => InstrumentKind::Etf,
32 pb::InstrumentType::Futures => InstrumentKind::Future,
33 pb::InstrumentType::Option => InstrumentKind::Option,
34 _ => InstrumentKind::Other,
35 }
36}
37
38pub fn instrument_from_full(i: &pb::Instrument) -> Instrument {
56 Instrument {
57 id: id_from_parts(&i.uid, &i.figi, &i.ticker, &i.class_code),
58 name: i.name.clone(),
59 isin: (!i.isin.is_empty()).then(|| i.isin.clone()),
60 kind: kind(i.instrument_kind()),
61 currency: i.currency.clone(),
62 exchange: i.class_code.clone(),
63 }
64}
65
66#[allow(clippy::too_many_arguments)]
69fn listed(
70 kind: InstrumentKind,
71 uid: &str,
72 figi: &str,
73 ticker: &str,
74 class_code: &str,
75 isin: &str,
76 name: &str,
77 currency: &str,
78) -> Instrument {
79 Instrument {
80 id: id_from_parts(uid, figi, ticker, class_code),
81 name: name.to_string(),
82 isin: (!isin.is_empty()).then(|| isin.to_string()),
83 kind,
84 currency: currency.to_string(),
85 exchange: class_code.to_string(),
86 }
87}
88
89pub fn from_share(i: &pb::Share) -> Instrument {
91 listed(
92 InstrumentKind::Stock,
93 &i.uid,
94 &i.figi,
95 &i.ticker,
96 &i.class_code,
97 &i.isin,
98 &i.name,
99 &i.currency,
100 )
101}
102
103pub fn from_bond(i: &pb::Bond) -> Instrument {
105 listed(
106 InstrumentKind::Bond,
107 &i.uid,
108 &i.figi,
109 &i.ticker,
110 &i.class_code,
111 &i.isin,
112 &i.name,
113 &i.currency,
114 )
115}
116
117pub fn from_etf(i: &pb::Etf) -> Instrument {
119 listed(
120 InstrumentKind::Etf,
121 &i.uid,
122 &i.figi,
123 &i.ticker,
124 &i.class_code,
125 &i.isin,
126 &i.name,
127 &i.currency,
128 )
129}
130
131pub fn from_future(i: &pb::Future) -> Instrument {
133 listed(
134 InstrumentKind::Future,
135 &i.uid,
136 &i.figi,
137 &i.ticker,
138 &i.class_code,
139 "",
140 &i.name,
141 &i.currency,
142 )
143}
144
145pub fn from_option(i: &pb::Option) -> Instrument {
147 listed(
148 InstrumentKind::Option,
149 &i.uid,
150 "",
151 &i.ticker,
152 &i.class_code,
153 "",
154 &i.name,
155 &i.currency,
156 )
157}
158
159pub fn from_currency(i: &pb::Currency) -> Instrument {
161 listed(
162 InstrumentKind::Currency,
163 &i.uid,
164 &i.figi,
165 &i.ticker,
166 &i.class_code,
167 &i.isin,
168 &i.name,
169 &i.currency,
170 )
171}
172
173pub fn instrument_from_short(i: &pb::InstrumentShort) -> Instrument {
175 Instrument {
176 id: id_from_parts(&i.uid, &i.figi, &i.ticker, &i.class_code),
177 name: i.name.clone(),
178 isin: (!i.isin.is_empty()).then(|| i.isin.clone()),
179 kind: kind(i.instrument_kind()),
180 currency: String::new(),
181 exchange: i.class_code.clone(),
182 }
183}
184
185pub fn trading_params(id: &InstrumentId, i: &pb::Instrument) -> TradingParams {
190 let step = i
191 .min_price_increment
192 .as_ref()
193 .map(price_of_quotation)
194 .unwrap_or(Price::ZERO);
195 TradingParams {
196 instrument: id.clone(),
197 lot_size: Qty::from(i.lot),
198 min_qty: Qty::from(i.lot),
199 price_step: step,
200 price_step_value: Money::new(step.decimal()),
201 currency: i.currency.clone(),
202 }
203}
204
205pub fn availability(s: pb::SecurityTradingStatus) -> TradingAvailability {
222 use pb::SecurityTradingStatus as S;
223 match s {
224 S::NormalTrading | S::SessionOpen | S::DealerNormalTrading => TradingAvailability::Open,
225 S::BreakInTrading | S::DealerBreakInTrading => TradingAvailability::Break,
226 S::NotAvailableForTrading
227 | S::SessionClose
228 | S::DealerNotAvailableForTrading
229 | S::SessionAssigned => TradingAvailability::Closed,
230 _ => TradingAvailability::Unknown,
232 }
233}
234
235pub fn trading_status(id: &InstrumentId, s: pb::SecurityTradingStatus) -> TradingStatus {
252 TradingStatus {
253 instrument: id.clone(),
254 availability: availability(s),
255 reason: Some(s.as_str_name().to_string()),
256 ts: Some(wall_nanos()),
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use brokapi_core::{Decimal, dec};
264 use brokapi_core::{InstrumentKey, NativeId};
265 use brokapi_tbank_protocol::money::quotation;
266
267 #[test]
268 fn full_instrument_mapping() {
269 let i = pb::Instrument {
270 uid: "u1".into(),
271 figi: "BBG".into(),
272 ticker: "SBER".into(),
273 class_code: "TQBR".into(),
274 isin: "RU0009029540".into(),
275 lot: 10,
276 currency: "rub".into(),
277 min_price_increment: Some(quotation("0.01")),
278 instrument_kind: pb::InstrumentType::Share as i32,
279 name: "Sberbank".into(),
280 ..Default::default()
281 };
282 let m = instrument_from_full(&i);
283 assert_eq!(m.id.key, InstrumentKey::new("SBER", "TQBR"));
284 assert_eq!(m.id.native, NativeId::Uid("u1".into()));
285 assert_eq!(m.kind, InstrumentKind::Stock);
286 let p = trading_params(&m.id, &i);
287 assert_eq!(
288 (
289 p.lot_size.decimal(),
290 p.price_step.decimal(),
291 p.min_qty.decimal()
292 ),
293 (dec!(10.0), dec!(0.01), dec!(10.0))
294 );
295 }
296
297 #[test]
298 fn trading_status_mapping() {
299 use pb::SecurityTradingStatus as S;
300 assert_eq!(availability(S::NormalTrading), TradingAvailability::Open);
301 assert_eq!(availability(S::BreakInTrading), TradingAvailability::Break);
302 assert_eq!(
303 availability(S::NotAvailableForTrading),
304 TradingAvailability::Closed
305 );
306 assert_eq!(
307 availability(S::ClosingAuction),
308 TradingAvailability::Unknown
309 );
310 assert_eq!(availability(S::Unspecified), TradingAvailability::Unknown);
311 }
312}