ccxt_exchanges/binance/parser/
market.rs1use super::value_to_hashmap;
2use ccxt_core::{
3 Result,
4 error::{Error, ParseError},
5 types::{Market, MarketLimits, MarketPrecision, MarketType, MinMax},
6};
7use rust_decimal::Decimal;
8use rust_decimal::prelude::FromStr;
9use serde_json::Value;
10
11pub fn parse_market(data: &Value) -> Result<Market> {
13 let symbol = data["symbol"]
14 .as_str()
15 .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
16 .to_string();
17
18 let base_asset = data["baseAsset"]
19 .as_str()
20 .ok_or_else(|| Error::from(ParseError::missing_field("baseAsset")))?
21 .to_string();
22
23 let quote_asset = data["quoteAsset"]
24 .as_str()
25 .ok_or_else(|| Error::from(ParseError::missing_field("quoteAsset")))?
26 .to_string();
27
28 let status = data["status"]
29 .as_str()
30 .ok_or_else(|| Error::from(ParseError::missing_field("status")))?;
31
32 let active = status == "TRADING";
33 let margin = data["isMarginTradingAllowed"].as_bool().unwrap_or(false);
34
35 let mut price_precision: Option<Decimal> = None;
36 let mut amount_precision: Option<Decimal> = None;
37 let mut min_amount: Option<Decimal> = None;
38 let mut max_amount: Option<Decimal> = None;
39 let mut min_cost: Option<Decimal> = None;
40 let mut min_price: Option<Decimal> = None;
41 let mut max_price: Option<Decimal> = None;
42
43 if let Some(filters) = data["filters"].as_array() {
44 for filter in filters {
45 let filter_type = filter["filterType"].as_str().unwrap_or("");
46
47 match filter_type {
48 "PRICE_FILTER" => {
49 if let Some(tick_size) = filter["tickSize"].as_str() {
50 if let Ok(dec) = Decimal::from_str(tick_size) {
51 price_precision = Some(dec);
52 }
53 }
54 if let Some(min) = filter["minPrice"].as_str() {
55 min_price = Decimal::from_str(min).ok();
56 }
57 if let Some(max) = filter["maxPrice"].as_str() {
58 max_price = Decimal::from_str(max).ok();
59 }
60 }
61 "LOT_SIZE" => {
62 if let Some(step_size) = filter["stepSize"].as_str() {
63 if let Ok(dec) = Decimal::from_str(step_size) {
64 amount_precision = Some(dec);
65 }
66 }
67 if let Some(min) = filter["minQty"].as_str() {
68 min_amount = Decimal::from_str(min).ok();
69 }
70 if let Some(max) = filter["maxQty"].as_str() {
71 max_amount = Decimal::from_str(max).ok();
72 }
73 }
74 "MIN_NOTIONAL" | "NOTIONAL" => {
75 if let Some(min) = filter["minNotional"].as_str() {
76 min_cost = Decimal::from_str(min).ok();
77 }
78 }
79 _ => {}
80 }
81 }
82 }
83
84 let unified_symbol = format!("{}/{}", base_asset, quote_asset);
85 let parsed_symbol = ccxt_core::symbol::SymbolParser::parse(&unified_symbol).ok();
86
87 Ok(Market {
88 id: symbol.clone(),
89 symbol: unified_symbol,
90 parsed_symbol,
91 base: base_asset.clone(),
92 quote: quote_asset.clone(),
93 settle: None,
94 base_id: Some(base_asset),
95 quote_id: Some(quote_asset),
96 settle_id: None,
97 market_type: MarketType::Spot,
98 active,
99 margin,
100 contract: Some(false),
101 linear: None,
102 inverse: None,
103 taker: Decimal::from_str("0.001").ok(),
104 maker: Decimal::from_str("0.001").ok(),
105 contract_size: None,
106 expiry: None,
107 expiry_datetime: None,
108 strike: None,
109 option_type: None,
110 percentage: Some(true),
111 tier_based: Some(false),
112 fee_side: Some("quote".to_string()),
113 precision: MarketPrecision {
114 price: price_precision,
115 amount: amount_precision,
116 base: None,
117 quote: None,
118 },
119 limits: MarketLimits {
120 amount: Some(MinMax {
121 min: min_amount,
122 max: max_amount,
123 }),
124 price: Some(MinMax {
125 min: min_price,
126 max: max_price,
127 }),
128 cost: Some(MinMax {
129 min: min_cost,
130 max: None,
131 }),
132 leverage: None,
133 },
134 info: value_to_hashmap(data),
135 })
136}
137
138pub fn parse_currency(data: &Value) -> Result<ccxt_core::types::Currency> {
140 use ccxt_core::types::{Currency, CurrencyNetwork, MinMax};
141 use std::collections::HashMap;
142
143 let code = data["coin"]
144 .as_str()
145 .ok_or_else(|| Error::from(ParseError::missing_field("coin")))?
146 .to_string();
147
148 let id = code.clone();
149 let name = data["name"].as_str().map(ToString::to_string);
150 let active = data["trading"].as_bool().unwrap_or(true);
151
152 let mut networks = HashMap::new();
153 let mut global_deposit = false;
154 let mut global_withdraw = false;
155 let mut global_fee = None;
156 let mut global_precision = None;
157 let mut global_limits = MinMax::default();
158
159 if let Some(network_list) = data["networkList"].as_array() {
160 for network_data in network_list {
161 let network_id = network_data["network"]
162 .as_str()
163 .unwrap_or(&code)
164 .to_string();
165
166 let is_default = network_data["isDefault"].as_bool().unwrap_or(false);
167 let deposit_enable = network_data["depositEnable"].as_bool().unwrap_or(false);
168 let withdraw_enable = network_data["withdrawEnable"].as_bool().unwrap_or(false);
169
170 if is_default {
171 global_deposit = deposit_enable;
172 global_withdraw = withdraw_enable;
173 }
174
175 let fee = network_data["withdrawFee"]
176 .as_str()
177 .and_then(|s| Decimal::from_str(s).ok());
178
179 if is_default && fee.is_some() {
180 global_fee = fee;
181 }
182
183 let precision = network_data["withdrawIntegerMultiple"]
184 .as_str()
185 .and_then(|s| Decimal::from_str(s).ok());
186
187 if is_default && precision.is_some() {
188 global_precision = precision;
189 }
190
191 let withdraw_min = network_data["withdrawMin"]
192 .as_str()
193 .and_then(|s| Decimal::from_str(s).ok());
194
195 let withdraw_max = network_data["withdrawMax"]
196 .as_str()
197 .and_then(|s| Decimal::from_str(s).ok());
198
199 let limits = MinMax {
200 min: withdraw_min,
201 max: withdraw_max,
202 };
203
204 if is_default {
205 global_limits = limits.clone();
206 }
207
208 let network = CurrencyNetwork {
209 network: network_id.clone(),
210 id: Some(network_id.clone()),
211 name: network_data["name"].as_str().map(ToString::to_string),
212 active: deposit_enable && withdraw_enable,
213 deposit: deposit_enable,
214 withdraw: withdraw_enable,
215 fee,
216 precision,
217 limits,
218 info: value_to_hashmap(network_data),
219 };
220
221 networks.insert(network_id, network);
222 }
223 }
224
225 if !networks.is_empty() && global_fee.is_none() {
226 if let Some(first) = networks.values().next() {
227 global_fee = first.fee;
228 global_precision = first.precision;
229 global_limits = first.limits.clone();
230 }
231 }
232
233 Ok(Currency {
234 code,
235 id,
236 name,
237 active,
238 deposit: global_deposit,
239 withdraw: global_withdraw,
240 fee: global_fee,
241 precision: global_precision,
242 limits: global_limits,
243 networks,
244 currency_type: if data["isLegalMoney"].as_bool().unwrap_or(false) {
245 Some("fiat".to_string())
246 } else {
247 Some("crypto".to_string())
248 },
249 info: value_to_hashmap(data),
250 })
251}
252
253pub fn parse_currencies(data: &Value) -> Result<Vec<ccxt_core::types::Currency>> {
255 if let Some(array) = data.as_array() {
256 array.iter().map(parse_currency).collect()
257 } else {
258 Ok(vec![parse_currency(data)?])
259 }
260}