1use std::collections::HashMap;
24
25use bullet_exchange_interface::types::MarketId;
26
27use crate::generated::types::Symbol;
28
29#[derive(Debug, Clone)]
31pub(crate) struct ExchangeMetadata {
32 symbols: Vec<SymbolInfo>,
33 by_name: HashMap<String, usize>,
35 by_id: HashMap<u16, usize>,
37}
38
39#[derive(Debug, Clone)]
41pub struct SymbolInfo {
42 pub symbol: String,
44 pub market_id: MarketId,
46 pub status: String,
48 pub base_asset: String,
50 pub quote_asset: String,
52 pub price_precision: u8,
54 pub quantity_precision: u8,
56}
57
58impl ExchangeMetadata {
59 pub(crate) fn from_symbols(raw: &[Symbol]) -> Self {
60 let symbols: Vec<SymbolInfo> = raw
61 .iter()
62 .map(|s| SymbolInfo {
63 symbol: s.symbol.clone(),
64 market_id: MarketId(s.market_id),
65 status: s.status.clone(),
66 base_asset: s.base_asset.clone(),
67 quote_asset: s.quote_asset.clone(),
68 price_precision: s.price_precision,
69 quantity_precision: s.quantity_precision,
70 })
71 .collect();
72
73 let by_name = symbols.iter().enumerate().map(|(i, s)| (s.symbol.clone(), i)).collect();
74
75 let by_id = symbols.iter().enumerate().map(|(i, s)| (s.market_id.0, i)).collect();
76
77 Self { symbols, by_name, by_id }
78 }
79
80 pub(crate) fn market_id(&self, symbol: &str) -> Option<MarketId> {
81 self.by_name.get(symbol).map(|&i| self.symbols[i].market_id)
82 }
83
84 pub(crate) fn symbol_info_by_name(&self, symbol: &str) -> Option<&SymbolInfo> {
85 self.by_name.get(symbol).map(|&i| &self.symbols[i])
86 }
87
88 pub(crate) fn symbol_info_by_id(&self, market_id: MarketId) -> Option<&SymbolInfo> {
89 self.by_id.get(&market_id.0).map(|&i| &self.symbols[i])
90 }
91
92 pub(crate) fn symbols(&self) -> &[SymbolInfo] {
93 &self.symbols
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::generated::types::Symbol;
101
102 fn mock_symbols() -> Vec<Symbol> {
103 vec![
104 Symbol {
105 symbol: "BTC-USD".into(),
106 market_id: 0,
107 status: "TRADING".into(),
108 base_asset: "BTC".into(),
109 quote_asset: "USD".into(),
110 price_precision: 2,
111 quantity_precision: 3,
112 pair: "BTCUSD".into(),
113 contract_type: "PERPETUAL".into(),
114 delivery_date: 0,
115 onboard_date: 0,
116 margin_asset: "USD".into(),
117 base_asset_precision: 8,
118 quote_precision: 8,
119 underlying_type: "COIN".into(),
120 underlying_sub_type: vec![],
121 settle_plan: 0,
122 trigger_protect: Default::default(),
123 filters: vec![],
124 order_types: vec![],
125 time_in_force: vec![],
126 liquidation_fee: Default::default(),
127 market_take_bound: Default::default(),
128 maker_fee_bps: vec![],
129 taker_fee_bps: vec![],
130 },
131 Symbol {
132 symbol: "ETH-USD".into(),
133 market_id: 1,
134 status: "TRADING".into(),
135 base_asset: "ETH".into(),
136 quote_asset: "USD".into(),
137 price_precision: 2,
138 quantity_precision: 4,
139 pair: "ETHUSD".into(),
140 contract_type: "PERPETUAL".into(),
141 delivery_date: 0,
142 onboard_date: 0,
143 margin_asset: "USD".into(),
144 base_asset_precision: 8,
145 quote_precision: 8,
146 underlying_type: "COIN".into(),
147 underlying_sub_type: vec![],
148 settle_plan: 0,
149 trigger_protect: Default::default(),
150 filters: vec![],
151 order_types: vec![],
152 time_in_force: vec![],
153 liquidation_fee: Default::default(),
154 market_take_bound: Default::default(),
155 maker_fee_bps: vec![],
156 taker_fee_bps: vec![],
157 },
158 ]
159 }
160
161 #[test]
162 fn market_id_lookup() {
163 let meta = ExchangeMetadata::from_symbols(&mock_symbols());
164 assert_eq!(meta.market_id("BTC-USD"), Some(MarketId(0)));
165 assert_eq!(meta.market_id("ETH-USD"), Some(MarketId(1)));
166 assert_eq!(meta.market_id("SOL-USD"), None);
167 }
168
169 #[test]
170 fn symbol_info_by_name_lookup() {
171 let meta = ExchangeMetadata::from_symbols(&mock_symbols());
172 let info = meta.symbol_info_by_name("ETH-USD").unwrap();
173 assert_eq!(info.base_asset, "ETH");
174 assert_eq!(info.quantity_precision, 4);
175 }
176
177 #[test]
178 fn symbol_info_by_id_lookup() {
179 let meta = ExchangeMetadata::from_symbols(&mock_symbols());
180 let info = meta.symbol_info_by_id(MarketId(0)).unwrap();
181 assert_eq!(info.symbol, "BTC-USD");
182 assert!(meta.symbol_info_by_id(MarketId(99)).is_none());
183 }
184
185 #[test]
186 fn symbols_returns_all() {
187 let meta = ExchangeMetadata::from_symbols(&mock_symbols());
188 assert_eq!(meta.symbols().len(), 2);
189 }
190
191 #[test]
192 fn empty_symbols() {
193 let meta = ExchangeMetadata::from_symbols(&[]);
194 assert!(meta.symbols().is_empty());
195 assert_eq!(meta.market_id("BTC-USD"), None);
196 }
197}