diff --git a/Cargo.lock b/Cargo.lock index 9ae2c66..0f9a20c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ dependencies = [ "clap", "eyre", "futures", + "once_cell", "paste", "rand", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index cc4d065..b701522 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,3 +49,6 @@ default = ["us", "non-us"] all = ["non-us", "us"] non-us = [] us = [] + +[dev-dependencies] +once_cell = "1.18.0" diff --git a/src/exchanges/binance/rest_api/endpoints/instruments.rs b/src/exchanges/binance/rest_api/endpoints/instruments.rs index b99add6..9f48896 100644 --- a/src/exchanges/binance/rest_api/endpoints/instruments.rs +++ b/src/exchanges/binance/rest_api/endpoints/instruments.rs @@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for BinanceAllInstruments { let instruments_value = val .get("symbols") - .ok_or(eyre::ErrReport::msg("could not find 'symbols' field in binance instruments response".to_string())) + .ok_or(eyre::ErrReport::msg(format!("could not find 'symbols' field in binance instruments response of {val:?}"))) .map_err(serde::de::Error::custom)?; let instruments = serde_json::from_value(instruments_value.clone()).map_err(serde::de::Error::custom)?; @@ -165,3 +165,56 @@ impl PartialEq for BinanceInstrument { equals } } + +#[cfg(test)] +mod tests { + use crate::normalized::types::NormalizedTradingPair; + + use super::*; + + #[test] + fn test_binance_instrument_normalize() { + let bi = BinanceInstrument { + symbol: BinanceTradingPair("ETHBTC".to_string()), + status: "TRADING".to_string(), + base_asset: "ETH".to_string(), + base_asset_precision: 8, + quote_asset: "BTC".to_string(), + quote_precision: 8, + quote_asset_precision: 8, + order_types: vec!["LIMIT".to_string(), "LIMIT_MAKER".to_string(), "MARKET".to_string(), "STOP_LOSS_LIMIT".to_string(), "TAKE_PROFIT_LIMIT".to_string()], + iceberg_allowed: true, + oco_allowed: true, + quote_order_qty_market_allowed: true, + allow_trailing_stop: true, + cancel_replace_allowed: true, + is_spot_trading_allowed: true, + is_margin_trading_allowed: true, + permission_sets: vec![vec![BinanceTradingType::Spot, BinanceTradingType::Margin, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other, BinanceTradingType::Other]], + permissions: vec![], + default_self_trade_prevention_mode: "EXPIRE_MAKER".to_string(), + allowed_self_trade_prevention_modes: vec!["EXPIRE_TAKER".to_string(), "EXPIRE_MAKER".to_string(), "EXPIRE_BOTH".to_string()], + }; + + let expected = vec![ + NormalizedInstrument { + exchange: CexExchange::Binance, + trading_pair: NormalizedTradingPair::new_base_quote(CexExchange::Binance, "ETH", "BTC", None, None), + trading_type: NormalizedTradingType::Spot, + base_asset_symbol: "ETH".to_string(), + quote_asset_symbol: "BTC".to_string(), + active: true, + futures_expiry: None + }, NormalizedInstrument { + exchange: CexExchange::Binance, + trading_pair: NormalizedTradingPair::new_base_quote(CexExchange::Binance, "ETH", "BTC", None, None), + trading_type: NormalizedTradingType::Margin, + base_asset_symbol: "ETH".to_string(), + quote_asset_symbol: "BTC".to_string(), + active: true, + futures_expiry: None + } + ]; + assert_eq!(bi.normalize(), expected); + } +} \ No newline at end of file diff --git a/src/exchanges/mod.rs b/src/exchanges/mod.rs index f7700ba..21172ba 100644 --- a/src/exchanges/mod.rs +++ b/src/exchanges/mod.rs @@ -159,7 +159,11 @@ impl CexExchange { /// /// if calling without a filter: /// ``` - /// CexExchange::Okex.get_all_currencies::(None).await; + /// use cex_exchanges::{CexExchange, EmptyFilter}; + /// async { + /// CexExchange::Okex.get_all_currencies::(None).await; + /// }; + /// () /// ``` pub async fn get_all_currencies(self, filter: Option) -> Result, RestApiError> where @@ -212,7 +216,11 @@ impl CexExchange { /// /// if calling without a filter: /// ``` - /// CexExchange::Okex.get_all_instruments::(None).await; + /// use cex_exchanges::{CexExchange, EmptyFilter}; + /// async { + /// CexExchange::Okex.get_all_instruments::(None).await; + /// }; + /// () /// ``` pub async fn get_all_instruments(self, filter: Option) -> Result, RestApiError> where diff --git a/src/exchanges/okex/ws/channels/tickers.rs b/src/exchanges/okex/ws/channels/tickers.rs index 2ed6713..02be8f6 100644 --- a/src/exchanges/okex/ws/channels/tickers.rs +++ b/src/exchanges/okex/ws/channels/tickers.rs @@ -22,9 +22,9 @@ pub struct OkexTicker { #[serde_as(as = "DisplayFromStr")] #[serde(rename = "lastSz")] pub last_size: f64, - #[serde_as(as = "DisplayFromStr")] + #[serde_as(as = "Option")] #[serde(rename = "askPx")] - pub ask_price: f64, + pub ask_price: Option, #[serde_as(as = "DisplayFromStr")] #[serde(rename = "askSz")] pub ask_amt: f64, @@ -74,7 +74,7 @@ impl OkexTicker { pair: self.pair.normalize(), time: DateTime::from_timestamp_millis(self.timestamp as i64).unwrap(), ask_amount: self.ask_amt, - ask_price: self.ask_price, + ask_price: self.ask_price.unwrap_or_default(), bid_amount: self.bid_amt, bid_price: self.bid_price, quote_id: None @@ -90,7 +90,7 @@ impl PartialEq for OkexTicker { && other.bid_amount == self.bid_amt && other.bid_price == self.bid_price && other.ask_amount == self.ask_amt - && other.ask_price == self.ask_price + && other.ask_price == self.ask_price.unwrap_or_default() && other.quote_id.is_none(); if !equals { diff --git a/tests/utils.rs b/tests/utils.rs index 75fb6a9..01c9598 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -1,5 +1,5 @@ #![allow(unused)] -use std::fmt::Debug; +use std::{fmt::Debug, sync::Once}; use cex_exchanges::{ clients::ws::{MutliWsStreamBuilder, WsStream}, @@ -120,28 +120,44 @@ where writeln!(f0, "{}", serde_json::to_string(&a).unwrap()).unwrap(); } +static TRACING: Once = Once::new(); + pub fn init_test_tracing() { - let data_filter = EnvFilter::builder() - .with_default_directive(format!("cex-exchanges={}", Level::TRACE).parse().unwrap()) - .from_env_lossy(); - - let data_layer = tracing_subscriber::fmt::layer() - .with_ansi(true) - .with_target(true) - .with_filter(data_filter) - .boxed(); - - let general_filter = EnvFilter::builder() - .with_default_directive(Level::DEBUG.into()) - .from_env_lossy(); - - let general_layer = tracing_subscriber::fmt::layer() - .with_ansi(true) - .with_target(true) - .with_filter(general_filter) - .boxed(); - - tracing_subscriber::registry() - .with(vec![data_layer, general_layer]) - .init(); + TRACING.call_once(|| { + let data_filter = EnvFilter::builder() + .with_default_directive(format!("cex-exchanges={}", Level::TRACE).parse().unwrap()) + .from_env_lossy(); + + let data_layer = tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_target(true) + .with_filter(data_filter) + .boxed(); + + let general_filter = EnvFilter::builder() + .with_default_directive(Level::DEBUG.into()) + .from_env_lossy(); + + let general_layer = tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_target(true) + .with_filter(general_filter) + .boxed(); + + tracing_subscriber::registry() + .with(vec![data_layer, general_layer]) + .init(); + }); +} + +pub async fn timeout_function(test_duration_sec: u64, f: impl futures::Future) -> bool +where +{ + let sleep = tokio::time::sleep(std::time::Duration::from_secs(test_duration_sec)); + tokio::pin!(sleep); + + tokio::select! { + _ = f => true, + _ = &mut sleep => false, + } } diff --git a/tests/ws.rs b/tests/ws.rs index 8f79411..fda14b3 100644 --- a/tests/ws.rs +++ b/tests/ws.rs @@ -30,7 +30,7 @@ mod coinbase_tests { let builder = CoinbaseWsBuilder::default().add_channel( CoinbaseWsChannel::new_match(vec![RawTradingPair::new_raw("ETH_USD", '_'), RawTradingPair::new_no_delim("BTC-USD")]).unwrap() ); - coinbase_util(builder, 5).await; + assert!(timeout_function(5, coinbase_util(builder, 5)).await); } #[tokio::test] @@ -40,7 +40,7 @@ mod coinbase_tests { let builder = CoinbaseWsBuilder::default().add_channel( CoinbaseWsChannel::new_ticker(vec![RawTradingPair::new_raw("ETH_USD", '_'), RawTradingPair::new_no_delim("BTC-USD")]).unwrap() ); - coinbase_util(builder, 5).await; + assert!(timeout_function(5, coinbase_util(builder, 5)).await); } #[tokio::test] @@ -56,7 +56,7 @@ mod coinbase_tests { .build_many_distributed() .unwrap(); - mutlistream_util(builder, 50).await; + assert!(timeout_function(30, mutlistream_util(builder, 50)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -69,7 +69,7 @@ mod coinbase_tests { .await .unwrap(); - mutlistream_util(builder, 1000).await; + assert!(timeout_function(45, mutlistream_util(builder, 1000)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -81,7 +81,7 @@ mod coinbase_tests { let builder = CoinbaseWsBuilder::build_from_all_instruments(&channels, Some(10)) .await .unwrap(); - mutlithreaded_util(builder, 1000).await; + assert!(timeout_function(45, mutlithreaded_util(builder, 1000)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -104,7 +104,7 @@ mod coinbase_tests { .collect::>() ); - normalized_mutlithreaded_util(builder, 1000).await; + assert!(timeout_function(45, normalized_mutlithreaded_util(builder, 1000)).await); } } @@ -127,7 +127,7 @@ mod okex_tests { use super::*; async fn okex_util(builder: OkexWsBuilder, iterations: usize) { - stream_util(builder.build_single(), iterations).await; + assert!(timeout_function(15, stream_util(builder.build_single(), iterations)).await); } #[tokio::test] @@ -136,7 +136,8 @@ mod okex_tests { init_test_tracing(); let builder = OkexWsBuilder::new(None) .add_channel(OkexWsChannel::new_trade(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdt")]).unwrap()); - okex_util(builder, 5).await; + + assert!(timeout_function(15, okex_util(builder, 5)).await); } #[tokio::test] @@ -146,7 +147,7 @@ mod okex_tests { let builder = OkexWsBuilder::new(None).add_channel( OkexWsChannel::new_book_ticker(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdc")]).unwrap() ); - okex_util(builder, 5).await; + assert!(timeout_function(5, okex_util(builder, 5)).await); } #[tokio::test] @@ -165,7 +166,7 @@ mod okex_tests { .build_many_distributed() .unwrap(); - mutlistream_util(builder, 50).await; + assert!(timeout_function(60, mutlistream_util(builder, 50)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -178,7 +179,7 @@ mod okex_tests { .await .unwrap(); - mutlistream_util(builder, 1000).await; + assert!(timeout_function(15, mutlistream_util(builder, 1000)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -190,7 +191,7 @@ mod okex_tests { let builder = OkexWsBuilder::build_from_all_instruments(&channels, None, Some(10)) .await .unwrap(); - mutlithreaded_util(builder, 1000).await; + assert!(timeout_function(15, mutlithreaded_util(builder, 1000)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -213,7 +214,7 @@ mod okex_tests { .collect::>() ); - normalized_mutlithreaded_util(builder, 1000).await; + assert!(timeout_function(25, normalized_mutlithreaded_util(builder, 1000)).await); } } @@ -242,7 +243,7 @@ mod binance_tests { let builder = BinanceWsBuilder::default().add_channel( BinanceWsChannel::new_trade(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdc")]).unwrap() ); - binance_util(builder, 5).await; + assert!(timeout_function(60, binance_util(builder, 5)).await); } #[tokio::test] @@ -252,7 +253,7 @@ mod binance_tests { let builder = BinanceWsBuilder::default().add_channel( BinanceWsChannel::new_book_ticker(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdc")]).unwrap() ); - binance_util(builder, 5).await; + assert!(timeout_function(5, binance_util(builder, 5)).await); } #[tokio::test] @@ -271,7 +272,7 @@ mod binance_tests { .build_many_distributed() .unwrap(); - mutlistream_util(builder, 50).await; + assert!(timeout_function(120, mutlistream_util(builder, 50)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -284,7 +285,7 @@ mod binance_tests { .await .unwrap(); - mutlistream_util(builder, 1000).await; + assert!(timeout_function(120, mutlistream_util(builder, 1000)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -296,7 +297,7 @@ mod binance_tests { let builder = BinanceWsBuilder::build_from_all_instruments(&channels) .await .unwrap(); - mutlithreaded_util(builder, 1000).await; + assert!(timeout_function(120, mutlithreaded_util(builder, 1000)).await); } } @@ -325,7 +326,7 @@ mod kucoin_tests { let builder = KucoinWsBuilder::default().add_channel( KucoinWsChannel::new_match(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdc")]).unwrap() ); - kucoin_util(builder, 5).await; + assert!(timeout_function(45, kucoin_util(builder, 5)).await); } #[tokio::test] @@ -335,7 +336,7 @@ mod kucoin_tests { let builder = KucoinWsBuilder::default().add_channel( KucoinWsChannel::new_ticker(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdc")]).unwrap() ); - kucoin_util(builder, 5).await; + assert!(timeout_function(5, kucoin_util(builder, 5)).await); } #[tokio::test] @@ -352,7 +353,7 @@ mod kucoin_tests { .build_many_distributed() .unwrap(); - mutlistream_util(builder, 50).await; + assert!(timeout_function(15, mutlistream_util(builder, 50)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -365,7 +366,7 @@ mod kucoin_tests { .await .unwrap(); - mutlistream_util(builder, 1000).await; + assert!(timeout_function(145, mutlistream_util(builder, 1000)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -377,7 +378,7 @@ mod kucoin_tests { let builder = KucoinWsBuilder::build_from_all_instruments(&channels) .await .unwrap(); - mutlithreaded_util(builder, 1000).await; + assert!(timeout_function(145, mutlithreaded_util(builder, 1000)).await); } } @@ -406,7 +407,7 @@ mod bybit_tests { let builder = BybitWsBuilder::default().add_channel( BybitWsChannel::new_trade(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdc")]).unwrap() ); - bybit_util(builder, 5).await; + assert!(timeout_function(5, bybit_util(builder, 5)).await); } #[tokio::test] @@ -416,7 +417,7 @@ mod bybit_tests { let builder = BybitWsBuilder::default().add_channel( BybitWsChannel::new_ticker(vec![RawTradingPair::new_raw("ETH_USDt", '_'), RawTradingPair::new_no_delim("BTC-USdc")]).unwrap() ); - bybit_util(builder, 5).await; + assert!(timeout_function(5, bybit_util(builder, 5)).await); } #[tokio::test] @@ -431,7 +432,7 @@ mod bybit_tests { .build_many_distributed() .unwrap(); - mutlistream_util(builder, 50).await; + assert!(timeout_function(5, mutlistream_util(builder, 50)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -444,7 +445,7 @@ mod bybit_tests { .await .unwrap(); - mutlistream_util(builder, 1000).await; + assert!(timeout_function(15, mutlistream_util(builder, 1000)).await); } #[tokio::test(flavor = "multi_thread", worker_threads = 3)] @@ -456,6 +457,6 @@ mod bybit_tests { let builder = BybitWsBuilder::build_from_all_instruments(&channels) .await .unwrap(); - mutlithreaded_util(builder, 1000).await; + assert!(timeout_function(15, mutlithreaded_util(builder, 1000)).await); } }