diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c558f369..5d9119bc 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,6 +20,11 @@ jobs: override: true components: clippy - run: cargo clippy --all-targets +# - name: Clean Cargo cache and reinstall dependencies +# run: | +# cargo clean +# cargo update -p openssl-src + test: name: test @@ -69,4 +74,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: fmt - args: --all -- --check + args: --all -- --check \ No newline at end of file diff --git a/.rustfmt.toml b/.rustfmt.toml index 18a1f7a1..75872d27 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,4 +1,4 @@ binop_separator = "Back" fn_args_layout = "Compressed" newline_style = "Unix" -reorder_imports = false +reorder_imports = false \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d1acbeb1..d901ce52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,19 +27,21 @@ serde = { version = "1.0.126", features = ["derive"] } serde_json = "1.0" error-chain = { version = "0.12.4", default-features = false } reqwest = { version = "0.11.4", features = ["blocking", "json"] } -tungstenite = { version = "0.18.0", features = ["native-tls"] } +tungstenite = { version = "0.19.0", features = ["native-tls"] } url = "2.2.2" [features] -vendored-tls = ["reqwest/native-tls-vendored", "tungstenite/native-tls-vendored"] +vendored-tls = [ + "reqwest/native-tls-vendored", + "tungstenite/native-tls-vendored", +] [dev-dependencies] -csv ="1.1.6" -mockito = "0.31.0" +csv = "1.1.6" +mockito = "0.32.5" env_logger = "0.9.0" criterion = "0.3" float-cmp = "0.9.0" -serde_json = "1.0" [[bench]] name = "websocket_benchmark" diff --git a/benches/websocket_benchmark.rs b/benches/websocket_benchmark.rs index d0a9df96..bbcacd5e 100644 --- a/benches/websocket_benchmark.rs +++ b/benches/websocket_benchmark.rs @@ -7,27 +7,33 @@ use core::time::Duration; fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("websockets-decoder"); - let all_symbols_json = reqwest::blocking::get("https://api.binance.com/api/v3/ticker/price") - .unwrap() - .text() - .unwrap(); - - let btc_symbol_json = - reqwest::blocking::get("https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT") - .unwrap() - .text() - .unwrap(); + let all_symbols_json = reqwest::blocking::get( + "https://api.binance.com/api/v3/ticker/price", + ) + .unwrap() + .text() + .unwrap(); + + let btc_symbol_json = reqwest::blocking::get( + "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT", + ) + .unwrap() + .text() + .unwrap(); let mut web_socket_subscribed: WebSockets<'_> = WebSockets::new(|_event: WebsocketEvent| Ok(())); web_socket_subscribed.connect("!ticker@arr").unwrap(); - let mut web_socket: WebSockets<'_> = WebSockets::new(|_event: WebsocketEvent| Ok(())); + let mut web_socket: WebSockets<'_> = + WebSockets::new(|_event: WebsocketEvent| Ok(())); group.sample_size(200); group.measurement_time(Duration::new(35, 0)); group.bench_function("handle_msg all symbols", |b| { - b.iter(|| web_socket_subscribed.test_handle_msg(&all_symbols_json)); + b.iter(|| { + web_socket_subscribed.test_handle_msg(&all_symbols_json) + }); }); group.bench_function("handle_msg BTCUSDT symbol", |b| { b.iter(|| web_socket.test_handle_msg(&btc_symbol_json)); diff --git a/examples/binance_endpoints.rs b/examples/binance_endpoints.rs index 8ea7bc6d..724f2eb6 100644 --- a/examples/binance_endpoints.rs +++ b/examples/binance_endpoints.rs @@ -1,11 +1,11 @@ +use binance::account::*; use binance::api::*; -use binance::savings::*; use binance::config::*; +use binance::errors::ErrorKind as BinanceLibErrorKind; use binance::general::*; -use binance::account::*; use binance::market::*; use binance::model::KlineSummary; -use binance::errors::ErrorKind as BinanceLibErrorKind; +use binance::savings::*; fn main() { // The general spot API endpoints; shown with @@ -24,7 +24,8 @@ fn main() { fn general(use_testnet: bool) { let general: General = if use_testnet { - let config = Config::default().set_rest_api_endpoint("https://testnet.binance.vision"); + let config = Config::default() + .set_rest_api_endpoint("https://testnet.binance.vision"); Binance::new_with_config(None, None, &config) } else { Binance::new(None, None) @@ -231,7 +232,9 @@ fn market_data() { match market.get_klines("BNBETH", "5m", 10, None, None) { Ok(klines) => { match klines { - binance::model::KlineSummaries::AllKlineSummaries(klines) => { + binance::model::KlineSummaries::AllKlineSummaries( + klines, + ) => { let kline: KlineSummary = klines[0].clone(); // You need to iterate over the klines println!( "Open: {}, High: {}, Low: {}", diff --git a/examples/binance_futures_endpoints.rs b/examples/binance_futures_endpoints.rs index 7cb1969f..bc155530 100644 --- a/examples/binance_futures_endpoints.rs +++ b/examples/binance_futures_endpoints.rs @@ -1,8 +1,8 @@ use binance::api::*; +use binance::errors::ErrorKind as BinanceLibErrorKind; use binance::futures::general::*; use binance::futures::market::*; use binance::futures::model::*; -use binance::errors::ErrorKind as BinanceLibErrorKind; fn main() { general(); @@ -47,22 +47,30 @@ fn market_data() { let market: FuturesMarket = Binance::new(None, None); match market.get_depth("btcusdt") { - Ok(answer) => println!("Depth update ID: {:?}", answer.last_update_id), + Ok(answer) => { + println!("Depth update ID: {:?}", answer.last_update_id) + } Err(e) => println!("Error: {}", e), } match market.get_trades("btcusdt") { - Ok(Trades::AllTrades(answer)) => println!("First trade: {:?}", answer[0]), + Ok(Trades::AllTrades(answer)) => { + println!("First trade: {:?}", answer[0]) + } Err(e) => println!("Error: {}", e), } match market.get_agg_trades("btcusdt", None, None, None, None) { - Ok(AggTrades::AllAggTrades(answer)) => println!("First aggregated trade: {:?}", answer[0]), + Ok(AggTrades::AllAggTrades(answer)) => { + println!("First aggregated trade: {:?}", answer[0]) + } Err(e) => println!("Error: {}", e), } match market.get_klines("btcusdt", "5m", 10, None, None) { - Ok(KlineSummaries::AllKlineSummaries(answer)) => println!("First kline: {:?}", answer[0]), + Ok(KlineSummaries::AllKlineSummaries(answer)) => { + println!("First kline: {:?}", answer[0]) + } Err(e) => println!("Error: {}", e), } @@ -77,7 +85,9 @@ fn market_data() { } match market.get_all_book_tickers() { - Ok(BookTickers::AllBookTickers(answer)) => println!("First book ticker: {:?}", answer[0]), + Ok(BookTickers::AllBookTickers(answer)) => { + println!("First book ticker: {:?}", answer[0]) + } Err(e) => println!("Error: {}", e), } @@ -87,7 +97,9 @@ fn market_data() { } match market.get_mark_prices() { - Ok(MarkPrices::AllMarkPrices(answer)) => println!("First mark Prices: {:?}", answer[0]), + Ok(MarkPrices::AllMarkPrices(answer)) => { + println!("First mark Prices: {:?}", answer[0]) + } Err(e) => println!("Error: {}", e), } diff --git a/examples/binance_futures_userstream.rs b/examples/binance_futures_userstream.rs index 0307638e..bec1d0b5 100644 --- a/examples/binance_futures_userstream.rs +++ b/examples/binance_futures_userstream.rs @@ -7,14 +7,17 @@ fn main() { fn user_stream() { let api_key_user = Some("YOUR_API_KEY".into()); - let user_stream: FuturesUserStream = Binance::new(api_key_user, None); + let user_stream: FuturesUserStream = + Binance::new(api_key_user, None); if let Ok(answer) = user_stream.start() { println!("Data Stream Started ..."); let listen_key = answer.listen_key; match user_stream.keep_alive(&listen_key) { - Ok(msg) => println!("Keepalive user data stream: {:?}", msg), + Ok(msg) => { + println!("Keepalive user data stream: {:?}", msg) + } Err(e) => println!("Error: {}", e), } @@ -23,6 +26,8 @@ fn user_stream() { Err(e) => println!("Error: {}", e), } } else { - println!("Not able to start an User Stream (Check your API_KEY)"); + println!( + "Not able to start an User Stream (Check your API_KEY)" + ); } } diff --git a/examples/binance_futures_websockets.rs b/examples/binance_futures_websockets.rs index 54d08635..f1d4dba3 100755 --- a/examples/binance_futures_websockets.rs +++ b/examples/binance_futures_websockets.rs @@ -12,9 +12,9 @@ fn market_websocket() { let keep_running = AtomicBool::new(true); let stream_examples_usd_m = vec![ // taken from https://binance-docs.github.io/apidocs/futures/en/#websocket-market-streams - "btcusdt@aggTrade", // @aggTrade - "btcusdt@markPrice", // @markPrice OR @markPrice@1s - "btcusdt@kline_1m", // @kline_ + "btcusdt@aggTrade", // @aggTrade + "btcusdt@markPrice", // @markPrice OR @markPrice@1s + "btcusdt@kline_1m", // @kline_ "btcusdt_perpetual@continuousKline_1m", // _@continuousKline_ e.g. "btcusd_next_quarter@continuousKline_1m" "btcusdt@miniTicker", // @miniTicker "!miniTicker@arr", @@ -27,7 +27,7 @@ fn market_websocket() { "btcusdt@forceOrder", // @forceOrder "!forceOrder@arr", "btcusdt@depth20@100ms", // @depth OR @depth@500ms OR @depth@100ms. - "btcusdt@depth@100ms", // @depth OR @depth@500ms OR @depth@100ms + "btcusdt@depth@100ms", // @depth OR @depth@500ms OR @depth@100ms ]; let stream_examples_coin_m = vec![ @@ -36,15 +36,15 @@ fn market_websocket() { // A possible symbol is btcusd_210924. This needs updates if the current date // is greater than 2021-09-24. It'd be nice to make this symbol automatically // generated, or find a that always works. - "btcusd_210924@aggTrade", // @aggTrade - "btcusd@indexPrice@1s", //@indexPrice OR @indexPrice@1s - "btcusd_210924@markPrice", // @markPrice OR @markPrice@1s - "btcusd@markPrice", // @markPrice OR @markPrice@1s - "btcusd_210924@kline_1m", // @kline_ + "btcusd_210924@aggTrade", // @aggTrade + "btcusd@indexPrice@1s", //@indexPrice OR @indexPrice@1s + "btcusd_210924@markPrice", // @markPrice OR @markPrice@1s + "btcusd@markPrice", // @markPrice OR @markPrice@1s + "btcusd_210924@kline_1m", // @kline_ "btcusd_next_quarter@continuousKline_1m", // _@continuousKline_ - "btcusd@indexPriceKline_1m", // @indexPriceKline_ - "btcusd_210924@markPriceKline_1m", // @markPriceKline_ - "btcusd_210924@miniTicker", // @miniTicker + "btcusd@indexPriceKline_1m", // @indexPriceKline_ + "btcusd_210924@markPriceKline_1m", // @markPriceKline_ + "btcusd_210924@miniTicker", // @miniTicker "!miniTicker@arr", "btcusd_210924@ticker", // @ticker "!ticker@arr", @@ -75,7 +75,8 @@ fn market_websocket() { println!("Starting with USD_M {:?}", stream_example); keep_running.swap(true, Ordering::Relaxed); - let mut web_socket: FuturesWebSockets<'_> = FuturesWebSockets::new(callback_fn); + let mut web_socket: FuturesWebSockets<'_> = + FuturesWebSockets::new(callback_fn); web_socket .connect(&FuturesMarket::USDM, stream_example) .unwrap(); @@ -88,7 +89,8 @@ fn market_websocket() { println!("Starting with COIN_M {:?}", stream_example); keep_running.swap(true, Ordering::Relaxed); - let mut web_socket: FuturesWebSockets<'_> = FuturesWebSockets::new(callback_fn); + let mut web_socket: FuturesWebSockets<'_> = + FuturesWebSockets::new(callback_fn); web_socket .connect(&FuturesMarket::COINM, stream_example) .unwrap(); diff --git a/examples/binance_save_all_trades.rs b/examples/binance_save_all_trades.rs index fe711775..5a9f7beb 100644 --- a/examples/binance_save_all_trades.rs +++ b/examples/binance_save_all_trades.rs @@ -1,10 +1,10 @@ +use csv::Writer; use std::error::Error; use std::fs::File; -use csv::Writer; -use std::sync::atomic::{AtomicBool}; +use std::sync::atomic::AtomicBool; +use binance::model::DayTickerEvent; use binance::websockets::*; -use binance::model::{DayTickerEvent}; fn main() { save_all_trades_websocket(); @@ -21,7 +21,10 @@ fn save_all_trades_websocket() { } // serialize DayTickerEvent as CSV records - pub fn write_to_file(&mut self, events: Vec) -> Result<(), Box> { + pub fn write_to_file( + &mut self, + events: Vec, + ) -> Result<(), Box> { for event in events { self.wrt.serialize(event)?; } @@ -35,17 +38,20 @@ fn save_all_trades_websocket() { let mut web_socket_handler = WebSocketHandler::new(local_wrt); let agg_trade = String::from("!ticker@arr"); - let mut web_socket = WebSockets::new(move |event: WebsocketEvent| { - if let WebsocketEvent::DayTickerAll(events) = event { - // You can break the event_loop if some condition is met be setting keep_running to false - // keep_running.store(false, Ordering::Relaxed); - if let Err(error) = web_socket_handler.write_to_file(events) { - println!("{}", error); + let mut web_socket = + WebSockets::new(move |event: WebsocketEvent| { + if let WebsocketEvent::DayTickerAll(events) = event { + // You can break the event_loop if some condition is met be setting keep_running to false + // keep_running.store(false, Ordering::Relaxed); + if let Err(error) = + web_socket_handler.write_to_file(events) + { + println!("{}", error); + } } - } - Ok(()) - }); + Ok(()) + }); web_socket.connect(&agg_trade).unwrap(); // check error if let Err(e) = web_socket.event_loop(&keep_running) { diff --git a/examples/binance_websockets.rs b/examples/binance_websockets.rs index f6cc6296..68491b99 100644 --- a/examples/binance_websockets.rs +++ b/examples/binance_websockets.rs @@ -24,7 +24,9 @@ fn user_stream() { let listen_key = answer.listen_key; match user_stream.keep_alive(&listen_key) { - Ok(msg) => println!("Keepalive user data stream: {:?}", msg), + Ok(msg) => { + println!("Keepalive user data stream: {:?}", msg) + } Err(e) => println!("Error: {}", e), } @@ -33,7 +35,9 @@ fn user_stream() { Err(e) => println!("Error: {}", e), } } else { - println!("Not able to start an User Stream (Check your API_KEY)"); + println!( + "Not able to start an User Stream (Check your API_KEY)" + ); } } @@ -45,30 +49,32 @@ fn user_stream_websocket() { if let Ok(answer) = user_stream.start() { let listen_key = answer.listen_key; - let mut web_socket: WebSockets<'_> = WebSockets::new(|event: WebsocketEvent| { - match event { - WebsocketEvent::AccountUpdate(account_update) => { - for balance in &account_update.data.balances { - println!( + let mut web_socket: WebSockets<'_> = WebSockets::new( + |event: WebsocketEvent| { + match event { + WebsocketEvent::AccountUpdate(account_update) => { + for balance in &account_update.data.balances { + println!( "Asset: {}, wallet_balance: {}, cross_wallet_balance: {}, balance: {}", balance.asset, balance.wallet_balance, balance.cross_wallet_balance, balance.balance_change ); + } } - } - WebsocketEvent::OrderTrade(trade) => { - println!( + WebsocketEvent::OrderTrade(trade) => { + println!( "Symbol: {}, Side: {}, Price: {}, Execution Type: {}", trade.symbol, trade.side, trade.price, trade.execution_type ); - } - _ => (), - }; + } + _ => (), + }; - Ok(()) - }); + Ok(()) + }, + ); web_socket.connect(&listen_key).unwrap(); // check error if let Err(e) = web_socket.event_loop(&keep_running) { @@ -78,38 +84,45 @@ fn user_stream_websocket() { web_socket.disconnect().unwrap(); println!("Userstrem closed and disconnected"); } else { - println!("Not able to start an User Stream (Check your API_KEY)"); + println!( + "Not able to start an User Stream (Check your API_KEY)" + ); } } fn market_websocket() { let keep_running = AtomicBool::new(true); // Used to control the event loop let agg_trade = String::from("ethbtc@aggTrade"); - let mut web_socket: WebSockets<'_> = WebSockets::new(|event: WebsocketEvent| { - match event { - WebsocketEvent::Trade(trade) => { - println!( - "Symbol: {}, price: {}, qty: {}", - trade.symbol, trade.price, trade.qty - ); - } - WebsocketEvent::DepthOrderBook(depth_order_book) => { - println!( - "Symbol: {}, Bids: {:?}, Ask: {:?}", - depth_order_book.symbol, depth_order_book.bids, depth_order_book.asks - ); - } - WebsocketEvent::OrderBook(order_book) => { - println!( - "last_update_id: {}, Bids: {:?}, Ask: {:?}", - order_book.last_update_id, order_book.bids, order_book.asks - ); - } - _ => (), - }; + let mut web_socket: WebSockets<'_> = + WebSockets::new(|event: WebsocketEvent| { + match event { + WebsocketEvent::Trade(trade) => { + println!( + "Symbol: {}, price: {}, qty: {}", + trade.symbol, trade.price, trade.qty + ); + } + WebsocketEvent::DepthOrderBook(depth_order_book) => { + println!( + "Symbol: {}, Bids: {:?}, Ask: {:?}", + depth_order_book.symbol, + depth_order_book.bids, + depth_order_book.asks + ); + } + WebsocketEvent::OrderBook(order_book) => { + println!( + "last_update_id: {}, Bids: {:?}, Ask: {:?}", + order_book.last_update_id, + order_book.bids, + order_book.asks + ); + } + _ => (), + }; - Ok(()) - }); + Ok(()) + }); web_socket.connect(&agg_trade).unwrap(); // check error if let Err(e) = web_socket.event_loop(&keep_running) { @@ -127,7 +140,9 @@ fn all_trades_websocket() { for tick_event in ticker_events { println!( "Symbol: {}, price: {}, qty: {}", - tick_event.symbol, tick_event.best_bid, tick_event.best_bid_qty + tick_event.symbol, + tick_event.best_bid, + tick_event.best_bid_qty ); } } @@ -150,7 +165,9 @@ fn kline_websocket() { if let WebsocketEvent::Kline(kline_event) = event { println!( "Symbol: {}, high: {}, low: {}", - kline_event.kline.symbol, kline_event.kline.low, kline_event.kline.high + kline_event.kline.symbol, + kline_event.kline.low, + kline_event.kline.high ); } @@ -173,7 +190,8 @@ fn last_price_for_one_symbol() { let mut web_socket = WebSockets::new(|event: WebsocketEvent| { if let WebsocketEvent::DayTicker(ticker_event) = event { btcusdt = ticker_event.average_price.parse().unwrap(); - let btcusdt_close: f32 = ticker_event.current_close.parse().unwrap(); + let btcusdt_close: f32 = + ticker_event.current_close.parse().unwrap(); println!("{} - {}", btcusdt, btcusdt_close); if btcusdt_close as i32 == 7000 { @@ -194,12 +212,13 @@ fn last_price_for_one_symbol() { } fn multiple_streams() { - let endpoints = - ["ETHBTC", "BNBETH"].map(|symbol| format!("{}@depth@100ms", symbol.to_lowercase())); + let endpoints = ["ETHBTC", "BNBETH"] + .map(|symbol| format!("{}@depth@100ms", symbol.to_lowercase())); let keep_running = AtomicBool::new(true); let mut web_socket = WebSockets::new(|event: WebsocketEvent| { - if let WebsocketEvent::DepthOrderBook(depth_order_book) = event { + if let WebsocketEvent::DepthOrderBook(depth_order_book) = event + { println!("{:?}", depth_order_book); } diff --git a/src/account.rs b/src/account.rs index 93ec116f..e8ff906d 100644 --- a/src/account.rs +++ b/src/account.rs @@ -1,15 +1,15 @@ use error_chain::bail; -use crate::util::build_signed_request; -use crate::model::{ - AccountInformation, Balance, Empty, Order, OrderCanceled, TradeHistory, Transaction, -}; +use crate::api::{Convert, Sapi, Spot, API}; use crate::client::Client; use crate::errors::Result; +use crate::model::{ + AccountInformation, AccountSnapshot, Balance, Empty, Order, OrderCanceled, Quote, + QuoteResponse, TradeHistory, Transaction, +}; +use crate::util::build_signed_request; use std::collections::BTreeMap; use std::fmt::Display; -use crate::api::API; -use crate::api::Spot; #[derive(Clone)] pub struct Account { @@ -37,6 +37,42 @@ struct OrderQuoteQuantityRequest { pub time_in_force: TimeInForce, pub new_client_order_id: Option, } +pub enum WalletType { + SPOT, + FUNDING, +} + +pub enum ValidTime { + TenSeconds, + ThirtySeconds, + OneMinutes, + TwoMinutes, +} + +pub enum QtyType> { + ///* "From" When specified, it is the amount you will be debited after the conversion + From(T), + ///* "To" When specified, it is the amount you will be credited after the conversion + To(T), +} + +struct OrderQuoteRequest> { + pub from_asset: String, + pub to_asset: String, + pub from_or_to_amount: QtyType, + pub wallet_type: Option, + // default 10s + pub valid_time: Option, +} + +struct AccountSnapshotRequest { + // "SPOT", "MARGIN", "FUTURES" + pub type_: String, + pub start_time: Option, + pub end_time: Option, + // min 7, max 30, default 7 + pub limit: Option, +} pub enum OrderType { Limit, @@ -765,4 +801,186 @@ impl Account { order_parameters } + + fn converter_order_to_btree_map>( + &self, order: OrderQuoteRequest, + ) -> BTreeMap { + let mut order_parameters: BTreeMap = BTreeMap::new(); + + order_parameters.insert("fromAsset".into(), order.from_asset.to_string()); + order_parameters.insert("toAsset".into(), order.to_asset.to_string()); + + match order.from_or_to_amount { + QtyType::From(v) => { + let qty: f64 = v.into(); + order_parameters.insert("fromAmount".into(), qty.to_string()); + } + QtyType::To(v) => { + let qty: f64 = v.into(); + order_parameters.insert("toAmount".into(), qty.to_string()); + } + }; + + if let Some(wallet_type) = order.wallet_type { + match wallet_type { + WalletType::SPOT => { + order_parameters.insert("walletType".into(), "SPOT".to_string()); + } + WalletType::FUNDING => { + order_parameters.insert("walletType".into(), "FUNDING".to_string()); + } + } + } + + if let Some(time) = order.valid_time { + match time { + ValidTime::TenSeconds => { + order_parameters.insert("validTime".into(), "10s".to_string()); + } + ValidTime::ThirtySeconds => { + order_parameters.insert("validTime".into(), "30s".to_string()); + } + ValidTime::OneMinutes => { + order_parameters.insert("validTime".into(), "1m".to_string()); + } + ValidTime::TwoMinutes => { + order_parameters.insert("validTime".into(), "2m".to_string()); + } + } + } + + order_parameters + } + + // build the request to convert + fn send_quote_request( + &self, symbol_from: S, symbol_to: S, qty: QtyType, wallet_type: Option, + valid_time: Option, + ) -> Result + where + S: Into, + F: Into, + { + let params = OrderQuoteRequest { + from_asset: symbol_from.into(), + to_asset: symbol_to.into(), + from_or_to_amount: qty, + wallet_type, + valid_time, + }; + + let order = self.converter_order_to_btree_map(params); + let request = build_signed_request(order, self.recv_window)?; + self.client + .post_signed(API::Convert(Convert::QuoteRequest), request) + } + + // method que aceita a negociação do convert + fn accept_quote(&self, quote: Result) -> Result { + let quote = quote?; + + //let quote = quote?; + let mut params: BTreeMap = BTreeMap::new(); + + if let Some(quote_id) = quote.quote_id { + params.insert("quoteId".into(), quote_id); + } else { + bail!("Not enough funds") + } + + let request: String = build_signed_request(params, self.recv_window)?; + self.client + .post_signed(API::Convert(Convert::AcceptQuote), request) + } + + /// # Example + /// Convert a currency to another. + /// + ///```no_run + /// use binance::api::Binance; + /// use binance::account::*; + /// + /// fn main() { + /// let api_key = Some("api_key".into()); + /// let secret_key = Some("secret_key".into()); + /// + /// let account: Account = Binance::new(api_key, secret_key); + /// + /// // QtyType::From converts "BTC" to "USDT", amount: 0.0001 BTC + /// let response = account.convert("BTC", "USDT", QtyType::From(0.0001)).unwrap(); + /// // QtyType::To converts "BTC" to "USDT", amount: 2.0 USDT + /// let response = account.convert("BTC", "USDT", QtyType::To(2.0)).unwrap(); + /// } + ///``` + pub fn convert( + &self, symbol_from: S, symbol_to: S, qty: QtyType, + ) -> Result + where + S: Into, + F: Into, + { + let quote = self.send_quote_request( + symbol_from, + symbol_to, + qty, + None, + Some(ValidTime::TenSeconds), + ); + + self.accept_quote(quote) + } + + fn daily_account_snapshot_to_btree_map( + &self, params: AccountSnapshotRequest, + ) -> BTreeMap { + let mut parameters: BTreeMap = BTreeMap::new(); + + parameters.insert("type".into(), params.type_); + + if let Some(start_time) = params.start_time { + parameters.insert("startTime".into(), start_time.to_string()); + } + + if let Some(end_time) = params.end_time { + parameters.insert("endTime".into(), end_time.to_string()); + } + + if let Some(limit) = params.limit { + parameters.insert("limit".into(), limit.to_string()); + } + + parameters + } + + /// # Example + /// Get the daily account snapshot. + /// + ///```no_run + /// use binance::api::Binance; + /// use binance::account::*; + /// + /// fn main() { + /// let api_key = Some("api_key".into()); + /// let secret_key = Some("secret_key".into()); + /// let account: Account = Binance::new(api_key, secret_key); + /// let answer = account.daily_account_snapshot().unwrap(); + /// } + ///``` + pub fn daily_account_snapshot(&self) -> Result { + let params = AccountSnapshotRequest { + type_: "SPOT".to_string(), + start_time: None, + end_time: None, + limit: None, + }; + let btree_params = self.daily_account_snapshot_to_btree_map(params); + + // this gets the timestamp and recv_windows to the btreemap + let request = build_signed_request(btree_params, self.recv_window)?; + + eprintln!("{:#?}", request); + + self.client + .get_signed(API::Savings(Sapi::AccountSnapshot), Some(request)) + } } diff --git a/src/api.rs b/src/api.rs index 6e9df21f..8ac3160c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -7,13 +7,14 @@ use crate::futures::market::FuturesMarket; use crate::futures::userstream::FuturesUserStream; use crate::general::General; use crate::market::Market; -use crate::userstream::UserStream; use crate::savings::Savings; +use crate::userstream::UserStream; #[allow(clippy::all)] pub enum API { Spot(Spot), Savings(Sapi), + Convert(Convert), Futures(Futures), } @@ -51,6 +52,12 @@ pub enum Sapi { AssetDetail, DepositAddress, SpotFuturesTransfer, + AccountSnapshot, +} + +pub enum Convert { + QuoteRequest, + AcceptQuote, } pub enum Futures { @@ -124,8 +131,17 @@ impl From for String { API::Savings(route) => match route { Sapi::AllCoins => "/sapi/v1/capital/config/getall", Sapi::AssetDetail => "/sapi/v1/asset/assetDetail", - Sapi::DepositAddress => "/sapi/v1/capital/deposit/address", - Sapi::SpotFuturesTransfer => "/sapi/v1/futures/transfer", + Sapi::DepositAddress => { + "/sapi/v1/capital/deposit/address" + } + Sapi::SpotFuturesTransfer => { + "/sapi/v1/futures/transfer" + } + Sapi::AccountSnapshot => "/sapi/v1/accountSnapshot", + }, + API::Convert(route) => match route { + Convert::QuoteRequest => "/sapi/v1/convert/getQuote", + Convert::AcceptQuote => "/sapi/v1/convert/acceptQuote", }, API::Futures(route) => match route { Futures::Ping => "/fapi/v1/ping", @@ -133,11 +149,17 @@ impl From for String { Futures::ExchangeInfo => "/fapi/v1/exchangeInfo", Futures::Depth => "/fapi/v1/depth", Futures::Trades => "/fapi/v1/trades", - Futures::HistoricalTrades => "/fapi/v1/historicalTrades", + Futures::HistoricalTrades => { + "/fapi/v1/historicalTrades" + } Futures::AggTrades => "/fapi/v1/aggTrades", Futures::Klines => "/fapi/v1/klines", - Futures::ContinuousKlines => "/fapi/v1/continuousKlines", - Futures::IndexPriceKlines => "/fapi/v1/indexPriceKlines", + Futures::ContinuousKlines => { + "/fapi/v1/continuousKlines" + } + Futures::IndexPriceKlines => { + "/fapi/v1/indexPriceKlines" + } Futures::MarkPriceKlines => "/fapi/v1/markPriceKlines", Futures::PremiumIndex => "/fapi/v1/premiumIndex", Futures::FundingRate => "/fapi/v1/fundingRate", @@ -153,11 +175,21 @@ impl From for String { Futures::PositionRisk => "/fapi/v2/positionRisk", Futures::Balance => "/fapi/v2/balance", Futures::OpenInterest => "/fapi/v1/openInterest", - Futures::OpenInterestHist => "/futures/data/openInterestHist", - Futures::TopLongShortAccountRatio => "/futures/data/topLongShortAccountRatio", - Futures::TopLongShortPositionRatio => "/futures/data/topLongShortPositionRatio", - Futures::GlobalLongShortAccountRatio => "/futures/data/globalLongShortAccountRatio", - Futures::TakerlongshortRatio => "/futures/data/takerlongshortRatio", + Futures::OpenInterestHist => { + "/futures/data/openInterestHist" + } + Futures::TopLongShortAccountRatio => { + "/futures/data/topLongShortAccountRatio" + } + Futures::TopLongShortPositionRatio => { + "/futures/data/topLongShortPositionRatio" + } + Futures::GlobalLongShortAccountRatio => { + "/futures/data/globalLongShortAccountRatio" + } + Futures::TakerlongshortRatio => { + "/futures/data/takerlongshortRatio" + } Futures::LvtKlines => "/fapi/v1/lvtKlines", Futures::IndexInfo => "/fapi/v1/indexInfo", Futures::ChangeInitialLeverage => "/fapi/v1/leverage", @@ -171,81 +203,129 @@ impl From for String { } pub trait Binance { - fn new(api_key: Option, secret_key: Option) -> Self; + fn new(api_key: Option, secret_key: Option) + -> Self; fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> Self; } impl Binance for General { - fn new(api_key: Option, secret_key: Option) -> General { + fn new( + api_key: Option, + secret_key: Option, + ) -> General { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> General { General { - client: Client::new(api_key, secret_key, config.rest_api_endpoint.clone()), + client: Client::new( + api_key, + secret_key, + config.rest_api_endpoint.clone(), + ), } } } impl Binance for Account { - fn new(api_key: Option, secret_key: Option) -> Account { + fn new( + api_key: Option, + secret_key: Option, + ) -> Account { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> Account { Account { - client: Client::new(api_key, secret_key, config.rest_api_endpoint.clone()), + client: Client::new( + api_key, + secret_key, + config.rest_api_endpoint.clone(), + ), recv_window: config.recv_window, } } } impl Binance for Savings { - fn new(api_key: Option, secret_key: Option) -> Self { + fn new( + api_key: Option, + secret_key: Option, + ) -> Self { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> Self { Self { - client: Client::new(api_key, secret_key, config.rest_api_endpoint.clone()), + client: Client::new( + api_key, + secret_key, + config.rest_api_endpoint.clone(), + ), recv_window: config.recv_window, } } } impl Binance for Market { - fn new(api_key: Option, secret_key: Option) -> Market { + fn new( + api_key: Option, + secret_key: Option, + ) -> Market { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> Market { Market { - client: Client::new(api_key, secret_key, config.rest_api_endpoint.clone()), + client: Client::new( + api_key, + secret_key, + config.rest_api_endpoint.clone(), + ), recv_window: config.recv_window, } } } impl Binance for UserStream { - fn new(api_key: Option, secret_key: Option) -> UserStream { + fn new( + api_key: Option, + secret_key: Option, + ) -> UserStream { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> UserStream { UserStream { - client: Client::new(api_key, secret_key, config.rest_api_endpoint.clone()), + client: Client::new( + api_key, + secret_key, + config.rest_api_endpoint.clone(), + ), recv_window: config.recv_window, } } @@ -256,12 +336,17 @@ impl Binance for UserStream { // ***************************************************** impl Binance for FuturesGeneral { - fn new(api_key: Option, secret_key: Option) -> FuturesGeneral { + fn new( + api_key: Option, + secret_key: Option, + ) -> FuturesGeneral { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> FuturesGeneral { FuturesGeneral { client: Client::new( @@ -274,12 +359,17 @@ impl Binance for FuturesGeneral { } impl Binance for FuturesMarket { - fn new(api_key: Option, secret_key: Option) -> FuturesMarket { + fn new( + api_key: Option, + secret_key: Option, + ) -> FuturesMarket { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> FuturesMarket { FuturesMarket { client: Client::new( @@ -293,12 +383,17 @@ impl Binance for FuturesMarket { } impl Binance for FuturesAccount { - fn new(api_key: Option, secret_key: Option) -> Self { + fn new( + api_key: Option, + secret_key: Option, + ) -> Self { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> Self { Self { client: Client::new( @@ -312,12 +407,17 @@ impl Binance for FuturesAccount { } impl Binance for FuturesUserStream { - fn new(api_key: Option, secret_key: Option) -> FuturesUserStream { + fn new( + api_key: Option, + secret_key: Option, + ) -> FuturesUserStream { Self::new_with_config(api_key, secret_key, &Config::default()) } fn new_with_config( - api_key: Option, secret_key: Option, config: &Config, + api_key: Option, + secret_key: Option, + config: &Config, ) -> FuturesUserStream { FuturesUserStream { client: Client::new( diff --git a/src/client.rs b/src/client.rs index 35032262..e740e202 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,13 +1,15 @@ +use crate::api::API; +use crate::errors::{BinanceContentError, ErrorKind, Result}; use error_chain::bail; use hex::encode as hex_encode; use hmac::{Hmac, Mac}; -use crate::errors::{BinanceContentError, ErrorKind, Result}; -use reqwest::StatusCode; use reqwest::blocking::Response; -use reqwest::header::{HeaderMap, HeaderName, HeaderValue, USER_AGENT, CONTENT_TYPE}; -use sha2::Sha256; +use reqwest::header::{ + HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE, USER_AGENT, +}; +use reqwest::StatusCode; use serde::de::DeserializeOwned; -use crate::api::API; +use sha2::Sha256; #[derive(Clone)] pub struct Client { @@ -18,7 +20,11 @@ pub struct Client { } impl Client { - pub fn new(api_key: Option, secret_key: Option, host: String) -> Self { + pub fn new( + api_key: Option, + secret_key: Option, + host: String, + ) -> Self { Client { api_key: api_key.unwrap_or_default(), secret_key: secret_key.unwrap_or_default(), @@ -31,7 +37,9 @@ impl Client { } pub fn get_signed( - &self, endpoint: API, request: Option, + &self, + endpoint: API, + request: Option, ) -> Result { let url = self.sign_request(endpoint, request); let client = &self.inner_client; @@ -43,7 +51,11 @@ impl Client { self.handler(response) } - pub fn post_signed(&self, endpoint: API, request: String) -> Result { + pub fn post_signed( + &self, + endpoint: API, + request: String, + ) -> Result { let url = self.sign_request(endpoint, Some(request)); let client = &self.inner_client; let response = client @@ -55,7 +67,9 @@ impl Client { } pub fn delete_signed( - &self, endpoint: API, request: Option, + &self, + endpoint: API, + request: Option, ) -> Result { let url = self.sign_request(endpoint, request); let client = &self.inner_client; @@ -67,8 +81,13 @@ impl Client { self.handler(response) } - pub fn get(&self, endpoint: API, request: Option) -> Result { - let mut url: String = format!("{}{}", self.host, String::from(endpoint)); + pub fn get( + &self, + endpoint: API, + request: Option, + ) -> Result { + let mut url: String = + format!("{}{}", self.host, String::from(endpoint)); if let Some(request) = request { if !request.is_empty() { url.push_str(format!("?{}", request).as_str()); @@ -81,8 +100,12 @@ impl Client { self.handler(response) } - pub fn post(&self, endpoint: API) -> Result { - let url: String = format!("{}{}", self.host, String::from(endpoint)); + pub fn post( + &self, + endpoint: API, + ) -> Result { + let url: String = + format!("{}{}", self.host, String::from(endpoint)); let client = &self.inner_client; let response = client @@ -93,8 +116,13 @@ impl Client { self.handler(response) } - pub fn put(&self, endpoint: API, listen_key: &str) -> Result { - let url: String = format!("{}{}", self.host, String::from(endpoint)); + pub fn put( + &self, + endpoint: API, + listen_key: &str, + ) -> Result { + let url: String = + format!("{}{}", self.host, String::from(endpoint)); let data: String = format!("listenKey={}", listen_key); let client = &self.inner_client; @@ -107,8 +135,13 @@ impl Client { self.handler(response) } - pub fn delete(&self, endpoint: API, listen_key: &str) -> Result { - let url: String = format!("{}{}", self.host, String::from(endpoint)); + pub fn delete( + &self, + endpoint: API, + listen_key: &str, + ) -> Result { + let url: String = + format!("{}{}", self.host, String::from(endpoint)); let data: String = format!("listenKey={}", listen_key); let client = &self.inner_client; @@ -122,30 +155,56 @@ impl Client { } // Request must be signed - fn sign_request(&self, endpoint: API, request: Option) -> String { + fn sign_request( + &self, + endpoint: API, + request: Option, + ) -> String { if let Some(request) = request { - let mut signed_key = - Hmac::::new_from_slice(self.secret_key.as_bytes()).unwrap(); + let mut signed_key = Hmac::::new_from_slice( + self.secret_key.as_bytes(), + ) + .unwrap(); signed_key.update(request.as_bytes()); - let signature = hex_encode(signed_key.finalize().into_bytes()); - let request_body: String = format!("{}&signature={}", request, signature); - format!("{}{}?{}", self.host, String::from(endpoint), request_body) + let signature = + hex_encode(signed_key.finalize().into_bytes()); + let request_body: String = + format!("{}&signature={}", request, signature); + format!( + "{}{}?{}", + self.host, + String::from(endpoint), + request_body + ) } else { - let signed_key = Hmac::::new_from_slice(self.secret_key.as_bytes()).unwrap(); - let signature = hex_encode(signed_key.finalize().into_bytes()); - let request_body: String = format!("&signature={}", signature); - format!("{}{}?{}", self.host, String::from(endpoint), request_body) + let signed_key = Hmac::::new_from_slice( + self.secret_key.as_bytes(), + ) + .unwrap(); + let signature = + hex_encode(signed_key.finalize().into_bytes()); + let request_body: String = + format!("&signature={}", signature); + format!( + "{}{}?{}", + self.host, + String::from(endpoint), + request_body + ) } } fn build_headers(&self, content_type: bool) -> Result { let mut custom_headers = HeaderMap::new(); - custom_headers.insert(USER_AGENT, HeaderValue::from_static("binance-rs")); + custom_headers + .insert(USER_AGENT, HeaderValue::from_static("binance-rs")); if content_type { custom_headers.insert( CONTENT_TYPE, - HeaderValue::from_static("application/x-www-form-urlencoded"), + HeaderValue::from_static( + "application/x-www-form-urlencoded", + ), ); } custom_headers.insert( @@ -156,7 +215,10 @@ impl Client { Ok(custom_headers) } - fn handler(&self, response: Response) -> Result { + fn handler( + &self, + response: Response, + ) -> Result { match response.status() { StatusCode::OK => Ok(response.json::()?), StatusCode::INTERNAL_SERVER_ERROR => { diff --git a/src/config.rs b/src/config.rs index 6bc849dc..486b3c6a 100755 --- a/src/config.rs +++ b/src/config.rs @@ -15,7 +15,8 @@ impl Default for Config { rest_api_endpoint: "https://api.binance.com".into(), ws_endpoint: "wss://stream.binance.com:9443/ws".into(), - futures_rest_api_endpoint: "https://fapi.binance.com".into(), + futures_rest_api_endpoint: "https://fapi.binance.com" + .into(), futures_ws_endpoint: "wss://fstream.binance.com/ws".into(), recv_window: 5000, @@ -28,27 +29,42 @@ impl Config { Self::default() .set_rest_api_endpoint("https://testnet.binance.vision") .set_ws_endpoint("wss://testnet.binance.vision/ws") - .set_futures_rest_api_endpoint("https://testnet.binancefuture.com") - .set_futures_ws_endpoint("https://testnet.binancefuture.com/ws") + .set_futures_rest_api_endpoint( + "https://testnet.binancefuture.com", + ) + .set_futures_ws_endpoint( + "https://testnet.binancefuture.com/ws", + ) } - pub fn set_rest_api_endpoint>(mut self, rest_api_endpoint: T) -> Self { + pub fn set_rest_api_endpoint>( + mut self, + rest_api_endpoint: T, + ) -> Self { self.rest_api_endpoint = rest_api_endpoint.into(); self } - pub fn set_ws_endpoint>(mut self, ws_endpoint: T) -> Self { + pub fn set_ws_endpoint>( + mut self, + ws_endpoint: T, + ) -> Self { self.ws_endpoint = ws_endpoint.into(); self } pub fn set_futures_rest_api_endpoint>( - mut self, futures_rest_api_endpoint: T, + mut self, + futures_rest_api_endpoint: T, ) -> Self { - self.futures_rest_api_endpoint = futures_rest_api_endpoint.into(); + self.futures_rest_api_endpoint = + futures_rest_api_endpoint.into(); self } - pub fn set_futures_ws_endpoint>(mut self, futures_ws_endpoint: T) -> Self { + pub fn set_futures_ws_endpoint>( + mut self, + futures_ws_endpoint: T, + ) -> Self { self.futures_ws_endpoint = futures_ws_endpoint.into(); self } diff --git a/src/errors.rs b/src/errors.rs index 208636ed..bd267f55 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,5 +1,5 @@ -use serde::Deserialize; use error_chain::error_chain; +use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct BinanceContentError { diff --git a/src/futures/account.rs b/src/futures/account.rs index 71fa8a8d..fde3f087 100644 --- a/src/futures/account.rs +++ b/src/futures/account.rs @@ -1,17 +1,17 @@ use std::collections::BTreeMap; use std::fmt::Display; -use crate::util::build_signed_request; -use crate::errors::Result; -use crate::client::Client; -use crate::api::{API, Futures}; -use crate::model::Empty; use crate::account::OrderSide; +use crate::api::{Futures, API}; +use crate::client::Client; +use crate::errors::Result; use crate::futures::model::{Order, TradeHistory}; +use crate::model::Empty; +use crate::util::build_signed_request; use super::model::{ - ChangeLeverageResponse, Transaction, CanceledOrder, PositionRisk, AccountBalance, - AccountInformation, + AccountBalance, AccountInformation, CanceledOrder, + ChangeLeverageResponse, PositionRisk, Transaction, }; #[derive(Clone)] @@ -34,7 +34,9 @@ impl From for String { ContractType::Perpetual => String::from("PERPETUAL"), ContractType::CurrentMonth => String::from("CURRENT_MONTH"), ContractType::NextMonth => String::from("NEXT_MONTH"), - ContractType::CurrentQuarter => String::from("CURRENT_QUARTER"), + ContractType::CurrentQuarter => { + String::from("CURRENT_QUARTER") + } ContractType::NextQuarter => String::from("NEXT_QUARTER"), } } @@ -75,7 +77,9 @@ impl Display for OrderType { Self::StopMarket => write!(f, "STOP_MARKET"), Self::TakeProfit => write!(f, "TAKE_PROFIT"), Self::TakeProfitMarket => write!(f, "TAKE_PROFIT_MARKET"), - Self::TrailingStopMarket => write!(f, "TRAILING_STOP_MARKET"), + Self::TrailingStopMarket => { + write!(f, "TRAILING_STOP_MARKET") + } } } } @@ -191,22 +195,35 @@ impl Display for IncomeType { Self::COMMISSION_REBATE => write!(f, "COMMISSION_REBATE"), Self::API_REBATE => write!(f, "API_REBATE"), Self::CONTEST_REWARD => write!(f, "CONTEST_REWARD"), - Self::CROSS_COLLATERAL_TRANSFER => write!(f, "CROSS_COLLATERAL_TRANSFER"), - Self::OPTIONS_PREMIUM_FEE => write!(f, "OPTIONS_PREMIUM_FEE"), - Self::OPTIONS_SETTLE_PROFIT => write!(f, "OPTIONS_SETTLE_PROFIT"), + Self::CROSS_COLLATERAL_TRANSFER => { + write!(f, "CROSS_COLLATERAL_TRANSFER") + } + Self::OPTIONS_PREMIUM_FEE => { + write!(f, "OPTIONS_PREMIUM_FEE") + } + Self::OPTIONS_SETTLE_PROFIT => { + write!(f, "OPTIONS_SETTLE_PROFIT") + } Self::INTERNAL_TRANSFER => write!(f, "INTERNAL_TRANSFER"), Self::AUTO_EXCHANGE => write!(f, "AUTO_EXCHANGE"), - Self::DELIVERED_SETTELMENT => write!(f, "DELIVERED_SETTELMENT"), + Self::DELIVERED_SETTELMENT => { + write!(f, "DELIVERED_SETTELMENT") + } Self::COIN_SWAP_DEPOSIT => write!(f, "COIN_SWAP_DEPOSIT"), Self::COIN_SWAP_WITHDRAW => write!(f, "COIN_SWAP_WITHDRAW"), - Self::POSITION_LIMIT_INCREASE_FEE => write!(f, "POSITION_LIMIT_INCREASE_FEE"), + Self::POSITION_LIMIT_INCREASE_FEE => { + write!(f, "POSITION_LIMIT_INCREASE_FEE") + } } } } impl FuturesAccount { pub fn limit_buy( - &self, symbol: impl Into, qty: impl Into, price: f64, + &self, + symbol: impl Into, + qty: impl Into, + price: f64, time_in_force: TimeInForce, ) -> Result { let buy = OrderRequest { @@ -232,7 +249,10 @@ impl FuturesAccount { } pub fn limit_sell( - &self, symbol: impl Into, qty: impl Into, price: f64, + &self, + symbol: impl Into, + qty: impl Into, + price: f64, time_in_force: TimeInForce, ) -> Result { let sell = OrderRequest { @@ -258,7 +278,11 @@ impl FuturesAccount { } // Place a MARKET order - BUY - pub fn market_buy(&self, symbol: S, qty: F) -> Result + pub fn market_buy( + &self, + symbol: S, + qty: F, + ) -> Result where S: Into, F: Into, @@ -286,7 +310,11 @@ impl FuturesAccount { } // Place a MARKET order - SELL - pub fn market_sell(&self, symbol: S, qty: F) -> Result + pub fn market_sell( + &self, + symbol: S, + qty: F, + ) -> Result where S: Into, F: Into, @@ -313,7 +341,11 @@ impl FuturesAccount { .post_signed(API::Futures(Futures::Order), request) } - pub fn cancel_order(&self, symbol: S, order_id: u64) -> Result + pub fn cancel_order( + &self, + symbol: S, + order_id: u64, + ) -> Result where S: Into, { @@ -321,28 +353,37 @@ impl FuturesAccount { parameters.insert("symbol".into(), symbol.into()); parameters.insert("orderId".into(), order_id.to_string()); - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client .delete_signed(API::Futures(Futures::Order), Some(request)) } pub fn cancel_order_with_client_id( - &self, symbol: S, orig_client_order_id: String, + &self, + symbol: S, + orig_client_order_id: String, ) -> Result where S: Into, { let mut parameters = BTreeMap::new(); parameters.insert("symbol".into(), symbol.into()); - parameters.insert("origClientOrderId".into(), orig_client_order_id); + parameters + .insert("origClientOrderId".into(), orig_client_order_id); - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client .delete_signed(API::Futures(Futures::Order), Some(request)) } // Place a STOP_MARKET close - BUY - pub fn stop_market_close_buy(&self, symbol: S, stop_price: F) -> Result + pub fn stop_market_close_buy( + &self, + symbol: S, + stop_price: F, + ) -> Result where S: Into, F: Into, @@ -370,7 +411,11 @@ impl FuturesAccount { } // Place a STOP_MARKET close - SELL - pub fn stop_market_close_sell(&self, symbol: S, stop_price: F) -> Result + pub fn stop_market_close_sell( + &self, + symbol: S, + stop_price: F, + ) -> Result where S: Into, F: Into, @@ -398,7 +443,10 @@ impl FuturesAccount { } // Custom order for for professional traders - pub fn custom_order(&self, order_request: CustomOrderRequest) -> Result { + pub fn custom_order( + &self, + order_request: CustomOrderRequest, + ) -> Result { let order = OrderRequest { symbol: order_request.symbol, side: order_request.side, @@ -422,7 +470,12 @@ impl FuturesAccount { } pub fn get_all_orders( - &self, symbol: S, order_id: F, start_time: F, end_time: F, limit: N, + &self, + symbol: S, + order_id: F, + start_time: F, + end_time: F, + limit: N, ) -> Result> where S: Into, @@ -435,7 +488,8 @@ impl FuturesAccount { parameters.insert("orderId".into(), order_id.to_string()); } if let Some(start_time) = start_time.into() { - parameters.insert("startTime".into(), start_time.to_string()); + parameters + .insert("startTime".into(), start_time.to_string()); } if let Some(end_time) = end_time.into() { parameters.insert("endTime".into(), end_time.to_string()); @@ -444,13 +498,19 @@ impl FuturesAccount { parameters.insert("limit".into(), limit.to_string()); } - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client .get_signed(API::Futures(Futures::AllOrders), Some(request)) } pub fn get_user_trades( - &self, symbol: S, from_id: F, start_time: F, end_time: F, limit: N, + &self, + symbol: S, + from_id: F, + start_time: F, + end_time: F, + limit: N, ) -> Result> where S: Into, @@ -463,7 +523,8 @@ impl FuturesAccount { parameters.insert("fromId".into(), order_id.to_string()); } if let Some(start_time) = start_time.into() { - parameters.insert("startTime".into(), start_time.to_string()); + parameters + .insert("startTime".into(), start_time.to_string()); } if let Some(end_time) = end_time.into() { parameters.insert("endTime".into(), end_time.to_string()); @@ -472,33 +533,49 @@ impl FuturesAccount { parameters.insert("limit".into(), limit.to_string()); } - let request = build_signed_request(parameters, self.recv_window)?; - self.client - .get_signed(API::Futures(Futures::UserTrades), Some(request)) + let request = + build_signed_request(parameters, self.recv_window)?; + self.client.get_signed( + API::Futures(Futures::UserTrades), + Some(request), + ) } - fn build_order(&self, order: OrderRequest) -> BTreeMap { + fn build_order( + &self, + order: OrderRequest, + ) -> BTreeMap { let mut parameters = BTreeMap::new(); parameters.insert("symbol".into(), order.symbol); parameters.insert("side".into(), order.side.to_string()); parameters.insert("type".into(), order.order_type.to_string()); if let Some(position_side) = order.position_side { - parameters.insert("positionSide".into(), position_side.to_string()); + parameters.insert( + "positionSide".into(), + position_side.to_string(), + ); } if let Some(time_in_force) = order.time_in_force { - parameters.insert("timeInForce".into(), time_in_force.to_string()); + parameters.insert( + "timeInForce".into(), + time_in_force.to_string(), + ); } if let Some(qty) = order.qty { parameters.insert("quantity".into(), qty.to_string()); } if let Some(reduce_only) = order.reduce_only { - parameters.insert("reduceOnly".into(), reduce_only.to_string().to_uppercase()); + parameters.insert( + "reduceOnly".into(), + reduce_only.to_string().to_uppercase(), + ); } if let Some(price) = order.price { parameters.insert("price".into(), price.to_string()); } if let Some(stop_price) = order.stop_price { - parameters.insert("stopPrice".into(), stop_price.to_string()); + parameters + .insert("stopPrice".into(), stop_price.to_string()); } if let Some(close_position) = order.close_position { parameters.insert( @@ -507,13 +584,20 @@ impl FuturesAccount { ); } if let Some(activation_price) = order.activation_price { - parameters.insert("activationPrice".into(), activation_price.to_string()); + parameters.insert( + "activationPrice".into(), + activation_price.to_string(), + ); } if let Some(callback_rate) = order.callback_rate { - parameters.insert("callbackRate".into(), callback_rate.to_string()); + parameters.insert( + "callbackRate".into(), + callback_rate.to_string(), + ); } if let Some(working_type) = order.working_type { - parameters.insert("workingType".into(), working_type.to_string()); + parameters + .insert("workingType".into(), working_type.to_string()); } if let Some(price_protect) = order.price_protect { parameters.insert( @@ -525,22 +609,29 @@ impl FuturesAccount { parameters } - pub fn position_information(&self, symbol: S) -> Result> + pub fn position_information( + &self, + symbol: S, + ) -> Result> where S: Into, { let mut parameters = BTreeMap::new(); parameters.insert("symbol".into(), symbol.into()); - let request = build_signed_request(parameters, self.recv_window)?; - self.client - .get_signed(API::Futures(Futures::PositionRisk), Some(request)) + let request = + build_signed_request(parameters, self.recv_window)?; + self.client.get_signed( + API::Futures(Futures::PositionRisk), + Some(request), + ) } pub fn account_information(&self) -> Result { let parameters = BTreeMap::new(); - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client .get_signed(API::Futures(Futures::Account), Some(request)) } @@ -548,13 +639,16 @@ impl FuturesAccount { pub fn account_balance(&self) -> Result> { let parameters = BTreeMap::new(); - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client .get_signed(API::Futures(Futures::Balance), Some(request)) } pub fn change_initial_leverage( - &self, symbol: S, leverage: u8, + &self, + symbol: S, + leverage: u8, ) -> Result where S: Into, @@ -563,19 +657,30 @@ impl FuturesAccount { parameters.insert("symbol".into(), symbol.into()); parameters.insert("leverage".into(), leverage.to_string()); - let request = build_signed_request(parameters, self.recv_window)?; - self.client - .post_signed(API::Futures(Futures::ChangeInitialLeverage), request) + let request = + build_signed_request(parameters, self.recv_window)?; + self.client.post_signed( + API::Futures(Futures::ChangeInitialLeverage), + request, + ) } - pub fn change_position_mode(&self, dual_side_position: bool) -> Result<()> { + pub fn change_position_mode( + &self, + dual_side_position: bool, + ) -> Result<()> { let mut parameters: BTreeMap = BTreeMap::new(); - let dual_side = if dual_side_position { "true" } else { "false" }; + let dual_side = + if dual_side_position { "true" } else { "false" }; parameters.insert("dualSidePosition".into(), dual_side.into()); - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client - .post_signed::(API::Futures(Futures::PositionSide), request) + .post_signed::( + API::Futures(Futures::PositionSide), + request, + ) .map(|_| ()) } @@ -585,35 +690,48 @@ impl FuturesAccount { { let mut parameters: BTreeMap = BTreeMap::new(); parameters.insert("symbol".into(), symbol.into()); - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client - .delete_signed::(API::Futures(Futures::AllOpenOrders), Some(request)) + .delete_signed::( + API::Futures(Futures::AllOpenOrders), + Some(request), + ) .map(|_| ()) } - pub fn get_all_open_orders(&self, symbol: S) -> Result> + pub fn get_all_open_orders( + &self, + symbol: S, + ) -> Result> where S: Into, { let mut parameters: BTreeMap = BTreeMap::new(); parameters.insert("symbol".into(), symbol.into()); - let request = build_signed_request(parameters, self.recv_window)?; - self.client - .get_signed(API::Futures(Futures::OpenOrders), Some(request)) + let request = + build_signed_request(parameters, self.recv_window)?; + self.client.get_signed( + API::Futures(Futures::OpenOrders), + Some(request), + ) } pub fn get_income( - &self, income_request: IncomeRequest, + &self, + income_request: IncomeRequest, ) -> Result> { let mut parameters: BTreeMap = BTreeMap::new(); if let Some(symbol) = income_request.symbol { parameters.insert("symbol".into(), symbol); } if let Some(income_type) = income_request.income_type { - parameters.insert("incomeType".into(), income_type.to_string()); + parameters + .insert("incomeType".into(), income_type.to_string()); } if let Some(start_time) = income_request.start_time { - parameters.insert("startTime".into(), start_time.to_string()); + parameters + .insert("startTime".into(), start_time.to_string()); } if let Some(end_time) = income_request.end_time { parameters.insert("endTime".into(), end_time.to_string()); @@ -622,7 +740,8 @@ impl FuturesAccount { parameters.insert("limit".into(), limit.to_string()); } - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; println!("{}", request); self.client .get_signed(API::Futures(Futures::Income), Some(request)) diff --git a/src/futures/general.rs b/src/futures/general.rs index 0b53b50e..57d3116f 100644 --- a/src/futures/general.rs +++ b/src/futures/general.rs @@ -1,10 +1,10 @@ use error_chain::bail; -use crate::futures::model::{ExchangeInformation, ServerTime, Symbol}; +use crate::api::Futures; +use crate::api::API; use crate::client::Client; use crate::errors::Result; -use crate::api::API; -use crate::api::Futures; +use crate::futures::model::{ExchangeInformation, ServerTime, Symbol}; #[derive(Clone)] pub struct FuturesGeneral { diff --git a/src/futures/market.rs b/src/futures/market.rs index 6def5883..bb97d361 100644 --- a/src/futures/market.rs +++ b/src/futures/market.rs @@ -20,17 +20,18 @@ - [ ] `Taker Buy/Sell Volume (MARKET_DATA)` */ -use crate::util::{build_request, build_signed_request}; -use crate::futures::model::{ - AggTrades, BookTickers, KlineSummaries, KlineSummary, LiquidationOrders, MarkPrices, - OpenInterest, OpenInterestHist, OrderBook, PriceStats, SymbolPrice, Tickers, Trades, -}; +use crate::api::Futures; +use crate::api::API; use crate::client::Client; use crate::errors::Result; -use std::collections::BTreeMap; +use crate::futures::model::{ + AggTrades, BookTickers, KlineSummaries, KlineSummary, + LiquidationOrders, MarkPrices, OpenInterest, OpenInterestHist, + OrderBook, PriceStats, SymbolPrice, Tickers, Trades, +}; +use crate::util::{build_request, build_signed_request}; use serde_json::Value; -use crate::api::API; -use crate::api::Futures; +use std::collections::BTreeMap; use std::convert::TryInto; // TODO @@ -60,7 +61,11 @@ impl FuturesMarket { // Order book at a custom depth. Currently supported values // are 5, 10, 20, 50, 100, 500, 1000 - pub fn get_custom_depth(&self, symbol: S, depth: u64) -> Result + pub fn get_custom_depth( + &self, + symbol: S, + depth: u64, + ) -> Result where S: Into, { @@ -84,7 +89,10 @@ impl FuturesMarket { // TODO This may be incomplete, as it hasn't been tested pub fn get_historical_trades( - &self, symbol: S1, from_id: S2, limit: S3, + &self, + symbol: S1, + from_id: S2, + limit: S3, ) -> Result where S1: Into, @@ -103,14 +111,22 @@ impl FuturesMarket { parameters.insert("fromId".into(), format!("{}", fi)); } - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; - self.client - .get_signed(API::Futures(Futures::HistoricalTrades), Some(request)) + self.client.get_signed( + API::Futures(Futures::HistoricalTrades), + Some(request), + ) } pub fn get_agg_trades( - &self, symbol: S1, from_id: S2, start_time: S3, end_time: S4, limit: S5, + &self, + symbol: S1, + from_id: S2, + start_time: S3, + end_time: S4, + limit: S5, ) -> Result where S1: Into, @@ -146,7 +162,12 @@ impl FuturesMarket { // Returns up to 'limit' klines for given symbol and interval ("1m", "5m", ...) // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data pub fn get_klines( - &self, symbol: S1, interval: S2, limit: S3, start_time: S4, end_time: S5, + &self, + symbol: S1, + interval: S2, + limit: S3, + start_time: S4, + end_time: S5, ) -> Result where S1: Into, @@ -187,7 +208,10 @@ impl FuturesMarket { } // 24hr ticker price change statistics - pub fn get_24h_price_stats(&self, symbol: S) -> Result + pub fn get_24h_price_stats( + &self, + symbol: S, + ) -> Result where S: Into, { @@ -246,7 +270,9 @@ impl FuturesMarket { self.client.get(API::Futures(Futures::PremiumIndex), None) } - pub fn get_all_liquidation_orders(&self) -> Result { + pub fn get_all_liquidation_orders( + &self, + ) -> Result { self.client.get(API::Futures(Futures::AllForceOrders), None) } @@ -262,7 +288,12 @@ impl FuturesMarket { } pub fn open_interest_statistics( - &self, symbol: S1, period: S2, limit: S3, start_time: S4, end_time: S5, + &self, + symbol: S1, + period: S2, + limit: S3, + start_time: S4, + end_time: S5, ) -> Result> where S1: Into, diff --git a/src/futures/model.rs b/src/futures/model.rs index e6debac4..d6c66cb9 100644 --- a/src/futures/model.rs +++ b/src/futures/model.rs @@ -1,9 +1,11 @@ +use crate::model::{ + string_or_bool, string_or_float, string_or_float_opt, +}; use serde::{Deserialize, Serialize}; -use crate::model::{string_or_float, string_or_float_opt, string_or_bool}; pub use crate::model::{ - Asks, Bids, BookTickers, Filters, KlineSummaries, KlineSummary, RateLimit, ServerTime, - SymbolPrice, Tickers, + Asks, Bids, BookTickers, Filters, KlineSummaries, KlineSummary, + RateLimit, ServerTime, SymbolPrice, Tickers, }; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -235,7 +237,10 @@ pub struct Order { #[serde(rename = "type")] pub order_type: String, pub orig_type: String, - #[serde(with = "string_or_float", default = "default_activation_price")] + #[serde( + with = "string_or_float", + default = "default_activation_price" + )] pub activation_price: f64, #[serde(with = "string_or_float", default = "default_price_rate")] pub price_rate: f64, diff --git a/src/futures/userstream.rs b/src/futures/userstream.rs index fdcad313..5eb1d7bb 100644 --- a/src/futures/userstream.rs +++ b/src/futures/userstream.rs @@ -1,8 +1,8 @@ -use crate::model::{Success, UserDataStream}; +use crate::api::Futures; +use crate::api::API; use crate::client::Client; use crate::errors::Result; -use crate::api::API; -use crate::api::Futures; +use crate::model::{Success, UserDataStream}; #[derive(Clone)] pub struct FuturesUserStream { diff --git a/src/futures/websockets.rs b/src/futures/websockets.rs index 4fe76ff8..0863bae9 100755 --- a/src/futures/websockets.rs +++ b/src/futures/websockets.rs @@ -1,20 +1,22 @@ -use crate::errors::Result; use crate::config::Config; +use crate::errors::Result; +use crate::futures::model; use crate::model::{ - AccountUpdateEvent, AggrTradesEvent, BookTickerEvent, ContinuousKlineEvent, DayTickerEvent, - DepthOrderBookEvent, IndexKlineEvent, IndexPriceEvent, KlineEvent, LiquidationEvent, - MarkPriceEvent, MiniTickerEvent, OrderBook, TradeEvent, UserDataStreamExpiredEvent, + AccountUpdateEvent, AggrTradesEvent, BookTickerEvent, + ContinuousKlineEvent, DayTickerEvent, DepthOrderBookEvent, + IndexKlineEvent, IndexPriceEvent, KlineEvent, LiquidationEvent, + MarkPriceEvent, MiniTickerEvent, OrderBook, TradeEvent, + UserDataStreamExpiredEvent, }; -use crate::futures::model; use error_chain::bail; -use url::Url; use serde::{Deserialize, Serialize}; -use std::sync::atomic::{AtomicBool, Ordering}; use std::net::TcpStream; -use tungstenite::{connect, Message}; +use std::sync::atomic::{AtomicBool, Ordering}; +use tungstenite::handshake::client::Response; use tungstenite::protocol::WebSocket; use tungstenite::stream::MaybeTlsStream; -use tungstenite::handshake::client::Response; +use tungstenite::{connect, Message}; +use url::Url; #[allow(clippy::all)] enum FuturesWebsocketAPI { Default, @@ -29,7 +31,11 @@ pub enum FuturesMarket { } impl FuturesWebsocketAPI { - fn params(self, market: &FuturesMarket, subscription: &str) -> String { + fn params( + self, + market: &FuturesMarket, + subscription: &str, + ) -> String { let baseurl = match market { FuturesMarket::USDM => "wss://fstream.binance.com", FuturesMarket::COINM => "wss://dstream.binance.com", @@ -73,7 +79,8 @@ pub enum FuturesWebsocketEvent { } pub struct FuturesWebSockets<'a> { - pub socket: Option<(WebSocket>, Response)>, + pub socket: + Option<(WebSocket>, Response)>, handler: Box Result<()> + 'a>, } @@ -112,22 +119,37 @@ impl<'a> FuturesWebSockets<'a> { } } - pub fn connect(&mut self, market: &FuturesMarket, subscription: &'a str) -> Result<()> { - self.connect_wss(&FuturesWebsocketAPI::Default.params(market, subscription)) + pub fn connect( + &mut self, + market: &FuturesMarket, + subscription: &'a str, + ) -> Result<()> { + self.connect_wss( + &FuturesWebsocketAPI::Default.params(market, subscription), + ) } pub fn connect_with_config( - &mut self, market: &FuturesMarket, subscription: &'a str, config: &'a Config, + &mut self, + market: &FuturesMarket, + subscription: &'a str, + config: &'a Config, ) -> Result<()> { self.connect_wss( - &FuturesWebsocketAPI::Custom(config.ws_endpoint.clone()).params(market, subscription), + &FuturesWebsocketAPI::Custom(config.ws_endpoint.clone()) + .params(market, subscription), ) } pub fn connect_multiple_streams( - &mut self, market: &FuturesMarket, endpoints: &[String], + &mut self, + market: &FuturesMarket, + endpoints: &[String], ) -> Result<()> { - self.connect_wss(&FuturesWebsocketAPI::MultiStream.params(market, &endpoints.join("/"))) + self.connect_wss( + &FuturesWebsocketAPI::MultiStream + .params(market, &endpoints.join("/")), + ) } fn connect_wss(&mut self, wss: &str) -> Result<()> { @@ -161,26 +183,64 @@ impl<'a> FuturesWebSockets<'a> { return Ok(()); } - if let Ok(events) = serde_json::from_value::(value) { + if let Ok(events) = + serde_json::from_value::(value) + { let action = match events { - FuturesEvents::Vec(v) => FuturesWebsocketEvent::DayTickerAll(v), - FuturesEvents::DayTickerEvent(v) => FuturesWebsocketEvent::DayTicker(v), - FuturesEvents::BookTickerEvent(v) => FuturesWebsocketEvent::BookTicker(v), - FuturesEvents::MiniTickerEvent(v) => FuturesWebsocketEvent::MiniTicker(v), - FuturesEvents::VecMiniTickerEvent(v) => FuturesWebsocketEvent::MiniTickerAll(v), - FuturesEvents::AccountUpdateEvent(v) => FuturesWebsocketEvent::AccountUpdate(v), - FuturesEvents::OrderTradeEvent(v) => FuturesWebsocketEvent::OrderTrade(v), - FuturesEvents::IndexPriceEvent(v) => FuturesWebsocketEvent::IndexPrice(v), - FuturesEvents::MarkPriceEvent(v) => FuturesWebsocketEvent::MarkPrice(v), - FuturesEvents::VecMarkPriceEvent(v) => FuturesWebsocketEvent::MarkPriceAll(v), - FuturesEvents::TradeEvent(v) => FuturesWebsocketEvent::Trade(v), - FuturesEvents::ContinuousKlineEvent(v) => FuturesWebsocketEvent::ContinuousKline(v), - FuturesEvents::IndexKlineEvent(v) => FuturesWebsocketEvent::IndexKline(v), - FuturesEvents::LiquidationEvent(v) => FuturesWebsocketEvent::Liquidation(v), - FuturesEvents::KlineEvent(v) => FuturesWebsocketEvent::Kline(v), - FuturesEvents::OrderBook(v) => FuturesWebsocketEvent::OrderBook(v), - FuturesEvents::DepthOrderBookEvent(v) => FuturesWebsocketEvent::DepthOrderBook(v), - FuturesEvents::AggrTradesEvent(v) => FuturesWebsocketEvent::AggrTrades(v), + FuturesEvents::Vec(v) => { + FuturesWebsocketEvent::DayTickerAll(v) + } + FuturesEvents::DayTickerEvent(v) => { + FuturesWebsocketEvent::DayTicker(v) + } + FuturesEvents::BookTickerEvent(v) => { + FuturesWebsocketEvent::BookTicker(v) + } + FuturesEvents::MiniTickerEvent(v) => { + FuturesWebsocketEvent::MiniTicker(v) + } + FuturesEvents::VecMiniTickerEvent(v) => { + FuturesWebsocketEvent::MiniTickerAll(v) + } + FuturesEvents::AccountUpdateEvent(v) => { + FuturesWebsocketEvent::AccountUpdate(v) + } + FuturesEvents::OrderTradeEvent(v) => { + FuturesWebsocketEvent::OrderTrade(v) + } + FuturesEvents::IndexPriceEvent(v) => { + FuturesWebsocketEvent::IndexPrice(v) + } + FuturesEvents::MarkPriceEvent(v) => { + FuturesWebsocketEvent::MarkPrice(v) + } + FuturesEvents::VecMarkPriceEvent(v) => { + FuturesWebsocketEvent::MarkPriceAll(v) + } + FuturesEvents::TradeEvent(v) => { + FuturesWebsocketEvent::Trade(v) + } + FuturesEvents::ContinuousKlineEvent(v) => { + FuturesWebsocketEvent::ContinuousKline(v) + } + FuturesEvents::IndexKlineEvent(v) => { + FuturesWebsocketEvent::IndexKline(v) + } + FuturesEvents::LiquidationEvent(v) => { + FuturesWebsocketEvent::Liquidation(v) + } + FuturesEvents::KlineEvent(v) => { + FuturesWebsocketEvent::Kline(v) + } + FuturesEvents::OrderBook(v) => { + FuturesWebsocketEvent::OrderBook(v) + } + FuturesEvents::DepthOrderBookEvent(v) => { + FuturesWebsocketEvent::DepthOrderBook(v) + } + FuturesEvents::AggrTradesEvent(v) => { + FuturesWebsocketEvent::AggrTrades(v) + } FuturesEvents::UserDataStreamExpiredEvent(v) => { FuturesWebsocketEvent::UserDataStreamExpiredEvent(v) } @@ -197,14 +257,24 @@ impl<'a> FuturesWebSockets<'a> { match message { Message::Text(msg) => { if let Err(e) = self.handle_msg(&msg) { - bail!(format!("Error on handling stream message: {}", e)); + bail!(format!( + "Error on handling stream message: {}", + e + )); } } Message::Ping(_) => { - socket.0.write_message(Message::Pong(vec![])).unwrap(); + socket + .0 + .write_message(Message::Pong(vec![])) + .unwrap(); + } + Message::Pong(_) + | Message::Binary(_) + | Message::Frame(_) => (), + Message::Close(e) => { + bail!(format!("Disconnected {:?}", e)) } - Message::Pong(_) | Message::Binary(_) | Message::Frame(_) => (), - Message::Close(e) => bail!(format!("Disconnected {:?}", e)), } } } diff --git a/src/general.rs b/src/general.rs index 357356b2..cadc3456 100644 --- a/src/general.rs +++ b/src/general.rs @@ -1,10 +1,10 @@ use error_chain::bail; -use crate::model::{Empty, ExchangeInformation, ServerTime, Symbol}; +use crate::api::Spot; +use crate::api::API; use crate::client::Client; use crate::errors::Result; -use crate::api::API; -use crate::api::Spot; +use crate::model::{Empty, ExchangeInformation, ServerTime, Symbol}; #[derive(Clone)] pub struct General { diff --git a/src/lib.rs b/src/lib.rs index 2b8e4396..02c9f44d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +//! Api in rust that use the binance api + #![deny( unstable_features, unused_must_use, diff --git a/src/market.rs b/src/market.rs index a3317268..0349dbee 100644 --- a/src/market.rs +++ b/src/market.rs @@ -1,14 +1,14 @@ -use crate::util::build_request; -use crate::model::{ - AggTrade, AveragePrice, BookTickers, KlineSummaries, KlineSummary, OrderBook, PriceStats, - Prices, SymbolPrice, Tickers, -}; +use crate::api::Spot; +use crate::api::API; use crate::client::Client; use crate::errors::Result; -use std::collections::BTreeMap; +use crate::model::{ + AggTrade, AveragePrice, BookTickers, KlineSummaries, KlineSummary, + OrderBook, PriceStats, Prices, SymbolPrice, Tickers, +}; +use crate::util::build_request; use serde_json::Value; -use crate::api::API; -use crate::api::Spot; +use std::collections::BTreeMap; use std::convert::TryInto; #[derive(Clone)] @@ -32,7 +32,11 @@ impl Market { // Order book at a custom depth. Currently supported values // are 5, 10, 20, 50, 100, 500, 1000 and 5000 - pub fn get_custom_depth(&self, symbol: S, depth: u64) -> Result + pub fn get_custom_depth( + &self, + symbol: S, + depth: u64, + ) -> Result where S: Into, { @@ -60,7 +64,10 @@ impl Market { } // Average price for ONE symbol. - pub fn get_average_price(&self, symbol: S) -> Result + pub fn get_average_price( + &self, + symbol: S, + ) -> Result where S: Into, { @@ -88,7 +95,10 @@ impl Market { } // 24hr ticker price change statistics - pub fn get_24h_price_stats(&self, symbol: S) -> Result + pub fn get_24h_price_stats( + &self, + symbol: S, + ) -> Result where S: Into, { @@ -108,7 +118,12 @@ impl Market { /// If you provide start_time, you also need to provide end_time. /// If from_id, start_time and end_time are omitted, the most recent trades are fetched. pub fn get_agg_trades( - &self, symbol: S1, from_id: S2, start_time: S3, end_time: S4, limit: S5, + &self, + symbol: S1, + from_id: S2, + start_time: S3, + end_time: S4, + limit: S5, ) -> Result> where S1: Into, @@ -143,7 +158,12 @@ impl Market { // Returns up to 'limit' klines for given symbol and interval ("1m", "5m", ...) // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data pub fn get_klines( - &self, symbol: S1, interval: S2, limit: S3, start_time: S4, end_time: S5, + &self, + symbol: S1, + interval: S2, + limit: S3, + start_time: S4, + end_time: S5, ) -> Result where S1: Into, @@ -169,7 +189,8 @@ impl Market { } let request = build_request(parameters); - let data: Vec> = self.client.get(API::Spot(Spot::Klines), Some(request))?; + let data: Vec> = + self.client.get(API::Spot(Spot::Klines), Some(request))?; let klines = KlineSummaries::AllKlineSummaries( data.iter() diff --git a/src/model.rs b/src/model.rs index 2ba3030d..72f22a28 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,7 @@ +use crate::errors::{Error, ErrorKind, Result}; use serde::{Deserialize, Serialize}; use serde_json::{from_value, Value}; use std::convert::TryFrom; -use crate::errors::{Error, ErrorKind, Result}; #[derive(Deserialize, Clone)] pub struct Empty {} @@ -197,6 +197,23 @@ pub struct TransactionId { pub tran_id: u64, } +/* { + "symbol": "BTCUSDT", + "orderId": 28, + "orderListId": -1, //Unless OCO, value will be -1 + "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", + "transactTime": 1507725176595, + "price": "0.00000000", + "origQty": "10.00000000", + "executedQty": "10.00000000", + "cummulativeQuoteQty": "10.00000000", + "status": "FILLED", + "timeInForce": "GTC", + "type": "MARKET", + "side": "SELL", + "workingTime": 1507725176595, + "selfTradePreventionMode": "NONE" +} */ #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Transaction { @@ -965,7 +982,11 @@ pub struct KlineSummary { pub taker_buy_quote_asset_volume: String, } -fn get_value(row: &[Value], index: usize, name: &'static str) -> Result { +fn get_value( + row: &[Value], + index: usize, + name: &'static str, +) -> Result { Ok(row .get(index) .ok_or_else(|| ErrorKind::KlineValueMissingError(index, name))? @@ -984,8 +1005,16 @@ impl TryFrom<&Vec> for KlineSummary { close: from_value(get_value(row, 4, "close")?)?, volume: from_value(get_value(row, 5, "volume")?)?, close_time: from_value(get_value(row, 6, "close_time")?)?, - quote_asset_volume: from_value(get_value(row, 7, "quote_asset_volume")?)?, - number_of_trades: from_value(get_value(row, 8, "number_of_trades")?)?, + quote_asset_volume: from_value(get_value( + row, + 7, + "quote_asset_volume", + )?)?, + number_of_trades: from_value(get_value( + row, + 8, + "number_of_trades", + )?)?, taker_buy_base_asset_volume: from_value(get_value( row, 9, @@ -1276,9 +1305,12 @@ pub struct DepositAddress { pub(crate) mod string_or_float { use std::fmt; - use serde::{de, Serializer, Deserialize, Deserializer}; + use serde::{de, Deserialize, Deserializer, Serializer}; - pub fn serialize(value: &T, serializer: S) -> Result + pub fn serialize( + value: &T, + serializer: S, + ) -> Result where T: fmt::Display, S: Serializer, @@ -1313,20 +1345,27 @@ pub(crate) mod string_or_float { pub(crate) mod string_or_float_opt { use std::fmt; - use serde::{Serializer, Deserialize, Deserializer}; + use serde::{Deserialize, Deserializer, Serializer}; - pub fn serialize(value: &Option, serializer: S) -> Result + pub fn serialize( + value: &Option, + serializer: S, + ) -> Result where T: fmt::Display, S: Serializer, { match value { - Some(v) => crate::model::string_or_float::serialize(v, serializer), + Some(v) => { + crate::model::string_or_float::serialize(v, serializer) + } None => serializer.serialize_none(), } } - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + pub fn deserialize<'de, D>( + deserializer: D, + ) -> Result, D::Error> where D: Deserializer<'de>, { @@ -1346,9 +1385,12 @@ pub(crate) mod string_or_float_opt { pub(crate) mod string_or_bool { use std::fmt; - use serde::{de, Serializer, Deserialize, Deserializer}; + use serde::{de, Deserialize, Deserializer, Serializer}; - pub fn serialize(value: &T, serializer: S) -> Result + pub fn serialize( + value: &T, + serializer: S, + ) -> Result where T: fmt::Display, S: Serializer, @@ -1356,7 +1398,9 @@ pub(crate) mod string_or_bool { serializer.collect_str(value) } - pub fn deserialize<'de, D>(deserializer: D) -> Result + pub fn deserialize<'de, D>( + deserializer: D, + ) -> Result where D: Deserializer<'de>, { @@ -1368,7 +1412,9 @@ pub(crate) mod string_or_bool { } match StringOrFloat::deserialize(deserializer)? { - StringOrFloat::String(s) => s.parse().map_err(de::Error::custom), + StringOrFloat::String(s) => { + s.parse().map_err(de::Error::custom) + } StringOrFloat::Bool(i) => Ok(i), } } @@ -1438,3 +1484,65 @@ fn test_account_update_event() { assert_eq!(format!("{:?}", v), res); //let event = from_value::(json).unwrap(); } + +/* { + "quoteId":"12415572564", + "ratio":"38163.7", + "inverseRatio":"0.0000262", + "validTimestamp":1623319461670, + "toAmount":"3816.37", + "fromAmount":"0.1" +} */ +// Quote from the convert api +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Quote { + pub quote_id: Option, + #[serde(with = "string_or_float")] + pub ratio: f64, + #[serde(with = "string_or_float")] + pub inverse_ratio: f64, + pub valid_timestamp: u64, + #[serde(with = "string_or_float")] + pub to_amount: f64, + #[serde(with = "string_or_float")] + pub from_amount: f64, +} + +/* { + "orderId":"933256278426274426", + "createTime":1623381330472, + "orderStatus":"PROCESS" //PROCESS/ACCEPT_SUCCESS/SUCCESS/FAIL +} */ +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct QuoteResponse { + pub order_id: String, + pub create_time: u64, + pub order_status: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct AccountSnapshot { + pub code: u16, + // error message + pub msg: String, + pub snapshot_vos: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SnapshotVo { + pub data: Data, + #[serde(rename = "type")] + pub account_type: String, + pub update_time: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Data { + pub balances: Vec, + pub total_asset_of_btc: String, +} diff --git a/src/savings.rs b/src/savings.rs index fbde1279..2b82bef8 100644 --- a/src/savings.rs +++ b/src/savings.rs @@ -1,10 +1,13 @@ -use crate::util::build_signed_request; -use crate::model::{AssetDetail, CoinInfo, DepositAddress, SpotFuturesTransferType, TransactionId}; +use crate::api::Sapi; +use crate::api::API; use crate::client::Client; use crate::errors::Result; +use crate::model::{ + AssetDetail, CoinInfo, DepositAddress, SpotFuturesTransferType, + TransactionId, +}; +use crate::util::build_signed_request; use std::collections::BTreeMap; -use crate::api::API; -use crate::api::Sapi; #[derive(Clone)] pub struct Savings { @@ -15,18 +18,23 @@ pub struct Savings { impl Savings { /// Get all coins available for deposit and withdrawal pub fn get_all_coins(&self) -> Result> { - let request = build_signed_request(BTreeMap::new(), self.recv_window)?; + let request = + build_signed_request(BTreeMap::new(), self.recv_window)?; self.client .get_signed(API::Savings(Sapi::AllCoins), Some(request)) } /// Fetch details of assets supported on Binance. - pub fn asset_detail(&self, asset: Option) -> Result> { + pub fn asset_detail( + &self, + asset: Option, + ) -> Result> { let mut parameters = BTreeMap::new(); if let Some(asset) = asset { parameters.insert("asset".into(), asset); } - let request = build_signed_request(parameters, self.recv_window)?; + let request = + build_signed_request(parameters, self.recv_window)?; self.client .get_signed(API::Savings(Sapi::AssetDetail), Some(request)) } @@ -35,7 +43,11 @@ impl Savings { /// /// You can get the available networks using `get_all_coins`. /// If no network is specified, the address for the default network is returned. - pub fn deposit_address(&self, coin: S, network: Option) -> Result + pub fn deposit_address( + &self, + coin: S, + network: Option, + ) -> Result where S: Into, { @@ -44,13 +56,19 @@ impl Savings { if let Some(network) = network { parameters.insert("network".into(), network); } - let request = build_signed_request(parameters, self.recv_window)?; - self.client - .get_signed(API::Savings(Sapi::DepositAddress), Some(request)) + let request = + build_signed_request(parameters, self.recv_window)?; + self.client.get_signed( + API::Savings(Sapi::DepositAddress), + Some(request), + ) } pub fn transfer_funds( - &self, asset: S, amount: f64, transfer_type: SpotFuturesTransferType, + &self, + asset: S, + amount: f64, + transfer_type: SpotFuturesTransferType, ) -> Result where S: Into, @@ -58,9 +76,13 @@ impl Savings { let mut parameters = BTreeMap::new(); parameters.insert("asset".into(), asset.into()); parameters.insert("amount".into(), amount.to_string()); - parameters.insert("type".into(), (transfer_type as u8).to_string()); - let request = build_signed_request(parameters, self.recv_window)?; - self.client - .post_signed(API::Savings(Sapi::SpotFuturesTransfer), request) + parameters + .insert("type".into(), (transfer_type as u8).to_string()); + let request = + build_signed_request(parameters, self.recv_window)?; + self.client.post_signed( + API::Savings(Sapi::SpotFuturesTransfer), + request, + ) } } diff --git a/src/userstream.rs b/src/userstream.rs index 5c279162..fbe23470 100644 --- a/src/userstream.rs +++ b/src/userstream.rs @@ -1,8 +1,8 @@ -use crate::model::{Success, UserDataStream}; +use crate::api::Spot; +use crate::api::API; use crate::client::Client; use crate::errors::Result; -use crate::api::API; -use crate::api::Spot; +use crate::model::{Success, UserDataStream}; #[derive(Clone)] pub struct UserStream { diff --git a/src/util.rs b/src/util.rs index 5761bf4c..ecb68875 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,8 @@ use crate::errors::Result; -use std::collections::BTreeMap; -use std::time::{SystemTime, UNIX_EPOCH}; use error_chain::bail; use serde_json::Value; +use std::collections::BTreeMap; +use std::time::{SystemTime, UNIX_EPOCH}; pub fn build_request(parameters: BTreeMap) -> String { let mut request = String::new(); @@ -15,13 +15,20 @@ pub fn build_request(parameters: BTreeMap) -> String { } pub fn build_signed_request( - parameters: BTreeMap, recv_window: u64, + parameters: BTreeMap, + recv_window: u64, ) -> Result { - build_signed_request_custom(parameters, recv_window, SystemTime::now()) + build_signed_request_custom( + parameters, + recv_window, + SystemTime::now(), + ) } pub fn build_signed_request_custom( - mut parameters: BTreeMap, recv_window: u64, start: SystemTime, + mut parameters: BTreeMap, + recv_window: u64, + start: SystemTime, ) -> Result { if recv_window > 0 { parameters.insert("recvWindow".into(), recv_window.to_string()); @@ -43,5 +50,6 @@ pub fn to_f64(v: &Value) -> f64 { fn get_timestamp(start: SystemTime) -> Result { let since_epoch = start.duration_since(UNIX_EPOCH)?; - Ok(since_epoch.as_secs() * 1000 + u64::from(since_epoch.subsec_nanos()) / 1_000_000) + Ok(since_epoch.as_secs() * 1000 + + u64::from(since_epoch.subsec_nanos()) / 1_000_000) } diff --git a/src/websockets.rs b/src/websockets.rs index 0ed7fb2f..98e18aee 100644 --- a/src/websockets.rs +++ b/src/websockets.rs @@ -1,19 +1,20 @@ -use crate::errors::Result; use crate::config::Config; +use crate::errors::Result; use crate::model::{ - AccountUpdateEvent, AggrTradesEvent, BalanceUpdateEvent, BookTickerEvent, DayTickerEvent, - DepthOrderBookEvent, KlineEvent, OrderBook, OrderTradeEvent, TradeEvent, + AccountUpdateEvent, AggrTradesEvent, BalanceUpdateEvent, + BookTickerEvent, DayTickerEvent, DepthOrderBookEvent, KlineEvent, + OrderBook, OrderTradeEvent, TradeEvent, }; use error_chain::bail; -use url::Url; use serde::{Deserialize, Serialize}; +use url::Url; -use std::sync::atomic::{AtomicBool, Ordering}; use std::net::TcpStream; -use tungstenite::{connect, Message}; +use std::sync::atomic::{AtomicBool, Ordering}; +use tungstenite::handshake::client::Response; use tungstenite::protocol::WebSocket; use tungstenite::stream::MaybeTlsStream; -use tungstenite::handshake::client::Response; +use tungstenite::{connect, Message}; #[allow(clippy::all)] enum WebsocketAPI { @@ -25,12 +26,17 @@ enum WebsocketAPI { impl WebsocketAPI { fn params(self, subscription: &str) -> String { match self { - WebsocketAPI::Default => format!("wss://stream.binance.com:9443/ws/{}", subscription), + WebsocketAPI::Default => format!( + "wss://stream.binance.com:9443/ws/{}", + subscription + ), WebsocketAPI::MultiStream => format!( "wss://stream.binance.com:9443/stream?streams={}", subscription ), - WebsocketAPI::Custom(url) => format!("{}/{}", url, subscription), + WebsocketAPI::Custom(url) => { + format!("{}/{}", url, subscription) + } } } } @@ -52,7 +58,8 @@ pub enum WebsocketEvent { } pub struct WebSockets<'a> { - pub socket: Option<(WebSocket>, Response)>, + pub socket: + Option<(WebSocket>, Response)>, handler: Box Result<()> + 'a>, } @@ -87,12 +94,24 @@ impl<'a> WebSockets<'a> { self.connect_wss(&WebsocketAPI::Default.params(subscription)) } - pub fn connect_with_config(&mut self, subscription: &str, config: &Config) -> Result<()> { - self.connect_wss(&WebsocketAPI::Custom(config.ws_endpoint.clone()).params(subscription)) + pub fn connect_with_config( + &mut self, + subscription: &str, + config: &Config, + ) -> Result<()> { + self.connect_wss( + &WebsocketAPI::Custom(config.ws_endpoint.clone()) + .params(subscription), + ) } - pub fn connect_multiple_streams(&mut self, endpoints: &[String]) -> Result<()> { - self.connect_wss(&WebsocketAPI::MultiStream.params(&endpoints.join("/"))) + pub fn connect_multiple_streams( + &mut self, + endpoints: &[String], + ) -> Result<()> { + self.connect_wss( + &WebsocketAPI::MultiStream.params(&endpoints.join("/")), + ) } fn connect_wss(&mut self, wss: &str) -> Result<()> { @@ -129,16 +148,30 @@ impl<'a> WebSockets<'a> { if let Ok(events) = serde_json::from_value::(value) { let action = match events { Events::Vec(v) => WebsocketEvent::DayTickerAll(v), - Events::BookTickerEvent(v) => WebsocketEvent::BookTicker(v), - Events::BalanceUpdateEvent(v) => WebsocketEvent::BalanceUpdate(v), - Events::AccountUpdateEvent(v) => WebsocketEvent::AccountUpdate(v), - Events::OrderTradeEvent(v) => WebsocketEvent::OrderTrade(v), - Events::AggrTradesEvent(v) => WebsocketEvent::AggrTrades(v), + Events::BookTickerEvent(v) => { + WebsocketEvent::BookTicker(v) + } + Events::BalanceUpdateEvent(v) => { + WebsocketEvent::BalanceUpdate(v) + } + Events::AccountUpdateEvent(v) => { + WebsocketEvent::AccountUpdate(v) + } + Events::OrderTradeEvent(v) => { + WebsocketEvent::OrderTrade(v) + } + Events::AggrTradesEvent(v) => { + WebsocketEvent::AggrTrades(v) + } Events::TradeEvent(v) => WebsocketEvent::Trade(v), - Events::DayTickerEvent(v) => WebsocketEvent::DayTicker(v), + Events::DayTickerEvent(v) => { + WebsocketEvent::DayTicker(v) + } Events::KlineEvent(v) => WebsocketEvent::Kline(v), Events::OrderBook(v) => WebsocketEvent::OrderBook(v), - Events::DepthOrderBookEvent(v) => WebsocketEvent::DepthOrderBook(v), + Events::DepthOrderBookEvent(v) => { + WebsocketEvent::DepthOrderBook(v) + } }; (self.handler)(action)?; } @@ -152,14 +185,24 @@ impl<'a> WebSockets<'a> { match message { Message::Text(msg) => { if let Err(e) = self.handle_msg(&msg) { - bail!(format!("Error on handling stream message: {}", e)); + bail!(format!( + "Error on handling stream message: {}", + e + )); } } Message::Ping(_) => { - socket.0.write_message(Message::Pong(vec![])).unwrap(); + socket + .0 + .write_message(Message::Pong(vec![])) + .unwrap(); + } + Message::Pong(_) + | Message::Binary(_) + | Message::Frame(_) => (), + Message::Close(e) => { + bail!(format!("Disconnected {:?}", e)) } - Message::Pong(_) | Message::Binary(_) | Message::Frame(_) => (), - Message::Close(e) => bail!(format!("Disconnected {:?}", e)), } } } diff --git a/tests/account_tests.rs b/tests/account_tests.rs index 7fce4720..407669c5 100755 --- a/tests/account_tests.rs +++ b/tests/account_tests.rs @@ -1,18 +1,22 @@ +use binance::account::*; use binance::api::*; use binance::config::*; -use binance::account::*; use binance::model::*; #[cfg(test)] mod tests { use super::*; - use mockito::{mock, Matcher}; + use float_cmp::*; + use mockito::{mock, Matcher}; #[test] fn get_account() { let mock_get_account = mock("GET", "/api/v3/account") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .match_query(Matcher::Regex( "recvWindow=1234×tamp=\\d+&signature=.*".into(), )) @@ -22,16 +26,37 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let account = account.get_account().unwrap(); mock_get_account.assert(); - assert!(approx_eq!(f32, account.maker_commission, 15.0, ulps = 2)); - assert!(approx_eq!(f32, account.taker_commission, 15.0, ulps = 2)); - assert!(approx_eq!(f32, account.buyer_commission, 0.0, ulps = 2)); - assert!(approx_eq!(f32, account.seller_commission, 0.0, ulps = 2)); + assert!(approx_eq!( + f32, + account.maker_commission, + 15.0, + ulps = 2 + )); + assert!(approx_eq!( + f32, + account.taker_commission, + 15.0, + ulps = 2 + )); + assert!(approx_eq!( + f32, + account.buyer_commission, + 0.0, + ulps = 2 + )); + assert!(approx_eq!( + f32, + account.seller_commission, + 0.0, + ulps = 2 + )); assert!(account.can_trade); assert!(account.can_withdraw); assert!(account.can_deposit); @@ -52,7 +77,10 @@ mod tests { #[test] fn get_balance() { let mock_get_account = mock("GET", "/api/v3/account") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .match_query(Matcher::Regex( "recvWindow=1234×tamp=\\d+&signature=.*".into(), )) @@ -62,7 +90,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let balance = account.get_balance("BTC").unwrap(); @@ -76,17 +105,23 @@ mod tests { #[test] fn get_open_orders() { let mock_open_orders = mock("GET", "/api/v3/openOrders") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .match_query(Matcher::Regex( "recvWindow=1234&symbol=LTCBTC×tamp=\\d+".into(), )) - .with_body_from_file("tests/mocks/account/get_open_orders.json") + .with_body_from_file( + "tests/mocks/account/get_open_orders.json", + ) .create(); let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let open_orders = account.get_open_orders("LTCBTC").unwrap(); @@ -118,15 +153,23 @@ mod tests { #[test] fn get_all_open_orders() { let mock_open_orders = mock("GET", "/api/v3/openOrders") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex("recvWindow=1234×tamp=\\d+".into())) - .with_body_from_file("tests/mocks/account/get_open_orders.json") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .match_query(Matcher::Regex( + "recvWindow=1234×tamp=\\d+".into(), + )) + .with_body_from_file( + "tests/mocks/account/get_open_orders.json", + ) .create(); let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let open_orders = account.get_all_open_orders().unwrap(); @@ -157,26 +200,36 @@ mod tests { #[test] fn cancel_all_open_orders() { - let mock_cancel_all_open_orders = mock("DELETE", "/api/v3/openOrders") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex( - "recvWindow=1234&symbol=BTCUSDT×tamp=\\d+".into(), - )) - .with_body_from_file("tests/mocks/account/cancel_all_open_orders.json") - .create(); + let mock_cancel_all_open_orders = + mock("DELETE", "/api/v3/openOrders") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .match_query(Matcher::Regex( + "recvWindow=1234&symbol=BTCUSDT×tamp=\\d+" + .into(), + )) + .with_body_from_file( + "tests/mocks/account/cancel_all_open_orders.json", + ) + .create(); let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let cancel_all_open_orders = account.cancel_all_open_orders("BTCUSDT").unwrap(); + let cancel_all_open_orders = + account.cancel_all_open_orders("BTCUSDT").unwrap(); mock_cancel_all_open_orders.assert(); assert!(cancel_all_open_orders.len() == 3); - let first_order_cancelled: OrderCanceled = cancel_all_open_orders[0].clone(); + let first_order_cancelled: OrderCanceled = + cancel_all_open_orders[0].clone(); assert_eq!(first_order_cancelled.symbol, "BTCUSDT"); assert_eq!( first_order_cancelled.orig_client_order_id.unwrap(), @@ -188,7 +241,8 @@ mod tests { "pXLV6Hz6mprAcVYpVMTGgx" ); - let second_order_cancelled: OrderCanceled = cancel_all_open_orders[1].clone(); + let second_order_cancelled: OrderCanceled = + cancel_all_open_orders[1].clone(); assert_eq!(second_order_cancelled.symbol, "BTCUSDT"); assert_eq!( second_order_cancelled.orig_client_order_id.unwrap(), @@ -214,9 +268,11 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let order_status: Order = account.order_status("LTCBTC", 1).unwrap(); + let order_status: Order = + account.order_status("LTCBTC", 1).unwrap(); mock_order_status.assert(); @@ -232,7 +288,12 @@ mod tests { assert_eq!(order_status.time_in_force, "GTC"); //Migrate to TimeInForce enum assert_eq!(order_status.type_name, "LIMIT"); assert_eq!(order_status.side, "BUY"); - assert!(approx_eq!(f64, order_status.stop_price, 0.0, ulps = 2)); + assert!(approx_eq!( + f64, + order_status.stop_price, + 0.0, + ulps = 2 + )); assert_eq!(order_status.iceberg_qty, "0.0"); assert_eq!(order_status.time, 1499827319559); assert_eq!(order_status.update_time, 1499827319559); @@ -253,7 +314,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.test_order_status("LTCBTC", 1).unwrap(); @@ -271,20 +333,30 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let transaction: Transaction = account.limit_buy("LTCBTC", 1, 0.1).unwrap(); + let transaction: Transaction = + account.limit_buy("LTCBTC", 1, 0.1).unwrap(); mock_limit_buy.assert(); assert_eq!(transaction.symbol, "LTCBTC"); assert_eq!(transaction.order_id, 1); assert_eq!(transaction.order_list_id.unwrap(), -1); - assert_eq!(transaction.client_order_id, "6gCrw2kRUAF9CvJDGP16IP"); + assert_eq!( + transaction.client_order_id, + "6gCrw2kRUAF9CvJDGP16IP" + ); assert_eq!(transaction.transact_time, 1507725176595); assert!(approx_eq!(f64, transaction.price, 0.1, ulps = 2)); assert!(approx_eq!(f64, transaction.orig_qty, 1.0, ulps = 2)); - assert!(approx_eq!(f64, transaction.executed_qty, 1.0, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.executed_qty, + 1.0, + ulps = 2 + )); assert!(approx_eq!( f64, transaction.cummulative_quote_qty, @@ -308,7 +380,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.test_limit_buy("LTCBTC", 1, 0.1).unwrap(); @@ -326,20 +399,30 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let transaction: Transaction = account.limit_sell("LTCBTC", 1, 0.1).unwrap(); + let transaction: Transaction = + account.limit_sell("LTCBTC", 1, 0.1).unwrap(); mock_limit_sell.assert(); assert_eq!(transaction.symbol, "LTCBTC"); assert_eq!(transaction.order_id, 1); assert_eq!(transaction.order_list_id.unwrap(), -1); - assert_eq!(transaction.client_order_id, "6gCrw2kRUAF9CvJDGP16IP"); + assert_eq!( + transaction.client_order_id, + "6gCrw2kRUAF9CvJDGP16IP" + ); assert_eq!(transaction.transact_time, 1507725176595); assert!(approx_eq!(f64, transaction.price, 0.1, ulps = 2)); assert!(approx_eq!(f64, transaction.orig_qty, 1.0, ulps = 2)); - assert!(approx_eq!(f64, transaction.executed_qty, 1.0, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.executed_qty, + 1.0, + ulps = 2 + )); assert!(approx_eq!( f64, transaction.cummulative_quote_qty, @@ -363,7 +446,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.test_limit_sell("LTCBTC", 1, 0.1).unwrap(); @@ -384,20 +468,30 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let transaction: Transaction = account.market_buy("LTCBTC", 1).unwrap(); + let transaction: Transaction = + account.market_buy("LTCBTC", 1).unwrap(); mock_market_buy.assert(); assert_eq!(transaction.symbol, "LTCBTC"); assert_eq!(transaction.order_id, 1); assert_eq!(transaction.order_list_id.unwrap(), -1); - assert_eq!(transaction.client_order_id, "6gCrw2kRUAF9CvJDGP16IP"); + assert_eq!( + transaction.client_order_id, + "6gCrw2kRUAF9CvJDGP16IP" + ); assert_eq!(transaction.transact_time, 1507725176595); assert!(approx_eq!(f64, transaction.price, 0.1, ulps = 2)); assert!(approx_eq!(f64, transaction.orig_qty, 1.0, ulps = 2)); - assert!(approx_eq!(f64, transaction.executed_qty, 1.0, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.executed_qty, + 1.0, + ulps = 2 + )); assert!(approx_eq!( f64, transaction.cummulative_quote_qty, @@ -424,7 +518,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.test_market_buy("LTCBTC", 1).unwrap(); @@ -442,7 +537,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); match account.market_buy_using_quote_quantity("BNBBTC", 0.002) { Ok(answer) => { @@ -465,7 +561,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account .test_market_buy_using_quote_quantity("BNBBTC", 0.002) @@ -488,20 +585,30 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let transaction: Transaction = account.market_sell("LTCBTC", 1).unwrap(); + let transaction: Transaction = + account.market_sell("LTCBTC", 1).unwrap(); mock_market_sell.assert(); assert_eq!(transaction.symbol, "LTCBTC"); assert_eq!(transaction.order_id, 1); assert_eq!(transaction.order_list_id.unwrap(), -1); - assert_eq!(transaction.client_order_id, "6gCrw2kRUAF9CvJDGP16IP"); + assert_eq!( + transaction.client_order_id, + "6gCrw2kRUAF9CvJDGP16IP" + ); assert_eq!(transaction.transact_time, 1507725176595); assert!(approx_eq!(f64, transaction.price, 0.1, ulps = 2)); assert!(approx_eq!(f64, transaction.orig_qty, 1.0, ulps = 2)); - assert!(approx_eq!(f64, transaction.executed_qty, 1.0, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.executed_qty, + 1.0, + ulps = 2 + )); assert!(approx_eq!( f64, transaction.cummulative_quote_qty, @@ -528,7 +635,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.test_market_sell("LTCBTC", 1).unwrap(); @@ -546,9 +654,11 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - match account.market_sell_using_quote_quantity("BNBBTC", 0.002) { + match account.market_sell_using_quote_quantity("BNBBTC", 0.002) + { Ok(answer) => { assert!(answer.order_id == 1); } @@ -569,7 +679,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account .test_market_sell_using_quote_quantity("BNBBTC", 0.002) @@ -589,10 +700,17 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let transaction: Transaction = account - .stop_limit_buy_order("LTCBTC", 1, 0.1, 0.09, TimeInForce::GTC) + .stop_limit_buy_order( + "LTCBTC", + 1, + 0.1, + 0.09, + TimeInForce::GTC, + ) .unwrap(); mock_stop_limit_buy_order.assert(); @@ -600,18 +718,31 @@ mod tests { assert_eq!(transaction.symbol, "LTCBTC"); assert_eq!(transaction.order_id, 1); assert_eq!(transaction.order_list_id.unwrap(), -1); - assert_eq!(transaction.client_order_id, "6gCrw2kRUAF9CvJDGP16IP"); + assert_eq!( + transaction.client_order_id, + "6gCrw2kRUAF9CvJDGP16IP" + ); assert_eq!(transaction.transact_time, 1507725176595); assert!(approx_eq!(f64, transaction.price, 0.1, ulps = 2)); assert!(approx_eq!(f64, transaction.orig_qty, 1.0, ulps = 2)); - assert!(approx_eq!(f64, transaction.executed_qty, 1.0, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.executed_qty, + 1.0, + ulps = 2 + )); assert!(approx_eq!( f64, transaction.cummulative_quote_qty, 0.0, ulps = 2 )); - assert!(approx_eq!(f64, transaction.stop_price, 0.09, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.stop_price, + 0.09, + ulps = 2 + )); assert_eq!(transaction.status, "NEW"); assert_eq!(transaction.time_in_force, "GTC"); //Migrate to TimeInForce enum assert_eq!(transaction.type_name, "STOP_LOSS_LIMIT"); @@ -629,10 +760,17 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account - .test_stop_limit_buy_order("LTCBTC", 1, 0.1, 0.09, TimeInForce::GTC) + .test_stop_limit_buy_order( + "LTCBTC", + 1, + 0.1, + 0.09, + TimeInForce::GTC, + ) .unwrap(); mock_test_stop_limit_buy_order.assert(); @@ -649,10 +787,17 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let transaction: Transaction = account - .stop_limit_sell_order("LTCBTC", 1, 0.1, 0.09, TimeInForce::GTC) + .stop_limit_sell_order( + "LTCBTC", + 1, + 0.1, + 0.09, + TimeInForce::GTC, + ) .unwrap(); mock_stop_limit_sell_order.assert(); @@ -660,18 +805,31 @@ mod tests { assert_eq!(transaction.symbol, "LTCBTC"); assert_eq!(transaction.order_id, 1); assert_eq!(transaction.order_list_id.unwrap(), -1); - assert_eq!(transaction.client_order_id, "6gCrw2kRUAF9CvJDGP16IP"); + assert_eq!( + transaction.client_order_id, + "6gCrw2kRUAF9CvJDGP16IP" + ); assert_eq!(transaction.transact_time, 1507725176595); assert!(approx_eq!(f64, transaction.price, 0.1, ulps = 2)); assert!(approx_eq!(f64, transaction.orig_qty, 1.0, ulps = 2)); - assert!(approx_eq!(f64, transaction.executed_qty, 1.0, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.executed_qty, + 1.0, + ulps = 2 + )); assert!(approx_eq!( f64, transaction.cummulative_quote_qty, 0.0, ulps = 2 )); - assert!(approx_eq!(f64, transaction.stop_price, 0.09, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.stop_price, + 0.09, + ulps = 2 + )); assert_eq!(transaction.status, "NEW"); assert_eq!(transaction.time_in_force, "GTC"); //Migrate to TimeInForce enum assert_eq!(transaction.type_name, "STOP_LOSS_LIMIT"); @@ -689,10 +847,17 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account - .test_stop_limit_sell_order("LTCBTC", 1, 0.1, 0.09, TimeInForce::GTC) + .test_stop_limit_sell_order( + "LTCBTC", + 1, + 0.1, + 0.09, + TimeInForce::GTC, + ) .unwrap(); mock_test_stop_limit_sell_order.assert(); @@ -709,7 +874,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let transaction: Transaction = account .custom_order( @@ -729,18 +895,31 @@ mod tests { assert_eq!(transaction.symbol, "LTCBTC"); assert_eq!(transaction.order_id, 1); assert_eq!(transaction.order_list_id.unwrap(), -1); - assert_eq!(transaction.client_order_id, "6gCrw2kRUAF9CvJDGP16IP"); + assert_eq!( + transaction.client_order_id, + "6gCrw2kRUAF9CvJDGP16IP" + ); assert_eq!(transaction.transact_time, 1507725176595); assert!(approx_eq!(f64, transaction.price, 0.1, ulps = 2)); assert!(approx_eq!(f64, transaction.orig_qty, 1.0, ulps = 2)); - assert!(approx_eq!(f64, transaction.executed_qty, 1.0, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.executed_qty, + 1.0, + ulps = 2 + )); assert!(approx_eq!( f64, transaction.cummulative_quote_qty, 0.0, ulps = 2 )); - assert!(approx_eq!(f64, transaction.stop_price, 0.09, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.stop_price, + 0.09, + ulps = 2 + )); assert_eq!(transaction.status, "NEW"); assert_eq!(transaction.time_in_force, "GTC"); //Migrate to TimeInForce enum assert_eq!(transaction.type_name, "STOP_LOSS_LIMIT"); @@ -758,7 +937,8 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account .test_custom_order( @@ -789,32 +969,45 @@ mod tests { let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let cancelled_order = account.cancel_order("BTCUSDT", 1).unwrap(); + let cancelled_order = + account.cancel_order("BTCUSDT", 1).unwrap(); mock_cancel_order.assert(); assert_eq!(cancelled_order.symbol, "LTCBTC"); - assert_eq!(cancelled_order.orig_client_order_id.unwrap(), "myOrder1"); + assert_eq!( + cancelled_order.orig_client_order_id.unwrap(), + "myOrder1" + ); assert_eq!(cancelled_order.order_id.unwrap(), 4); - assert_eq!(cancelled_order.client_order_id.unwrap(), "cancelMyOrder1"); + assert_eq!( + cancelled_order.client_order_id.unwrap(), + "cancelMyOrder1" + ); } #[test] fn test_cancel_order() { - let mock_test_cancel_order = mock("DELETE", "/api/v3/order/test") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex( - "orderId=1&recvWindow=1234&symbol=BTCUSDT×tamp=\\d+".into(), - )) - .with_body_from_file("tests/mocks/account/cancel_order.json") - .create(); + let mock_test_cancel_order = mock( + "DELETE", + "/api/v3/order/test", + ) + .with_header("content-type", "application/json;charset=UTF-8") + .match_query(Matcher::Regex( + "orderId=1&recvWindow=1234&symbol=BTCUSDT×tamp=\\d+" + .into(), + )) + .with_body_from_file("tests/mocks/account/cancel_order.json") + .create(); let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.test_cancel_order("BTCUSDT", 1).unwrap(); @@ -824,17 +1017,23 @@ mod tests { #[test] fn trade_history() { let mock_trade_history = mock("GET", "/api/v3/myTrades") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .match_query(Matcher::Regex( "recvWindow=1234&symbol=BTCUSDT×tamp=\\d+".into(), )) - .with_body_from_file("tests/mocks/account/trade_history.json") + .with_body_from_file( + "tests/mocks/account/trade_history.json", + ) .create(); let config = Config::default() .set_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: Account = Binance::new_with_config(None, None, &config); + let account: Account = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let histories = account.trade_history("BTCUSDT").unwrap(); @@ -854,4 +1053,89 @@ mod tests { assert!(!history.is_maker); assert!(history.is_best_match); } + + #[test] + fn test_convert() { + // Set up the first mock server to respond to the quote request + let mock_quote = mock("POST", "/sapi/v1/convert/getQuote") + .with_header("content-type", "application/json;charset=UTF-8") + // the test only works with this parameters + .match_query(Matcher::Regex("fromAmount=1&fromAsset=BTC&recvWindow=1234×tamp=\\d+&toAsset=ETH&validTime=10s".into())) + .with_body_from_file("tests/mocks/account/quote.json") + .create(); + + // Set up the second mock server to respond to the accept quote request + let mock_accept_quote = + mock("POST", "/sapi/v1/convert/acceptQuote") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .match_query(Matcher::Regex( + "quoteId=12415572564".into(), + )) + .with_body_from_file( + "tests/mocks/account/accept_quote.json", + ) + .create(); + + // Configure the Binance API client with the mock server's URL + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()) + .set_recv_window(1234); + + // Create a new Binance API client using the mock server's URL + let account: Account = + Binance::new_with_config(None, None, &config); + let _ = env_logger::try_init(); + + // Call the convert function and assert that the returned QuoteResponse object matches the expected values + let convert_response = + account.convert("BTC", "ETH", QtyType::From(1)).unwrap(); + + assert_eq!(convert_response.order_status, "PROCESS"); + + // Assert that the mock servers were called as expected + mock_quote.assert(); + mock_accept_quote.assert(); + } + + #[test] + fn test_daily_account_snapshot() { + let mock_server: mockito::Mock = + mock("GET", "/sapi/v1/accountSnapshot") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + // the test only works with this parameters + .match_query(Matcher::Regex( + "recvWindow=1234×tamp=\\d+&type=SPOT".into(), + )) + .with_body_from_file( + "tests/mocks/account/daily_account_snapshot.json", + ) + .create(); + + // config of mock server's URL + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()) + .set_recv_window(1234); + + // binance client using the mock server's URL + let account: Account = + Binance::new_with_config(None, None, &config); + let _ = env_logger::try_init(); + + let convert_response = + account.daily_account_snapshot().expect("erro merda"); + + assert_eq!( + convert_response.snapshot_vos[0].data.total_asset_of_btc, + "0.09942700" + ); + + // assert that the mock servers were called + mock_server.assert(); + } } diff --git a/tests/futures_account_tests.rs b/tests/futures_account_tests.rs index 895a2955..ec6a2a33 100644 --- a/tests/futures_account_tests.rs +++ b/tests/futures_account_tests.rs @@ -5,10 +5,10 @@ use binance::futures::account::*; #[cfg(test)] mod tests { use super::*; - use mockito::{mock, Matcher}; - use float_cmp::*; use binance::account::OrderSide; use binance::futures::model::Transaction; + use float_cmp::*; + use mockito::{mock, Matcher}; #[test] fn change_initial_leverage() { @@ -23,9 +23,11 @@ mod tests { let config = Config::default() .set_futures_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: FuturesAccount = Binance::new_with_config(None, None, &config); + let account: FuturesAccount = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let response = account.change_initial_leverage("LTCUSDT", 2).unwrap(); + let response = + account.change_initial_leverage("LTCUSDT", 2).unwrap(); mock_change_leverage.assert(); @@ -52,7 +54,8 @@ mod tests { let config = Config::default() .set_futures_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: FuturesAccount = Binance::new_with_config(None, None, &config); + let account: FuturesAccount = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.cancel_all_open_orders("BTCUSDT").unwrap(); @@ -72,7 +75,8 @@ mod tests { let config = Config::default() .set_futures_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: FuturesAccount = Binance::new_with_config(None, None, &config); + let account: FuturesAccount = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); account.change_position_mode(true).unwrap(); @@ -90,9 +94,11 @@ mod tests { let config = Config::default() .set_futures_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: FuturesAccount = Binance::new_with_config(None, None, &config); + let account: FuturesAccount = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let transaction: Transaction = account.stop_market_close_buy("SRMUSDT", 10.5).unwrap(); + let transaction: Transaction = + account.stop_market_close_buy("SRMUSDT", 10.5).unwrap(); mock_stop_market_close_sell.assert(); @@ -100,7 +106,12 @@ mod tests { assert_eq!(transaction.side, "BUY"); assert_eq!(transaction.orig_type, "STOP_MARKET"); assert!(transaction.close_position); - assert!(approx_eq!(f64, transaction.stop_price, 10.5, ulps = 2)); + assert!(approx_eq!( + f64, + transaction.stop_price, + 10.5, + ulps = 2 + )); } #[test] @@ -114,9 +125,11 @@ mod tests { let config = Config::default() .set_futures_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: FuturesAccount = Binance::new_with_config(None, None, &config); + let account: FuturesAccount = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); - let transaction: Transaction = account.stop_market_close_sell("SRMUSDT", 7.4).unwrap(); + let transaction: Transaction = + account.stop_market_close_sell("SRMUSDT", 7.4).unwrap(); mock_stop_market_close_sell.assert(); @@ -138,7 +151,8 @@ mod tests { let config = Config::default() .set_futures_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: FuturesAccount = Binance::new_with_config(None, None, &config); + let account: FuturesAccount = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let custom_order = CustomOrderRequest { symbol: "SRMUSDT".into(), @@ -156,7 +170,8 @@ mod tests { working_type: None, price_protect: None, }; - let transaction: Transaction = account.custom_order(custom_order).unwrap(); + let transaction: Transaction = + account.custom_order(custom_order).unwrap(); mock_custom_order.assert(); @@ -182,7 +197,8 @@ mod tests { let config = Config::default() .set_futures_rest_api_endpoint(mockito::server_url()) .set_recv_window(1234); - let account: FuturesAccount = Binance::new_with_config(None, None, &config); + let account: FuturesAccount = + Binance::new_with_config(None, None, &config); let _ = env_logger::try_init(); let income_request = IncomeRequest { symbol: Some("BTCUSDT".into()), diff --git a/tests/futures_market_test.rs b/tests/futures_market_test.rs index c11c9e41..3a6bdeb4 100644 --- a/tests/futures_market_test.rs +++ b/tests/futures_market_test.rs @@ -10,14 +10,23 @@ mod tests { #[test] fn open_interest_statistics() { - let mock_open_interest_statistics = mock("GET", "/futures/data/openInterestHist") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex("limit=10&period=5m&symbol=BTCUSDT".into())) - .with_body_from_file("tests/mocks/futures/market/open_interest_statistics.json") - .create(); + let mock_open_interest_statistics = mock( + "GET", + "/futures/data/openInterestHist", + ) + .with_header("content-type", "application/json;charset=UTF-8") + .match_query(Matcher::Regex( + "limit=10&period=5m&symbol=BTCUSDT".into(), + )) + .with_body_from_file( + "tests/mocks/futures/market/open_interest_statistics.json", + ) + .create(); - let config = Config::default().set_futures_rest_api_endpoint(mockito::server_url()); - let market: FuturesMarket = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_futures_rest_api_endpoint(mockito::server_url()); + let market: FuturesMarket = + Binance::new_with_config(None, None, &config); let open_interest_hists = market .open_interest_statistics("BTCUSDT", "5m", 10, None, None) diff --git a/tests/general_tests.rs b/tests/general_tests.rs index ca667aff..a9d23737 100755 --- a/tests/general_tests.rs +++ b/tests/general_tests.rs @@ -6,18 +6,23 @@ use binance::model::*; #[cfg(test)] mod tests { use super::*; - use mockito::mock; use float_cmp::*; + use mockito::mock; #[test] fn ping() { let mock_ping = mock("GET", "/api/v3/ping") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .with_body("{}") .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let general: General = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let general: General = + Binance::new_with_config(None, None, &config); let pong = general.ping().unwrap(); mock_ping.assert(); @@ -28,12 +33,17 @@ mod tests { #[test] fn get_server_time() { let mock_server_time = mock("GET", "/api/v3/time") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .with_body_from_file("tests/mocks/general/server_time.json") .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let general: General = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let general: General = + Binance::new_with_config(None, None, &config); let server_time = general.get_server_time().unwrap(); mock_server_time.assert(); @@ -44,12 +54,19 @@ mod tests { #[test] fn exchange_info() { let mock_exchange_info = mock("GET", "/api/v3/exchangeInfo") - .with_header("content-type", "application/json;charset=UTF-8") - .with_body_from_file("tests/mocks/general/exchange_info.json") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .with_body_from_file( + "tests/mocks/general/exchange_info.json", + ) .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let general: General = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let general: General = + Binance::new_with_config(None, None, &config); let exchange_info = general.exchange_info().unwrap(); mock_exchange_info.assert(); @@ -60,12 +77,19 @@ mod tests { #[test] fn get_symbol_info() { let mock_exchange_info = mock("GET", "/api/v3/exchangeInfo") - .with_header("content-type", "application/json;charset=UTF-8") - .with_body_from_file("tests/mocks/general/exchange_info.json") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .with_body_from_file( + "tests/mocks/general/exchange_info.json", + ) .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let general: General = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let general: General = + Binance::new_with_config(None, None, &config); let symbol = general.get_symbol_info("BNBBTC").unwrap(); mock_exchange_info.assert(); @@ -108,7 +132,12 @@ mod tests { } => { assert_eq!(multiplier_up, "5"); assert_eq!(multiplier_down, "0.2"); - assert!(approx_eq!(f64, avg_price_mins.unwrap(), 5.0, ulps = 2)); + assert!(approx_eq!( + f64, + avg_price_mins.unwrap(), + 5.0, + ulps = 2 + )); } Filters::LotSize { min_qty, @@ -128,7 +157,12 @@ mod tests { assert!(notional.is_none()); assert_eq!(min_notional.unwrap(), "0.00010000"); assert!(apply_to_market.unwrap()); - assert!(approx_eq!(f64, avg_price_mins.unwrap(), 5.0, ulps = 2)); + assert!(approx_eq!( + f64, + avg_price_mins.unwrap(), + 5.0, + ulps = 2 + )); } Filters::IcebergParts { limit } => { assert_eq!(limit.unwrap(), 10); diff --git a/tests/market_tests.rs b/tests/market_tests.rs index 126b2423..26372829 100644 --- a/tests/market_tests.rs +++ b/tests/market_tests.rs @@ -6,54 +6,79 @@ use binance::model::*; #[cfg(test)] mod tests { use super::*; - use mockito::{mock, Matcher}; use float_cmp::*; + use mockito::{mock, Matcher}; #[test] fn get_depth() { let mock_get_depth = mock("GET", "/api/v3/depth") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .match_query(Matcher::Regex("symbol=LTCBTC".into())) .with_body_from_file("tests/mocks/market/get_depth.json") .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let order_book = market.get_depth("LTCBTC").unwrap(); mock_get_depth.assert(); assert_eq!(order_book.last_update_id, 1027024); - assert_eq!(order_book.bids[0], Bids::new(4.00000000, 431.00000000)); + assert_eq!( + order_book.bids[0], + Bids::new(4.00000000, 431.00000000) + ); } #[test] fn get_custom_depth() { let mock_get_custom_depth = mock("GET", "/api/v3/depth") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex("limit=10&symbol=LTCBTC".into())) + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .match_query(Matcher::Regex( + "limit=10&symbol=LTCBTC".into(), + )) .with_body_from_file("tests/mocks/market/get_depth.json") .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let order_book = market.get_custom_depth("LTCBTC", 10).unwrap(); mock_get_custom_depth.assert(); assert_eq!(order_book.last_update_id, 1027024); - assert_eq!(order_book.bids[0], Bids::new(4.00000000, 431.00000000)); + assert_eq!( + order_book.bids[0], + Bids::new(4.00000000, 431.00000000) + ); } #[test] fn get_all_prices() { let mock_get_all_prices = mock("GET", "/api/v3/ticker/price") - .with_header("content-type", "application/json;charset=UTF-8") - .with_body_from_file("tests/mocks/market/get_all_prices.json") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .with_body_from_file( + "tests/mocks/market/get_all_prices.json", + ) .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let prices: Prices = market.get_all_prices().unwrap(); mock_get_all_prices.assert(); @@ -63,10 +88,20 @@ mod tests { assert!(!symbols.is_empty()); let first_symbol = symbols[0].clone(); assert_eq!(first_symbol.symbol, "LTCBTC"); - assert!(approx_eq!(f64, first_symbol.price, 4.00000200, ulps = 2)); + assert!(approx_eq!( + f64, + first_symbol.price, + 4.00000200, + ulps = 2 + )); let second_symbol = symbols[1].clone(); assert_eq!(second_symbol.symbol, "ETHBTC"); - assert!(approx_eq!(f64, second_symbol.price, 0.07946600, ulps = 2)); + assert!(approx_eq!( + f64, + second_symbol.price, + 0.07946600, + ulps = 2 + )); } } } @@ -74,13 +109,18 @@ mod tests { #[test] fn get_price() { let mock_get_price = mock("GET", "/api/v3/ticker/price") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .match_query(Matcher::Regex("symbol=LTCBTC".into())) .with_body_from_file("tests/mocks/market/get_price.json") .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let symbol = market.get_price("LTCBTC").unwrap(); mock_get_price.assert(); @@ -92,13 +132,20 @@ mod tests { #[test] fn get_average_price() { let mock_get_average_price = mock("GET", "/api/v3/avgPrice") - .with_header("content-type", "application/json;charset=UTF-8") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) .match_query(Matcher::Regex("symbol=LTCBTC".into())) - .with_body_from_file("tests/mocks/market/get_average_price.json") + .with_body_from_file( + "tests/mocks/market/get_average_price.json", + ) .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let symbol = market.get_average_price("LTCBTC").unwrap(); mock_get_average_price.assert(); @@ -109,13 +156,21 @@ mod tests { #[test] fn get_all_book_tickers() { - let mock_get_all_book_tickers = mock("GET", "/api/v3/ticker/bookTicker") - .with_header("content-type", "application/json;charset=UTF-8") - .with_body_from_file("tests/mocks/market/get_all_book_tickers.json") - .create(); - - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let mock_get_all_book_tickers = + mock("GET", "/api/v3/ticker/bookTicker") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .with_body_from_file( + "tests/mocks/market/get_all_book_tickers.json", + ) + .create(); + + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let book_tickers = market.get_all_book_tickers().unwrap(); mock_get_all_book_tickers.assert(); @@ -143,7 +198,12 @@ mod tests { 4.00000200, ulps = 2 )); - assert!(approx_eq!(f64, first_ticker.ask_qty, 9.00000000, ulps = 2)); + assert!(approx_eq!( + f64, + first_ticker.ask_qty, + 9.00000000, + ulps = 2 + )); let second_ticker = tickers[1].clone(); assert_eq!(second_ticker.symbol, "ETHBTC"); assert!(approx_eq!( @@ -152,7 +212,12 @@ mod tests { 0.07946700, ulps = 2 )); - assert!(approx_eq!(f64, second_ticker.bid_qty, 9.00000000, ulps = 2)); + assert!(approx_eq!( + f64, + second_ticker.bid_qty, + 9.00000000, + ulps = 2 + )); assert!(approx_eq!( f64, second_ticker.ask_price, @@ -171,35 +236,71 @@ mod tests { #[test] fn get_book_ticker() { - let mock_get_book_ticker = mock("GET", "/api/v3/ticker/bookTicker") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex("symbol=LTCBTC".into())) - .with_body_from_file("tests/mocks/market/get_book_ticker.json") - .create(); - - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let mock_get_book_ticker = + mock("GET", "/api/v3/ticker/bookTicker") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .match_query(Matcher::Regex("symbol=LTCBTC".into())) + .with_body_from_file( + "tests/mocks/market/get_book_ticker.json", + ) + .create(); + + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let book_ticker = market.get_book_ticker("LTCBTC").unwrap(); mock_get_book_ticker.assert(); assert_eq!(book_ticker.symbol, "LTCBTC"); - assert!(approx_eq!(f64, book_ticker.bid_price, 4.00000000, ulps = 2)); - assert!(approx_eq!(f64, book_ticker.bid_qty, 431.00000000, ulps = 2)); - assert!(approx_eq!(f64, book_ticker.ask_price, 4.00000200, ulps = 2)); - assert!(approx_eq!(f64, book_ticker.ask_qty, 9.00000000, ulps = 2)); + assert!(approx_eq!( + f64, + book_ticker.bid_price, + 4.00000000, + ulps = 2 + )); + assert!(approx_eq!( + f64, + book_ticker.bid_qty, + 431.00000000, + ulps = 2 + )); + assert!(approx_eq!( + f64, + book_ticker.ask_price, + 4.00000200, + ulps = 2 + )); + assert!(approx_eq!( + f64, + book_ticker.ask_qty, + 9.00000000, + ulps = 2 + )); } #[test] fn get_24h_price_stats() { - let mock_get_24h_price_stats = mock("GET", "/api/v3/ticker/24hr") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex("symbol=BNBBTC".into())) - .with_body_from_file("tests/mocks/market/get_24h_price_stats.json") - .create(); - - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let mock_get_24h_price_stats = + mock("GET", "/api/v3/ticker/24hr") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .match_query(Matcher::Regex("symbol=BNBBTC".into())) + .with_body_from_file( + "tests/mocks/market/get_24h_price_stats.json", + ) + .create(); + + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let price_stats = market.get_24h_price_stats("BNBBTC").unwrap(); mock_get_24h_price_stats.assert(); @@ -220,8 +321,18 @@ mod tests { 4.00000200, ulps = 2 )); - assert!(approx_eq!(f64, price_stats.bid_price, 4.00000000, ulps = 2)); - assert!(approx_eq!(f64, price_stats.ask_price, 4.00000200, ulps = 2)); + assert!(approx_eq!( + f64, + price_stats.bid_price, + 4.00000000, + ulps = 2 + )); + assert!(approx_eq!( + f64, + price_stats.ask_price, + 4.00000200, + ulps = 2 + )); assert!(approx_eq!( f64, price_stats.open_price, @@ -234,8 +345,18 @@ mod tests { 100.00000000, ulps = 2 )); - assert!(approx_eq!(f64, price_stats.low_price, 0.10000000, ulps = 2)); - assert!(approx_eq!(f64, price_stats.volume, 8913.30000000, ulps = 2)); + assert!(approx_eq!( + f64, + price_stats.low_price, + 0.10000000, + ulps = 2 + )); + assert!(approx_eq!( + f64, + price_stats.volume, + 8913.30000000, + ulps = 2 + )); assert_eq!(price_stats.open_time, 1499783499040); assert_eq!(price_stats.close_time, 1499869899040); assert_eq!(price_stats.first_id, 28385); @@ -245,13 +366,21 @@ mod tests { #[test] fn get_all_24h_price_stats() { - let mock_get_all_24h_price_stats = mock("GET", "/api/v3/ticker/24hr") - .with_header("content-type", "application/json;charset=UTF-8") - .with_body_from_file("tests/mocks/market/get_all_24h_price_stats.json") - .create(); - - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let mock_get_all_24h_price_stats = + mock("GET", "/api/v3/ticker/24hr") + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .with_body_from_file( + "tests/mocks/market/get_all_24h_price_stats.json", + ) + .create(); + + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); let prices_stats = market.get_all_24h_price_stats().unwrap(); mock_get_all_24h_price_stats.assert(); @@ -276,8 +405,18 @@ mod tests { 4.00000200, ulps = 2 )); - assert!(approx_eq!(f64, price_stats.bid_price, 4.00000000, ulps = 2)); - assert!(approx_eq!(f64, price_stats.ask_price, 4.00000200, ulps = 2)); + assert!(approx_eq!( + f64, + price_stats.bid_price, + 4.00000000, + ulps = 2 + )); + assert!(approx_eq!( + f64, + price_stats.ask_price, + 4.00000200, + ulps = 2 + )); assert!(approx_eq!( f64, price_stats.open_price, @@ -290,8 +429,18 @@ mod tests { 100.00000000, ulps = 2 )); - assert!(approx_eq!(f64, price_stats.low_price, 0.10000000, ulps = 2)); - assert!(approx_eq!(f64, price_stats.volume, 8913.30000000, ulps = 2)); + assert!(approx_eq!( + f64, + price_stats.low_price, + 0.10000000, + ulps = 2 + )); + assert!(approx_eq!( + f64, + price_stats.volume, + 8913.30000000, + ulps = 2 + )); assert_eq!(price_stats.open_time, 1499783499040); assert_eq!(price_stats.close_time, 1499869899040); assert_eq!(price_stats.first_id, 28385); @@ -302,19 +451,29 @@ mod tests { #[test] fn get_klines() { let mock_get_klines = mock("GET", "/api/v3/klines") - .with_header("content-type", "application/json;charset=UTF-8") - .match_query(Matcher::Regex("interval=5m&limit=10&symbol=LTCBTC".into())) + .with_header( + "content-type", + "application/json;charset=UTF-8", + ) + .match_query(Matcher::Regex( + "interval=5m&limit=10&symbol=LTCBTC".into(), + )) .with_body_from_file("tests/mocks/market/get_klines.json") .create(); - let config = Config::default().set_rest_api_endpoint(mockito::server_url()); - let market: Market = Binance::new_with_config(None, None, &config); + let config = Config::default() + .set_rest_api_endpoint(mockito::server_url()); + let market: Market = + Binance::new_with_config(None, None, &config); - let klines = market.get_klines("LTCBTC", "5m", 10, None, None).unwrap(); + let klines = + market.get_klines("LTCBTC", "5m", 10, None, None).unwrap(); mock_get_klines.assert(); match klines { - binance::model::KlineSummaries::AllKlineSummaries(klines) => { + binance::model::KlineSummaries::AllKlineSummaries( + klines, + ) => { assert!(!klines.is_empty()); let kline: KlineSummary = klines[0].clone(); @@ -327,8 +486,14 @@ mod tests { assert_eq!(kline.close_time, 1499644799999); assert_eq!(kline.quote_asset_volume, "2434.19055334"); assert_eq!(kline.number_of_trades, 308); - assert_eq!(kline.taker_buy_base_asset_volume, "1756.87402397"); - assert_eq!(kline.taker_buy_quote_asset_volume, "28.46694368"); + assert_eq!( + kline.taker_buy_base_asset_volume, + "1756.87402397" + ); + assert_eq!( + kline.taker_buy_quote_asset_volume, + "28.46694368" + ); } } } diff --git a/tests/mocks/account/accept_quote.json b/tests/mocks/account/accept_quote.json new file mode 100644 index 00000000..4094b051 --- /dev/null +++ b/tests/mocks/account/accept_quote.json @@ -0,0 +1,5 @@ +{ + "orderId": "933256278426274426", + "createTime": 1623381330472, + "orderStatus": "PROCESS" +} \ No newline at end of file diff --git a/tests/mocks/account/daily_account_snapshot.json b/tests/mocks/account/daily_account_snapshot.json new file mode 100644 index 00000000..3a8dac9e --- /dev/null +++ b/tests/mocks/account/daily_account_snapshot.json @@ -0,0 +1,25 @@ +{ + "code": 200, + "msg": "", + "snapshotVos": [ + { + "data": { + "balances": [ + { + "asset": "BTC", + "free": "0.09905021", + "locked": "0.00000000" + }, + { + "asset": "USDT", + "free": "1.89109409", + "locked": "0.00000000" + } + ], + "totalAssetOfBtc": "0.09942700" + }, + "type": "spot", + "updateTime": 1576281599000 + } + ] +} \ No newline at end of file diff --git a/tests/mocks/account/quote.json b/tests/mocks/account/quote.json new file mode 100644 index 00000000..52e89354 --- /dev/null +++ b/tests/mocks/account/quote.json @@ -0,0 +1,8 @@ +{ + "quoteId": "12415572564", + "ratio": "38163.7", + "inverseRatio": "0.0000262", + "validTimestamp": 1623319461670, + "toAmount": "0.3", + "fromAmount": "1" +} \ No newline at end of file diff --git a/tests/util_tests.rs b/tests/util_tests.rs index cc51fc18..62dc1f33 100644 --- a/tests/util_tests.rs +++ b/tests/util_tests.rs @@ -3,9 +3,9 @@ use binance::util::*; #[cfg(test)] mod tests { use super::*; + use float_cmp::*; use std::collections::BTreeMap; use std::time::{SystemTime, UNIX_EPOCH}; - use float_cmp::*; #[test] fn build_request_empty() { @@ -28,16 +28,23 @@ mod tests { let recv_window = 1234; let since_epoch = now.duration_since(UNIX_EPOCH).unwrap(); - let timestamp = - since_epoch.as_secs() * 1000 + u64::from(since_epoch.subsec_nanos()) / 1_000_000; + let timestamp = since_epoch.as_secs() * 1000 + + u64::from(since_epoch.subsec_nanos()) / 1_000_000; let parameters: BTreeMap = BTreeMap::new(); - let result = - binance::util::build_signed_request_custom(parameters, recv_window, now).unwrap(); + let result = binance::util::build_signed_request_custom( + parameters, + recv_window, + now, + ) + .unwrap(); assert_eq!( result, - format!("recvWindow={}×tamp={}", recv_window, timestamp) + format!( + "recvWindow={}×tamp={}", + recv_window, timestamp + ) ); } @@ -59,4 +66,10 @@ mod tests { ulps = 2 )); } + + #[test] + fn test_print() { + println!("bosta"); + assert_eq!(1, 1); + } }