From 991395fe218da9b7ee86c761824b53a1ab1d004b Mon Sep 17 00:00:00 2001 From: adorewisdom Date: Sat, 23 Mar 2024 09:30:26 +0000 Subject: [PATCH] implement parse_l2_snapshot for bitget --- .../src/exchanges/bitget/bitget_mix.rs | 62 +++++++ crypto-msg-parser/src/exchanges/bitget/mod.rs | 10 +- crypto-msg-parser/src/lib.rs | 1 + crypto-msg-parser/tests/bitget.rs | 159 ++++++++++++++++-- 4 files changed, 221 insertions(+), 11 deletions(-) diff --git a/crypto-msg-parser/src/exchanges/bitget/bitget_mix.rs b/crypto-msg-parser/src/exchanges/bitget/bitget_mix.rs index dafdce6..6939b99 100644 --- a/crypto-msg-parser/src/exchanges/bitget/bitget_mix.rs +++ b/crypto-msg-parser/src/exchanges/bitget/bitget_mix.rs @@ -3,6 +3,8 @@ use crypto_msg_type::MessageType; use crypto_message::{CandlestickMsg, Order, OrderBookMsg, TradeMsg, TradeSide}; +use crate::exchanges::utils::calc_quantity_and_volume; + use super::EXCHANGE_NAME; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -35,6 +37,24 @@ struct RawOrderBook { extra: HashMap, } +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +struct RestMsg { + code: String, + msg: String, + requestTime: i64, + data: T, + #[serde(flatten)] + extra: HashMap, +} + +#[derive(Serialize, Deserialize)] +struct RawL2SnapshotData { + asks: Vec<[String; 2]>, + bids: Vec<[String; 2]>, + timestamp: String, +} + fn extract_market_type_and_symbol(arg: &Arg) -> (MarketType, String) { match arg.instType.as_str() { "sp" => (MarketType::Spot, format!("{}_SPBL", arg.instId)), @@ -191,6 +211,48 @@ pub(super) fn parse_l2(msg: &str) -> Result, SimpleError> { Ok(orderbooks) } +/// docs: +/// * https://bitgetlimited.github.io/apidoc/en/spot/ +/// * https://api.bitget.com/api/spot/v1/market/depth?symbol=BTCUSDT_SPBL&type=step0 +/// * https://bitgetlimited.github.io/apidoc/en/mix/#restapi +/// * https://api.bitget.com/api/mix/v1/market/depth?symbol=BTCUSDT_UMCBL&limit=100 +pub(super) fn parse_l2_snapshot( + market_type: MarketType, + msg: &str, + symbol: Option<&str>, +) -> Result, SimpleError> { + let rs_msg = serde_json::from_str::>(msg) + .map_err(|_e| SimpleError::new(format!("Failed to parse JSON string {msg}")))?; + + let symbol = symbol.unwrap_or_default(); + let pair = crypto_pair::normalize_pair(symbol, EXCHANGE_NAME).unwrap(); + + let parse_order = |raw_order: &[String; 2]| -> Order { + let price = raw_order[0].parse::().unwrap(); + let quantity = raw_order[1].parse::().unwrap(); + 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 orderbook_msg = OrderBookMsg { + exchange: EXCHANGE_NAME.to_string(), + market_type, + symbol: symbol.to_string(), + pair: pair.clone(), + msg_type: MessageType::L2Snapshot, + timestamp: rs_msg.data.timestamp.parse::().unwrap(), + seq_id: None, + prev_seq_id: None, + asks: rs_msg.data.asks.iter().map(parse_order).collect(), + bids: rs_msg.data.bids.iter().map(parse_order).collect(), + snapshot: true, + json: serde_json::to_string(&rs_msg).unwrap(), + }; + + Ok(vec![orderbook_msg]) +} + /// docs: /// * https://bitgetlimited.github.io/apidoc/en/spot/#candlesticks-channel /// * https://bitgetlimited.github.io/apidoc/en/mix/#candlesticks-channel diff --git a/crypto-msg-parser/src/exchanges/bitget/mod.rs b/crypto-msg-parser/src/exchanges/bitget/mod.rs index fe9b59d..0d384a0 100644 --- a/crypto-msg-parser/src/exchanges/bitget/mod.rs +++ b/crypto-msg-parser/src/exchanges/bitget/mod.rs @@ -89,7 +89,7 @@ pub(crate) fn get_msg_type(msg: &str) -> MessageType { } } } else { - MessageType::Other + MessageType::L2Snapshot } } @@ -185,3 +185,11 @@ pub(crate) fn parse_candlestick( Err(SimpleError::new(format!("Unsupported Candlestick message {msg}"))) } } + +pub(crate) fn parse_l2_snapshot( + market_type: MarketType, + msg: &str, + symbol: Option<&str>, +) -> Result, SimpleError> { + bitget_mix::parse_l2_snapshot(market_type, msg, symbol) +} diff --git a/crypto-msg-parser/src/lib.rs b/crypto-msg-parser/src/lib.rs index b763099..022f5df 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), + "bitget" => exchanges::bitget::parse_l2_snapshot(market_type, msg, symbol), _ => Err(SimpleError::new(format!("Unknown exchange {exchange}"))), }; match ret { diff --git a/crypto-msg-parser/tests/bitget.rs b/crypto-msg-parser/tests/bitget.rs index ac3c23a..a001020 100644 --- a/crypto-msg-parser/tests/bitget.rs +++ b/crypto-msg-parser/tests/bitget.rs @@ -685,42 +685,181 @@ 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#"{"code":"00000","msg":"success","requestTime":1654232411024,"data":{"asks":[["30413.33","0.0141"],["30413.37","0.0112"],["30413.41","0.0179"]],"bids":[["30413.27","0.0296"],["30413.23","0.0484"],["30413.19","0.1272"]],"timestamp":"1654232411024"}}"#; - + let raw_msg = r#"{"code":"00000","msg":"success","requestTime":1677628818447,"data":{"asks":[["23141.01","0.0763"],["23141.16","0.1244"],["23141.4","0.0120"]],"bids":[["23139.91","0.7697"],["23139.76","1.2558"],["23139.56","0.0120"]],"timestamp":"1677628818450"}}"#; + // let raw_msg = + // r#"{"code":"00000","msg":"success","requestTime":1654232411024,"data":{"asks" + // :[["30413.33","0.0141"],["30413.37","0.0112"],["30413.41","0.0179"]],"bids": + // [["30413.27","0.0296"],["30413.23","0.0484"],["30413.19","0.1272"]]," + // timestamp":"1654232411024"}}"#; + let symbol = Some("BTCUSDT_SPBL"); assert_eq!("NONE", extract_symbol(EXCHANGE_NAME, MarketType::Spot, raw_msg).unwrap()); assert_eq!( - 1654232411024, + 1677628818450, extract_timestamp(EXCHANGE_NAME, MarketType::Spot, raw_msg).unwrap().unwrap() ); + + let orderbook = + &parse_l2_snapshot(EXCHANGE_NAME, MarketType::Spot, raw_msg, symbol, None).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(), + symbol.unwrap().to_string(), + orderbook, + raw_msg, + ); + + assert_eq!(orderbook.timestamp, 1677628818450); + assert_eq!(orderbook.seq_id, None); + assert_eq!(orderbook.prev_seq_id, None); + //[["23139.91","0.7697"],["23139.76","1.2558"],["23139.56","0.0120"]] descending + assert_eq!(orderbook.bids[0].price, 23139.91); + assert_eq!(orderbook.bids[0].quantity_base, 0.7697); + assert_eq!(orderbook.bids[0].quantity_quote, round(23139.91 * 0.7697)); + assert_eq!(orderbook.bids[0].quantity_contract, None); + + assert_eq!(orderbook.bids[2].price, 23139.56); + assert_eq!(orderbook.bids[2].quantity_base, 0.0120); + assert_eq!(orderbook.bids[2].quantity_quote, round(23139.56 * 0.0120)); + assert_eq!(orderbook.bids[2].quantity_contract, None); + //[["23141.01","0.0763"],["23141.16","0.1244"],["23141.4","0.0120"]] ascending + assert_eq!(orderbook.asks[0].price, 23141.01); + assert_eq!(orderbook.asks[0].quantity_base, 0.0763); + assert_eq!(orderbook.asks[0].quantity_quote, round(23141.01 * 0.0763)); + assert_eq!(orderbook.asks[0].quantity_contract, None); + + assert_eq!(orderbook.asks[2].price, 23141.4); + assert_eq!(orderbook.asks[2].quantity_base, 0.0120); + assert_eq!(orderbook.asks[2].quantity_quote, round(23141.4 * 0.0120)); + assert_eq!(orderbook.asks[2].quantity_contract, None); } #[test] fn inverse_swap() { - let raw_msg = r#"{"code":"00000","msg":"success","requestTime":1654234201087,"data":{"asks":[["30521","1.661"],["30521.5","2.312"],["30522","0.139"]],"bids":[["3.052E+4","0.256"],["30519.5","0.482"],["30519","0.079"]],"timestamp":"1654234201087"}}"#; - + //let raw_msg = + // r#"{"code":"00000","msg":"success","requestTime":1654234201087,"data":{"asks" + // :[["30521","1.661"],["30521.5","2.312"],["30522","0.139"]],"bids":[["3. + // 052E+4","0.256"],["30519.5","0.482"],["30519","0.079"]],"timestamp":" + // 1654234201087"}}"#; + let raw_msg = r#"{"code":"00000","msg":"success","requestTime":0,"data":{"asks":[["23146","3.666"],["23146.5","5.562"],["2.315E+4","0.5"]],"bids":[["23145","0.107"],["23143","0.363"],["23141.5","0.518"]],"timestamp":"1677628802358"}}"#; + let symbol = Some("BTCUSD_DMCBL"); assert_eq!( "NONE", extract_symbol(EXCHANGE_NAME, MarketType::InverseSwap, raw_msg).unwrap() ); assert_eq!( - 1654234201087, + 1677628802358, extract_timestamp(EXCHANGE_NAME, MarketType::InverseSwap, raw_msg).unwrap().unwrap() ); + let orderbook = + &parse_l2_snapshot(EXCHANGE_NAME, MarketType::InverseSwap, raw_msg, symbol, None) + .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::InverseSwap, + MessageType::L2Snapshot, + "BTC/USD".to_string(), + symbol.unwrap().to_string(), + orderbook, + raw_msg, + ); + + assert_eq!(orderbook.timestamp, 1677628802358); + assert_eq!(orderbook.seq_id, None); + assert_eq!(orderbook.prev_seq_id, None); + //"bids":[["23145","0.107"],["23143","0.363"],["23141.5","0.518"]] descending + assert_eq!(orderbook.bids[0].price, 23145.0); + assert_eq!(orderbook.bids[0].quantity_base, 0.107 * 1.0 / 23145.0); + assert_eq!(orderbook.bids[0].quantity_quote, 0.107); + assert_eq!(orderbook.bids[0].quantity_contract, Some(0.107)); + + assert_eq!(orderbook.bids[2].price, 23141.5); + assert_eq!(orderbook.bids[2].quantity_base, 0.518 / 23141.5); + assert_eq!(orderbook.bids[2].quantity_quote, 0.518); + assert_eq!(orderbook.bids[2].quantity_contract, Some(0.518)); + //"asks":[["23146","3.666"],["23146.5","5.562"],["2.315E+4","0.5"]] ascending + assert_eq!(orderbook.asks[0].price, 23146.0); + assert_eq!(orderbook.asks[0].quantity_base, 3.666 / 23146.0); + assert_eq!(orderbook.asks[0].quantity_quote, 3.666); + assert_eq!(orderbook.asks[0].quantity_contract, Some(3.666)); + + assert_eq!(orderbook.asks[2].price, 2.315E+4); + assert_eq!(orderbook.asks[2].quantity_base, 0.5 / 2.315E+4); + assert_eq!(orderbook.asks[2].quantity_quote, 0.5); + assert_eq!(orderbook.asks[2].quantity_contract, Some(0.5)); } #[test] fn linear_swap() { - let raw_msg = r#"{"code":"00000","msg":"success","requestTime":1654234229348,"data":{"asks":[["30544","13.904"],["30544.5","2.678"],["30545","4.923"]],"bids":[["30543","0.756"],["30542.5","0.749"],["30542","15.438"]],"timestamp":"1654234229348"}}"#; - + //let raw_msg = + // r#"{"code":"00000","msg":"success","requestTime":1654234229348,"data":{"asks" + // :[["30544","13.904"],["30544.5","2.678"],["30545","4.923"]],"bids":[["30543", + // "0.756"],["30542.5","0.749"],["30542","15.438"]],"timestamp":"1654234229348" + // }}"#; + let raw_msg = r#"{"code":"00000","msg":"success","requestTime":0,"data":{"asks":[["23133.5","28.326"],["23134","3.027"],["23134.5","128.392"]],"bids":[["23133","16.282"],["23132.5","0.074"],["23132","0.039"]],"timestamp":"1677628852476"}}"#; + let symbol = Some("BTCUSDT_UMCBL"); assert_eq!("NONE", extract_symbol(EXCHANGE_NAME, MarketType::LinearSwap, raw_msg).unwrap()); assert_eq!( - 1654234229348, + 1677628852476, extract_timestamp(EXCHANGE_NAME, MarketType::LinearSwap, raw_msg).unwrap().unwrap() ); + let orderbook = + &parse_l2_snapshot(EXCHANGE_NAME, MarketType::LinearSwap, raw_msg, symbol, None) + .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::LinearSwap, + MessageType::L2Snapshot, + "BTC/USDT".to_string(), + symbol.unwrap().to_string(), + orderbook, + raw_msg, + ); + + assert_eq!(orderbook.timestamp, 1677628852476); + assert_eq!(orderbook.seq_id, None); + assert_eq!(orderbook.prev_seq_id, None); + //"bids":[["23133","16.282"],["23132.5","0.074"],["23132","0.039"]] descending + assert_eq!(orderbook.bids[0].price, 23133.0); + assert_eq!(orderbook.bids[0].quantity_base, 16.282 * 0.001); + assert_eq!(orderbook.bids[0].quantity_quote, round(16.282 * 0.001 * 23133.0)); + assert_eq!(orderbook.bids[0].quantity_contract, Some(16.282)); + + assert_eq!(orderbook.bids[2].price, 23132.0); + assert_eq!(orderbook.bids[2].quantity_base, 0.039 * 0.001); + assert_eq!(orderbook.bids[2].quantity_quote, round(0.039 * 0.001 * 23132.0)); + assert_eq!(orderbook.bids[2].quantity_contract, Some(0.039)); + //"asks":[["23133.5","28.326"],["23134","3.027"],["23134.5","128.392"]] ascending + assert_eq!(orderbook.asks[0].price, 23133.5); + assert_eq!(orderbook.asks[0].quantity_base, 28.326 * 0.001); + assert_eq!(orderbook.asks[0].quantity_quote, round(28.326 * 0.001 * 23133.5)); + assert_eq!(orderbook.asks[0].quantity_contract, Some(28.326)); + + assert_eq!(orderbook.asks[2].price, 23134.5); + assert_eq!(orderbook.asks[2].quantity_base, 128.392 * 0.001); + assert_eq!(orderbook.asks[2].quantity_quote, round(128.392 * 0.001 * 23134.5)); + assert_eq!(orderbook.asks[2].quantity_contract, Some(128.392)); } }