diff --git a/crates/exchanges/binance/src/convert/market_data.rs b/crates/exchanges/binance/src/convert/market_data.rs index 6bc99fb..a0cf02b 100644 --- a/crates/exchanges/binance/src/convert/market_data.rs +++ b/crates/exchanges/binance/src/convert/market_data.rs @@ -243,6 +243,9 @@ pub(crate) fn market_info_from_exchange_symbol( .quote_asset .ok_or_else(|| crate::error::missing_field(operation, "quoteAsset"))?, ) + .base_asset_precision(symbol_definition.base_asset_precision) + .quote_precision(symbol_definition.quote_precision) + .quote_asset_precision(symbol_definition.quote_asset_precision) .trading_permissions(trading_permissions) .trading_constraints(trading_constraints) .build() @@ -458,3 +461,61 @@ pub(crate) fn klines_from_rows( }) .collect() } + +#[cfg(test)] +mod tests { + use super::market_info_from_exchange_symbol; + use binance_sdk::spot::rest_api::{ + ExchangeInfoResponseSymbolsInner, ExchangeInfoSymbolStatusEnum, LotSizeFilter, PriceFilter, + SymbolFilters, + }; + use rust_decimal::Decimal; + use std::str::FromStr; + + #[test] + fn market_info_preserves_symbol_precision_fields() { + let symbol = ExchangeInfoResponseSymbolsInner { + symbol: Some(String::from("BTCUSDT")), + status: Some(ExchangeInfoSymbolStatusEnum::Trading.as_str().to_string()), + base_asset: Some(String::from("BTC")), + base_asset_precision: Some(8), + quote_asset: Some(String::from("USDT")), + quote_precision: Some(8), + quote_asset_precision: Some(8), + order_types: Some(vec![String::from("MARKET")]), + quote_order_qty_market_allowed: Some(true), + is_spot_trading_allowed: Some(true), + filters: Some(vec![ + SymbolFilters::PriceFilter(Box::new(PriceFilter { + filter_type: Some(String::from("PRICE_FILTER")), + price_exponent: None, + min_price: Some(String::from("0.01000000")), + max_price: Some(String::from("1000000.00000000")), + tick_size: Some(String::from("0.01000000")), + })), + SymbolFilters::LotSize(Box::new(LotSizeFilter { + filter_type: Some(String::from("LOT_SIZE")), + qty_exponent: None, + min_qty: Some(String::from("0.00001000")), + max_qty: Some(String::from("9000.00000000")), + step_size: Some(String::from("0.00001000")), + })), + ]), + ..ExchangeInfoResponseSymbolsInner::new() + }; + + let market = market_info_from_exchange_symbol(symbol, "spot.exchange_info") + .expect("market should convert"); + + assert_eq!(market.base_asset_precision, Some(8)); + assert_eq!(market.quote_precision, Some(8)); + assert_eq!(market.quote_asset_precision, Some(8)); + assert_eq!( + market + .trading_constraints + .price_filter + .and_then(|filter| filter.tick_size), + Some(Decimal::from_str("0.01").expect("decimal")) + ); + } +} diff --git a/crates/mkt-types/src/market.rs b/crates/mkt-types/src/market.rs index 32c9ecc..3614138 100644 --- a/crates/mkt-types/src/market.rs +++ b/crates/mkt-types/src/market.rs @@ -388,14 +388,41 @@ impl TradingConstraints { #[derive(Debug, Clone, PartialEq, Builder)] #[builder(pattern = "owned", setter(into))] pub struct MarketInfo { + /// Canonical exchange identifier for the venue that exposed this market. pub exchange_id: ExchangeId, + /// Unified symbol descriptor, including market kind and venue-specific symbol string. pub symbol: Symbol, + /// Current lifecycle status reported by the exchange. pub status: MarketStatus, + /// Venue-reported base asset code for this market. pub base_asset: String, + /// Venue-reported quote asset code for this market. pub quote_asset: String, #[builder(default)] + /// Exchange-reported precision for base-asset quantities at the symbol level, when available. + /// + /// This is raw venue metadata. It may differ from executable step-size filters and should not + /// be treated as a substitute for lot-size validation. + pub base_asset_precision: Option, + #[builder(default)] + /// Exchange-reported precision for quote-denominated values at the symbol level, when + /// available. + /// + /// On venues such as Binance spot, this is the most relevant symbol-level precision hint for + /// quote-sized order entry like `quoteOrderQty`. + pub quote_precision: Option, + #[builder(default)] + /// Exchange-reported precision for the quote asset at the symbol level, when available. + /// + /// Some venues expose both `quote_precision` and `quote_asset_precision`. When they differ, + /// adapter code should prefer the field that the venue documents for order-entry precision and + /// treat this field as auxiliary metadata or fallback. + pub quote_asset_precision: Option, + #[builder(default)] + /// Venue capabilities describing which order types, sides, and quantity modes are supported. pub trading_permissions: TradingPermissions, #[builder(default)] + /// Venue execution constraints such as price filters, lot sizes, and notional limits. pub trading_constraints: TradingConstraints, }