From b6f2de63b1846815ca0fec7bc9acb097e03d3b06 Mon Sep 17 00:00:00 2001 From: adorewisdom Date: Sat, 23 Mar 2024 03:00:28 +0000 Subject: [PATCH] implement parse_l2_snapshot for bitfinex --- crypto-msg-parser/src/exchanges/bitfinex.rs | 66 +++++++++++ crypto-msg-parser/src/lib.rs | 1 + crypto-msg-parser/tests/bitfinex.rs | 117 +++++++++++++++++++- 3 files changed, 180 insertions(+), 4 deletions(-) diff --git a/crypto-msg-parser/src/exchanges/bitfinex.rs b/crypto-msg-parser/src/exchanges/bitfinex.rs index 8639732..6bdf84b 100644 --- a/crypto-msg-parser/src/exchanges/bitfinex.rs +++ b/crypto-msg-parser/src/exchanges/bitfinex.rs @@ -208,6 +208,72 @@ pub(crate) fn parse_l2( Ok(vec![orderbook]) } + + +// See https://docs.bitfinex.com/reference/rest-public-book +// See https://api-pub.bitfinex.com/v2/book/{symbol}/{precision} +// request example https://api-pub.bitfinex.com/v2/book/tBTCUSD/P0?len=100 +//[[68361,2,0.17328582],[68360,1,0.65244918],[68357,1,0.07]] +//price ,count ,amount +// See https://binance-docs.github.io/apidocs/spot/en/#order-book + + +pub(crate) fn parse_l2_snapshot( + market_type: MarketType, + msg: &str, + symbol:Option<&str>, + received_at: Option +) -> Result, SimpleError> { + let rs_msg = serde_json::from_str::>(msg) + .map_err(|_e| SimpleError::new(format!("Failed to deserialize {msg} to Vec<[Value;3]]>")))?; + + let s=symbol.unwrap_or("").to_string(); + let pair = crypto_pair::normalize_pair(&s, EXCHANGE_NAME) + .ok_or_else(|| SimpleError::new(format!("Failed to normalize {s} from {msg}")))?; + + let snapshot = true; + let parse_order = |x: &[Value; 3]| -> Order { + let price = x[0].as_f64().unwrap(); + // delete price level if count = 0 + let quantity = x[2].as_f64().unwrap().abs(); + + let (quantity_base, quantity_quote, quantity_contract) = + calc_quantity_and_volume(EXCHANGE_NAME, market_type, &pair, price, quantity); + + Order { price, quantity_base, quantity_quote, quantity_contract } + }; + + let mut asks=Vec::new(); + let mut bids=Vec::new(); + + for raw_order in rs_msg.iter() { + let order = parse_order(raw_order); + if raw_order[2].as_f64().unwrap() > 0.0 { + bids.push(order); + } else { + asks.push(order); + } + } + + let orderbook = OrderBookMsg { + exchange: EXCHANGE_NAME.to_string(), + market_type, + symbol: s, + pair: pair.clone(), + msg_type: MessageType::L2Snapshot, + timestamp:received_at.unwrap(), + seq_id: None, + prev_seq_id: None, + asks: asks.clone(), + bids: bids.clone(), + snapshot, + json: msg.to_string(), + }; + + Ok(vec![orderbook]) +} + + fn parse_one_candle( market_type: MarketType, symbol: &str, diff --git a/crypto-msg-parser/src/lib.rs b/crypto-msg-parser/src/lib.rs index b763099..8acde23 100644 --- a/crypto-msg-parser/src/lib.rs +++ b/crypto-msg-parser/src/lib.rs @@ -292,6 +292,7 @@ pub fn parse_l2_snapshot( ) -> Result, SimpleError> { let ret = match exchange { "binance" => exchanges::binance::parse_l2_snapshot(market_type, msg, symbol, received_at), + "bitfinex" => exchanges::bitfinex::parse_l2_snapshot(market_type, msg, symbol,received_at), _ => Err(SimpleError::new(format!("Unknown exchange {exchange}"))), }; match ret { diff --git a/crypto-msg-parser/tests/bitfinex.rs b/crypto-msg-parser/tests/bitfinex.rs index eab691d..c05a31b 100644 --- a/crypto-msg-parser/tests/bitfinex.rs +++ b/crypto-msg-parser/tests/bitfinex.rs @@ -500,24 +500,133 @@ mod ticker { mod l2_snapshot { use super::EXCHANGE_NAME; use crypto_market_type::MarketType; - use crypto_msg_parser::{extract_symbol, extract_timestamp}; + use crypto_msg_parser::{extract_symbol, extract_timestamp, parse_l2_snapshot, round}; + use crypto_msg_type::MessageType; #[test] fn spot() { - let raw_msg = r#"[[30428,1,0.01],[30426,1,0.1],[30424,1,0.2954],[30423,1,0.3333],[30422,3,0.72231346],[30420,2,0.3349],[30416,2,0.29700845],[30415,3,0.482257],[30414,1,0.4],[30413,1,0.15439084]]"#; - + + /* data sample excerpt + 1677628803683 1677628803683 [[23144,3,0.87789],[23142,3,0.27991505],[23141,4,0.48547974], + [23325,5,-0.00297898],[23326,1,-0.00060965],[23327,2,-0.00119078]] + */ + + //let raw_msg = r#"[[30428,1,0.01],[30426,1,0.1],[30424,1,0.2954],[30423,1,0.3333],[30422,3,0.72231346],[30420,2,0.3349],[30416,2,0.29700845],[30415,3,0.482257],[30414,1,0.4],[30413,1,0.15439084]]"#; + let raw_msg = r#"[[23144,3,0.87789],[23142,3,0.27991505],[23141,4,0.48547974],[23325,5,-0.00297898],[23326,1,-0.00060965],[23327,2,-0.00119078]]"#; + let received_at=Some(1677628803683) ; assert_eq!("NONE", extract_symbol(EXCHANGE_NAME, MarketType::Spot, raw_msg).unwrap()); assert_eq!(None, extract_timestamp(EXCHANGE_NAME, MarketType::Spot, raw_msg).unwrap()); + + let orderbook = &parse_l2_snapshot( + EXCHANGE_NAME, + MarketType::Spot, + raw_msg, + Some("tBTCUSD"), + received_at, + ) + .unwrap()[0]; + + assert_eq!(orderbook.asks.len(), 3); + assert_eq!(orderbook.bids.len(), 3); + assert!(orderbook.snapshot); + + crate::utils::check_orderbook_fields( + EXCHANGE_NAME, + MarketType::Spot, + MessageType::L2Snapshot, + "BTC/USD".to_string(), + "tBTCUSD".to_string(), + orderbook, + raw_msg, + ); + + assert_eq!(orderbook.timestamp, received_at.unwrap()); + assert_eq!(orderbook.seq_id, None); + assert_eq!(orderbook.prev_seq_id, None); + //"bids":[23144,3,0.87789],[23142,3,0.27991505],[23141,4,0.48547974] descending + + + assert_eq!(orderbook.bids[0].price, 23144.0); + assert_eq!(orderbook.bids[0].quantity_base, 0.87789); + assert_eq!(orderbook.bids[0].quantity_quote, 23144.0 * 0.87789); + assert_eq!(orderbook.bids[0].quantity_contract, None); + + assert_eq!(orderbook.bids[2].price, 23141.0); + assert_eq!(orderbook.bids[2].quantity_base, 0.48547974); + assert_eq!(orderbook.bids[2].quantity_quote, 23141.0 * 0.48547974); + assert_eq!(orderbook.bids[2].quantity_contract, None); + //"asks":[[23325,5,-0.00297898],[23326,1,-0.00060965],[23327,2,-0.00119078]] ascending + assert_eq!(orderbook.asks[0].price, 23325.0); + assert_eq!(orderbook.asks[0].quantity_base, 0.00297898); + assert_eq!(orderbook.asks[0].quantity_quote, 23325.0 * 0.00297898); + assert_eq!(orderbook.asks[0].quantity_contract, None); + + assert_eq!(orderbook.asks[2].price, 23327.0); + assert_eq!(orderbook.asks[2].quantity_base, 0.00119078); + assert_eq!(orderbook.asks[2].quantity_quote, 23327.0 * 0.00119078); + assert_eq!(orderbook.asks[2].quantity_contract, None); } #[test] fn linear_swap() { - let raw_msg = r#"[[28293,1,0.0350506],[28291,1,0.0526735],[28289,2,0.1037385],[28287,1,0.1059222],[28285,1,0.1324028],[28284,1,0.1765371],[28282,1,0.2206713],[28280,1,0.2427385],[28277,1,0.2648056]]"#; + /* + 1677628819111 1677628819111 [[23143,5,0.4721613],[23142,3,0.22947044],[23141,2,0.6234],[23906,1,-0.005],[23920,1,-0.0026],[23923,1,-0.02]] + */ + //let raw_msg = r#"[[28293,1,0.0350506],[28291,1,0.0526735],[28289,2,0.1037385],[28287,1,0.1059222],[28285,1,0.1324028],[28284,1,0.1765371],[28282,1,0.2206713],[28280,1,0.2427385],[28277,1,0.2648056]]"#; + let raw_msg = r#"[[23143,5,0.4721613],[23142,3,0.22947044],[23141,2,0.6234],[23906,1,-0.005],[23920,1,-0.0026],[23923,1,-0.02]]"#; + let received_at=Some(1677628819111) ; assert_eq!("NONE", extract_symbol(EXCHANGE_NAME, MarketType::LinearSwap, raw_msg).unwrap()); assert_eq!( None, extract_timestamp(EXCHANGE_NAME, MarketType::LinearSwap, raw_msg).unwrap() ); + + let orderbook = &parse_l2_snapshot( + EXCHANGE_NAME, + MarketType::Spot, + raw_msg, + Some("tBTCF0:USTF0"), + received_at + ) + .unwrap()[0]; + + assert_eq!(orderbook.asks.len(), 3); + assert_eq!(orderbook.bids.len(), 3); + assert!(orderbook.snapshot); + + crate::utils::check_orderbook_fields( + EXCHANGE_NAME, + MarketType::Spot, + MessageType::L2Snapshot, + "BTC/USDT".to_string(), + "tBTCF0:USTF0".to_string(), + orderbook, + raw_msg, + ); + + assert_eq!(orderbook.timestamp, received_at.unwrap()); + assert_eq!(orderbook.seq_id, None); + assert_eq!(orderbook.prev_seq_id, None); + //"bids":[23143,5,0.4721613],[23142,3,0.22947044],[23141,2,0.6234] descending + assert_eq!(orderbook.bids[2].price, 23141.0); + assert_eq!(orderbook.bids[2].quantity_base, 0.6234); + assert_eq!(orderbook.bids[2].quantity_quote, round(23141.0 * 0.6234)); + assert_eq!(orderbook.bids[2].quantity_contract, None); + + assert_eq!(orderbook.bids[0].price, 23143.0); + assert_eq!(orderbook.bids[0].quantity_base, 0.4721613); + assert_eq!(orderbook.bids[0].quantity_quote, round(23143.0 * 0.4721613)); + assert_eq!(orderbook.bids[0].quantity_contract, None); + //"asks":[[23906,1,-0.005],[23920,1,-0.0026],[23923,1,-0.02]] ascending + assert_eq!(orderbook.asks[0].price, 23906.0); + assert_eq!(orderbook.asks[0].quantity_base, 0.005); + assert_eq!(orderbook.asks[0].quantity_quote, round(23906.0 * 0.005)); + assert_eq!(orderbook.asks[0].quantity_contract, None); + + assert_eq!(orderbook.asks[2].price, 23923.0); + assert_eq!(orderbook.asks[2].quantity_base, 0.02); + assert_eq!(orderbook.asks[2].quantity_quote, round(23923.0 * 0.02)); + assert_eq!(orderbook.asks[2].quantity_contract, None); } }