exc_binance/types/adaptations/
instrument.rs

1use exc_core::{
2    types::instrument::{Attributes, FetchInstruments, InstrumentMeta},
3    Adaptor, ExchangeError,
4};
5use futures::{stream, StreamExt};
6use rust_decimal::Decimal;
7
8use crate::{
9    http::{
10        request::{self, Payload, RestRequest},
11        response::{
12            self,
13            instrument::{Filter, SymbolFilter},
14        },
15    },
16    Request,
17};
18
19impl Adaptor<FetchInstruments> for Request {
20    fn from_request(_req: FetchInstruments) -> Result<Self, ExchangeError>
21    where
22        Self: Sized,
23    {
24        Ok(Request::Http(RestRequest::from(Payload::new(
25            request::ExchangeInfo,
26        ))))
27    }
28
29    fn into_response(
30        resp: Self::Response,
31    ) -> Result<<FetchInstruments as exc_core::Request>::Response, ExchangeError> {
32        let info = resp.into_response::<response::ExchangeInfo>()?;
33        match info {
34            response::ExchangeInfo::UsdMarginFutures(info) => {
35                Ok(stream::iter(info.symbols.into_iter().filter_map(|symbol| {
36                    let mut price_tick = None;
37                    let mut size_tick = None;
38                    let mut min_size = None;
39                    let mut min_value = None;
40                    for filter in &symbol.filters {
41                        if let Filter::Symbol(filter) = filter {
42                            match filter {
43                                SymbolFilter::PriceFilter { tick_size, .. } => {
44                                    price_tick = Some(tick_size.normalize());
45                                }
46                                SymbolFilter::LotSize {
47                                    min_qty, step_size, ..
48                                } => {
49                                    min_size = Some(min_qty.normalize());
50                                    size_tick = Some(step_size.normalize());
51                                }
52                                SymbolFilter::MinNotional { notional } => {
53                                    min_value = Some(notional);
54                                }
55                                _ => {}
56                            }
57                        }
58                    }
59                    let attrs = Attributes {
60                        reversed: false,
61                        unit: Decimal::ONE,
62                        price_tick: price_tick?,
63                        size_tick: size_tick?,
64                        min_size: min_size?,
65                        min_value: min_value.copied()?,
66                    };
67                    Some(Ok(InstrumentMeta::new(
68                        symbol.symbol.to_lowercase(),
69                        symbol
70                            .to_exc_symbol()
71                            .map_err(|err| {
72                                tracing::debug!(%err, "cannot build exc symbol from {}", symbol.symbol);
73                                err
74                            })
75                            .ok()?,
76                        attrs,
77                    ).with_live(symbol.is_live())))
78                }))
79                .boxed())
80            }
81            response::ExchangeInfo::Spot(info) => {
82                Ok(stream::iter(info.symbols.into_iter().filter_map(|symbol| {
83                    let mut price_tick = None;
84                    let mut size_tick = None;
85                    let mut min_size = None;
86                    let mut min_value = None;
87                    for filter in &symbol.filters {
88                        if let Filter::Symbol(filter) = filter {
89                            match filter {
90                                SymbolFilter::PriceFilter { tick_size, .. } => {
91                                    price_tick = Some(tick_size.normalize());
92                                }
93                                SymbolFilter::LotSize {
94                                    min_qty, step_size, ..
95                                } => {
96                                    min_size = Some(min_qty.normalize());
97                                    size_tick = Some(step_size.normalize());
98                                }
99                                SymbolFilter::Notional { min_notional } => {
100                                    min_value = Some(min_notional.normalize());
101                                }
102                                _ => {}
103                            }
104                        }
105                    }
106                    tracing::debug!("{price_tick:?} {size_tick:?} {min_size:?} {min_value:?}");
107                    let attrs = Attributes {
108                        reversed: false,
109                        unit: Decimal::ONE,
110                        price_tick: price_tick?,
111                        size_tick: size_tick?,
112                        min_size: min_size?,
113                        min_value: min_value?,
114                    };
115                    Some(Ok(InstrumentMeta::new(
116                        symbol.symbol.to_lowercase(),
117                        symbol
118                            .to_exc_symbol()
119                            .map_err(|err| {
120                                tracing::debug!(%err, "cannot build exc symbol from {}", symbol.symbol);
121                                err
122                            })
123                            .ok()?,
124                        attrs,
125                    ).with_live(symbol.is_live())))
126                }))
127                .boxed())
128            }
129            response::ExchangeInfo::EuropeanOptions(info) => {
130                Ok(stream::iter(info.option_symbols.into_iter().filter_map(|symbol| {
131                    let mut size_tick = None;
132                    for filter in symbol.filters.iter() {
133                        if let Filter::Symbol(SymbolFilter::LotSize { step_size, .. }) = filter {
134                            size_tick = Some(step_size.normalize());
135                        }
136                    }
137                    let Some(size_tick) = size_tick else {
138                        tracing::warn!("missing size tick for {}", symbol.symbol);
139                        return None;
140                    };
141                    let attrs = Attributes { reversed: false, unit: Decimal::from(symbol.unit), price_tick: Decimal::new(1, symbol.price_scale), size_tick, min_size: symbol.min_qty, min_value: Decimal::ZERO };
142                    let expire = symbol.expire_ts().map_err(|err| {
143                        tracing::warn!(%err, "cannot parse expire for {}", symbol.symbol);
144                    }).ok()?;
145                    let meta = InstrumentMeta::new(&symbol.symbol, symbol.to_exc_symbol().map_err(|err| {
146                        tracing::warn!(%err, "cannot build exc symbol from {}", symbol.symbol);
147                    }).ok()?, attrs).with_live(symbol.is_live()).with_expire(expire);
148                    Some(Ok(meta))
149                })).boxed())
150            }
151        }
152    }
153}