diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml new file mode 100644 index 0000000..12076c1 --- /dev/null +++ b/.github/workflows/rust.yaml @@ -0,0 +1,33 @@ +name: Rust Main Workflow + +on: + push: + branches: [master] + pull_request: + branches: [master, rc-**] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Fix Rust Version + run: rustup install 1.65 + + - name: Test + run: cargo test + + - name: Format Check + run: cargo fmt -- --check + + - name: Lint Check + run: cargo clippy --all-targets -- -D clippy::all + + - name: Examples Format Check + working-directory: ./examples + run: cargo fmt -- --check + + - name: Examples Lint Check + working-directory: ./examples + run: cargo clippy --all-targets -- -D clippy::all diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d68534d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +debug/ +target/ +Cargo.lock +**/*.rs.bk +*.pdb \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3b5d26f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +## 0.0.1 - Undefined \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fbb9e43 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "binance_spot_connector_rust" +version = "1.0.0" +authors = ["Binance"] +edition = "2021" +resolver = "2" +autoexamples = false +description = "This is a lightweight library that works as a connector to the Binance public API" +readme = "README.md" +repository = "https://github.com/binance/binance-connector-rust" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["enable-ureq", "enable-tungstenite"] +enable-hyper = [ "hyper", "hyper-tls", "serde_json", "futures-util", "tokio" ] +enable-ureq = [ "ureq", "serde_json" ] +enable-tungstenite = ["tungstenite"] +enable-tokio-tungstenite = ["tokio-tungstenite"] +full = ["enable-hyper", "enable-tungstenite", "enable-ureq", "enable-tokio-tungstenite"] + +[dependencies] +hmac = "0.12.0" +log = "0.4.14" +serde = { version = "1.0.136", features = ["derive"] } +sha2 = { version = "0.10.6", default-features = false, features = ["oid"] } +url = "2.2.2" +rust_decimal = "1.24.0" +http = "0.2.7" +strum = { version = "0.24", features = ["derive"] } +rsa = { version = "0.7.1", features = ["pkcs5"] } +rand = "0.8.5" +signature = "1.6.4" +base64 = "0.13.1" + +# enable-ureq +ureq = { version = "2.4.0", optional = true } + +# enable-hyper +hyper = { version = "0.14.16", features = ["full"], optional = true } +serde_json = { version = "1.0.78", optional = true } +hyper-tls = {version = "0.5.0", optional = true } +futures-util = {version = "0.3.21", optional = true } +tokio = { version = "1", features = ["time"], optional = true } + +# enable-tungstenite +tungstenite = {version = "0.16.0", features = ["native-tls"], optional = true} + +# enable-tokio-tungstenite +tokio-tungstenite = {version = "0.17.1", features = ["native-tls"], optional = true} + +[dev-dependencies] +tokio = { version = "1", features = ["full"] } +env_logger = "0.10.0" +tower = "0.4.12" +rust_decimal_macros = "1.24.0" +cargo-audit = "0.17.4" diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..51296e5 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2022] [Binance] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d3dc0b --- /dev/null +++ b/README.md @@ -0,0 +1,246 @@ +# Binance Public API Connector Rust + +This is a lightweight library that works as a connector to [Binance public API](https://github.com/binance/binance-spot-api-docs) + +- Supported APIs: + - `/api/*` + - `/sapi/*` + - Spot Websocket Market Stream + - Spot User Data Stream +- Test cases and examples +- Customizable base URL and request timeout +- Blocking and Non-blocking clients +- Response Metadata + +## RESTful APIs + +The Binance Rust Connector exposes two abstraction layers to integrete with Binance RESTful APIs; a high level +abstraction consisting of maintained functions mapped one-to-one with Binance API endpoints, and a low level +generic abstraction for more control over the request. + +**High Level Usage Example** + +```rust +use env_logger::Builder; +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + market::{self, klines::KlineInterval}, + trade +}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + + // Get candlesticks for BTCUSDT with a 1 minute interval + let data = client.send(market::klines("BTCUSDT", KlineInterval::Minutes1)).await + .expect("Request failed") + .into_body_str().await + .expect("Failed to read response body"); + log::info!("{}", data); + + // Get the last 10 candlesticks for BTCUSDT with a 1 hour interval + let data = client.send(market::klines("BTCUSDT", KlineInterval::Hours1).limit(10)).await + .expect("Request failed") + .into_body_str().await + .expect("Failed to read response body"); + log::info!("{}", data); + + // Get account information + let data = client.send(trade::account()).await + .expect("Request failed") + .into_body_str().await + .expect("Failed to read response body"); + log::info!("{}", data); + + Ok(()) +} +``` + +Examples for other endpoints are available in the `examples` folder. + +**Low Level Usage Example** + +```rust +use binance_spot_connector_rust::{ + http::{request::RequestBuilder, Method}, + hyper::{BinanceHttpClient, Error}, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + + let builder = RequestBuilder::new(Method::Get, "/api/v3/klines") + .params(vec![("symbol", "BTCUSDT"), ("interval", "1m")]); + + // Get candlesticks for BTCUSDT with a 1 minute interval + let data = client + .send(builder) + .await + .expect("Request failed") + .into_body_str() + .await + .expect("Failed to read response body"); + log::info!("{}", data); + + Ok(()) +} +``` + +## Websocket + +```rust +use binance_spot_connector_rust::{ + market::klines::KlineInterval, market_stream::kline::KlineStream, + tokio_tungstenite::BinanceWebSocketClient, +}; +use env_logger::Builder; +use futures_util::StreamExt; + +#[tokio::main] +async fn main() { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + // Establish connection + let (mut conn, _) = BinanceWebSocketClient::connect_async_default() + .await + .expect("Failed to connect"); + // Subscribe to streams + conn.subscribe(vec![ + &KlineStream::new("BTCUSDT", KlineInterval::Minutes1).into() + ]) + .await; + // Read messages + while let Some(message) = conn.as_mut().next().await { + match message { + Ok(message) => { + let binary_data = message.into_data(); + let data = std::str::from_utf8(&binary_data).expect("Failed to parse message"); + log::info!("{:?}", data); + } + Err(_) => break, + } + } + // Disconnect + conn.close().await.expect("Failed to disconnect"); +} + +``` + +Examples for other websocket streams are available in the `examples` folder. + +### Heartbeat + +Once connected, the websocket server sends a ping frame every 3 minutes and requires a response pong frame back within +a 10 minutes period. This package handles the pong responses automatically. + +### Testnet + +`/api/*` endpoints can be tested in [Spot Testnet](https://testnet.binance.vision/). `/sapi/*` endpoints are not supported. + +```rust +let client = BinanceHttpClient::with_url("https://testnet.binance.vision"); +``` + +### Base URL + +It's recommended to pass in the `baseUrl` parameter, even in production as Binance provides alternative URLs +in case of performance issues: + +- `https://api1.binance.com` +- `https://api2.binance.com` +- `https://api3.binance.com` + +### Timeout + +The default timeout is 100,000 milliseconds (100 seconds). + +### Logging + +This library implements the standard rust logging framework which works with a variety of built-in and third-party logging providers. + +For more information on how to configure logging in Rust, visit [Rust Log](https://docs.rs/log/latest/log/) + +**Usage Example** + +```rust +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Debug) + .init(); + + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + + // Get candlesticks for BTCUSDT with a 1 minute interval + let data = client + .send(wallet::system_status()) + .await + .expect("Request failed") + .into_body_str() + .await + .expect("Failed to read response body"); + + Ok(()) +} +``` + +**Sample Output** + +``` +[2022-02-22T00:00:00Z DEBUG binance_spot_connector_rust::hyper::client] https://api.binance.com/sapi/v1/system/status +[2022-02-22T00:00:00Z DEBUG binance_spot_connector_rust::hyper::client] 200 OK +[2022-02-22T00:00:00Z INFO binance_spot_connector_rust::hyper::client] {"status":0,"msg":"normal"} +``` + +## Test Cases + +```bash +cargo test +``` + +## Examples + +All snippets for spot endpoints and streams can be found in the `examples` folder. Example names are a concatanation of the example directory name and the example file name separated by an underscore. + +```bash +cd examples && cargo run --example market_exchange_info +``` + +## Limitations + +Futures and Vanilla Options APIs are not supported: + +- /fapi/\* +- /dapi/\* +- /vapi/\* +- Associated Websocket Market and User Data Streams + +## Contributing + +Contributions are welcome. + +If you've found a bug within this project, please open an issue to discuss what you would like to change. + +If it's an issue with the API, please open a topic at [Binance Developer Community](https://dev.binance.vision) diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 0000000..5d19e33 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,446 @@ +[package] +name = "binance-connector-rust-examples" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +binance_spot_connector_rust = { path = "..", features = ["full"]} +log = "0.4.14" +tokio = { version = "1", features = ["full"] } +futures-util = "0.3.21" +env_logger = "0.9.0" +rust_decimal = "1.24.0" +rust_decimal_macros = "1.24.0" + +[[example]] +name="hyper" +path="hyper.rs" + +[[example]] +name="ureq" +path="ureq.rs" + +[[example]] +name="tokio_tungstenite" +path="tokio_tungstenite.rs" + +[[example]] +name="tungstenite" +path="tungstenite.rs" + +[[example]] +name="market_ping" +path="market/ping.rs" + +[[example]] +name="market_time" +path="market/time.rs" + +[[example]] +name="market_exchange_info" +path="market/exchange_info.rs" + +[[example]] +name="market_depth" +path="market/depth.rs" + +[[example]] +name="market_trades" +path="market/trades.rs" + +[[example]] +name="market_historical_trades" +path="market/historical_trades.rs" + +[[example]] +name="market_agg_trades" +path="market/agg_trades.rs" + +[[example]] +name="market_klines" +path="market/klines.rs" + +[[example]] +name="market_avg_price" +path="market/avg_price.rs" + +[[example]] +name="market_ticker_twenty_four_hr" +path="market/ticker_twenty_four_hr.rs" + +[[example]] +name="market_ticker_price" +path="market/ticker_price.rs" + +[[example]] +name="market_book_ticker" +path="market/book_ticker.rs" + +[[example]] +name="market_rolling_window_price_change_statistics" +path="market/rolling_window_price_change_statistics.rs" + +[[example]] +name="trade_new_order_test" +path="trade/new_order_test.rs" + +[[example]] +name="trade_get_order" +path="trade/get_order.rs" + +[[example]] +name="trade_cancel_an_existing_order_and_send_a_new_order" +path="trade/cancel_an_existing_order_and_send_a_new_order.rs" + +[[example]] +name="trade_new_order" +path="trade/new_order.rs" + +[[example]] +name="trade_cancel_order" +path="trade/cancel_order.rs" + +[[example]] +name="trade_open_orders" +path="trade/open_orders.rs" + +[[example]] +name="trade_cancel_open_orders" +path="trade/cancel_open_orders.rs" + +[[example]] +name="trade_all_orders" +path="trade/all_orders.rs" + +[[example]] +name="trade_new_oco_order" +path="trade/new_oco_order.rs" + +[[example]] +name="trade_get_oco_order" +path="trade/get_oco_order.rs" + +[[example]] +name="trade_cancel_oco_order" +path="trade/cancel_oco_order.rs" + +[[example]] +name="trade_get_oco_orders" +path="trade/get_oco_orders.rs" + +[[example]] +name="trade_get_open_oco_orders" +path="trade/get_open_oco_orders.rs" + +[[example]] +name="trade_account" +path="trade/account.rs" + +[[example]] +name="trade_my_trades" +path="trade/my_trades.rs" + +[[example]] +name="trade_order_limit_usage" +path="trade/order_limit_usage.rs" + +[[example]] +name="margin_margin_transfer" +path="margin/margin_transfer.rs" + +[[example]] +name="margin_margin_transfer_history" +path="margin/margin_transfer_history.rs" + +[[example]] +name="margin_margin_borrow" +path="margin/margin_borrow.rs" + +[[example]] +name="margin_margin_loan_record" +path="margin/margin_loan_record.rs" + +[[example]] +name="margin_margin_repay" +path="margin/margin_repay.rs" + +[[example]] +name="margin_margin_repay_record" +path="margin/margin_repay_record.rs" + +[[example]] +name="margin_margin_asset" +path="margin/margin_asset.rs" + +[[example]] +name="margin_margin_pair" +path="margin/margin_pair.rs" + +[[example]] +name="margin_margin_all_assets" +path="margin/margin_all_assets.rs" + +[[example]] +name="margin_margin_all_pairs" +path="margin/margin_all_pairs.rs" + +[[example]] +name="margin_margin_price_index" +path="margin/margin_price_index.rs" + +[[example]] +name="margin_margin_order" +path="margin/margin_order.rs" + +[[example]] +name="margin_margin_new_order" +path="margin/margin_new_order.rs" + +[[example]] +name="margin_margin_cancel_order" +path="margin/margin_cancel_order.rs" + +[[example]] +name="margin_margin_interest_history" +path="margin/margin_interest_history.rs" + +[[example]] +name="margin_margin_force_liquidation_record" +path="margin/margin_force_liquidation_record.rs" + +[[example]] +name="margin_margin_account" +path="margin/margin_account.rs" + +[[example]] +name="margin_margin_open_orders" +path="margin/margin_open_orders.rs" + +[[example]] +name="margin_margin_cancel_open_orders" +path="margin/margin_cancel_open_orders.rs" + +[[example]] +name="margin_margin_all_orders" +path="margin/margin_all_orders.rs" + +[[example]] +name="margin_margin_new_oco_order" +path="margin/margin_new_oco_order.rs" + +[[example]] +name="margin_margin_oco_order" +path="margin/margin_oco_order.rs" + +[[example]] +name="margin_margin_cancel_oco_order" +path="margin/margin_cancel_oco_order.rs" + +[[example]] +name="margin_margin_all_oco_order" +path="margin/margin_all_oco_order.rs" + +[[example]] +name="margin_margin_open_oco_order" +path="margin/margin_open_oco_order.rs" + +[[example]] +name="margin_margin_my_trades" +path="margin/margin_my_trades.rs" + +[[example]] +name="margin_margin_max_borrowable" +path="margin/margin_max_borrowable.rs" + +[[example]] +name="margin_margin_max_transferable" +path="margin/margin_max_transferable.rs" + +[[example]] +name="margin_isolated_margin_transfer_history" +path="margin/isolated_margin_transfer_history.rs" + +[[example]] +name="margin_isolated_margin_transfer" +path="margin/isolated_margin_transfer.rs" + +[[example]] +name="margin_isolated_margin_account" +path="margin/isolated_margin_account.rs" + +[[example]] +name="margin_isolated_margin_disable_account" +path="margin/isolated_margin_disable_account.rs" + +[[example]] +name="margin_isolated_margin_enable_account" +path="margin/isolated_margin_enable_account.rs" + +[[example]] +name="margin_isolated_margin_account_limit" +path="margin/isolated_margin_account_limit.rs" + +[[example]] +name="margin_isolated_margin_symbol" +path="margin/isolated_margin_symbol.rs" + +[[example]] +name="margin_isolated_margin_all_symbols" +path="margin/isolated_margin_all_symbols.rs" + +[[example]] +name="margin_toggle_bnb_burn" +path="margin/toggle_bnb_burn.rs" + +[[example]] +name="margin_bnb_burn_status" +path="margin/bnb_burn_status.rs" + +[[example]] +name="margin_margin_interest_rate_history" +path="margin/margin_interest_rate_history.rs" + +[[example]] +name="margin_margin_fee_data" +path="margin/margin_fee_data.rs" + +[[example]] +name="margin_isolated_margin_fee_data" +path="margin/isolated_margin_fee_data.rs" + +[[example]] +name="margin_isolated_margin_tier_data" +path="margin/isolated_margin_tier_data.rs" + +[[example]] +name="margin_margin_order_count_usage" +path="margin/margin_order_count_usage.rs" + +[[example]] +name="margin_margin_dustlog" +path="margin/margin_dustlog.rs" + +[[example]] +name="wallet_system_status" +path="wallet/system_status.rs" + +[[example]] +name="wallet_coin_info" +path="wallet/coin_info.rs" + +[[example]] +name="wallet_account_snapshot" +path="wallet/account_snapshot.rs" + +[[example]] +name="wallet_disable_fast_withdraw" +path="wallet/disable_fast_withdraw.rs" + +[[example]] +name="wallet_enable_fast_withdraw" +path="wallet/enable_fast_withdraw.rs" + +[[example]] +name="wallet_withdraw" +path="wallet/withdraw.rs" + +[[example]] +name="wallet_deposit_history" +path="wallet/deposit_history.rs" + +[[example]] +name="wallet_withdraw_history" +path="wallet/withdraw_history.rs" + +[[example]] +name="wallet_deposit_address" +path="wallet/deposit_address.rs" + +[[example]] +name="wallet_account_status" +path="wallet/account_status.rs" + +[[example]] +name="wallet_api_trading_status" +path="wallet/api_trading_status.rs" + +[[example]] +name="wallet_dust_log" +path="wallet/dust_log.rs" + +[[example]] +name="wallet_dustable_assets" +path="wallet/dustable_assets.rs" + +[[example]] +name="wallet_dust_transfer" +path="wallet/dust_transfer.rs" + +[[example]] +name="wallet_asset_dividend_record" +path="wallet/asset_dividend_record.rs" + +[[example]] +name="wallet_asset_detail" +path="wallet/asset_detail.rs" + +[[example]] +name="wallet_trade_fee" +path="wallet/trade_fee.rs" + +[[example]] +name="wallet_universal_transfer_history" +path="wallet/universal_transfer_history.rs" + +[[example]] +name="wallet_universal_transfer" +path="wallet/universal_transfer.rs" + +[[example]] +name="wallet_funding_wallet" +path="wallet/funding_wallet.rs" + +[[example]] +name="wallet_user_asset" +path="wallet/user_asset.rs" + +[[example]] +name="wallet_api_key_permission" +path="wallet/api_key_permission.rs" + +[[example]] +name="stream_new_listen_key" +path="stream/new_listen_key.rs" + +[[example]] +name="stream_renew_listen_key" +path="stream/renew_listen_key.rs" + +[[example]] +name="stream_close_listen_key" +path="stream/close_listen_key.rs" + +[[example]] +name="margin_stream_margin_new_listen_key" +path="margin_stream/new_listen_key.rs" + +[[example]] +name="margin_stream_margin_renew_listen_key" +path="margin_stream/renew_listen_key.rs" + +[[example]] +name="margin_stream_margin_close_listen_key" +path="margin_stream/close_listen_key.rs" + +[[example]] +name="isolated_margin_stream_isolated_margin_new_listen_key" +path="isolated_margin_stream/new_listen_key.rs" + +[[example]] +name="isolated_margin_stream_isolated_margin_renew_listen_key" +path="isolated_margin_stream/renew_listen_key.rs" + +[[example]] +name="isolated_margin_stream_isolated_margin_close_listen_key" +path="isolated_margin_stream/close_listen_key.rs" diff --git a/examples/hyper.rs b/examples/hyper.rs new file mode 100644 index 0000000..c72549e --- /dev/null +++ b/examples/hyper.rs @@ -0,0 +1,24 @@ +use binance_spot_connector_rust::{ + http::{request::RequestBuilder, Credentials, Method}, + hyper::{BinanceHttpClient, Error}, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = RequestBuilder::new(Method::Post, "/api/v3/order").params(vec![ + ("symbol", "BNBUSDT"), + ("side", "SELL"), + ("type", "LIMIT"), + ("quantity", "0.1"), + ("price", "320.2"), + ]); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/isolated_margin_stream/close_listen_key.rs b/examples/isolated_margin_stream/close_listen_key.rs new file mode 100644 index 0000000..bd2dbac --- /dev/null +++ b/examples/isolated_margin_stream/close_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + isolated_margin_stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = isolated_margin_stream::close_listen_key("BTCUSDT", "listen-key"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/isolated_margin_stream/new_listen_key.rs b/examples/isolated_margin_stream/new_listen_key.rs new file mode 100644 index 0000000..4fbb36f --- /dev/null +++ b/examples/isolated_margin_stream/new_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + isolated_margin_stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = isolated_margin_stream::new_listen_key("BTCUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/isolated_margin_stream/renew_listen_key.rs b/examples/isolated_margin_stream/renew_listen_key.rs new file mode 100644 index 0000000..3796537 --- /dev/null +++ b/examples/isolated_margin_stream/renew_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + isolated_margin_stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = isolated_margin_stream::renew_listen_key("BTCUSDT", "listen-key"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/bnb_burn_status.rs b/examples/margin/bnb_burn_status.rs new file mode 100644 index 0000000..5dbcf7e --- /dev/null +++ b/examples/margin/bnb_burn_status.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::bnb_burn_status(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_account.rs b/examples/margin/isolated_margin_account.rs new file mode 100644 index 0000000..479437a --- /dev/null +++ b/examples/margin/isolated_margin_account.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_account().symbols(vec!["BTCUSDT", "BNBBTC"]); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_account_limit.rs b/examples/margin/isolated_margin_account_limit.rs new file mode 100644 index 0000000..cc5cb83 --- /dev/null +++ b/examples/margin/isolated_margin_account_limit.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_account_limit(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_all_symbols.rs b/examples/margin/isolated_margin_all_symbols.rs new file mode 100644 index 0000000..114473a --- /dev/null +++ b/examples/margin/isolated_margin_all_symbols.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_all_symbols(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_disable_account.rs b/examples/margin/isolated_margin_disable_account.rs new file mode 100644 index 0000000..9c55322 --- /dev/null +++ b/examples/margin/isolated_margin_disable_account.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_disable_account("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_enable_account.rs b/examples/margin/isolated_margin_enable_account.rs new file mode 100644 index 0000000..16c43cb --- /dev/null +++ b/examples/margin/isolated_margin_enable_account.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_enable_account("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_fee_data.rs b/examples/margin/isolated_margin_fee_data.rs new file mode 100644 index 0000000..63b070b --- /dev/null +++ b/examples/margin/isolated_margin_fee_data.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_fee_data().symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_symbol.rs b/examples/margin/isolated_margin_symbol.rs new file mode 100644 index 0000000..467103a --- /dev/null +++ b/examples/margin/isolated_margin_symbol.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_tier_data.rs b/examples/margin/isolated_margin_tier_data.rs new file mode 100644 index 0000000..aa43b6e --- /dev/null +++ b/examples/margin/isolated_margin_tier_data.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_tier_data("BNBUSDT").tier("1"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_transfer.rs b/examples/margin/isolated_margin_transfer.rs new file mode 100644 index 0000000..140fab5 --- /dev/null +++ b/examples/margin/isolated_margin_transfer.rs @@ -0,0 +1,21 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = + margin::isolated_margin_transfer("BTC", "BNBUSDT", "SPOT", "ISOLATED_MARGIN", dec!(1.01)); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/isolated_margin_transfer_history.rs b/examples/margin/isolated_margin_transfer_history.rs new file mode 100644 index 0000000..61aa503 --- /dev/null +++ b/examples/margin/isolated_margin_transfer_history.rs @@ -0,0 +1,24 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::isolated_margin_transfer_history("BNBUSDT") + .asset("BNB") + .trans_from("SPOT") + .trans_to("ISOLATED_MARGIN") + .current(1) + .size(100); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_account.rs b/examples/margin/margin_account.rs new file mode 100644 index 0000000..d2d34ad --- /dev/null +++ b/examples/margin/margin_account.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_account(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_all_assets.rs b/examples/margin/margin_all_assets.rs new file mode 100644 index 0000000..08415a6 --- /dev/null +++ b/examples/margin/margin_all_assets.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_all_assets(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_all_oco_order.rs b/examples/margin/margin_all_oco_order.rs new file mode 100644 index 0000000..39d378a --- /dev/null +++ b/examples/margin/margin_all_oco_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_all_oco_order(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_all_orders.rs b/examples/margin/margin_all_orders.rs new file mode 100644 index 0000000..83c3c04 --- /dev/null +++ b/examples/margin/margin_all_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_all_orders("BNBUSDT").limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_all_pairs.rs b/examples/margin/margin_all_pairs.rs new file mode 100644 index 0000000..efbbd95 --- /dev/null +++ b/examples/margin/margin_all_pairs.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_all_pairs(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_asset.rs b/examples/margin/margin_asset.rs new file mode 100644 index 0000000..4b8bfbb --- /dev/null +++ b/examples/margin/margin_asset.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_asset("BTC"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_borrow.rs b/examples/margin/margin_borrow.rs new file mode 100644 index 0000000..2bcbc4a --- /dev/null +++ b/examples/margin/margin_borrow.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_borrow("BTC", dec!(1.01)).symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_cancel_oco_order.rs b/examples/margin/margin_cancel_oco_order.rs new file mode 100644 index 0000000..edad91a --- /dev/null +++ b/examples/margin/margin_cancel_oco_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_cancel_oco_order("BNBUSDT").order_list_id(10000); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_cancel_open_orders.rs b/examples/margin/margin_cancel_open_orders.rs new file mode 100644 index 0000000..12483c0 --- /dev/null +++ b/examples/margin/margin_cancel_open_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_cancel_open_orders("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_cancel_order.rs b/examples/margin/margin_cancel_order.rs new file mode 100644 index 0000000..6bf7293 --- /dev/null +++ b/examples/margin/margin_cancel_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_cancel_order("BNBUSDT").order_id(10); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_dustlog.rs b/examples/margin/margin_dustlog.rs new file mode 100644 index 0000000..98d454e --- /dev/null +++ b/examples/margin/margin_dustlog.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_dustlog(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_fee_data.rs b/examples/margin/margin_fee_data.rs new file mode 100644 index 0000000..efec562 --- /dev/null +++ b/examples/margin/margin_fee_data.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_fee_data().coin("BNB"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_force_liquidation_record.rs b/examples/margin/margin_force_liquidation_record.rs new file mode 100644 index 0000000..294868a --- /dev/null +++ b/examples/margin/margin_force_liquidation_record.rs @@ -0,0 +1,21 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_force_liquidation_record() + .current(1) + .size(100); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_interest_history.rs b/examples/margin/margin_interest_history.rs new file mode 100644 index 0000000..94a4d42 --- /dev/null +++ b/examples/margin/margin_interest_history.rs @@ -0,0 +1,22 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_interest_history() + .asset("BNB") + .current(1) + .size(100); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_interest_rate_history.rs b/examples/margin/margin_interest_rate_history.rs new file mode 100644 index 0000000..7208427 --- /dev/null +++ b/examples/margin/margin_interest_rate_history.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_interest_rate_history("BTC"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_loan_record.rs b/examples/margin/margin_loan_record.rs new file mode 100644 index 0000000..c27ac27 --- /dev/null +++ b/examples/margin/margin_loan_record.rs @@ -0,0 +1,22 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_loan_record("BTC") + .tx_id(123456789) + .current(1) + .size(100); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_max_borrowable.rs b/examples/margin/margin_max_borrowable.rs new file mode 100644 index 0000000..6a2f74e --- /dev/null +++ b/examples/margin/margin_max_borrowable.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_max_borrowable("BTC"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_max_transferable.rs b/examples/margin/margin_max_transferable.rs new file mode 100644 index 0000000..b7208a2 --- /dev/null +++ b/examples/margin/margin_max_transferable.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_max_transferable("BTC"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_my_trades.rs b/examples/margin/margin_my_trades.rs new file mode 100644 index 0000000..84dc4be --- /dev/null +++ b/examples/margin/margin_my_trades.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_my_trades("BNBUSDT").limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_new_oco_order.rs b/examples/margin/margin_new_oco_order.rs new file mode 100644 index 0000000..11bd18b --- /dev/null +++ b/examples/margin/margin_new_oco_order.rs @@ -0,0 +1,23 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = + margin::margin_new_oco_order("BNBUSDT", "SELL", dec!(0.1), dec!(400.15), dec!(390.3)) + .stop_limit_price(dec!(290)) + .stop_limit_time_in_force("GTC"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_new_order.rs b/examples/margin/margin_new_order.rs new file mode 100644 index 0000000..0da07b0 --- /dev/null +++ b/examples/margin/margin_new_order.rs @@ -0,0 +1,24 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_new_order("BNBUSDT", "SELL", "MARKET") + .quantity(dec!(1.01)) + .price(dec!(10)) + .stop_price(dec!(20.01)) + .time_in_force("GTC"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_oco_order.rs b/examples/margin/margin_oco_order.rs new file mode 100644 index 0000000..f60ef9e --- /dev/null +++ b/examples/margin/margin_oco_order.rs @@ -0,0 +1,21 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_oco_order() + .symbol("BNBUSDT") + .order_list_id(27); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_open_oco_order.rs b/examples/margin/margin_open_oco_order.rs new file mode 100644 index 0000000..9097227 --- /dev/null +++ b/examples/margin/margin_open_oco_order.rs @@ -0,0 +1,21 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_open_oco_order() + .is_isolated(true) + .symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_open_orders.rs b/examples/margin/margin_open_orders.rs new file mode 100644 index 0000000..4468171 --- /dev/null +++ b/examples/margin/margin_open_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_open_orders().symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_order.rs b/examples/margin/margin_order.rs new file mode 100644 index 0000000..ab2d294 --- /dev/null +++ b/examples/margin/margin_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_order("BNBUSDT").order_id(213205622); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_order_count_usage.rs b/examples/margin/margin_order_count_usage.rs new file mode 100644 index 0000000..741a886 --- /dev/null +++ b/examples/margin/margin_order_count_usage.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_order_count_usage(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_pair.rs b/examples/margin/margin_pair.rs new file mode 100644 index 0000000..11fa953 --- /dev/null +++ b/examples/margin/margin_pair.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_pair("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_price_index.rs b/examples/margin/margin_price_index.rs new file mode 100644 index 0000000..6ac7600 --- /dev/null +++ b/examples/margin/margin_price_index.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_price_index("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_repay.rs b/examples/margin/margin_repay.rs new file mode 100644 index 0000000..859fc9d --- /dev/null +++ b/examples/margin/margin_repay.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_repay("BTC", dec!(1.01)).symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_repay_record.rs b/examples/margin/margin_repay_record.rs new file mode 100644 index 0000000..b90adf1 --- /dev/null +++ b/examples/margin/margin_repay_record.rs @@ -0,0 +1,22 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_repay_record("BTC") + .tx_id(2970933056) + .current(1) + .size(100); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_transfer.rs b/examples/margin/margin_transfer.rs new file mode 100644 index 0000000..4187a52 --- /dev/null +++ b/examples/margin/margin_transfer.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_transfer("BTC", dec!(1.01), 1); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/margin_transfer_history.rs b/examples/margin/margin_transfer_history.rs new file mode 100644 index 0000000..7f6787a --- /dev/null +++ b/examples/margin/margin_transfer_history.rs @@ -0,0 +1,22 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::margin_transfer_history() + .asset("BNB") + .current(1) + .size(100); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin/toggle_bnb_burn.rs b/examples/margin/toggle_bnb_burn.rs new file mode 100644 index 0000000..26e2a32 --- /dev/null +++ b/examples/margin/toggle_bnb_burn.rs @@ -0,0 +1,21 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin::toggle_bnb_burn() + .spot_bnb_burn(true) + .interest_bnb_burn(false); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin_stream/close_listen_key.rs b/examples/margin_stream/close_listen_key.rs new file mode 100644 index 0000000..9c4e8b1 --- /dev/null +++ b/examples/margin_stream/close_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin_stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin_stream::close_listen_key("listen-key"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin_stream/new_listen_key.rs b/examples/margin_stream/new_listen_key.rs new file mode 100644 index 0000000..462bbd2 --- /dev/null +++ b/examples/margin_stream/new_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin_stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin_stream::new_listen_key(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/margin_stream/renew_listen_key.rs b/examples/margin_stream/renew_listen_key.rs new file mode 100644 index 0000000..7aaf58e --- /dev/null +++ b/examples/margin_stream/renew_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + margin_stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = margin_stream::renew_listen_key("listen-key"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/agg_trades.rs b/examples/market/agg_trades.rs new file mode 100644 index 0000000..847c4b3 --- /dev/null +++ b/examples/market/agg_trades.rs @@ -0,0 +1,22 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::agg_trades("BNBUSDT") + .from_id(123) + .start_time(1640995200000) + .end_time(1640995200000) + .limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/avg_price.rs b/examples/market/avg_price.rs new file mode 100644 index 0000000..8f74b71 --- /dev/null +++ b/examples/market/avg_price.rs @@ -0,0 +1,18 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::avg_price("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/book_ticker.rs b/examples/market/book_ticker.rs new file mode 100644 index 0000000..5263f0d --- /dev/null +++ b/examples/market/book_ticker.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::book_ticker() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/depth.rs b/examples/market/depth.rs new file mode 100644 index 0000000..7524566 --- /dev/null +++ b/examples/market/depth.rs @@ -0,0 +1,18 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::depth("BNBUSDT").limit(100); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/exchange_info.rs b/examples/market/exchange_info.rs new file mode 100644 index 0000000..0b3424f --- /dev/null +++ b/examples/market/exchange_info.rs @@ -0,0 +1,22 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let testnet_http_client = BinanceHttpClient::with_url("https://testnet.binance.vision"); + let request = market::exchange_info().symbol("BNBUSDT"); + let data = testnet_http_client + .send(request) + .await? + .into_body_str() + .await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/historical_trades.rs b/examples/market/historical_trades.rs new file mode 100644 index 0000000..23763d9 --- /dev/null +++ b/examples/market/historical_trades.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = market::historical_trades("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/klines.rs b/examples/market/klines.rs new file mode 100644 index 0000000..acf68e9 --- /dev/null +++ b/examples/market/klines.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market::{self, klines::KlineInterval}, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::klines("BNBUSDT", KlineInterval::Hours1) + .start_time(1654079109000) + .end_time(1654079209000); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/ping.rs b/examples/market/ping.rs new file mode 100644 index 0000000..7082ece --- /dev/null +++ b/examples/market/ping.rs @@ -0,0 +1,18 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::ping(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/rolling_window_price_change_statistics.rs b/examples/market/rolling_window_price_change_statistics.rs new file mode 100644 index 0000000..f928239 --- /dev/null +++ b/examples/market/rolling_window_price_change_statistics.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::rolling_window_price_change_statistics() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/ticker_price.rs b/examples/market/ticker_price.rs new file mode 100644 index 0000000..35e25e4 --- /dev/null +++ b/examples/market/ticker_price.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::ticker_price() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/ticker_twenty_four_hr.rs b/examples/market/ticker_twenty_four_hr.rs new file mode 100644 index 0000000..de32e17 --- /dev/null +++ b/examples/market/ticker_twenty_four_hr.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::ticker_twenty_four_hr() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/time.rs b/examples/market/time.rs new file mode 100644 index 0000000..c9fbe4d --- /dev/null +++ b/examples/market/time.rs @@ -0,0 +1,18 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::time(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/market/trades.rs b/examples/market/trades.rs new file mode 100644 index 0000000..0a20aca --- /dev/null +++ b/examples/market/trades.rs @@ -0,0 +1,18 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + market, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = market::trades("BNBUSDT").limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/stream/close_listen_key.rs b/examples/stream/close_listen_key.rs new file mode 100644 index 0000000..7a36189 --- /dev/null +++ b/examples/stream/close_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = stream::close_listen_key("listen-key"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/stream/new_listen_key.rs b/examples/stream/new_listen_key.rs new file mode 100644 index 0000000..d21fe7d --- /dev/null +++ b/examples/stream/new_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = stream::new_listen_key(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/stream/renew_listen_key.rs b/examples/stream/renew_listen_key.rs new file mode 100644 index 0000000..799a9fa --- /dev/null +++ b/examples/stream/renew_listen_key.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + stream, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = stream::renew_listen_key("listen-key"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/tokio_tungstenite.rs b/examples/tokio_tungstenite.rs new file mode 100644 index 0000000..c6fe8fc --- /dev/null +++ b/examples/tokio_tungstenite.rs @@ -0,0 +1,37 @@ +use binance_spot_connector_rust::{ + market::klines::KlineInterval, market_stream::kline::KlineStream, + tokio_tungstenite::BinanceWebSocketClient, +}; +use env_logger::Builder; +use futures_util::StreamExt; + +const BINANCE_WSS_BASE_URL: &str = "wss://stream.binance.com:9443/ws"; + +#[tokio::main] +async fn main() { + Builder::from_default_env() + .filter(None, log::LevelFilter::Debug) + .init(); + // Establish connection + let (mut conn, _) = BinanceWebSocketClient::connect_async(BINANCE_WSS_BASE_URL) + .await + .expect("Failed to connect"); + // Subscribe to streams + conn.subscribe(vec![ + &KlineStream::new("BTCUSDT", KlineInterval::Minutes1).into() + ]) + .await; + // Read messages + while let Some(message) = conn.as_mut().next().await { + match message { + Ok(message) => { + let data = message.into_data(); + let string_data = String::from_utf8(data).expect("Found invalid UTF-8 chars"); + log::info!("{}", &string_data); + } + Err(_) => break, + } + } + // Disconnect + conn.close().await.expect("Failed to disconnect"); +} diff --git a/examples/trade/account.rs b/examples/trade/account.rs new file mode 100644 index 0000000..d87d930 --- /dev/null +++ b/examples/trade/account.rs @@ -0,0 +1,48 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; +use std::fs; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Debug) + .init(); + + // Hmac signature + let the_api_key = ""; + let the_api_secret = ""; + let credentials = Credentials::from_hmac(the_api_key, the_api_secret); + let client = + BinanceHttpClient::with_url("https://testnet.binance.vision").credentials(credentials); + let request = trade::account(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + + // // Signature by RSA key + let private_key_file = "/Users/john/ssl/Private_key.pem"; + let the_api_key = ""; + let private_key = fs::read_to_string(private_key_file).expect("Failed to read the private key"); + let credentials = Credentials::from_rsa(the_api_key, private_key); + let client = + BinanceHttpClient::with_url("https://testnet.binance.vision").credentials(credentials); + let request = trade::account(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + + // Signature by encrypted RSA key + let private_key_file = "/Users/john/ssl/private_key_encrypted.pem"; + let the_api_key = "the_api_key"; + let rsa_key_password = "password"; + let private_key = fs::read_to_string(private_key_file).expect("Failed to read the private key"); + let credentials = Credentials::from_rsa_protected(the_api_key, private_key, rsa_key_password); + let client = + BinanceHttpClient::with_url("https://testnet.binance.vision").credentials(credentials); + let request = trade::account(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/all_orders.rs b/examples/trade/all_orders.rs new file mode 100644 index 0000000..dce9be4 --- /dev/null +++ b/examples/trade/all_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::all_orders("BNBUSDT").limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/cancel_an_existing_order_and_send_a_new_order.rs b/examples/trade/cancel_an_existing_order_and_send_a_new_order.rs new file mode 100644 index 0000000..01c16f9 --- /dev/null +++ b/examples/trade/cancel_an_existing_order_and_send_a_new_order.rs @@ -0,0 +1,33 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade::{ + self, + order::{CancelReplaceMode, Side, TimeInForce}, + }, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::cancel_an_existing_order_and_send_a_new_order( + "BNBUSDT", + Side::Sell, + "LIMIT", + CancelReplaceMode::StopOnFailure, + ) + .time_in_force(TimeInForce::Gtc) + .quantity(dec!(10.1)) + .price(dec!(295.92)) + .cancel_order_id(12) + .stop_price(dec!(20.01)); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/cancel_oco_order.rs b/examples/trade/cancel_oco_order.rs new file mode 100644 index 0000000..0777584 --- /dev/null +++ b/examples/trade/cancel_oco_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::cancel_oco_order("BNBUSDT").order_list_id(12); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/cancel_open_orders.rs b/examples/trade/cancel_open_orders.rs new file mode 100644 index 0000000..9daa0d7 --- /dev/null +++ b/examples/trade/cancel_open_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::cancel_open_orders("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/cancel_order.rs b/examples/trade/cancel_order.rs new file mode 100644 index 0000000..8c584ef --- /dev/null +++ b/examples/trade/cancel_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::cancel_order("BNBUSDT").order_id(12); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/get_oco_order.rs b/examples/trade/get_oco_order.rs new file mode 100644 index 0000000..eca1793 --- /dev/null +++ b/examples/trade/get_oco_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::get_oco_order().order_list_id(11); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/get_oco_orders.rs b/examples/trade/get_oco_orders.rs new file mode 100644 index 0000000..d9a704d --- /dev/null +++ b/examples/trade/get_oco_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::get_oco_orders().from_id(11).limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/get_open_oco_orders.rs b/examples/trade/get_open_oco_orders.rs new file mode 100644 index 0000000..e1c6323 --- /dev/null +++ b/examples/trade/get_open_oco_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::get_open_oco_orders(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/get_order.rs b/examples/trade/get_order.rs new file mode 100644 index 0000000..5ee411e --- /dev/null +++ b/examples/trade/get_order.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Debug) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::get_order("BNBUSDT").order_id(11); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/my_trades.rs b/examples/trade/my_trades.rs new file mode 100644 index 0000000..0120004 --- /dev/null +++ b/examples/trade/my_trades.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::my_trades("BNBUSDT").limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/new_oco_order.rs b/examples/trade/new_oco_order.rs new file mode 100644 index 0000000..603253a --- /dev/null +++ b/examples/trade/new_oco_order.rs @@ -0,0 +1,25 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade::{ + self, + order::{Side, TimeInForce}, + }, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::new_oco_order("BNBUSDT", Side::Sell, dec!(0.1), dec!(400.15), dec!(390.3)) + .stop_limit_price(dec!(380.3)) + .stop_limit_time_in_force(TimeInForce::Gtc); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/new_order.rs b/examples/trade/new_order.rs new file mode 100644 index 0000000..974a6fa --- /dev/null +++ b/examples/trade/new_order.rs @@ -0,0 +1,21 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade::{self, order::Side}, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Debug) + .init(); + let credentials = Credentials::from_hmac("the_api_key".to_owned(), "the_api_secret".to_owned()); + let client = + BinanceHttpClient::with_url("https://testnet.binance.vision").credentials(credentials); + let request = trade::new_order("BNBUSDT", Side::Sell, "MARKET").quantity(dec!(0.1)); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/new_order_test.rs b/examples/trade/new_order_test.rs new file mode 100644 index 0000000..2b3ed36 --- /dev/null +++ b/examples/trade/new_order_test.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade::{self, order::Side}, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::new_order_test("BNBUSDT", Side::Sell, "MARKET").stop_price(dec!(20.01)); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/open_orders.rs b/examples/trade/open_orders.rs new file mode 100644 index 0000000..2e5c554 --- /dev/null +++ b/examples/trade/open_orders.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::open_orders().symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/trade/order_limit_usage.rs b/examples/trade/order_limit_usage.rs new file mode 100644 index 0000000..721b907 --- /dev/null +++ b/examples/trade/order_limit_usage.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + trade, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = trade::order_limit_usage(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/tungstenite.rs b/examples/tungstenite.rs new file mode 100644 index 0000000..80f3251 --- /dev/null +++ b/examples/tungstenite.rs @@ -0,0 +1,29 @@ +use binance_spot_connector_rust::{ + market::klines::KlineInterval, market_stream::kline::KlineStream, + tungstenite::BinanceWebSocketClient, +}; +use env_logger::Builder; + +const BINANCE_WSS_BASE_URL: &str = "wss://stream.binance.com:9443/ws"; + +fn main() { + Builder::from_default_env() + .filter(None, log::LevelFilter::Debug) + .init(); + // Establish connection + let mut conn = + BinanceWebSocketClient::connect_with_url(BINANCE_WSS_BASE_URL).expect("Failed to connect"); + // Subscribe to streams + conn.subscribe(vec![ + &KlineStream::new("BTCUSDT", KlineInterval::Minutes1).into(), + &KlineStream::new("BNBBUSD", KlineInterval::Minutes3).into(), + ]); + // Read messages + while let Ok(message) = conn.as_mut().read_message() { + let data = message.into_data(); + let string_data = String::from_utf8(data).expect("Found invalid UTF-8 chars"); + log::info!("{}", &string_data); + } + // Disconnect + conn.close().expect("Failed to disconnect"); +} diff --git a/examples/ureq.rs b/examples/ureq.rs new file mode 100644 index 0000000..879ec19 --- /dev/null +++ b/examples/ureq.rs @@ -0,0 +1,23 @@ +use binance_spot_connector_rust::{ + http::{request::RequestBuilder, Credentials, Method}, + ureq::{BinanceHttpClient, Error}, +}; +use env_logger::Builder; + +fn main() -> Result<(), Box> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = RequestBuilder::new(Method::Post, "/api/v3/order").params(vec![ + ("symbol", "BNBUSDT"), + ("side", "SELL"), + ("type", "LIMIT"), + ("quantity", "0.1"), + ("price", "320.2"), + ]); + let data = client.send(request)?.into_body_str()?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/account_snapshot.rs b/examples/wallet/account_snapshot.rs new file mode 100644 index 0000000..486d15d --- /dev/null +++ b/examples/wallet/account_snapshot.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::account_snapshot("SPOT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/account_status.rs b/examples/wallet/account_status.rs new file mode 100644 index 0000000..5cd724f --- /dev/null +++ b/examples/wallet/account_status.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::account_status(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/api_key_permission.rs b/examples/wallet/api_key_permission.rs new file mode 100644 index 0000000..9cb1232 --- /dev/null +++ b/examples/wallet/api_key_permission.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::api_key_permission(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/api_trading_status.rs b/examples/wallet/api_trading_status.rs new file mode 100644 index 0000000..5398210 --- /dev/null +++ b/examples/wallet/api_trading_status.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::api_trading_status(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/asset_detail.rs b/examples/wallet/asset_detail.rs new file mode 100644 index 0000000..0ad066b --- /dev/null +++ b/examples/wallet/asset_detail.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::asset_detail().asset("BNB"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/asset_dividend_record.rs b/examples/wallet/asset_dividend_record.rs new file mode 100644 index 0000000..617e826 --- /dev/null +++ b/examples/wallet/asset_dividend_record.rs @@ -0,0 +1,23 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::asset_dividend_record() + .asset("BNB") + .start_time(1640995200000) + .end_time(1640995200000) + .limit(123); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/coin_info.rs b/examples/wallet/coin_info.rs new file mode 100644 index 0000000..7ae04de --- /dev/null +++ b/examples/wallet/coin_info.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::coin_info(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/deposit_address.rs b/examples/wallet/deposit_address.rs new file mode 100644 index 0000000..80d6fcc --- /dev/null +++ b/examples/wallet/deposit_address.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::deposit_address("BNB").network("ETH"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/deposit_history.rs b/examples/wallet/deposit_history.rs new file mode 100644 index 0000000..6685dca --- /dev/null +++ b/examples/wallet/deposit_history.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::deposit_history().coin("BNB").limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/disable_fast_withdraw.rs b/examples/wallet/disable_fast_withdraw.rs new file mode 100644 index 0000000..0de54aa --- /dev/null +++ b/examples/wallet/disable_fast_withdraw.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::disable_fast_withdraw(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/dust_log.rs b/examples/wallet/dust_log.rs new file mode 100644 index 0000000..368dbfd --- /dev/null +++ b/examples/wallet/dust_log.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::dust_log(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/dust_transfer.rs b/examples/wallet/dust_transfer.rs new file mode 100644 index 0000000..9c7bc25 --- /dev/null +++ b/examples/wallet/dust_transfer.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::dust_transfer(vec!["BTC", "USDT"]); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/dustable_assets.rs b/examples/wallet/dustable_assets.rs new file mode 100644 index 0000000..84f6cb2 --- /dev/null +++ b/examples/wallet/dustable_assets.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::dustable_assets(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/enable_fast_withdraw.rs b/examples/wallet/enable_fast_withdraw.rs new file mode 100644 index 0000000..e6ce240 --- /dev/null +++ b/examples/wallet/enable_fast_withdraw.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::enable_fast_withdraw(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/funding_wallet.rs b/examples/wallet/funding_wallet.rs new file mode 100644 index 0000000..f60dc4f --- /dev/null +++ b/examples/wallet/funding_wallet.rs @@ -0,0 +1,21 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::funding_wallet() + .asset("BNB") + .need_btc_valuation(true); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/system_status.rs b/examples/wallet/system_status.rs new file mode 100644 index 0000000..cb352cc --- /dev/null +++ b/examples/wallet/system_status.rs @@ -0,0 +1,18 @@ +use binance_spot_connector_rust::{ + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + + let client = BinanceHttpClient::default(); + let request = wallet::system_status(); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/trade_fee.rs b/examples/wallet/trade_fee.rs new file mode 100644 index 0000000..767a4de --- /dev/null +++ b/examples/wallet/trade_fee.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::trade_fee().symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/universal_transfer.rs b/examples/wallet/universal_transfer.rs new file mode 100644 index 0000000..2ed9e38 --- /dev/null +++ b/examples/wallet/universal_transfer.rs @@ -0,0 +1,22 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::universal_transfer("MAIN_UMFUTURE", "BTC", dec!(1.01)) + .from_symbol("BNBUSDT") + .to_symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/universal_transfer_history.rs b/examples/wallet/universal_transfer_history.rs new file mode 100644 index 0000000..e2ca9e9 --- /dev/null +++ b/examples/wallet/universal_transfer_history.rs @@ -0,0 +1,25 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::universal_transfer_history("MAIN_UMFUTURE") + .start_time(1640995200000) + .end_time(1640995200000) + .current(1) + .size(100) + .from_symbol("BNBUSDT") + .to_symbol("BNBUSDT"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/user_asset.rs b/examples/wallet/user_asset.rs new file mode 100644 index 0000000..06cd83f --- /dev/null +++ b/examples/wallet/user_asset.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::user_asset().asset("BNB"); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/withdraw.rs b/examples/wallet/withdraw.rs new file mode 100644 index 0000000..e9692a2 --- /dev/null +++ b/examples/wallet/withdraw.rs @@ -0,0 +1,20 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; +use rust_decimal_macros::dec; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::withdraw("BNB", "address", dec!(1.01)); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/examples/wallet/withdraw_history.rs b/examples/wallet/withdraw_history.rs new file mode 100644 index 0000000..590d8cb --- /dev/null +++ b/examples/wallet/withdraw_history.rs @@ -0,0 +1,19 @@ +use binance_spot_connector_rust::{ + http::Credentials, + hyper::{BinanceHttpClient, Error}, + wallet, +}; +use env_logger::Builder; + +#[tokio::main] +async fn main() -> Result<(), Error> { + Builder::from_default_env() + .filter(None, log::LevelFilter::Info) + .init(); + let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); + let client = BinanceHttpClient::default().credentials(credentials); + let request = wallet::withdraw_history().coin("BNB").limit(500); + let data = client.send(request).await?.into_body_str().await?; + log::info!("{}", data); + Ok(()) +} diff --git a/src/http/credentials.rs b/src/http/credentials.rs new file mode 100644 index 0000000..ce0d4e3 --- /dev/null +++ b/src/http/credentials.rs @@ -0,0 +1,73 @@ +/// Binance API Credentials. +/// +/// Communication with Binance API USER_DATA endpoints requires +/// valid API credentials. +/// +/// Note: Production and TESTNET API Credentials are not +/// interchangeable. +/// +/// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#api-key-restrictions) +/// +#[derive(PartialEq, Eq, Clone)] +pub struct Credentials { + pub api_key: String, + pub signature: Signature, +} + +#[derive(PartialEq, Eq, Clone)] +pub enum Signature { + Hmac(HmacSignature), + Rsa(RsaSignature), +} + +#[derive(PartialEq, Eq, Clone)] +pub struct HmacSignature { + pub api_secret: String, +} + +#[derive(PartialEq, Eq, Clone)] +pub struct RsaSignature { + pub key: String, + pub password: Option, +} + +impl Credentials { + pub fn from_rsa(api_key: impl Into, key: impl Into) -> Self { + Credentials { + api_key: api_key.into(), + signature: Signature::Rsa(RsaSignature { + key: key.into(), + password: None, + }), + } + } + pub fn from_rsa_protected( + api_key: impl Into, + key: impl Into, + password: impl Into, + ) -> Self { + Credentials { + api_key: api_key.into(), + signature: Signature::Rsa(RsaSignature { + key: key.into(), + password: Some(password.into()), + }), + } + } + pub fn from_hmac(api_key: impl Into, api_secret: impl Into) -> Self { + Credentials { + api_key: api_key.into(), + signature: Signature::Hmac(HmacSignature { + api_secret: api_secret.into(), + }), + } + } +} + +impl std::fmt::Debug for Credentials { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Credentials") + .field("api_key", &"[redacted]") + .finish() + } +} diff --git a/src/http/error.rs b/src/http/error.rs new file mode 100644 index 0000000..e729158 --- /dev/null +++ b/src/http/error.rs @@ -0,0 +1,46 @@ +use serde::Deserialize; +use std::collections::HashMap; + +/// Unsuccesful response from the Binance API. +#[derive(Debug)] +pub enum ClientError { + /// API server error complying with the error schema. + Structured(HttpError), + /// API server error not complying with the error schema. + Raw(HttpError), +} + +/// Generic Http Error +#[derive(Debug)] +pub struct HttpError { + /// Http status code + pub status_code: u16, + /// Response body content + pub data: T, + /// Response headers + pub headers: HashMap, +} + +impl HttpError { + pub fn new(status_code: u16, data: T, headers: HashMap) -> Self { + Self { + status_code, + data, + headers, + } + } +} + +/// Structured Binance server error +#[derive(Deserialize, Debug)] +pub struct BinanceApiError { + /// Error code + /// + /// [API Documentation](https://binance-docs.github.io/apidocs/spot/en/#error-codes) + #[serde(rename(deserialize = "code"))] + pub code: i16, + + ///Error description + #[serde(rename(deserialize = "msg"))] + pub message: String, +} diff --git a/src/http/method.rs b/src/http/method.rs new file mode 100644 index 0000000..4f4e606 --- /dev/null +++ b/src/http/method.rs @@ -0,0 +1,18 @@ +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum Method { + Get, + Post, + Put, + Delete, +} + +impl AsRef for Method { + fn as_ref(&self) -> &str { + match self { + Method::Post => "POST", + Method::Delete => "DELETE", + Method::Get => "GET", + Method::Put => "PUT", + } + } +} diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..9021881 --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1,11 @@ +mod credentials; +mod method; + +pub mod error; +pub mod request; + +pub use credentials::Credentials; +pub use credentials::HmacSignature; +pub use credentials::RsaSignature; +pub use credentials::Signature; +pub use method::Method; diff --git a/src/http/request.rs b/src/http/request.rs new file mode 100644 index 0000000..dea2be9 --- /dev/null +++ b/src/http/request.rs @@ -0,0 +1,89 @@ +use crate::http::{Credentials, Method}; + +#[derive(PartialEq, Eq, Debug)] +pub struct Request { + pub(crate) method: Method, + pub(crate) path: String, + pub(crate) params: Vec<(String, String)>, + pub(crate) credentials: Option, + pub(crate) sign: bool, +} + +impl Request { + pub fn method(&self) -> &Method { + &self.method + } + pub fn path(&self) -> &str { + &self.path + } + pub fn params(&self) -> &[(String, String)] { + &self.params + } + pub fn credentials(&self) -> &Option { + &self.credentials + } + pub fn sign(&self) -> &bool { + &self.sign + } +} + +/// /// API HTTP Request +/// +/// A low-level request builder for API integration +/// decoupled from any specific underlying HTTP library. +pub struct RequestBuilder { + method: Method, + path: String, + params: Vec<(String, String)>, + credentials: Option, + sign: bool, +} + +impl RequestBuilder { + pub fn new(method: Method, path: &str) -> Self { + Self { + method, + path: path.to_owned(), + params: vec![], + credentials: None, + sign: false, + } + } + + /// Append `params` to the request's query string. Parameters may + /// share the same key, and will result in a query string with one or + /// more duplicated query parameter keys. + pub fn params<'a>(mut self, params: impl IntoIterator) -> Self { + self.params.extend( + params + .into_iter() + .map(|param| (param.0.to_owned(), param.1.to_owned())), + ); + + self + } + + pub fn credentials(mut self, credentials: Credentials) -> Self { + self.credentials = Some(credentials); + + self + } + + pub fn sign(mut self) -> Self { + self.sign = true; + + self + } +} + +impl From for Request { + fn from(builder: RequestBuilder) -> Request { + Request { + method: builder.method, + path: builder.path, + params: builder.params, + credentials: builder.credentials, + sign: builder.sign, + } + } +} diff --git a/src/hyper/client.rs b/src/hyper/client.rs new file mode 100644 index 0000000..3a0725a --- /dev/null +++ b/src/hyper/client.rs @@ -0,0 +1,598 @@ +use crate::http::{request::Request, Credentials, Method}; +use crate::hyper::{Error, Response}; +use hyper::{client::connect::Connect, client::HttpConnector, Body, Client, Uri}; +use hyper_tls::HttpsConnector; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Clone)] +pub struct BinanceHttpClient +where + T: Connect + Clone + Send + Sync + 'static, +{ + client: Client, + base_url: String, + timestamp_delta: i64, + credentials: Option, +} + +impl BinanceHttpClient +where + T: Connect + Clone + Send + Sync + 'static, +{ + pub fn new(client: Client, base_url: &str) -> Self { + Self { + client, + base_url: base_url.to_owned(), + timestamp_delta: 0, + credentials: None, + } + } + + pub fn credentials(mut self, credentials: Credentials) -> Self { + self.credentials = Some(credentials); + self + } + + pub fn timestamp_delta(mut self, timestamp_delta: i64) -> Self { + self.timestamp_delta = timestamp_delta; + self + } +} + +impl BinanceHttpClient> { + pub fn with_url(base_url: &str) -> BinanceHttpClient> { + BinanceHttpClient { + client: Client::builder().build::<_, hyper::Body>(HttpsConnector::new()), + base_url: base_url.to_owned(), + timestamp_delta: 0, + credentials: None, + } + } +} + +impl BinanceHttpClient +where + T: Connect + Clone + Send + Sync + 'static, +{ + pub async fn send>(&self, request: R) -> Result { + let Request { + method, + path, + params, + credentials, + sign, + } = request.into(); + let mut url_parts = vec![self.base_url.to_owned(), path]; + let has_params = !params.is_empty(); + let mut serializer = url::form_urlencoded::Serializer::new(String::new()); + if has_params { + for (k, v) in params.iter() { + serializer.append_pair(k, v); + } + } + let mut query_string = serializer.finish(); + let mut hyper_request = hyper::Request::builder().method(method); + let client_credentials = self.credentials.as_ref(); + let request_credentials = credentials.as_ref(); + if let Some(Credentials { api_key, signature }) = request_credentials.or(client_credentials) + { + hyper_request = hyper_request.header("X-MBX-APIKEY", api_key); + if sign { + let mut timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Clock may have gone backwards") + .as_millis(); + timestamp -= self.timestamp_delta as u128; + + if has_params { + query_string.push_str(format!("×tamp={}", timestamp).as_str()); + } else { + query_string.push_str(format!("timestamp={}", timestamp).as_str()); + } + + let signature = crate::utils::sign(&query_string, signature) + .map_err(|_| Error::InvalidApiSecret)?; + let encoded_signature: String = + url::form_urlencoded::byte_serialize(signature.as_bytes()).collect(); + query_string.push_str(format!("&signature={}", encoded_signature).as_str()); + } + } + + if !query_string.is_empty() { + url_parts.push(String::from("?")); + url_parts.push(query_string); + } + let uri: Uri = url_parts.join("").parse()?; + log::debug!("{}", uri); + let hyper_request = hyper_request.uri(uri); + let request = hyper_request + .body(Body::empty()) + .map_err(|err| Error::Parse(err))?; + let response = self + .client + .request(request) + .await + .map_err(|err| Error::Send(err))?; + log::debug!("{}", response.status()); + + Ok(Response::from(response)) + } +} + +impl Default for BinanceHttpClient> { + fn default() -> Self { + Self::new( + Client::builder().build::<_, hyper::Body>(HttpsConnector::new()), + "https://api.binance.com", + ) + } +} + +impl From for hyper::Method { + fn from(method: Method) -> hyper::Method { + match method { + Method::Post => hyper::Method::POST, + Method::Delete => hyper::Method::DELETE, + Method::Get => hyper::Method::GET, + Method::Put => hyper::Method::PUT, + } + } +} + +#[cfg(test)] +mod tests { + use super::BinanceHttpClient; + use crate::http::{error::ClientError, request::Request, Credentials, Method}; + use crate::hyper::Error; + use hyper::client::connect::Connected; + use hyper::{Client, Uri}; + use std::collections::HashMap; + use std::future::Future; + use std::pin::Pin; + use std::str; + use std::task::{Context, Poll, Waker}; + use tokio::io::Error as IoError; + use tokio::io::ReadBuf; + + #[tokio::test] + async fn client_respects_request_basic_configuration_test() { + let client = Client::builder().build( + MockConnector::new() + .method("GET") + .path("/path") + .base_url("https://base-url.com") + .param("testparam", Some("testparamvalue")) + .response(200, "Test Response"), + ); + let client = BinanceHttpClient::new(client, "https://base-url.com"); + + let request = Request { + method: Method::Get, + path: "/path".to_owned(), + params: vec![("testparam".to_owned(), "testparamvalue".to_owned())], + credentials: None, + sign: false, + }; + + let data = client + .send(request) + .await + .unwrap() + .into_body_str() + .await + .unwrap(); + + assert_eq!(data, "Test Response".to_owned()); + } + + #[tokio::test] + async fn client_respects_request_credentials_no_signature_test() { + let client = Client::builder().build( + MockConnector::new() + .header("X-MBX-APIKEY", Some("api-key")) + .response(200, "Test Response"), + ); + let client = BinanceHttpClient::new(client, "https://base-url.com"); + + let request = Request { + method: Method::Get, + path: "/path".to_owned(), + params: vec![], + credentials: Some(Credentials::from_hmac( + "api-key".to_string(), + "api-secret".to_string(), + )), + sign: false, + }; + + let data = client + .send(request) + .await + .unwrap() + .into_body_str() + .await + .unwrap(); + + assert_eq!(data, "Test Response".to_owned()); + } + + #[tokio::test] + async fn client_respects_request_credentials_with_signature_test() { + let client = Client::builder().build( + MockConnector::new() + .header("X-MBX-APIKEY", Some("api-key")) + .param("timestamp", None) + .param("signature", None) + .response(200, "Test Response"), + ); + let client = BinanceHttpClient::new(client, "https://base-url.com"); + + let request = Request { + method: Method::Get, + path: "/path".to_owned(), + params: vec![], + credentials: Some(Credentials::from_hmac( + "api-key".to_string(), + "api-secret".to_string(), + )), + sign: true, + }; + + let data = client + .send(request) + .await + .unwrap() + .into_body_str() + .await + .unwrap(); + + assert_eq!(data, "Test Response".to_owned()); + } + + #[tokio::test] + async fn client_handles_not_found_error_test() { + let client = Client::builder().build(MockConnector::new().response(404, "")); + let client = BinanceHttpClient::new(client, "https://base-url.com"); + + let request = Request { + method: Method::Get, + path: "/path".to_owned(), + params: vec![], + credentials: None, + sign: false, + }; + + let err = client + .send(request) + .await + .unwrap() + .into_body_str() + .await + .unwrap_err(); + + match err { + Error::Client(ClientError::Raw(err)) => assert_eq!(err.status_code, 404), + _ => panic!("Invalid error"), + } + } + + #[tokio::test] + async fn client_handles_structured_bad_request_test() { + let client = Client::builder().build( + MockConnector::new() + .response(400, "{ \"code\": -1102, \"msg\": \"Mandatory parameter 'symbol' was not sent, was empty/null, or malformed.\" }"), + ); + let client = BinanceHttpClient::new(client, "https://base-url.com"); + + let request = Request { + method: Method::Get, + path: "/path".to_owned(), + params: vec![], + credentials: None, + sign: false, + }; + + let err = client + .send(request) + .await + .unwrap() + .into_body_str() + .await + .unwrap_err(); + + match err { + Error::Client(ClientError::Structured(err)) => assert_eq!(err.status_code, 400), + _ => panic!("Invalid error"), + } + } + + #[tokio::test] + async fn client_handles_raw_bad_request_test() { + let client = Client::builder().build(MockConnector::new().response(400, "Error")); + let client = BinanceHttpClient::new(client, "https://base-url.com"); + + let request = Request { + method: Method::Get, + path: "/path".to_owned(), + params: vec![], + credentials: None, + sign: false, + }; + + let err = client + .send(request) + .await + .unwrap() + .into_body_str() + .await + .unwrap_err(); + + match err { + Error::Client(ClientError::Raw(err)) => assert_eq!(err.status_code, 400), + _ => panic!("Invalid error"), + } + } + + #[tokio::test] + async fn client_handles_server_error_test() { + let client = Client::builder().build(MockConnector::new().response(500, "Error")); + let client = BinanceHttpClient::new(client, "https://base-url.com"); + + let request = Request { + method: Method::Get, + path: "/path".to_owned(), + params: vec![], + credentials: None, + sign: false, + }; + + let err = client + .send(request) + .await + .unwrap() + .into_body_str() + .await + .unwrap_err(); + + match err { + Error::Server(err) => assert_eq!(err.status_code, 500), + _ => panic!("Invalid error"), + } + } + + #[derive(Clone)] + struct MockConnector { + base_url: Option, + path: Option, + method: Option, + params: Vec<(String, Option)>, + headers: HashMap>, + response_code: Option, + response_body: Option, + } + + impl MockConnector { + pub fn new() -> Self { + Self { + base_url: None, + path: None, + method: None, + params: vec![], + headers: HashMap::new(), + response_code: None, + response_body: None, + } + } + + pub fn method(mut self, method: &str) -> Self { + self.method = Some(method.to_string()); + self + } + + pub fn param(mut self, name: &str, value: Option<&str>) -> Self { + self.params + .push((name.to_string(), value.map(|v| v.to_owned()))); + self + } + + pub fn base_url(mut self, base_url: &str) -> Self { + self.base_url = Some(base_url.to_string()); + self + } + + pub fn path(mut self, path: &str) -> Self { + self.path = Some(path.to_string()); + self + } + + pub fn header(mut self, name: &str, value: Option<&str>) -> Self { + self.headers + .insert(name.to_string(), value.map(|v| v.to_owned())); + self + } + + pub fn response(mut self, code: u16, body: &str) -> Self { + self.response_code = Some(code); + self.response_body = Some(body.to_owned()); + self + } + } + + struct MockResponse { + response_code: u16, + response_body: String, + path: Option, + params: Vec<(String, Option)>, + method: Option, + headers: HashMap>, + ready: bool, + pos: usize, + waker: Option, + is_match: bool, + } + impl MockResponse { + pub fn new( + is_match: bool, + path: Option, + params: Vec<(String, Option)>, + method: Option, + headers: HashMap>, + code: u16, + body: &str, + ) -> Self { + Self { + response_code: code, + response_body: body.to_owned(), + path, + params, + method, + headers, + ready: false, + waker: None, + pos: 0, + is_match, + } + } + + fn has_header(msg: &str, name: &str, value: &Option) -> bool { + let n = msg.find("\r\n\r\n").unwrap_or(msg.len()); + + let expected_header = format!( + "{}: {}", + name.to_lowercase(), + value.clone().unwrap_or_default() + ); + msg[..n].contains(&expected_header) + } + } + + impl tokio::io::AsyncRead for MockResponse { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if !self.ready { + self.waker = Some(cx.waker().clone()); + Poll::Pending + } else { + let data = if self.is_match { + format!( + "HTTP/1.1 {}\r\n\ + \r\n\ + {}", + self.response_code, self.response_body + ) + } else { + format!( + "HTTP/1.1 {}\r\n\ + \r\n\ + {}", + 404, "" + ) + }; + let data = data.as_bytes(); + + let n = std::cmp::min(buf.remaining(), data.len() - self.pos); + let read_until = self.pos + n; + buf.put_slice(&data[self.pos..read_until]); + self.pos = read_until; + self.waker = Some(cx.waker().clone()); + Poll::Ready(Ok(())) + } + } + } + + impl tokio::io::AsyncWrite for MockResponse { + fn poll_write( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let msg = str::from_utf8(buf).unwrap(); + let first_line_end = msg.find("\r\n").unwrap_or(msg.len()); + let mut first_line = msg[..first_line_end].split(" "); + let method = first_line.next().unwrap(); + let path_and_query = first_line.next().unwrap(); + + self.is_match = self.is_match + // Assert request path. + && self + .path + .as_ref() + .map(|p| path_and_query.starts_with(p)) + .unwrap_or(true) + // Assert request query parameters, if they exist. + && self.params.iter().all(|(k, v)| { + let expected_query_param = format!("{}{}", k, v.as_ref().map(|v| format!("={}", v)).unwrap_or_default()); + path_and_query.contains(&expected_query_param) + }) + && self + .method + .as_ref() + .map(|m| method == m) + .unwrap_or(true) + && self + .headers + .iter() + .all(|(k, v)| Self::has_header(msg, k, v)); + + let Self { ready, waker, .. } = self.get_mut(); + *ready = true; + waker.take().map(|w| w.wake()); + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + } + + impl hyper::client::connect::Connection for MockResponse { + fn connected(&self) -> Connected { + Connected::new() + } + } + + impl tower::Service for MockConnector { + type Response = MockResponse; + type Error = http::Error; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Uri) -> Self::Future { + let is_match = self + .base_url + .as_ref() + .map(|u| u == &format!("{}://{}", req.scheme().unwrap(), req.host().unwrap())) + .unwrap_or(true); + + // Create the HTTP response + let resp = MockResponse::new( + is_match, + self.path.clone(), + self.params.clone(), + self.method.clone(), + self.headers.clone(), + self.response_code.unwrap_or(200), + match &self.response_body { + Some(body) => body.as_str(), + None => "", + }, + ); + + // create a response in a future. + let fut = async { Ok(resp) }; + + // Return the response as an immediate future + Box::pin(fut) + } + } +} diff --git a/src/hyper/error.rs b/src/hyper/error.rs new file mode 100644 index 0000000..0c2fd90 --- /dev/null +++ b/src/hyper/error.rs @@ -0,0 +1,22 @@ +use crate::http::error::{ClientError, HttpError as BinanceHttpError}; +use http::{uri::InvalidUri, Error as HttpError}; +use hyper::Error as HyperError; + +/// Communication error with the server. +#[derive(Debug)] +pub enum Error { + /// 4XX error from the server. + Client(ClientError), + /// 5XX error from the server. + Server(BinanceHttpError), + /// The format of the API secret is invalid. + InvalidApiSecret, + Parse(HttpError), + Send(HyperError), +} + +impl From for Error { + fn from(err: InvalidUri) -> Error { + Error::Parse(err.into()) + } +} diff --git a/src/hyper/mod.rs b/src/hyper/mod.rs new file mode 100644 index 0000000..f95fa72 --- /dev/null +++ b/src/hyper/mod.rs @@ -0,0 +1,38 @@ +//! Binance client using Hyper. +//! +//! # Example +//! +//! ```no_run +//! use binance_spot_connector_rust::{ +//! http::{request::RequestBuilder, Credentials, Method}, +//! hyper::{BinanceHttpClient, Error}, +//! }; +//! use env_logger::Builder; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Error> { +//! Builder::from_default_env() +//! .filter(None, log::LevelFilter::Info) +//! .init(); +//! let credentials = Credentials::from_hmac("api-key".to_owned(), "api-secret".to_owned()); +//! let client = BinanceHttpClient::default().credentials(credentials); +//! let request = RequestBuilder::new(Method::Post, "/api/v3/order").params(vec![ +//! ("symbol", "BNBUSDT"), +//! ("side", "SELL"), +//! ("type", "LIMIT"), +//! ("quantity", "0.1"), +//! ("price", "320.2"), +//! ]); +//! let data = client.send(request).await?.into_body_str().await?; +//! log::info!("{}", data); +//! Ok(()) +//! } +//! ``` + +mod client; +mod error; +mod response; + +pub use client::*; +pub use error::*; +pub use response::*; diff --git a/src/hyper/response.rs b/src/hyper/response.rs new file mode 100644 index 0000000..694cd3f --- /dev/null +++ b/src/hyper/response.rs @@ -0,0 +1,71 @@ +use crate::http::error::{BinanceApiError, ClientError, HttpError}; +use crate::hyper::Error; +use hyper::Body; +use std::collections::HashMap; + +/// REST Response +#[derive(Debug)] +pub struct Response { + inner_response: hyper::Response, +} + +impl Response { + pub async fn into_body_str(self) -> Result { + let status = self.inner_response.status().as_u16(); + if 400 <= status { + let headers: HashMap = + self.inner_response + .headers() + .iter() + .fold(HashMap::new(), |mut headers, (k, v)| { + headers.entry(k.as_str().to_owned()).or_insert_with(|| { + // Assume all Binance response headers can convert to String. + v.to_str() + .expect("Failed to convert response header value to string") + .to_owned() + }); + headers + }); + + let content = hyper_body_to_string(self.inner_response.into_body()).await?; + if 500 <= status { + Err(Error::Server(HttpError::new(status, content, headers))) + } else { + let client_error = match serde_json::from_str::(&content) { + Ok(err) => ClientError::Structured(HttpError::new(status, err, headers)), + Err(_) => ClientError::Raw(HttpError::new(status, content, headers)), + }; + + Err(Error::Client(client_error)) + } + } else { + Ok(hyper_body_to_string(self.inner_response.into_body()).await?) + } + } +} + +impl From> for Response { + fn from(response: hyper::Response) -> Response { + Response { + inner_response: response, + } + } +} + +impl From for hyper::Response { + fn from(response: Response) -> hyper::Response { + response.inner_response + } +} + +async fn hyper_body_to_string(body: Body) -> Result { + // Assume all Binance responses are of a reasonable size. + let body = hyper::body::to_bytes(body) + .await + .expect("Failed to collect response body."); + + // Assume all Binance responses are in UTF-8. + let content = String::from_utf8(body.to_vec()).expect("Response failed UTF-8 encoding."); + + Ok(content) +} diff --git a/src/isolated_margin_stream/close_listen_key.rs b/src/isolated_margin_stream/close_listen_key.rs new file mode 100644 index 0000000..b166048 --- /dev/null +++ b/src/isolated_margin_stream/close_listen_key.rs @@ -0,0 +1,84 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `DELETE /sapi/v1/userDataStream/isolated` +/// +/// Close out a user data stream. +/// +/// Weight: 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::isolated_margin_stream; +/// +/// let request = isolated_margin_stream::close_listen_key("BTCUSDT", "listen-key"); +/// ``` +pub struct CloseListenKey { + symbol: String, + listen_key: String, + credentials: Option, +} + +impl CloseListenKey { + pub fn new(symbol: &str, listen_key: &str) -> Self { + Self { + symbol: symbol.to_owned(), + listen_key: listen_key.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: CloseListenKey) -> Request { + let params = vec![ + ("symbol".to_owned(), request.symbol.to_string()), + ("listenKey".to_owned(), request.listen_key.to_string()), + ]; + + Request { + path: "/sapi/v1/userDataStream/isolated".to_owned(), + method: Method::Delete, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::CloseListenKey; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn isolated_margin_stream_close_listen_key_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = CloseListenKey::new("BTCUSDT", "listen-key") + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/userDataStream/isolated".to_owned(), + credentials: Some(credentials), + method: Method::Delete, + params: vec![ + ("symbol".to_owned(), "BTCUSDT".to_string()), + ("listenKey".to_owned(), "listen-key".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/isolated_margin_stream/mod.rs b/src/isolated_margin_stream/mod.rs new file mode 100644 index 0000000..5588ca4 --- /dev/null +++ b/src/isolated_margin_stream/mod.rs @@ -0,0 +1,21 @@ +//! Market Data + +pub mod close_listen_key; +pub mod new_listen_key; +pub mod renew_listen_key; + +use close_listen_key::CloseListenKey; +use new_listen_key::NewListenKey; +use renew_listen_key::RenewListenKey; + +pub fn new_listen_key(symbol: &str) -> NewListenKey { + NewListenKey::new(symbol) +} + +pub fn renew_listen_key(symbol: &str, listen_key: &str) -> RenewListenKey { + RenewListenKey::new(symbol, listen_key) +} + +pub fn close_listen_key(symbol: &str, listen_key: &str) -> CloseListenKey { + CloseListenKey::new(symbol, listen_key) +} diff --git a/src/isolated_margin_stream/new_listen_key.rs b/src/isolated_margin_stream/new_listen_key.rs new file mode 100644 index 0000000..b677377 --- /dev/null +++ b/src/isolated_margin_stream/new_listen_key.rs @@ -0,0 +1,77 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `POST /sapi/v1/userDataStream/isolated` +/// +/// Start a new user data stream. +/// The stream will close after 60 minutes unless a keepalive is sent. If the account has an active `listenKey`, that `listenKey` will be returned and its validity will be extended for 60 minutes. +/// +/// Weight: 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::isolated_margin_stream; +/// +/// let request = isolated_margin_stream::new_listen_key("BTCUSDT"); +/// ``` +pub struct NewListenKey { + symbol: String, + credentials: Option, +} + +impl NewListenKey { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: NewListenKey) -> Request { + let params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + Request { + path: "/sapi/v1/userDataStream/isolated".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::NewListenKey; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn isolated_margin_stream_new_listen_key_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = NewListenKey::new("BTCUSDT") + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/userDataStream/isolated".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![("symbol".to_owned(), "BTCUSDT".to_string()),], + sign: false + } + ); + } +} diff --git a/src/isolated_margin_stream/renew_listen_key.rs b/src/isolated_margin_stream/renew_listen_key.rs new file mode 100644 index 0000000..18d3ceb --- /dev/null +++ b/src/isolated_margin_stream/renew_listen_key.rs @@ -0,0 +1,84 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `PUT /sapi/v1/userDataStream/isolated` +/// +/// Keepalive a user data stream to prevent a time out. User data streams will close after 60 minutes. It's recommended to send a ping about every 30 minutes. +/// +/// Weight: 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::isolated_margin_stream; +/// +/// let request = isolated_margin_stream::renew_listen_key("BTCUSDT", "listen-key"); +/// ``` +pub struct RenewListenKey { + symbol: String, + listen_key: String, + credentials: Option, +} + +impl RenewListenKey { + pub fn new(symbol: &str, listen_key: &str) -> Self { + Self { + symbol: symbol.to_owned(), + listen_key: listen_key.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: RenewListenKey) -> Request { + let params = vec![ + ("symbol".to_owned(), request.symbol.to_string()), + ("listenKey".to_owned(), request.listen_key.to_string()), + ]; + + Request { + path: "/sapi/v1/userDataStream/isolated".to_owned(), + method: Method::Put, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::RenewListenKey; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn isolated_margin_stream_renew_listen_key_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = RenewListenKey::new("BTCUSDT", "listen-key") + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/userDataStream/isolated".to_owned(), + credentials: Some(credentials), + method: Method::Put, + params: vec![ + ("symbol".to_owned(), "BTCUSDT".to_string()), + ("listenKey".to_owned(), "listen-key".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0aae1e1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,106 @@ +//! A lightweight library to integrate with the Binance Spot API. +//! +//! * Spot REST API endpoints (`/api/*` and `/sapi/*`) +//! * Spot Market and User Data web-socket streams. +//! * Spot Testnet and Production environments. +//! * Tests and examples +//! * Blocking and non-blocking http clients +//! * Generic http request framework for custom requests. +//! +//! ## Features +//! +//! By default, **binance-spot** only enables blocking clients to minimize the size of compiled code. +//! To enable other clients, use the named features below when adding the crate as a dependancy. +//! To include all features, you may use the `all` feature. +//! +//! The following optional features are available: +//! +//! * `enable-ureq`: For a blocking http client powered by [`ureq`](https://docs.rs/ureq/2.4.0/ureq/). +//! * `enable-hyper`: For a non-blocking http client powered by [`hyper`](https://docs.rs/hyper/0.14.16/hyper/). +//! * `enable-tungstenite`: For a blocking web-socket client powered by [`tungstenite`](https://docs.rs/tungstenite/0.16.0/tungstenite/). +//! * `enable-tokio-tungstenite`: For a non-blocking web-socket client powered by [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite/0.17.1/tokio_tungstenite/). +//! +//! # Testnet +//! +//! Http clients and web-socket clients can be configured to communicate with the testnet environment by specifying the base url on initialization. +//! +//! ``` +//! use binance_spot_connector_rust::ureq::BinanceHttpClient; +//! +//! let testnet_http_client = BinanceHttpClient::with_url("https://testnet.binance.vision"); +//! ``` +//! +//! ```no_run +//! use binance_spot_connector_rust::tungstenite::BinanceWebSocketClient; +//! +//! let testnet_websocket_client = BinanceWebSocketClient::connect_with_url("wss://testnet.binance.vision/ws"); +//! ``` +//! +//! # Logging +//! +//! Internally, the crate invokes log requests using the official [`log`](https://docs.rs/log/0.4.14/log/) framework to give visibility +//! to the requests' url and their respective response status code. +//! +//! Here is a snippet requesting the current average price for BNBUSDT using [`env_logger`](https://docs.rs/env_logger/0.9.0/env_logger/). +//! +//! ```no_run +//! use binance_spot_connector_rust::{market, ureq::BinanceHttpClient}; +//! use env_logger::Builder; +//! +//! Builder::from_default_env() +//! .filter_level(log::LevelFilter::Info) +//! .init(); +//! +//! let client = BinanceHttpClient::default(); +//! +//! let response = client.send(market::historical_trades("BNBUSDT")); +//! +//! let body = response.expect("Request failed").into_body_str().expect("Failed to parse body"); +//! +//! log::info!("{}", body); +//! ``` +//! +//! Generated logs. +//! ```ignore +//! [2022-01-01T00:00:01Z INFO binance_spot_connector_rust::ureq::client] https://api.binance.com/api/v3/avgPrice?symbol=BNBUSDT +//! [2022-01-01T00:00:01Z INFO binance_spot_connector_rust::ureq::client] 200 +//! [2022-01-01T00:00:01Z INFO market_current_avg_price] {"mins":5,"price":"306.08482159"} +//! ``` +//! +//! To ignore log requests by the library use `env_logger::Builder::filter`. If no loggger is configured, the library's internal log requests will be ignored. +//! +//! ```ignore +//! builder.filter(Some("binance_spot_connector_rust"), log::LevelFilter::Off) +//! ``` +//! +//! # Errors +//! +//! All errors emitted by the library can be converted to the generic error type of the respective client used to avoid complex error handling when using the pre-defined clients. + +mod utils; +mod websocket; + +#[cfg(feature = "enable-tokio-tungstenite")] +pub mod tokio_tungstenite; + +#[cfg(feature = "enable-tungstenite")] +pub mod tungstenite; + +#[cfg(feature = "enable-hyper")] +pub mod hyper; + +#[cfg(feature = "enable-ureq")] +pub mod ureq; + +pub mod http; + +pub mod isolated_margin_stream; +pub mod margin_stream; +pub mod market_stream; +pub mod stream; +pub mod user_data_stream; + +pub mod margin; +pub mod market; +pub mod trade; +pub mod wallet; diff --git a/src/margin/bnb_burn_status.rs b/src/margin/bnb_burn_status.rs new file mode 100644 index 0000000..736b713 --- /dev/null +++ b/src/margin/bnb_burn_status.rs @@ -0,0 +1,90 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/bnbBurn` +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::bnb_burn_status(); +/// ``` +pub struct BNBBurnStatus { + recv_window: Option, + credentials: Option, +} + +impl BNBBurnStatus { + pub fn new() -> Self { + Self { + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: BNBBurnStatus) -> Request { + let mut params = vec![]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/bnbBurn".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for BNBBurnStatus { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::BNBBurnStatus; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_bnb_burn_status_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = BNBBurnStatus::new() + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/bnbBurn".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("recvWindow".to_owned(), "5000".to_string()),], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_account.rs b/src/margin/isolated_margin_account.rs new file mode 100644 index 0000000..58114e3 --- /dev/null +++ b/src/margin/isolated_margin_account.rs @@ -0,0 +1,108 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/isolated/account` +/// +/// * If "symbols" is not sent, all isolated assets will be returned. +/// * If "symbols" is sent, only the isolated assets of the sent symbols will be returned. +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_account().symbols(vec!["BTCUSDT","BNBBTC"]); +/// ``` +pub struct IsolatedMarginAccount { + symbols: Option>, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginAccount { + pub fn new() -> Self { + Self { + symbols: None, + recv_window: None, + credentials: None, + } + } + + pub fn symbols(mut self, symbols: Vec<&str>) -> Self { + self.symbols = Some(symbols.iter().map(|s| s.to_string()).collect()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginAccount) -> Request { + let mut params = vec![]; + + if let Some(symbols) = request.symbols { + params.push(("symbols".to_owned(), symbols.join(","))); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/account".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for IsolatedMarginAccount { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginAccount; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_account_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginAccount::new() + .symbols(vec!["BTCUSDT", "BNBBTC"]) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/account".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbols".to_owned(), "BTCUSDT,BNBBTC".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_account_limit.rs b/src/margin/isolated_margin_account_limit.rs new file mode 100644 index 0000000..88e5ed2 --- /dev/null +++ b/src/margin/isolated_margin_account_limit.rs @@ -0,0 +1,92 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/isolated/accountLimit` +/// +/// Query enabled isolated margin account limit. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_account_limit(); +/// ``` +pub struct IsolatedMarginAccountLimit { + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginAccountLimit { + pub fn new() -> Self { + Self { + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginAccountLimit) -> Request { + let mut params = vec![]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/accountLimit".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for IsolatedMarginAccountLimit { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginAccountLimit; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_account_limit_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginAccountLimit::new() + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/accountLimit".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("recvWindow".to_owned(), "5000".to_string()),], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_all_symbols.rs b/src/margin/isolated_margin_all_symbols.rs new file mode 100644 index 0000000..9f6fbe4 --- /dev/null +++ b/src/margin/isolated_margin_all_symbols.rs @@ -0,0 +1,90 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/isolated/allPairs` +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_all_symbols(); +/// ``` +pub struct IsolatedMarginAllSymbols { + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginAllSymbols { + pub fn new() -> Self { + Self { + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginAllSymbols) -> Request { + let mut params = vec![]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/allPairs".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for IsolatedMarginAllSymbols { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginAllSymbols; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_all_symbols_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginAllSymbols::new() + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/allPairs".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("recvWindow".to_owned(), "5000".to_string()),], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_disable_account.rs b/src/margin/isolated_margin_disable_account.rs new file mode 100644 index 0000000..f9e68c7 --- /dev/null +++ b/src/margin/isolated_margin_disable_account.rs @@ -0,0 +1,91 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `DELETE /sapi/v1/margin/isolated/account` +/// +/// Disable isolated margin account for a specific symbol. Each trading pair can only be deactivated once every 24 hours . +/// +/// Weight(UID): 300 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_disable_account("BNBUSDT"); +/// ``` +pub struct IsolatedMarginDisableAccount { + symbol: String, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginDisableAccount { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginDisableAccount) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/account".to_owned(), + method: Method::Delete, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginDisableAccount; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_disable_account_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginDisableAccount::new("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/account".to_owned(), + credentials: Some(credentials), + method: Method::Delete, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_enable_account.rs b/src/margin/isolated_margin_enable_account.rs new file mode 100644 index 0000000..e547207 --- /dev/null +++ b/src/margin/isolated_margin_enable_account.rs @@ -0,0 +1,91 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `POST /sapi/v1/margin/isolated/account` +/// +/// Enable isolated margin account for a specific symbol. +/// +/// Weight(UID): 300 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_enable_account("BNBUSDT"); +/// ``` +pub struct IsolatedMarginEnableAccount { + symbol: String, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginEnableAccount { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginEnableAccount) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/account".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginEnableAccount; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_enable_account_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginEnableAccount::new("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/account".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_fee_data.rs b/src/margin/isolated_margin_fee_data.rs new file mode 100644 index 0000000..52baea6 --- /dev/null +++ b/src/margin/isolated_margin_fee_data.rs @@ -0,0 +1,118 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/isolatedMarginData` +/// +/// Get isolated margin fee data collection with any vip level or user's current specific data as https://www.binance.com/en/margin-fee +/// +/// Weight(IP): 1 when a single is specified; 10 when the symbol parameter is omitted +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_fee_data().symbol("BNBUSDT"); +/// ``` +pub struct IsolatedMarginFeeData { + vip_level: Option, + symbol: Option, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginFeeData { + pub fn new() -> Self { + Self { + vip_level: None, + symbol: None, + recv_window: None, + credentials: None, + } + } + + pub fn vip_level(mut self, vip_level: u32) -> Self { + self.vip_level = Some(vip_level); + self + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginFeeData) -> Request { + let mut params = vec![]; + + if let Some(vip_level) = request.vip_level { + params.push(("vipLevel".to_owned(), vip_level.to_string())); + } + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolatedMarginData".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for IsolatedMarginFeeData { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginFeeData; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_fee_data_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginFeeData::new() + .symbol("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolatedMarginData".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_symbol.rs b/src/margin/isolated_margin_symbol.rs new file mode 100644 index 0000000..d5c6086 --- /dev/null +++ b/src/margin/isolated_margin_symbol.rs @@ -0,0 +1,89 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/isolated/pair` +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_symbol("BNBUSDT"); +/// ``` +pub struct IsolatedMarginSymbol { + symbol: String, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginSymbol { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginSymbol) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/pair".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginSymbol; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_symbol_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginSymbol::new("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/pair".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_tier_data.rs b/src/margin/isolated_margin_tier_data.rs new file mode 100644 index 0000000..8d0f0e0 --- /dev/null +++ b/src/margin/isolated_margin_tier_data.rs @@ -0,0 +1,104 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/isolatedMarginTier` +/// +/// Get isolated margin tier data collection with any tier as https://www.binance.com/en/margin-data +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_tier_data("BNBUSDT").tier("1"); +/// ``` +pub struct IsolatedMarginTierData { + symbol: String, + tier: Option, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginTierData { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + tier: None, + recv_window: None, + credentials: None, + } + } + + pub fn tier(mut self, tier: &str) -> Self { + self.tier = Some(tier.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginTierData) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(tier) = request.tier { + params.push(("tier".to_owned(), tier)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolatedMarginTier".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginTierData; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_tier_data_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginTierData::new("BNBUSDT") + .tier("1") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolatedMarginTier".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("tier".to_owned(), "1".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_transfer.rs b/src/margin/isolated_margin_transfer.rs new file mode 100644 index 0000000..59e6a25 --- /dev/null +++ b/src/margin/isolated_margin_transfer.rs @@ -0,0 +1,117 @@ +use crate::http::{request::Request, Credentials, Method}; +use rust_decimal::Decimal; + +/// `POST /sapi/v1/margin/isolated/transfer` +/// +/// Weight(UID): 600 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// use rust_decimal_macros::dec; +/// +/// let request = margin::isolated_margin_transfer("BTC", "BNBUSDT", "SPOT", "ISOLATED_MARGIN", dec!(1.01)); +/// ``` +pub struct IsolatedMarginTransfer { + asset: String, + symbol: String, + trans_from: String, + trans_to: String, + amount: Decimal, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginTransfer { + pub fn new( + asset: &str, + symbol: &str, + trans_from: &str, + trans_to: &str, + amount: Decimal, + ) -> Self { + Self { + asset: asset.to_owned(), + symbol: symbol.to_owned(), + trans_from: trans_from.to_owned(), + trans_to: trans_to.to_owned(), + amount, + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginTransfer) -> Request { + let mut params = vec![ + ("asset".to_owned(), request.asset.to_string()), + ("symbol".to_owned(), request.symbol.to_string()), + ("transFrom".to_owned(), request.trans_from.to_string()), + ("transTo".to_owned(), request.trans_to.to_string()), + ("amount".to_owned(), request.amount.to_string()), + ]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/transfer".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginTransfer; + use crate::http::{request::Request, Credentials, Method}; + use rust_decimal_macros::dec; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_transfer_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = + IsolatedMarginTransfer::new("BTC", "BNBUSDT", "SPOT", "ISOLATED_MARGIN", dec!(1.01)) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/transfer".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("transFrom".to_owned(), "SPOT".to_string()), + ("transTo".to_owned(), "ISOLATED_MARGIN".to_string()), + ("amount".to_owned(), "1.01".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/isolated_margin_transfer_history.rs b/src/margin/isolated_margin_transfer_history.rs new file mode 100644 index 0000000..2ee3819 --- /dev/null +++ b/src/margin/isolated_margin_transfer_history.rs @@ -0,0 +1,187 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/isolated/transfer` +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::isolated_margin_transfer_history("BNBUSDT").asset("BNB").trans_from("SPOT").trans_to("ISOLATED_MARGIN").current(1).size(100); +/// ``` +pub struct IsolatedMarginTransferHistory { + symbol: String, + asset: Option, + trans_from: Option, + trans_to: Option, + start_time: Option, + end_time: Option, + current: Option, + size: Option, + archived: Option, + recv_window: Option, + credentials: Option, +} + +impl IsolatedMarginTransferHistory { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + asset: None, + trans_from: None, + trans_to: None, + start_time: None, + end_time: None, + current: None, + size: None, + archived: None, + recv_window: None, + credentials: None, + } + } + + pub fn asset(mut self, asset: &str) -> Self { + self.asset = Some(asset.to_owned()); + self + } + + pub fn trans_from(mut self, trans_from: &str) -> Self { + self.trans_from = Some(trans_from.to_owned()); + self + } + + pub fn trans_to(mut self, trans_to: &str) -> Self { + self.trans_to = Some(trans_to.to_owned()); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn current(mut self, current: u64) -> Self { + self.current = Some(current); + self + } + + pub fn size(mut self, size: u64) -> Self { + self.size = Some(size); + self + } + + pub fn archived(mut self, archived: bool) -> Self { + self.archived = Some(archived); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: IsolatedMarginTransferHistory) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(asset) = request.asset { + params.push(("asset".to_owned(), asset)); + } + + if let Some(trans_from) = request.trans_from { + params.push(("transFrom".to_owned(), trans_from)); + } + + if let Some(trans_to) = request.trans_to { + params.push(("transTo".to_owned(), trans_to)); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(current) = request.current { + params.push(("current".to_owned(), current.to_string())); + } + + if let Some(size) = request.size { + params.push(("size".to_owned(), size.to_string())); + } + + if let Some(archived) = request.archived { + params.push(("archived".to_owned(), archived.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/isolated/transfer".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::IsolatedMarginTransferHistory; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_isolated_margin_transfer_history_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = IsolatedMarginTransferHistory::new("BNBUSDT") + .asset("BNB") + .trans_from("SPOT") + .trans_to("ISOLATED_MARGIN") + .current(1) + .size(100) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/isolated/transfer".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("asset".to_owned(), "BNB".to_string()), + ("transFrom".to_owned(), "SPOT".to_string()), + ("transTo".to_owned(), "ISOLATED_MARGIN".to_string()), + ("current".to_owned(), "1".to_string()), + ("size".to_owned(), "100".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_account.rs b/src/margin/margin_account.rs new file mode 100644 index 0000000..e365a35 --- /dev/null +++ b/src/margin/margin_account.rs @@ -0,0 +1,90 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/account` +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_account(); +/// ``` +pub struct MarginAccount { + recv_window: Option, + credentials: Option, +} + +impl MarginAccount { + pub fn new() -> Self { + Self { + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginAccount) -> Request { + let mut params = vec![]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/account".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for MarginAccount { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::MarginAccount; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_account_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginAccount::new() + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/account".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("recvWindow".to_owned(), "5000".to_string()),], + sign: true + } + ); + } +} diff --git a/src/margin/margin_all_assets.rs b/src/margin/margin_all_assets.rs new file mode 100644 index 0000000..32f7054 --- /dev/null +++ b/src/margin/margin_all_assets.rs @@ -0,0 +1,74 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/allAssets` +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_all_assets(); +/// ``` +pub struct MarginAllAssets { + credentials: Option, +} + +impl MarginAllAssets { + pub fn new() -> Self { + Self { credentials: None } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(_request: MarginAllAssets) -> Request { + let params = vec![]; + + Request { + path: "/sapi/v1/margin/allAssets".to_owned(), + method: Method::Get, + params, + credentials: _request.credentials, + sign: false, + } + } +} + +impl Default for MarginAllAssets { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::MarginAllAssets; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_all_assets_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginAllAssets::new().credentials(&credentials).into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/allAssets".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![], + sign: false + } + ); + } +} diff --git a/src/margin/margin_all_oco_order.rs b/src/margin/margin_all_oco_order.rs new file mode 100644 index 0000000..057998a --- /dev/null +++ b/src/margin/margin_all_oco_order.rs @@ -0,0 +1,161 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/allOrderList` +/// +/// Retrieves all OCO for a specific margin account based on provided optional parameters +/// +/// Weight(IP): 200 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_all_oco_order(); +/// ``` +pub struct MarginAllOCOOrder { + is_isolated: Option, + symbol: Option, + from_id: Option, + start_time: Option, + end_time: Option, + limit: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginAllOCOOrder { + pub fn new() -> Self { + Self { + is_isolated: None, + symbol: None, + from_id: None, + start_time: None, + end_time: None, + limit: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn from_id(mut self, from_id: u64) -> Self { + self.from_id = Some(from_id); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn limit(mut self, limit: u32) -> Self { + self.limit = Some(limit); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginAllOCOOrder) -> Request { + let mut params = vec![]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(from_id) = request.from_id { + params.push(("fromId".to_owned(), from_id.to_string())); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(limit) = request.limit { + params.push(("limit".to_owned(), limit.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/allOrderList".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for MarginAllOCOOrder { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::MarginAllOCOOrder; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_all_oco_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginAllOCOOrder::new() + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/allOrderList".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("recvWindow".to_owned(), "5000".to_string()),], + sign: true + } + ); + } +} diff --git a/src/margin/margin_all_orders.rs b/src/margin/margin_all_orders.rs new file mode 100644 index 0000000..97eeb1d --- /dev/null +++ b/src/margin/margin_all_orders.rs @@ -0,0 +1,154 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/allOrders` +/// +/// * If `orderId` is set, it will get orders >= that orderId. Otherwise most recent orders are returned. +/// * For some historical orders `cummulativeQuoteQty` will be < 0, meaning the data is not available at this time. +/// +/// Weight(IP): 200 +/// +/// Request Limit: 60 times/min per IP +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_all_orders("BNBUSDT").limit(500); +/// ``` +pub struct MarginAllOrders { + symbol: String, + is_isolated: Option, + order_id: Option, + start_time: Option, + end_time: Option, + limit: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginAllOrders { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + is_isolated: None, + order_id: None, + start_time: None, + end_time: None, + limit: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn order_id(mut self, order_id: u64) -> Self { + self.order_id = Some(order_id); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn limit(mut self, limit: u32) -> Self { + self.limit = Some(limit); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginAllOrders) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(order_id) = request.order_id { + params.push(("orderId".to_owned(), order_id.to_string())); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(limit) = request.limit { + params.push(("limit".to_owned(), limit.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/allOrders".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginAllOrders; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_all_orders_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginAllOrders::new("BNBUSDT") + .limit(500) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/allOrders".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("limit".to_owned(), "500".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_all_pairs.rs b/src/margin/margin_all_pairs.rs new file mode 100644 index 0000000..496547d --- /dev/null +++ b/src/margin/margin_all_pairs.rs @@ -0,0 +1,74 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/allPairs` +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_all_pairs(); +/// ``` +pub struct MarginAllPairs { + credentials: Option, +} + +impl MarginAllPairs { + pub fn new() -> Self { + Self { credentials: None } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(_request: MarginAllPairs) -> Request { + let params = vec![]; + + Request { + path: "/sapi/v1/margin/allPairs".to_owned(), + method: Method::Get, + params, + credentials: _request.credentials, + sign: false, + } + } +} + +impl Default for MarginAllPairs { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::MarginAllPairs; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_all_pairs_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginAllPairs::new().credentials(&credentials).into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/allPairs".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![], + sign: false + } + ); + } +} diff --git a/src/margin/margin_asset.rs b/src/margin/margin_asset.rs new file mode 100644 index 0000000..22b82bd --- /dev/null +++ b/src/margin/margin_asset.rs @@ -0,0 +1,72 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/asset` +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_asset("BTC"); +/// ``` +pub struct MarginAsset { + asset: String, + credentials: Option, +} + +impl MarginAsset { + pub fn new(asset: &str) -> Self { + Self { + asset: asset.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginAsset) -> Request { + let params = vec![("asset".to_owned(), request.asset.to_string())]; + + Request { + path: "/sapi/v1/margin/asset".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginAsset; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_asset_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginAsset::new("BTC").credentials(&credentials).into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/asset".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("asset".to_owned(), "BTC".to_string()),], + sign: false + } + ); + } +} diff --git a/src/margin/margin_borrow.rs b/src/margin/margin_borrow.rs new file mode 100644 index 0000000..bb784d9 --- /dev/null +++ b/src/margin/margin_borrow.rs @@ -0,0 +1,130 @@ +use crate::http::{request::Request, Credentials, Method}; +use rust_decimal::Decimal; + +/// `POST /sapi/v1/margin/loan` +/// +/// Apply for a loan. +/// +/// * If "isIsolated" = "TRUE", "symbol" must be sent +/// * "isIsolated" = "FALSE" for crossed margin loan +/// +/// Weight(UID): 3000 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// use rust_decimal_macros::dec; +/// +/// let request = margin::margin_borrow("BTC", dec!(1.01)).symbol("BNBUSDT"); +/// ``` +pub struct MarginBorrow { + asset: String, + amount: Decimal, + is_isolated: Option, + symbol: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginBorrow { + pub fn new(asset: &str, amount: Decimal) -> Self { + Self { + asset: asset.to_owned(), + amount, + is_isolated: None, + symbol: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginBorrow) -> Request { + let mut params = vec![ + ("asset".to_owned(), request.asset.to_string()), + ("amount".to_owned(), request.amount.to_string()), + ]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/loan".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginBorrow; + use crate::http::{request::Request, Credentials, Method}; + use rust_decimal_macros::dec; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_borrow_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginBorrow::new("BTC", dec!(1.01)) + .symbol("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/loan".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("amount".to_owned(), "1.01".to_string()), + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_cancel_oco_order.rs b/src/margin/margin_cancel_oco_order.rs new file mode 100644 index 0000000..2085bd2 --- /dev/null +++ b/src/margin/margin_cancel_oco_order.rs @@ -0,0 +1,143 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `DELETE /sapi/v1/margin/orderList` +/// +/// Cancel an entire Order List for a margin account +/// +/// * Canceling an individual leg will cancel the entire OCO +/// * Either `orderListId` or `listClientOrderId` must be provided +/// +/// Weight(UID): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_cancel_oco_order("BNBUSDT").order_list_id(10000); +/// ``` +pub struct MarginCancelOCOOrder { + symbol: String, + is_isolated: Option, + order_list_id: Option, + list_client_order_id: Option, + new_client_order_id: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginCancelOCOOrder { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + is_isolated: None, + order_list_id: None, + list_client_order_id: None, + new_client_order_id: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn order_list_id(mut self, order_list_id: u64) -> Self { + self.order_list_id = Some(order_list_id); + self + } + + pub fn list_client_order_id(mut self, list_client_order_id: &str) -> Self { + self.list_client_order_id = Some(list_client_order_id.to_owned()); + self + } + + pub fn new_client_order_id(mut self, new_client_order_id: &str) -> Self { + self.new_client_order_id = Some(new_client_order_id.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginCancelOCOOrder) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(order_list_id) = request.order_list_id { + params.push(("orderListId".to_owned(), order_list_id.to_string())); + } + + if let Some(list_client_order_id) = request.list_client_order_id { + params.push(("listClientOrderId".to_owned(), list_client_order_id)); + } + + if let Some(new_client_order_id) = request.new_client_order_id { + params.push(("newClientOrderId".to_owned(), new_client_order_id)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/orderList".to_owned(), + method: Method::Delete, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginCancelOCOOrder; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_cancel_oco_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginCancelOCOOrder::new("BNBUSDT") + .order_list_id(10000) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/orderList".to_owned(), + credentials: Some(credentials), + method: Method::Delete, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("orderListId".to_owned(), "10000".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_cancel_open_orders.rs b/src/margin/margin_cancel_open_orders.rs new file mode 100644 index 0000000..8650800 --- /dev/null +++ b/src/margin/margin_cancel_open_orders.rs @@ -0,0 +1,107 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `DELETE /sapi/v1/margin/openOrders` +/// +/// * Cancels all active orders on a symbol for margin account. +/// * This includes OCO orders. +/// +/// Weight(IP): 1 +/// +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_cancel_open_orders("BNBUSDT"); +/// ``` +pub struct MarginCancelOpenOrders { + symbol: String, + is_isolated: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginCancelOpenOrders { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + is_isolated: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginCancelOpenOrders) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/openOrders".to_owned(), + method: Method::Delete, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginCancelOpenOrders; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_cancel_open_orders_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginCancelOpenOrders::new("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/openOrders".to_owned(), + credentials: Some(credentials), + method: Method::Delete, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_cancel_order.rs b/src/margin/margin_cancel_order.rs new file mode 100644 index 0000000..34ac794 --- /dev/null +++ b/src/margin/margin_cancel_order.rs @@ -0,0 +1,142 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `DELETE /sapi/v1/margin/order` +/// +/// Cancel an active order for margin account. +/// +/// Either `orderId` or `origClientOrderId` must be sent. +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_cancel_order("BNBUSDT").order_id(10); +/// ``` +pub struct MarginCancelOrder { + symbol: String, + is_isolated: Option, + order_id: Option, + orig_client_order_id: Option, + new_client_order_id: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginCancelOrder { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + is_isolated: None, + order_id: None, + orig_client_order_id: None, + new_client_order_id: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn order_id(mut self, order_id: u64) -> Self { + self.order_id = Some(order_id); + self + } + + pub fn orig_client_order_id(mut self, orig_client_order_id: &str) -> Self { + self.orig_client_order_id = Some(orig_client_order_id.to_owned()); + self + } + + pub fn new_client_order_id(mut self, new_client_order_id: &str) -> Self { + self.new_client_order_id = Some(new_client_order_id.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginCancelOrder) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(order_id) = request.order_id { + params.push(("orderId".to_owned(), order_id.to_string())); + } + + if let Some(orig_client_order_id) = request.orig_client_order_id { + params.push(("origClientOrderId".to_owned(), orig_client_order_id)); + } + + if let Some(new_client_order_id) = request.new_client_order_id { + params.push(("newClientOrderId".to_owned(), new_client_order_id)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/order".to_owned(), + method: Method::Delete, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginCancelOrder; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_cancel_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginCancelOrder::new("BNBUSDT") + .order_id(10) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/order".to_owned(), + credentials: Some(credentials), + method: Method::Delete, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("orderId".to_owned(), "10".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_dustlog.rs b/src/margin/margin_dustlog.rs new file mode 100644 index 0000000..46bde56 --- /dev/null +++ b/src/margin/margin_dustlog.rs @@ -0,0 +1,114 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/dribblet` +/// +/// Query the historical information of user's margin account small-value asset conversion BNB. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_dustlog(); +/// ``` +pub struct MarginDustlog { + start_time: Option, + end_time: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginDustlog { + pub fn new() -> Self { + Self { + start_time: None, + end_time: None, + recv_window: None, + credentials: None, + } + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl Default for MarginDustlog { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: MarginDustlog) -> Request { + let mut params = vec![]; + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/dribblet".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginDustlog; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_dustlog_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginDustlog::new() + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/dribblet".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("recvWindow".to_owned(), "5000".to_string()),], + sign: true + } + ); + } +} diff --git a/src/margin/margin_fee_data.rs b/src/margin/margin_fee_data.rs new file mode 100644 index 0000000..7b03c89 --- /dev/null +++ b/src/margin/margin_fee_data.rs @@ -0,0 +1,118 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/crossMarginData` +/// +/// Get cross margin fee data collection with any vip level or user's current specific data as https://www.binance.com/en/margin-fee +/// +/// Weight(IP): 1 when coin is specified; 5 when the coin parameter is omitted +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_fee_data().coin("BNB"); +/// ``` +pub struct MarginFeeData { + vip_level: Option, + coin: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginFeeData { + pub fn new() -> Self { + Self { + vip_level: None, + coin: None, + recv_window: None, + credentials: None, + } + } + + pub fn vip_level(mut self, vip_level: u32) -> Self { + self.vip_level = Some(vip_level); + self + } + + pub fn coin(mut self, coin: &str) -> Self { + self.coin = Some(coin.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl Default for MarginFeeData { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: MarginFeeData) -> Request { + let mut params = vec![]; + + if let Some(vip_level) = request.vip_level { + params.push(("vipLevel".to_owned(), vip_level.to_string())); + } + + if let Some(coin) = request.coin { + params.push(("coin".to_owned(), coin)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/crossMarginData".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginFeeData; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_fee_data_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginFeeData::new() + .coin("BNB") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/crossMarginData".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("coin".to_owned(), "BNB".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_force_liquidation_record.rs b/src/margin/margin_force_liquidation_record.rs new file mode 100644 index 0000000..067683a --- /dev/null +++ b/src/margin/margin_force_liquidation_record.rs @@ -0,0 +1,153 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/forceLiquidationRec` +/// +/// * Response in descending order +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_force_liquidation_record().current(1).size(100); +/// ``` +pub struct MarginForceLiquidationRecord { + start_time: Option, + end_time: Option, + isolated_symbol: Option, + current: Option, + size: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginForceLiquidationRecord { + pub fn new() -> Self { + Self { + start_time: None, + end_time: None, + isolated_symbol: None, + current: None, + size: None, + recv_window: None, + credentials: None, + } + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn isolated_symbol(mut self, isolated_symbol: &str) -> Self { + self.isolated_symbol = Some(isolated_symbol.to_owned()); + self + } + + pub fn current(mut self, current: u64) -> Self { + self.current = Some(current); + self + } + + pub fn size(mut self, size: u64) -> Self { + self.size = Some(size); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl Default for MarginForceLiquidationRecord { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: MarginForceLiquidationRecord) -> Request { + let mut params = vec![]; + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(isolated_symbol) = request.isolated_symbol { + params.push(("isolatedSymbol".to_owned(), isolated_symbol)); + } + + if let Some(current) = request.current { + params.push(("current".to_owned(), current.to_string())); + } + + if let Some(size) = request.size { + params.push(("size".to_owned(), size.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/forceLiquidationRec".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginForceLiquidationRecord; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_force_liquidation_record_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginForceLiquidationRecord::new() + .current(1) + .size(100) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/forceLiquidationRec".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("current".to_owned(), "1".to_string()), + ("size".to_owned(), "100".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_interest_history.rs b/src/margin/margin_interest_history.rs new file mode 100644 index 0000000..fb7b8d9 --- /dev/null +++ b/src/margin/margin_interest_history.rs @@ -0,0 +1,184 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/interestHistory` +/// +/// * Response in descending order +/// * If `isolatedSymbol` is not sent, crossed margin data will be returned +/// * Set `archived` to `true` to query data from 6 months ago +/// * `type` in response has 4 enums: +/// - `PERIODIC` interest charged per hour +/// - `ON_BORROW` first interest charged on borrow +/// - `PERIODIC_CONVERTED` interest charged per hour converted into BNB +/// - `ON_BORROW_CONVERTED` first interest charged on borrow converted into BNB +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_interest_history().asset("BNB").current(1).size(100); +/// ``` +pub struct MarginInterestHistory { + asset: Option, + isolated_symbol: Option, + start_time: Option, + end_time: Option, + current: Option, + size: Option, + archived: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginInterestHistory { + pub fn new() -> Self { + Self { + asset: None, + isolated_symbol: None, + start_time: None, + end_time: None, + current: None, + size: None, + archived: None, + recv_window: None, + credentials: None, + } + } + + pub fn asset(mut self, asset: &str) -> Self { + self.asset = Some(asset.to_owned()); + self + } + + pub fn isolated_symbol(mut self, isolated_symbol: &str) -> Self { + self.isolated_symbol = Some(isolated_symbol.to_owned()); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn current(mut self, current: u64) -> Self { + self.current = Some(current); + self + } + + pub fn size(mut self, size: u64) -> Self { + self.size = Some(size); + self + } + + pub fn archived(mut self, archived: bool) -> Self { + self.archived = Some(archived); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginInterestHistory) -> Request { + let mut params = vec![]; + + if let Some(asset) = request.asset { + params.push(("asset".to_owned(), asset)); + } + + if let Some(isolated_symbol) = request.isolated_symbol { + params.push(("isolatedSymbol".to_owned(), isolated_symbol)); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(current) = request.current { + params.push(("current".to_owned(), current.to_string())); + } + + if let Some(size) = request.size { + params.push(("size".to_owned(), size.to_string())); + } + + if let Some(archived) = request.archived { + params.push(("archived".to_owned(), archived.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/interestHistory".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for MarginInterestHistory { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::MarginInterestHistory; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_interest_history_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginInterestHistory::new() + .asset("BNB") + .current(1) + .size(100) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/interestHistory".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("asset".to_owned(), "BNB".to_string()), + ("current".to_owned(), "1".to_string()), + ("size".to_owned(), "100".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_interest_rate_history.rs b/src/margin/margin_interest_rate_history.rs new file mode 100644 index 0000000..2c416a8 --- /dev/null +++ b/src/margin/margin_interest_rate_history.rs @@ -0,0 +1,124 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/interestRateHistory` +/// +/// The max interval between startTime and endTime is 30 days. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_interest_rate_history("BTC"); +/// ``` +pub struct MarginInterestRateHistory { + asset: String, + vip_level: Option, + start_time: Option, + end_time: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginInterestRateHistory { + pub fn new(asset: &str) -> Self { + Self { + asset: asset.to_owned(), + vip_level: None, + start_time: None, + end_time: None, + recv_window: None, + credentials: None, + } + } + + pub fn vip_level(mut self, vip_level: u32) -> Self { + self.vip_level = Some(vip_level); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginInterestRateHistory) -> Request { + let mut params = vec![("asset".to_owned(), request.asset.to_string())]; + + if let Some(vip_level) = request.vip_level { + params.push(("vipLevel".to_owned(), vip_level.to_string())); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/interestRateHistory".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginInterestRateHistory; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_interest_rate_history_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginInterestRateHistory::new("BTC") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/interestRateHistory".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_loan_record.rs b/src/margin/margin_loan_record.rs new file mode 100644 index 0000000..4c513a8 --- /dev/null +++ b/src/margin/margin_loan_record.rs @@ -0,0 +1,177 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/loan` +/// +/// * `txId` or `startTime` must be sent. `txId` takes precedence. +/// * Response in descending order +/// * If `isolatedSymbol` is not sent, crossed margin data will be returned +/// * Set `archived` to `true` to query data from 6 months ago +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_loan_record("BTC").tx_id(123456789).current(1).size(100); +/// ``` +pub struct MarginLoanRecord { + asset: String, + isolated_symbol: Option, + tx_id: Option, + start_time: Option, + end_time: Option, + current: Option, + size: Option, + archived: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginLoanRecord { + pub fn new(asset: &str) -> Self { + Self { + asset: asset.to_owned(), + isolated_symbol: None, + tx_id: None, + start_time: None, + end_time: None, + current: None, + size: None, + archived: None, + recv_window: None, + credentials: None, + } + } + + pub fn isolated_symbol(mut self, isolated_symbol: &str) -> Self { + self.isolated_symbol = Some(isolated_symbol.to_owned()); + self + } + + pub fn tx_id(mut self, tx_id: u64) -> Self { + self.tx_id = Some(tx_id); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn current(mut self, current: u64) -> Self { + self.current = Some(current); + self + } + + pub fn size(mut self, size: u64) -> Self { + self.size = Some(size); + self + } + + pub fn archived(mut self, archived: bool) -> Self { + self.archived = Some(archived); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginLoanRecord) -> Request { + let mut params = vec![("asset".to_owned(), request.asset.to_string())]; + + if let Some(isolated_symbol) = request.isolated_symbol { + params.push(("isolatedSymbol".to_owned(), isolated_symbol)); + } + + if let Some(tx_id) = request.tx_id { + params.push(("txId".to_owned(), tx_id.to_string())); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(current) = request.current { + params.push(("current".to_owned(), current.to_string())); + } + + if let Some(size) = request.size { + params.push(("size".to_owned(), size.to_string())); + } + + if let Some(archived) = request.archived { + params.push(("archived".to_owned(), archived.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/loan".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginLoanRecord; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_loan_record_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginLoanRecord::new("BTC") + .tx_id(123456789) + .current(1) + .size(100) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/loan".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("txId".to_owned(), "123456789".to_string()), + ("current".to_owned(), "1".to_string()), + ("size".to_owned(), "100".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_max_borrowable.rs b/src/margin/margin_max_borrowable.rs new file mode 100644 index 0000000..7dd415e --- /dev/null +++ b/src/margin/margin_max_borrowable.rs @@ -0,0 +1,103 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/maxBorrowable` +/// +/// * If `isolatedSymbol` is not sent, crossed margin data will be sent. +/// * `borrowLimit` is also available from https://www.binance.com/en/margin-fee +/// +/// Weight(IP): 50 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_max_borrowable("BTC"); +/// ``` +pub struct MarginMaxBorrowable { + asset: String, + isolated_symbol: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginMaxBorrowable { + pub fn new(asset: &str) -> Self { + Self { + asset: asset.to_owned(), + isolated_symbol: None, + recv_window: None, + credentials: None, + } + } + + pub fn isolated_symbol(mut self, isolated_symbol: &str) -> Self { + self.isolated_symbol = Some(isolated_symbol.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginMaxBorrowable) -> Request { + let mut params = vec![("asset".to_owned(), request.asset.to_string())]; + + if let Some(isolated_symbol) = request.isolated_symbol { + params.push(("isolatedSymbol".to_owned(), isolated_symbol)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/maxBorrowable".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginMaxBorrowable; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_max_borrowable_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginMaxBorrowable::new("BTC") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/maxBorrowable".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_max_transferable.rs b/src/margin/margin_max_transferable.rs new file mode 100644 index 0000000..73b1602 --- /dev/null +++ b/src/margin/margin_max_transferable.rs @@ -0,0 +1,102 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/maxTransferable` +/// +/// * If `isolatedSymbol` is not sent, crossed margin data will be sent. +/// +/// Weight(IP): 50 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_max_transferable("BTC"); +/// ``` +pub struct MarginMaxTransferable { + asset: String, + isolated_symbol: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginMaxTransferable { + pub fn new(asset: &str) -> Self { + Self { + asset: asset.to_owned(), + isolated_symbol: None, + recv_window: None, + credentials: None, + } + } + + pub fn isolated_symbol(mut self, isolated_symbol: &str) -> Self { + self.isolated_symbol = Some(isolated_symbol.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginMaxTransferable) -> Request { + let mut params = vec![("asset".to_owned(), request.asset.to_string())]; + + if let Some(isolated_symbol) = request.isolated_symbol { + params.push(("isolatedSymbol".to_owned(), isolated_symbol)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/maxTransferable".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginMaxTransferable; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_max_transferable_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginMaxTransferable::new("BTC") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/maxTransferable".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_my_trades.rs b/src/margin/margin_my_trades.rs new file mode 100644 index 0000000..0061681 --- /dev/null +++ b/src/margin/margin_my_trades.rs @@ -0,0 +1,151 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/myTrades` +/// +/// * If `fromId` is set, it will get orders >= that `fromId`. Otherwise most recent trades are returned. +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_my_trades("BNBUSDT").limit(500); +/// ``` +pub struct MarginMyTrades { + symbol: String, + is_isolated: Option, + start_time: Option, + end_time: Option, + from_id: Option, + limit: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginMyTrades { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + is_isolated: None, + start_time: None, + end_time: None, + from_id: None, + limit: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn from_id(mut self, from_id: u64) -> Self { + self.from_id = Some(from_id); + self + } + + pub fn limit(mut self, limit: u32) -> Self { + self.limit = Some(limit); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginMyTrades) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(from_id) = request.from_id { + params.push(("fromId".to_owned(), from_id.to_string())); + } + + if let Some(limit) = request.limit { + params.push(("limit".to_owned(), limit.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/myTrades".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginMyTrades; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_my_trades_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginMyTrades::new("BNBUSDT") + .limit(500) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/myTrades".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("limit".to_owned(), "500".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_new_oco_order.rs b/src/margin/margin_new_oco_order.rs new file mode 100644 index 0000000..ca3d291 --- /dev/null +++ b/src/margin/margin_new_oco_order.rs @@ -0,0 +1,245 @@ +use crate::http::{request::Request, Credentials, Method}; +use rust_decimal::Decimal; + +/// `POST /sapi/v1/margin/order/oco` +/// +/// Send in a new OCO for a margin account +/// +/// * Price Restrictions: +/// - SELL: Limit Price > Last Price > Stop Price +/// - BUY: Limit Price < Last Price < Stop Price +/// * Quantity Restrictions: +/// - Both legs must have the same quantity +/// - ICEBERG quantities however do not have to be the same. +/// * Order Rate Limit +/// - OCO counts as 2 orders against the order rate limit. +/// +/// Weight(UID): 6 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// use rust_decimal_macros::dec; +/// +/// let request = margin::margin_new_oco_order("BNBUSDT", "SELL", dec!(0.1), dec!(400.15), dec!(390.3)).stop_limit_price(dec!(290)).stop_limit_time_in_force("GTC"); +/// ``` +pub struct MarginNewOCOOrder { + symbol: String, + side: String, + quantity: Decimal, + price: Decimal, + stop_price: Decimal, + is_isolated: Option, + list_client_order_id: Option, + limit_client_order_id: Option, + limit_iceberg_qty: Option, + stop_client_order_id: Option, + stop_limit_price: Option, + stop_iceberg_qty: Option, + stop_limit_time_in_force: Option, + new_order_resp_type: Option, + side_effect_type: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginNewOCOOrder { + pub fn new( + symbol: &str, + side: &str, + quantity: Decimal, + price: Decimal, + stop_price: Decimal, + ) -> Self { + Self { + symbol: symbol.to_owned(), + side: side.to_owned(), + quantity, + price, + stop_price, + is_isolated: None, + list_client_order_id: None, + limit_client_order_id: None, + limit_iceberg_qty: None, + stop_client_order_id: None, + stop_limit_price: None, + stop_iceberg_qty: None, + stop_limit_time_in_force: None, + new_order_resp_type: None, + side_effect_type: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn list_client_order_id(mut self, list_client_order_id: &str) -> Self { + self.list_client_order_id = Some(list_client_order_id.to_owned()); + self + } + + pub fn limit_client_order_id(mut self, limit_client_order_id: &str) -> Self { + self.limit_client_order_id = Some(limit_client_order_id.to_owned()); + self + } + + pub fn limit_iceberg_qty(mut self, limit_iceberg_qty: Decimal) -> Self { + self.limit_iceberg_qty = Some(limit_iceberg_qty); + self + } + + pub fn stop_client_order_id(mut self, stop_client_order_id: &str) -> Self { + self.stop_client_order_id = Some(stop_client_order_id.to_owned()); + self + } + + pub fn stop_limit_price(mut self, stop_limit_price: Decimal) -> Self { + self.stop_limit_price = Some(stop_limit_price); + self + } + + pub fn stop_iceberg_qty(mut self, stop_iceberg_qty: Decimal) -> Self { + self.stop_iceberg_qty = Some(stop_iceberg_qty); + self + } + + pub fn stop_limit_time_in_force(mut self, stop_limit_time_in_force: &str) -> Self { + self.stop_limit_time_in_force = Some(stop_limit_time_in_force.to_owned()); + self + } + + pub fn new_order_resp_type(mut self, new_order_resp_type: &str) -> Self { + self.new_order_resp_type = Some(new_order_resp_type.to_owned()); + self + } + + pub fn side_effect_type(mut self, side_effect_type: &str) -> Self { + self.side_effect_type = Some(side_effect_type.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginNewOCOOrder) -> Request { + let mut params = vec![ + ("symbol".to_owned(), request.symbol.to_string()), + ("side".to_owned(), request.side.to_string()), + ("quantity".to_owned(), request.quantity.to_string()), + ("price".to_owned(), request.price.to_string()), + ("stopPrice".to_owned(), request.stop_price.to_string()), + ]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(list_client_order_id) = request.list_client_order_id { + params.push(("listClientOrderId".to_owned(), list_client_order_id)); + } + + if let Some(limit_client_order_id) = request.limit_client_order_id { + params.push(("limitClientOrderId".to_owned(), limit_client_order_id)); + } + + if let Some(limit_iceberg_qty) = request.limit_iceberg_qty { + params.push(("limitIcebergQty".to_owned(), limit_iceberg_qty.to_string())); + } + + if let Some(stop_client_order_id) = request.stop_client_order_id { + params.push(("stopClientOrderId".to_owned(), stop_client_order_id)); + } + + if let Some(stop_limit_price) = request.stop_limit_price { + params.push(("stopLimitPrice".to_owned(), stop_limit_price.to_string())); + } + + if let Some(stop_iceberg_qty) = request.stop_iceberg_qty { + params.push(("stopIcebergQty".to_owned(), stop_iceberg_qty.to_string())); + } + + if let Some(stop_limit_time_in_force) = request.stop_limit_time_in_force { + params.push(("stopLimitTimeInForce".to_owned(), stop_limit_time_in_force)); + } + + if let Some(new_order_resp_type) = request.new_order_resp_type { + params.push(("newOrderRespType".to_owned(), new_order_resp_type)); + } + + if let Some(side_effect_type) = request.side_effect_type { + params.push(("sideEffectType".to_owned(), side_effect_type)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/order/oco".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginNewOCOOrder; + use crate::http::{request::Request, Credentials, Method}; + use rust_decimal_macros::dec; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_new_oco_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = + MarginNewOCOOrder::new("BNBUSDT", "SELL", dec!(0.1), dec!(400.15), dec!(390.3)) + .stop_limit_price(dec!(290)) + .stop_limit_time_in_force("GTC") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/order/oco".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("side".to_owned(), "SELL".to_string()), + ("quantity".to_owned(), "0.1".to_string()), + ("price".to_owned(), "400.15".to_string()), + ("stopPrice".to_owned(), "390.3".to_string()), + ("stopLimitPrice".to_owned(), "290".to_string()), + ("stopLimitTimeInForce".to_owned(), "GTC".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_new_order.rs b/src/margin/margin_new_order.rs new file mode 100644 index 0000000..43885fe --- /dev/null +++ b/src/margin/margin_new_order.rs @@ -0,0 +1,225 @@ +use crate::http::{request::Request, Credentials, Method}; +use rust_decimal::Decimal; + +/// `POST /sapi/v1/margin/order` +/// +/// Post a new order for margin account. +/// +/// Weight(UID): 6 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// use rust_decimal_macros::dec; +/// +/// let request = margin::margin_new_order("BNBUSDT", "SELL", "MARKET").quantity(dec!(1.01)).price(dec!(10)).stop_price(dec!(20.01)).time_in_force("GTC"); +/// ``` +pub struct MarginNewOrder { + symbol: String, + side: String, + r#type: String, + is_isolated: Option, + quantity: Option, + quote_order_qty: Option, + price: Option, + stop_price: Option, + new_client_order_id: Option, + iceberg_qty: Option, + new_order_resp_type: Option, + side_effect_type: Option, + time_in_force: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginNewOrder { + pub fn new(symbol: &str, side: &str, r#type: &str) -> Self { + Self { + symbol: symbol.to_owned(), + side: side.to_owned(), + r#type: r#type.to_owned(), + is_isolated: None, + quantity: None, + quote_order_qty: None, + price: None, + stop_price: None, + new_client_order_id: None, + iceberg_qty: None, + new_order_resp_type: None, + side_effect_type: None, + time_in_force: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn quantity(mut self, quantity: Decimal) -> Self { + self.quantity = Some(quantity); + self + } + + pub fn quote_order_qty(mut self, quote_order_qty: Decimal) -> Self { + self.quote_order_qty = Some(quote_order_qty); + self + } + + pub fn price(mut self, price: Decimal) -> Self { + self.price = Some(price); + self + } + + pub fn stop_price(mut self, stop_price: Decimal) -> Self { + self.stop_price = Some(stop_price); + self + } + + pub fn new_client_order_id(mut self, new_client_order_id: &str) -> Self { + self.new_client_order_id = Some(new_client_order_id.to_owned()); + self + } + + pub fn iceberg_qty(mut self, iceberg_qty: Decimal) -> Self { + self.iceberg_qty = Some(iceberg_qty); + self + } + + pub fn new_order_resp_type(mut self, new_order_resp_type: &str) -> Self { + self.new_order_resp_type = Some(new_order_resp_type.to_owned()); + self + } + + pub fn side_effect_type(mut self, side_effect_type: &str) -> Self { + self.side_effect_type = Some(side_effect_type.to_owned()); + self + } + + pub fn time_in_force(mut self, time_in_force: &str) -> Self { + self.time_in_force = Some(time_in_force.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginNewOrder) -> Request { + let mut params = vec![ + ("symbol".to_owned(), request.symbol.to_string()), + ("side".to_owned(), request.side.to_string()), + ("type".to_owned(), request.r#type.to_string()), + ]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(quantity) = request.quantity { + params.push(("quantity".to_owned(), quantity.to_string())); + } + + if let Some(quote_order_qty) = request.quote_order_qty { + params.push(("quoteOrderQty".to_owned(), quote_order_qty.to_string())); + } + + if let Some(price) = request.price { + params.push(("price".to_owned(), price.to_string())); + } + + if let Some(stop_price) = request.stop_price { + params.push(("stopPrice".to_owned(), stop_price.to_string())); + } + + if let Some(new_client_order_id) = request.new_client_order_id { + params.push(("newClientOrderId".to_owned(), new_client_order_id)); + } + + if let Some(iceberg_qty) = request.iceberg_qty { + params.push(("icebergQty".to_owned(), iceberg_qty.to_string())); + } + + if let Some(new_order_resp_type) = request.new_order_resp_type { + params.push(("newOrderRespType".to_owned(), new_order_resp_type)); + } + + if let Some(side_effect_type) = request.side_effect_type { + params.push(("sideEffectType".to_owned(), side_effect_type)); + } + + if let Some(time_in_force) = request.time_in_force { + params.push(("timeInForce".to_owned(), time_in_force)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/order".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginNewOrder; + use crate::http::{request::Request, Credentials, Method}; + use rust_decimal_macros::dec; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_new_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginNewOrder::new("BNBUSDT", "SELL", "MARKET") + .quantity(dec!(1.01)) + .price(dec!(10)) + .stop_price(dec!(20.01)) + .time_in_force("GTC") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/order".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("side".to_owned(), "SELL".to_string()), + ("type".to_owned(), "MARKET".to_string()), + ("quantity".to_owned(), "1.01".to_string()), + ("price".to_owned(), "10".to_string()), + ("stopPrice".to_owned(), "20.01".to_string()), + ("timeInForce".to_owned(), "GTC".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_oco_order.rs b/src/margin/margin_oco_order.rs new file mode 100644 index 0000000..62d8b4e --- /dev/null +++ b/src/margin/margin_oco_order.rs @@ -0,0 +1,147 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/orderList` +/// +/// Retrieves a specific OCO based on provided optional parameters +/// +/// * Either `orderListId` or `origClientOrderId` must be provided +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_oco_order().symbol("BNBUSDT").order_list_id(27); +/// ``` +pub struct MarginOCOOrder { + is_isolated: Option, + symbol: Option, + order_list_id: Option, + orig_client_order_id: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginOCOOrder { + pub fn new() -> Self { + Self { + is_isolated: None, + symbol: None, + order_list_id: None, + orig_client_order_id: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn order_list_id(mut self, order_list_id: u64) -> Self { + self.order_list_id = Some(order_list_id); + self + } + + pub fn orig_client_order_id(mut self, orig_client_order_id: &str) -> Self { + self.orig_client_order_id = Some(orig_client_order_id.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl Default for MarginOCOOrder { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: MarginOCOOrder) -> Request { + let mut params = vec![]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(order_list_id) = request.order_list_id { + params.push(("orderListId".to_owned(), order_list_id.to_string())); + } + + if let Some(orig_client_order_id) = request.orig_client_order_id { + params.push(("origClientOrderId".to_owned(), orig_client_order_id)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/orderList".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginOCOOrder; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_oco_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginOCOOrder::new() + .symbol("BNBUSDT") + .order_list_id(27) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/orderList".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("orderListId".to_owned(), "27".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_open_oco_order.rs b/src/margin/margin_open_oco_order.rs new file mode 100644 index 0000000..bdf4061 --- /dev/null +++ b/src/margin/margin_open_oco_order.rs @@ -0,0 +1,121 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/openOrderList` +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_open_oco_order().is_isolated(true).symbol("BNBUSDT"); +/// ``` +pub struct MarginOpenOCOOrder { + is_isolated: Option, + symbol: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginOpenOCOOrder { + pub fn new() -> Self { + Self { + is_isolated: None, + symbol: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl Default for MarginOpenOCOOrder { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: MarginOpenOCOOrder) -> Request { + let mut params = vec![]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/openOrderList".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginOpenOCOOrder; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_open_oco_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginOpenOCOOrder::new() + .is_isolated(true) + .symbol("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/openOrderList".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("isIsolated".to_owned(), "TRUE".to_string()), + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_open_orders.rs b/src/margin/margin_open_orders.rs new file mode 100644 index 0000000..c8072b8 --- /dev/null +++ b/src/margin/margin_open_orders.rs @@ -0,0 +1,123 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/openOrders` +/// +/// * If the `symbol` is not sent, orders for all symbols will be returned in an array. +/// * When all symbols are returned, the number of requests counted against the rate limiter is equal to the number of symbols currently trading on the exchange +/// * If isIsolated ="TRUE", symbol must be sent. +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_open_orders().symbol("BNBUSDT"); +/// ``` +pub struct MarginOpenOrders { + symbol: Option, + is_isolated: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginOpenOrders { + pub fn new() -> Self { + Self { + symbol: None, + is_isolated: None, + recv_window: None, + credentials: None, + } + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl Default for MarginOpenOrders { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: MarginOpenOrders) -> Request { + let mut params = vec![]; + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/openOrders".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginOpenOrders; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_open_orders_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginOpenOrders::new() + .symbol("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/openOrders".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_order.rs b/src/margin/margin_order.rs new file mode 100644 index 0000000..9ff0a2e --- /dev/null +++ b/src/margin/margin_order.rs @@ -0,0 +1,130 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/order` +/// +/// * Either `orderId` or `origClientOrderId` must be sent. +/// * For some historical orders `cummulativeQuoteQty` will be < 0, meaning the data is not available at this time. +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_order("BNBUSDT").order_id(213205622); +/// ``` +pub struct MarginOrder { + symbol: String, + is_isolated: Option, + order_id: Option, + orig_client_order_id: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginOrder { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + is_isolated: None, + order_id: None, + orig_client_order_id: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn order_id(mut self, order_id: u64) -> Self { + self.order_id = Some(order_id); + self + } + + pub fn orig_client_order_id(mut self, orig_client_order_id: &str) -> Self { + self.orig_client_order_id = Some(orig_client_order_id.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginOrder) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(order_id) = request.order_id { + params.push(("orderId".to_owned(), order_id.to_string())); + } + + if let Some(orig_client_order_id) = request.orig_client_order_id { + params.push(("origClientOrderId".to_owned(), orig_client_order_id)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/order".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginOrder; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_order_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginOrder::new("BNBUSDT") + .order_id(213205622) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/order".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("orderId".to_owned(), "213205622".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_order_count_usage.rs b/src/margin/margin_order_count_usage.rs new file mode 100644 index 0000000..94c208a --- /dev/null +++ b/src/margin/margin_order_count_usage.rs @@ -0,0 +1,117 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/rateLimit/order` +/// +/// Displays the user's current margin order count usage for all intervals. +/// +/// Weight(IP): 20 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_order_count_usage(); +/// ``` +pub struct MarginOrderCountUsage { + is_isolated: Option, + symbol: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginOrderCountUsage { + pub fn new() -> Self { + Self { + is_isolated: None, + symbol: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginOrderCountUsage) -> Request { + let mut params = vec![]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/rateLimit/order".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for MarginOrderCountUsage { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::MarginOrderCountUsage; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_order_count_usage_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginOrderCountUsage::new() + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/rateLimit/order".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("recvWindow".to_owned(), "5000".to_string()),], + sign: true + } + ); + } +} diff --git a/src/margin/margin_pair.rs b/src/margin/margin_pair.rs new file mode 100644 index 0000000..3cfc87c --- /dev/null +++ b/src/margin/margin_pair.rs @@ -0,0 +1,72 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/pair` +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_pair("BNBUSDT"); +/// ``` +pub struct MarginPair { + symbol: String, + credentials: Option, +} + +impl MarginPair { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginPair) -> Request { + let params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + Request { + path: "/sapi/v1/margin/pair".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginPair; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_pair_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginPair::new("BNBUSDT").credentials(&credentials).into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/pair".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("symbol".to_owned(), "BNBUSDT".to_string()),], + sign: false + } + ); + } +} diff --git a/src/margin/margin_price_index.rs b/src/margin/margin_price_index.rs new file mode 100644 index 0000000..6bb8481 --- /dev/null +++ b/src/margin/margin_price_index.rs @@ -0,0 +1,74 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/priceIndex` +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_price_index("BNBUSDT"); +/// ``` +pub struct MarginPriceIndex { + symbol: String, + credentials: Option, +} + +impl MarginPriceIndex { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginPriceIndex) -> Request { + let params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + Request { + path: "/sapi/v1/margin/priceIndex".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginPriceIndex; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_price_index_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginPriceIndex::new("BNBUSDT") + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/priceIndex".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![("symbol".to_owned(), "BNBUSDT".to_string()),], + sign: false + } + ); + } +} diff --git a/src/margin/margin_repay.rs b/src/margin/margin_repay.rs new file mode 100644 index 0000000..e4af0b8 --- /dev/null +++ b/src/margin/margin_repay.rs @@ -0,0 +1,130 @@ +use crate::http::{request::Request, Credentials, Method}; +use rust_decimal::Decimal; + +/// `POST /sapi/v1/margin/repay` +/// +/// Repay loan for margin account. +/// +/// * If "isIsolated" = "TRUE", "symbol" must be sent +/// * "isIsolated" = "FALSE" for crossed margin repay +/// +/// Weight(IP): 3000 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// use rust_decimal_macros::dec; +/// +/// let request = margin::margin_repay("BTC", dec!(1.01)).symbol("BNBUSDT"); +/// ``` +pub struct MarginRepay { + asset: String, + amount: Decimal, + is_isolated: Option, + symbol: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginRepay { + pub fn new(asset: &str, amount: Decimal) -> Self { + Self { + asset: asset.to_owned(), + amount, + is_isolated: None, + symbol: None, + recv_window: None, + credentials: None, + } + } + + pub fn is_isolated(mut self, is_isolated: bool) -> Self { + self.is_isolated = Some(is_isolated); + self + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginRepay) -> Request { + let mut params = vec![ + ("asset".to_owned(), request.asset.to_string()), + ("amount".to_owned(), request.amount.to_string()), + ]; + + if let Some(is_isolated) = request.is_isolated { + params.push(( + "isIsolated".to_owned(), + is_isolated.to_string().to_uppercase(), + )); + } + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/repay".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginRepay; + use crate::http::{request::Request, Credentials, Method}; + use rust_decimal_macros::dec; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_repay_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginRepay::new("BTC", dec!(1.01)) + .symbol("BNBUSDT") + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/repay".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("amount".to_owned(), "1.01".to_string()), + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_repay_record.rs b/src/margin/margin_repay_record.rs new file mode 100644 index 0000000..58243b0 --- /dev/null +++ b/src/margin/margin_repay_record.rs @@ -0,0 +1,177 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/repay` +/// +/// * `txId` or `startTime` must be sent. `txId` takes precedence. +/// * Response in descending order +/// * If `isolatedSymbol` is not sent, crossed margin data will be returned +/// * Set `archived` to `true` to query data from 6 months ago +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_repay_record("BTC").tx_id(2970933056).current(1).size(100); +/// ``` +pub struct MarginRepayRecord { + asset: String, + isolated_symbol: Option, + tx_id: Option, + start_time: Option, + end_time: Option, + current: Option, + size: Option, + archived: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginRepayRecord { + pub fn new(asset: &str) -> Self { + Self { + asset: asset.to_owned(), + isolated_symbol: None, + tx_id: None, + start_time: None, + end_time: None, + current: None, + size: None, + archived: None, + recv_window: None, + credentials: None, + } + } + + pub fn isolated_symbol(mut self, isolated_symbol: &str) -> Self { + self.isolated_symbol = Some(isolated_symbol.to_owned()); + self + } + + pub fn tx_id(mut self, tx_id: u64) -> Self { + self.tx_id = Some(tx_id); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn current(mut self, current: u64) -> Self { + self.current = Some(current); + self + } + + pub fn size(mut self, size: u64) -> Self { + self.size = Some(size); + self + } + + pub fn archived(mut self, archived: bool) -> Self { + self.archived = Some(archived); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginRepayRecord) -> Request { + let mut params = vec![("asset".to_owned(), request.asset.to_string())]; + + if let Some(isolated_symbol) = request.isolated_symbol { + params.push(("isolatedSymbol".to_owned(), isolated_symbol)); + } + + if let Some(tx_id) = request.tx_id { + params.push(("txId".to_owned(), tx_id.to_string())); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(current) = request.current { + params.push(("current".to_owned(), current.to_string())); + } + + if let Some(size) = request.size { + params.push(("size".to_owned(), size.to_string())); + } + + if let Some(archived) = request.archived { + params.push(("archived".to_owned(), archived.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/repay".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginRepayRecord; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_repay_record_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginRepayRecord::new("BTC") + .tx_id(2970933056) + .current(1) + .size(100) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/repay".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("txId".to_owned(), "2970933056".to_string()), + ("current".to_owned(), "1".to_string()), + ("size".to_owned(), "100".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_transfer.rs b/src/margin/margin_transfer.rs new file mode 100644 index 0000000..9fedbd6 --- /dev/null +++ b/src/margin/margin_transfer.rs @@ -0,0 +1,104 @@ +use crate::http::{request::Request, Credentials, Method}; +use rust_decimal::Decimal; + +/// `POST /sapi/v1/margin/transfer` +/// +/// Execute transfer between spot account and cross margin account. +/// +/// Weight(IP): 600 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// use rust_decimal_macros::dec; +/// +/// let request = margin::margin_transfer("BTC", dec!(1.01), 1); +/// ``` +pub struct MarginTransfer { + asset: String, + amount: Decimal, + r#type: u32, + recv_window: Option, + credentials: Option, +} + +impl MarginTransfer { + pub fn new(asset: &str, amount: Decimal, r#type: u32) -> Self { + Self { + asset: asset.to_owned(), + amount, + r#type, + recv_window: None, + credentials: None, + } + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginTransfer) -> Request { + let mut params = vec![ + ("asset".to_owned(), request.asset.to_string()), + ("amount".to_owned(), request.amount.to_string()), + ("type".to_owned(), request.r#type.to_string()), + ]; + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/transfer".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::MarginTransfer; + use crate::http::{request::Request, Credentials, Method}; + use rust_decimal_macros::dec; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_transfer_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginTransfer::new("BTC", dec!(1.01), 1) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/transfer".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("asset".to_owned(), "BTC".to_string()), + ("amount".to_owned(), "1.01".to_string()), + ("type".to_owned(), "1".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/margin_transfer_history.rs b/src/margin/margin_transfer_history.rs new file mode 100644 index 0000000..4eec490 --- /dev/null +++ b/src/margin/margin_transfer_history.rs @@ -0,0 +1,179 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /sapi/v1/margin/transfer` +/// +/// * Response in descending order +/// * Returns data for last 7 days by default +/// * Set `archived` to `true` to query data from 6 months ago +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::margin_transfer_history().asset("BNB").current(1).size(100); +/// ``` +pub struct MarginTransferHistory { + asset: Option, + r#type: Option, + start_time: Option, + end_time: Option, + current: Option, + size: Option, + archived: Option, + recv_window: Option, + credentials: Option, +} + +impl MarginTransferHistory { + pub fn new() -> Self { + Self { + asset: None, + r#type: None, + start_time: None, + end_time: None, + current: None, + size: None, + archived: None, + recv_window: None, + credentials: None, + } + } + + pub fn asset(mut self, asset: &str) -> Self { + self.asset = Some(asset.to_owned()); + self + } + + pub fn r#type(mut self, r#type: &str) -> Self { + self.r#type = Some(r#type.to_owned()); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn current(mut self, current: u32) -> Self { + self.current = Some(current); + self + } + + pub fn size(mut self, size: u32) -> Self { + self.size = Some(size); + self + } + + pub fn archived(mut self, archived: bool) -> Self { + self.archived = Some(archived); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: MarginTransferHistory) -> Request { + let mut params = vec![]; + + if let Some(asset) = request.asset { + params.push(("asset".to_owned(), asset)); + } + + if let Some(r#type) = request.r#type { + params.push(("type".to_owned(), r#type)); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(current) = request.current { + params.push(("current".to_owned(), current.to_string())); + } + + if let Some(size) = request.size { + params.push(("size".to_owned(), size.to_string())); + } + + if let Some(archived) = request.archived { + params.push(("archived".to_owned(), archived.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/margin/transfer".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for MarginTransferHistory { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::MarginTransferHistory; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_margin_transfer_history_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = MarginTransferHistory::new() + .asset("BNB") + .current(1) + .size(100) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/margin/transfer".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("asset".to_owned(), "BNB".to_string()), + ("current".to_owned(), "1".to_string()), + ("size".to_owned(), "100".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin/mod.rs b/src/margin/mod.rs new file mode 100644 index 0000000..4530230 --- /dev/null +++ b/src/margin/mod.rs @@ -0,0 +1,281 @@ +//! Market Data + +pub mod bnb_burn_status; +pub mod isolated_margin_account; +pub mod isolated_margin_account_limit; +pub mod isolated_margin_all_symbols; +pub mod isolated_margin_disable_account; +pub mod isolated_margin_enable_account; +pub mod isolated_margin_fee_data; +pub mod isolated_margin_symbol; +pub mod isolated_margin_tier_data; +pub mod isolated_margin_transfer; +pub mod isolated_margin_transfer_history; +pub mod margin_account; +pub mod margin_all_assets; +pub mod margin_all_oco_order; +pub mod margin_all_orders; +pub mod margin_all_pairs; +pub mod margin_asset; +pub mod margin_borrow; +pub mod margin_cancel_oco_order; +pub mod margin_cancel_open_orders; +pub mod margin_cancel_order; +pub mod margin_dustlog; +pub mod margin_fee_data; +pub mod margin_force_liquidation_record; +pub mod margin_interest_history; +pub mod margin_interest_rate_history; +pub mod margin_loan_record; +pub mod margin_max_borrowable; +pub mod margin_max_transferable; +pub mod margin_my_trades; +pub mod margin_new_oco_order; +pub mod margin_new_order; +pub mod margin_oco_order; +pub mod margin_open_oco_order; +pub mod margin_open_orders; +pub mod margin_order; +pub mod margin_order_count_usage; +pub mod margin_pair; +pub mod margin_price_index; +pub mod margin_repay; +pub mod margin_repay_record; +pub mod margin_transfer; +pub mod margin_transfer_history; +pub mod toggle_bnb_burn; + +use rust_decimal::Decimal; + +use bnb_burn_status::BNBBurnStatus; +use isolated_margin_account::IsolatedMarginAccount; +use isolated_margin_account_limit::IsolatedMarginAccountLimit; +use isolated_margin_all_symbols::IsolatedMarginAllSymbols; +use isolated_margin_disable_account::IsolatedMarginDisableAccount; +use isolated_margin_enable_account::IsolatedMarginEnableAccount; +use isolated_margin_fee_data::IsolatedMarginFeeData; +use isolated_margin_symbol::IsolatedMarginSymbol; +use isolated_margin_tier_data::IsolatedMarginTierData; +use isolated_margin_transfer::IsolatedMarginTransfer; +use isolated_margin_transfer_history::IsolatedMarginTransferHistory; +use margin_account::MarginAccount; +use margin_all_assets::MarginAllAssets; +use margin_all_oco_order::MarginAllOCOOrder; +use margin_all_orders::MarginAllOrders; +use margin_all_pairs::MarginAllPairs; +use margin_asset::MarginAsset; +use margin_borrow::MarginBorrow; +use margin_cancel_oco_order::MarginCancelOCOOrder; +use margin_cancel_open_orders::MarginCancelOpenOrders; +use margin_cancel_order::MarginCancelOrder; +use margin_dustlog::MarginDustlog; +use margin_fee_data::MarginFeeData; +use margin_force_liquidation_record::MarginForceLiquidationRecord; +use margin_interest_history::MarginInterestHistory; +use margin_interest_rate_history::MarginInterestRateHistory; +use margin_loan_record::MarginLoanRecord; +use margin_max_borrowable::MarginMaxBorrowable; +use margin_max_transferable::MarginMaxTransferable; +use margin_my_trades::MarginMyTrades; +use margin_new_oco_order::MarginNewOCOOrder; +use margin_new_order::MarginNewOrder; +use margin_oco_order::MarginOCOOrder; +use margin_open_oco_order::MarginOpenOCOOrder; +use margin_open_orders::MarginOpenOrders; +use margin_order::MarginOrder; +use margin_order_count_usage::MarginOrderCountUsage; +use margin_pair::MarginPair; +use margin_price_index::MarginPriceIndex; +use margin_repay::MarginRepay; +use margin_repay_record::MarginRepayRecord; +use margin_transfer::MarginTransfer; +use margin_transfer_history::MarginTransferHistory; +use toggle_bnb_burn::ToggleBNBBurn; + +pub fn margin_transfer(asset: &str, amount: Decimal, r#type: u32) -> MarginTransfer { + MarginTransfer::new(asset, amount, r#type) +} + +pub fn margin_transfer_history() -> MarginTransferHistory { + MarginTransferHistory::new() +} + +pub fn margin_borrow(asset: &str, amount: Decimal) -> MarginBorrow { + MarginBorrow::new(asset, amount) +} + +pub fn margin_loan_record(asset: &str) -> MarginLoanRecord { + MarginLoanRecord::new(asset) +} + +pub fn margin_repay(asset: &str, amount: Decimal) -> MarginRepay { + MarginRepay::new(asset, amount) +} + +pub fn margin_repay_record(asset: &str) -> MarginRepayRecord { + MarginRepayRecord::new(asset) +} + +pub fn margin_asset(asset: &str) -> MarginAsset { + MarginAsset::new(asset) +} + +pub fn margin_pair(symbol: &str) -> MarginPair { + MarginPair::new(symbol) +} + +pub fn margin_all_assets() -> MarginAllAssets { + MarginAllAssets::new() +} + +pub fn margin_all_pairs() -> MarginAllPairs { + MarginAllPairs::new() +} + +pub fn margin_price_index(symbol: &str) -> MarginPriceIndex { + MarginPriceIndex::new(symbol) +} + +pub fn margin_order(symbol: &str) -> MarginOrder { + MarginOrder::new(symbol) +} + +pub fn margin_new_order(symbol: &str, side: &str, r#type: &str) -> MarginNewOrder { + MarginNewOrder::new(symbol, side, r#type) +} + +pub fn margin_cancel_order(symbol: &str) -> MarginCancelOrder { + MarginCancelOrder::new(symbol) +} + +pub fn margin_interest_history() -> MarginInterestHistory { + MarginInterestHistory::new() +} + +pub fn margin_force_liquidation_record() -> MarginForceLiquidationRecord { + MarginForceLiquidationRecord::new() +} + +pub fn margin_account() -> MarginAccount { + MarginAccount::new() +} + +pub fn margin_open_orders() -> MarginOpenOrders { + MarginOpenOrders::new() +} + +pub fn margin_cancel_open_orders(symbol: &str) -> MarginCancelOpenOrders { + MarginCancelOpenOrders::new(symbol) +} + +pub fn margin_all_orders(symbol: &str) -> MarginAllOrders { + MarginAllOrders::new(symbol) +} + +pub fn margin_new_oco_order( + symbol: &str, + side: &str, + quantity: Decimal, + price: Decimal, + stop_price: Decimal, +) -> MarginNewOCOOrder { + MarginNewOCOOrder::new(symbol, side, quantity, price, stop_price) +} + +pub fn margin_oco_order() -> MarginOCOOrder { + MarginOCOOrder::new() +} + +pub fn margin_cancel_oco_order(symbol: &str) -> MarginCancelOCOOrder { + MarginCancelOCOOrder::new(symbol) +} + +pub fn margin_all_oco_order() -> MarginAllOCOOrder { + MarginAllOCOOrder::new() +} + +pub fn margin_open_oco_order() -> MarginOpenOCOOrder { + MarginOpenOCOOrder::new() +} + +pub fn margin_my_trades(symbol: &str) -> MarginMyTrades { + MarginMyTrades::new(symbol) +} + +pub fn margin_max_borrowable(asset: &str) -> MarginMaxBorrowable { + MarginMaxBorrowable::new(asset) +} + +pub fn margin_max_transferable(asset: &str) -> MarginMaxTransferable { + MarginMaxTransferable::new(asset) +} + +pub fn isolated_margin_transfer_history(symbol: &str) -> IsolatedMarginTransferHistory { + IsolatedMarginTransferHistory::new(symbol) +} + +pub fn isolated_margin_transfer( + asset: &str, + symbol: &str, + trans_from: &str, + trans_to: &str, + amount: Decimal, +) -> IsolatedMarginTransfer { + IsolatedMarginTransfer::new(asset, symbol, trans_from, trans_to, amount) +} + +pub fn isolated_margin_account() -> IsolatedMarginAccount { + IsolatedMarginAccount::new() +} + +pub fn isolated_margin_disable_account(symbol: &str) -> IsolatedMarginDisableAccount { + IsolatedMarginDisableAccount::new(symbol) +} + +pub fn isolated_margin_enable_account(symbol: &str) -> IsolatedMarginEnableAccount { + IsolatedMarginEnableAccount::new(symbol) +} + +pub fn isolated_margin_account_limit() -> IsolatedMarginAccountLimit { + IsolatedMarginAccountLimit::new() +} + +pub fn isolated_margin_symbol(symbol: &str) -> IsolatedMarginSymbol { + IsolatedMarginSymbol::new(symbol) +} + +pub fn isolated_margin_all_symbols() -> IsolatedMarginAllSymbols { + IsolatedMarginAllSymbols::new() +} + +pub fn toggle_bnb_burn() -> ToggleBNBBurn { + ToggleBNBBurn::new() +} + +pub fn bnb_burn_status() -> BNBBurnStatus { + BNBBurnStatus::new() +} + +pub fn margin_interest_rate_history(asset: &str) -> MarginInterestRateHistory { + MarginInterestRateHistory::new(asset) +} + +pub fn margin_fee_data() -> MarginFeeData { + MarginFeeData::new() +} + +pub fn isolated_margin_fee_data() -> IsolatedMarginFeeData { + IsolatedMarginFeeData::new() +} + +pub fn isolated_margin_tier_data(symbol: &str) -> IsolatedMarginTierData { + IsolatedMarginTierData::new(symbol) +} + +pub fn margin_order_count_usage() -> MarginOrderCountUsage { + MarginOrderCountUsage::new() +} + +pub fn margin_dustlog() -> MarginDustlog { + MarginDustlog::new() +} diff --git a/src/margin/toggle_bnb_burn.rs b/src/margin/toggle_bnb_burn.rs new file mode 100644 index 0000000..69daee8 --- /dev/null +++ b/src/margin/toggle_bnb_burn.rs @@ -0,0 +1,120 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `POST /sapi/v1/bnbBurn` +/// +/// * "spotBNBBurn" and "interestBNBBurn" should be sent at least one. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin; +/// +/// let request = margin::toggle_bnb_burn().spot_bnb_burn(true).interest_bnb_burn(false); +/// ``` +pub struct ToggleBNBBurn { + spot_bnb_burn: Option, + interest_bnb_burn: Option, + recv_window: Option, + credentials: Option, +} + +impl ToggleBNBBurn { + pub fn new() -> Self { + Self { + spot_bnb_burn: None, + interest_bnb_burn: None, + recv_window: None, + credentials: None, + } + } + + pub fn spot_bnb_burn(mut self, spot_bnb_burn: bool) -> Self { + self.spot_bnb_burn = Some(spot_bnb_burn); + self + } + + pub fn interest_bnb_burn(mut self, interest_bnb_burn: bool) -> Self { + self.interest_bnb_burn = Some(interest_bnb_burn); + self + } + + pub fn recv_window(mut self, recv_window: u64) -> Self { + self.recv_window = Some(recv_window); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: ToggleBNBBurn) -> Request { + let mut params = vec![]; + + if let Some(spot_bnb_burn) = request.spot_bnb_burn { + params.push(("spotBNBBurn".to_owned(), spot_bnb_burn.to_string())); + } + + if let Some(interest_bnb_burn) = request.interest_bnb_burn { + params.push(("interestBNBBurn".to_owned(), interest_bnb_burn.to_string())); + } + + if let Some(recv_window) = request.recv_window { + params.push(("recvWindow".to_owned(), recv_window.to_string())); + } + + Request { + path: "/sapi/v1/bnbBurn".to_owned(), + method: Method::Post, + params, + credentials: request.credentials, + sign: true, + } + } +} + +impl Default for ToggleBNBBurn { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::ToggleBNBBurn; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_toggle_bnb_burn_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = ToggleBNBBurn::new() + .spot_bnb_burn(true) + .interest_bnb_burn(false) + .recv_window(5000) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/bnbBurn".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![ + ("spotBNBBurn".to_owned(), "true".to_string()), + ("interestBNBBurn".to_owned(), "false".to_string()), + ("recvWindow".to_owned(), "5000".to_string()), + ], + sign: true + } + ); + } +} diff --git a/src/margin_stream/close_listen_key.rs b/src/margin_stream/close_listen_key.rs new file mode 100644 index 0000000..7ab667c --- /dev/null +++ b/src/margin_stream/close_listen_key.rs @@ -0,0 +1,76 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `DELETE /sapi/v1/userDataStream` +/// +/// Close out a user data stream. +/// +/// Weight: 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin_stream; +/// +/// let request = margin_stream::close_listen_key("listen-key"); +/// ``` +pub struct CloseListenKey { + listen_key: String, + credentials: Option, +} + +impl CloseListenKey { + pub fn new(listen_key: &str) -> Self { + Self { + listen_key: listen_key.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: CloseListenKey) -> Request { + let params = vec![("listenKey".to_owned(), request.listen_key.to_string())]; + + Request { + path: "/sapi/v1/userDataStream".to_owned(), + method: Method::Delete, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::CloseListenKey; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_stream_close_listen_key_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = CloseListenKey::new("listen-key") + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/userDataStream".to_owned(), + credentials: Some(credentials), + method: Method::Delete, + params: vec![("listenKey".to_owned(), "listen-key".to_string()),], + sign: false + } + ); + } +} diff --git a/src/margin_stream/mod.rs b/src/margin_stream/mod.rs new file mode 100644 index 0000000..7d0c77f --- /dev/null +++ b/src/margin_stream/mod.rs @@ -0,0 +1,21 @@ +//! Market Data + +pub mod close_listen_key; +pub mod new_listen_key; +pub mod renew_listen_key; + +use close_listen_key::CloseListenKey; +use new_listen_key::NewListenKey; +use renew_listen_key::RenewListenKey; + +pub fn new_listen_key() -> NewListenKey { + NewListenKey::new() +} + +pub fn renew_listen_key(listen_key: &str) -> RenewListenKey { + RenewListenKey::new(listen_key) +} + +pub fn close_listen_key(listen_key: &str) -> CloseListenKey { + CloseListenKey::new(listen_key) +} diff --git a/src/margin_stream/new_listen_key.rs b/src/margin_stream/new_listen_key.rs new file mode 100644 index 0000000..ac114ca --- /dev/null +++ b/src/margin_stream/new_listen_key.rs @@ -0,0 +1,77 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `POST /sapi/v1/userDataStream` +/// +/// Start a new user data stream. +/// The stream will close after 60 minutes unless a keepalive is sent. If the account has an active `listenKey`, that `listenKey` will be returned and its validity will be extended for 60 minutes. +/// +/// Weight: 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin_stream; +/// +/// let request = margin_stream::new_listen_key(); +/// ``` +pub struct NewListenKey { + credentials: Option, +} + +impl NewListenKey { + pub fn new() -> Self { + Self { credentials: None } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(_request: NewListenKey) -> Request { + let params = vec![]; + + Request { + path: "/sapi/v1/userDataStream".to_owned(), + method: Method::Post, + params, + credentials: _request.credentials, + sign: false, + } + } +} + +impl Default for NewListenKey { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::NewListenKey; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_stream_new_listen_key_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = NewListenKey::new().credentials(&credentials).into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/userDataStream".to_owned(), + credentials: Some(credentials), + method: Method::Post, + params: vec![], + sign: false + } + ); + } +} diff --git a/src/margin_stream/renew_listen_key.rs b/src/margin_stream/renew_listen_key.rs new file mode 100644 index 0000000..632d45f --- /dev/null +++ b/src/margin_stream/renew_listen_key.rs @@ -0,0 +1,76 @@ +use crate::http::{request::Request, Credentials, Method}; + +/// `PUT /sapi/v1/userDataStream` +/// +/// Keepalive a user data stream to prevent a time out. User data streams will close after 60 minutes. It's recommended to send a ping about every 30 minutes. +/// +/// Weight: 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::margin_stream; +/// +/// let request = margin_stream::renew_listen_key("listen-key"); +/// ``` +pub struct RenewListenKey { + listen_key: String, + credentials: Option, +} + +impl RenewListenKey { + pub fn new(listen_key: &str) -> Self { + Self { + listen_key: listen_key.to_owned(), + credentials: None, + } + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: RenewListenKey) -> Request { + let params = vec![("listenKey".to_owned(), request.listen_key.to_string())]; + + Request { + path: "/sapi/v1/userDataStream".to_owned(), + method: Method::Put, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::RenewListenKey; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn margin_stream_renew_listen_key_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = RenewListenKey::new("listen-key") + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/sapi/v1/userDataStream".to_owned(), + credentials: Some(credentials), + method: Method::Put, + params: vec![("listenKey".to_owned(), "listen-key".to_string()),], + sign: false + } + ); + } +} diff --git a/src/market/agg_trades.rs b/src/market/agg_trades.rs new file mode 100644 index 0000000..b00db8c --- /dev/null +++ b/src/market/agg_trades.rs @@ -0,0 +1,119 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/aggTrades` +/// +/// Get compressed, aggregate trades. Trades that fill at the time, from the same order, with the same price will have the quantity aggregated. +/// * If `startTime` and `endTime` are sent, time between startTime and endTime must be less than 1 hour. +/// * If `fromId`, `startTime`, and `endTime` are not sent, the most recent aggregate trades will be returned. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::agg_trades("BNBUSDT").from_id(123).start_time(1640995200000).end_time(1640995200000).limit(500); +/// ``` +pub struct AggTrades { + symbol: String, + from_id: Option, + start_time: Option, + end_time: Option, + limit: Option, +} + +impl AggTrades { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + from_id: None, + start_time: None, + end_time: None, + limit: None, + } + } + + pub fn from_id(mut self, from_id: u64) -> Self { + self.from_id = Some(from_id); + self + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn limit(mut self, limit: u32) -> Self { + self.limit = Some(limit); + self + } +} + +impl From for Request { + fn from(request: AggTrades) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(from_id) = request.from_id { + params.push(("fromId".to_owned(), from_id.to_string())); + } + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(limit) = request.limit { + params.push(("limit".to_owned(), limit.to_string())); + } + + Request { + path: "/api/v3/aggTrades".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::AggTrades; + use crate::http::{request::Request, Method}; + + #[test] + fn market_agg_trades_convert_to_request_test() { + let request: Request = AggTrades::new("BNBUSDT") + .from_id(123) + .start_time(1640995200000) + .end_time(1640995200000) + .limit(500) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/aggTrades".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("fromId".to_owned(), "123".to_string()), + ("startTime".to_owned(), "1640995200000".to_string()), + ("endTime".to_owned(), "1640995200000".to_string()), + ("limit".to_owned(), "500".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/avg_price.rs b/src/market/avg_price.rs new file mode 100644 index 0000000..dc27796 --- /dev/null +++ b/src/market/avg_price.rs @@ -0,0 +1,62 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/avgPrice` +/// +/// Current average price for a symbol. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::avg_price("BNBUSDT"); +/// ``` +pub struct AvgPrice { + symbol: String, +} + +impl AvgPrice { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + } + } +} + +impl From for Request { + fn from(request: AvgPrice) -> Request { + let params = vec![("symbol".to_owned(), request.symbol)]; + + Request { + path: "/api/v3/avgPrice".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::AvgPrice; + use crate::http::{request::Request, Method}; + + #[test] + fn market_avg_price_convert_to_request_test() { + let request: Request = AvgPrice::new("BNBUSDT").into(); + + assert_eq!( + request, + Request { + path: "/api/v3/avgPrice".to_owned(), + credentials: None, + method: Method::Get, + params: vec![("symbol".to_owned(), "BNBUSDT".to_string()),], + sign: false + } + ); + } +} diff --git a/src/market/book_ticker.rs b/src/market/book_ticker.rs new file mode 100644 index 0000000..5a8df89 --- /dev/null +++ b/src/market/book_ticker.rs @@ -0,0 +1,101 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/ticker/bookTicker` +/// +/// Best price/qty on the order book for a symbol or symbols. +/// +/// * If the symbol is not sent, bookTickers for all symbols will be returned in an array. +/// +/// Weight(IP): +/// * `1` for a single symbol; +/// * `2` when the symbol parameter is omitted; +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::book_ticker().symbol("BNBUSDT").symbols(vec!["BTCUSDT","BNBBTC"]); +/// ``` +pub struct BookTicker { + symbol: Option, + symbols: Option>, +} + +impl BookTicker { + pub fn new() -> Self { + Self { + symbol: None, + symbols: None, + } + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn symbols(mut self, symbols: Vec<&str>) -> Self { + self.symbols = Some(symbols.iter().map(|s| s.to_string()).collect()); + self + } +} + +impl Default for BookTicker { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: BookTicker) -> Request { + let mut params = vec![]; + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(symbols) = request.symbols { + params.push(( + "symbols".to_owned(), + format!("[\"{}\"]", symbols.join("\",\"")), + )); + } + + Request { + path: "/api/v3/ticker/bookTicker".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::BookTicker; + use crate::http::{request::Request, Method}; + + #[test] + fn market_book_ticker_convert_to_request_test() { + let request: Request = BookTicker::new() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/ticker/bookTicker".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("symbols".to_owned(), "[\"BTCUSDT\",\"BNBBTC\"]".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/depth.rs b/src/market/depth.rs new file mode 100644 index 0000000..d11ae3c --- /dev/null +++ b/src/market/depth.rs @@ -0,0 +1,79 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/depth` +/// +/// | Limit | Weight(IP) | +/// |---------------------|-------------| +/// | 1-100 | 1 | +/// | 101-500 | 5 | +/// | 501-1000 | 10 | +/// | 1001-5000 | 50 | +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::depth("BNBUSDT").limit(100); +/// ``` +pub struct Depth { + symbol: String, + limit: Option, +} + +impl Depth { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + limit: None, + } + } + + pub fn limit(mut self, limit: u32) -> Self { + self.limit = Some(limit); + self + } +} + +impl From for Request { + fn from(request: Depth) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol.to_string())]; + + if let Some(limit) = request.limit { + params.push(("limit".to_owned(), limit.to_string())); + } + + Request { + path: "/api/v3/depth".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::Depth; + use crate::http::{request::Request, Method}; + + #[test] + fn market_depth_convert_to_request_test() { + let request: Request = Depth::new("BNBUSDT").limit(100).into(); + + assert_eq!( + request, + Request { + path: "/api/v3/depth".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("limit".to_owned(), "100".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/exchange_info.rs b/src/market/exchange_info.rs new file mode 100644 index 0000000..4dc92ef --- /dev/null +++ b/src/market/exchange_info.rs @@ -0,0 +1,99 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/exchangeInfo` +/// +/// Current exchange trading rules and symbol information +/// +/// * If any symbol provided in either symbol or symbols do not exist, the endpoint will throw an error. +/// +/// Weight(IP): 10 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::exchange_info().symbol("BNBUSDT").symbols(vec!["BTCUSDT","BNBBTC"]); +/// ``` +pub struct ExchangeInfo { + symbol: Option, + symbols: Option>, +} + +impl ExchangeInfo { + pub fn new() -> Self { + Self { + symbol: None, + symbols: None, + } + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn symbols(mut self, symbols: Vec<&str>) -> Self { + self.symbols = Some(symbols.iter().map(|s| s.to_string()).collect()); + self + } +} + +impl From for Request { + fn from(request: ExchangeInfo) -> Request { + let mut params = vec![]; + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(symbols) = request.symbols { + params.push(( + "symbols".to_owned(), + format!("[\"{}\"]", symbols.join("\",\"")), + )); + } + + Request { + path: "/api/v3/exchangeInfo".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +impl Default for ExchangeInfo { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::ExchangeInfo; + use crate::http::{request::Request, Method}; + + #[test] + fn market_exchange_info_convert_to_request_test() { + let request: Request = ExchangeInfo::new() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/exchangeInfo".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("symbols".to_owned(), "[\"BTCUSDT\",\"BNBBTC\"]".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/historical_trades.rs b/src/market/historical_trades.rs new file mode 100644 index 0000000..ac2689e --- /dev/null +++ b/src/market/historical_trades.rs @@ -0,0 +1,107 @@ +#![allow(clippy::wrong_self_convention)] + +use crate::http::{request::Request, Credentials, Method}; + +/// `GET /api/v3/historicalTrades` +/// +/// Get older market trades. +/// +/// Weight(IP): 5 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::historical_trades("BNBUSDT") +/// .limit(100); +/// ``` +pub struct HistoricalTrades { + symbol: String, + limit: Option, + from_id: Option, + credentials: Option, +} + +impl HistoricalTrades { + pub fn new(symbol: &str) -> Self { + Self { + symbol: symbol.to_owned(), + limit: None, + from_id: None, + credentials: None, + } + } + + pub fn limit(mut self, limit: u32) -> Self { + self.limit = Some(limit); + self + } + + pub fn from_id(mut self, from_id: u64) -> Self { + self.from_id = Some(from_id); + self + } + + pub fn credentials(mut self, credentials: &Credentials) -> Self { + self.credentials = Some(credentials.clone()); + self + } +} + +impl From for Request { + fn from(request: HistoricalTrades) -> Request { + let mut params = vec![("symbol".to_owned(), request.symbol)]; + + if let Some(limit) = request.limit { + params.push(("limit".to_owned(), limit.to_string())); + } + + if let Some(from_id) = request.from_id { + params.push(("fromId".to_owned(), from_id.to_string())); + } + + Request { + path: "/api/v3/historicalTrades".to_owned(), + method: Method::Get, + params, + credentials: request.credentials, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::HistoricalTrades; + use crate::http::{request::Request, Credentials, Method}; + + static API_KEY: &str = "api-key"; + static API_SECRET: &str = "api-secret"; + + #[test] + fn market_historical_trades_convert_to_request_test() { + let credentials = Credentials::from_hmac(API_KEY.to_owned(), API_SECRET.to_owned()); + + let request: Request = HistoricalTrades::new("BNBUSDT") + .from_id(123) + .limit(100) + .credentials(&credentials) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/historicalTrades".to_owned(), + credentials: Some(credentials), + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("limit".to_owned(), 100.to_string()), + ("fromId".to_owned(), 123.to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/klines.rs b/src/market/klines.rs new file mode 100644 index 0000000..10ff2bf --- /dev/null +++ b/src/market/klines.rs @@ -0,0 +1,150 @@ +use crate::http::{request::Request, Method}; +use strum::Display; + +#[derive(Copy, Clone, Display)] +pub enum KlineInterval { + #[strum(serialize = "1m")] + Minutes1, + #[strum(serialize = "3m")] + Minutes3, + #[strum(serialize = "5m")] + Minutes5, + #[strum(serialize = "15m")] + Minutes15, + #[strum(serialize = "30m")] + Minutes30, + #[strum(serialize = "1h")] + Hours1, + #[strum(serialize = "2h")] + Hours2, + #[strum(serialize = "4h")] + Hours4, + #[strum(serialize = "6h")] + Hours6, + #[strum(serialize = "8h")] + Hours8, + #[strum(serialize = "12h")] + Hours12, + #[strum(serialize = "1d")] + Days1, + #[strum(serialize = "3d")] + Days3, + #[strum(serialize = "1w")] + Weeks1, + #[strum(serialize = "1M")] + Months1, +} + +/// `GET /api/v3/klines` +/// +/// Kline/candlestick bars for a symbol. +/// Klines are uniquely identified by their open time. +/// +/// * If `startTime` and `endTime` are not sent, the most recent klines are returned. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market::{self, klines::KlineInterval}; +/// +/// let request = market::klines("BTCUSDT", KlineInterval::Minutes1) +/// .start_time(1654079109000) +/// .end_time(1654079209000); +/// ``` +pub struct Klines { + symbol: String, + interval: KlineInterval, + start_time: Option, + end_time: Option, + limit: Option, +} + +impl Klines { + pub fn new(symbol: &str, interval: KlineInterval) -> Self { + Self { + symbol: symbol.to_owned(), + interval, + start_time: None, + end_time: None, + limit: None, + } + } + + pub fn start_time(mut self, start_time: u64) -> Self { + self.start_time = Some(start_time); + self + } + + pub fn end_time(mut self, end_time: u64) -> Self { + self.end_time = Some(end_time); + self + } + + pub fn limit(mut self, limit: u32) -> Self { + self.limit = Some(limit); + self + } +} + +impl From for Request { + fn from(request: Klines) -> Request { + let mut params = vec![ + ("symbol".to_owned(), request.symbol), + ("interval".to_owned(), request.interval.to_string()), + ]; + + if let Some(start_time) = request.start_time { + params.push(("startTime".to_owned(), start_time.to_string())); + } + + if let Some(end_time) = request.end_time { + params.push(("endTime".to_owned(), end_time.to_string())); + } + + if let Some(limit) = request.limit { + params.push(("limit".to_owned(), limit.to_string())); + } + + Request { + path: "/api/v3/klines".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::{KlineInterval, Klines}; + use crate::http::{request::Request, Method}; + + #[test] + fn market_kline_candlestick_data_convert_to_request_test() { + let request: Request = Klines::new("BTCUSDT", KlineInterval::Minutes1) + .start_time(1654079109000) + .end_time(1654079209000) + .limit(100) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/klines".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BTCUSDT".to_string()), + ("interval".to_owned(), "1m".to_string()), + ("startTime".to_owned(), "1654079109000".to_string()), + ("endTime".to_owned(), "1654079209000".to_string()), + ("limit".to_owned(), "100".to_string()) + ], + sign: false + } + ) + } +} diff --git a/src/market/mod.rs b/src/market/mod.rs new file mode 100644 index 0000000..85f38b2 --- /dev/null +++ b/src/market/mod.rs @@ -0,0 +1,81 @@ +//! Market Data + +pub mod agg_trades; +pub mod avg_price; +pub mod book_ticker; +pub mod depth; +pub mod exchange_info; +pub mod historical_trades; +pub mod klines; +pub mod ping; +pub mod rolling_window_price_change_statistics; +pub mod ticker_price; +pub mod ticker_twenty_four_hr; +pub mod time; +pub mod trades; + +use agg_trades::AggTrades; +use avg_price::AvgPrice; +use book_ticker::BookTicker; +use depth::Depth; +use exchange_info::ExchangeInfo; +use historical_trades::HistoricalTrades; +use klines::{KlineInterval, Klines}; +use ping::Ping; +use rolling_window_price_change_statistics::RollingWindowPriceChangeStatistics; +use ticker_price::TickerPrice; +use ticker_twenty_four_hr::Ticker24hr; +use time::Time; +use trades::Trades; + +pub fn ping() -> Ping { + Ping::new() +} + +pub fn time() -> Time { + Time::new() +} + +pub fn exchange_info() -> ExchangeInfo { + ExchangeInfo::new() +} + +pub fn depth(symbol: &str) -> Depth { + Depth::new(symbol) +} + +pub fn trades(symbol: &str) -> Trades { + Trades::new(symbol) +} + +pub fn historical_trades(symbol: &str) -> HistoricalTrades { + HistoricalTrades::new(symbol) +} + +pub fn agg_trades(symbol: &str) -> AggTrades { + AggTrades::new(symbol) +} + +pub fn klines(symbol: &str, interval: KlineInterval) -> Klines { + Klines::new(symbol, interval) +} + +pub fn avg_price(symbol: &str) -> AvgPrice { + AvgPrice::new(symbol) +} + +pub fn ticker_twenty_four_hr() -> Ticker24hr { + Ticker24hr::new() +} + +pub fn ticker_price() -> TickerPrice { + TickerPrice::new() +} + +pub fn book_ticker() -> BookTicker { + BookTicker::new() +} + +pub fn rolling_window_price_change_statistics() -> RollingWindowPriceChangeStatistics { + RollingWindowPriceChangeStatistics::new() +} diff --git a/src/market/ping.rs b/src/market/ping.rs new file mode 100644 index 0000000..c19fff1 --- /dev/null +++ b/src/market/ping.rs @@ -0,0 +1,64 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/ping` +/// +/// Test connectivity to the Rest API. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::ping(); +/// ``` +pub struct Ping {} + +impl Ping { + pub fn new() -> Self { + Self {} + } +} + +impl From for Request { + fn from(_request: Ping) -> Request { + let params = vec![]; + + Request { + path: "/api/v3/ping".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +impl Default for Ping { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::Ping; + use crate::http::{request::Request, Method}; + + #[test] + fn market_ping_convert_to_request_test() { + let request: Request = Ping::new().into(); + + assert_eq!( + request, + Request { + path: "/api/v3/ping".to_owned(), + credentials: None, + method: Method::Get, + params: vec![], + sign: false + } + ); + } +} diff --git a/src/market/rolling_window_price_change_statistics.rs b/src/market/rolling_window_price_change_statistics.rs new file mode 100644 index 0000000..c32bc94 --- /dev/null +++ b/src/market/rolling_window_price_change_statistics.rs @@ -0,0 +1,114 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/ticker` +/// +/// The window used to compute statistics is typically slightly wider than requested windowSize. +/// +/// openTime for /api/v3/ticker always starts on a minute, while the closeTime is the current time of the request. As such, the effective window might be up to 1 minute wider than requested. +/// +/// E.g. If the closeTime is 1641287867099 (January 04, 2022 09:17:47:099 UTC) , and the windowSize is 1d. the openTime will be: 1641201420000 (January 3, 2022, 09:17:00 UTC) +/// +/// Weight(IP): 2 for each requested symbol regardless of windowSize. +/// +/// The weight for this request will cap at 100 once the number of symbols in the request is more than 50. +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::rolling_window_price_change_statistics().symbol("BNBUSDT").symbols(vec!["BTCUSDT","BNBBTC"]); +/// ``` +pub struct RollingWindowPriceChangeStatistics { + symbol: Option, + symbols: Option>, + window_size: Option, +} + +impl RollingWindowPriceChangeStatistics { + pub fn new() -> Self { + Self { + symbol: None, + symbols: None, + window_size: None, + } + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn symbols(mut self, symbols: Vec<&str>) -> Self { + self.symbols = Some(symbols.iter().map(|s| s.to_string()).collect()); + self + } + + pub fn window_size(mut self, window_size: &str) -> Self { + self.window_size = Some(window_size.to_owned()); + self + } +} + +impl Default for RollingWindowPriceChangeStatistics { + fn default() -> Self { + Self::new() + } +} + +impl From for Request { + fn from(request: RollingWindowPriceChangeStatistics) -> Request { + let mut params = vec![]; + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(symbols) = request.symbols { + params.push(( + "symbols".to_owned(), + format!("[\"{}\"]", symbols.join("\",\"")), + )); + } + + if let Some(window_size) = request.window_size { + params.push(("windowSize".to_owned(), window_size)); + } + + Request { + path: "/api/v3/ticker".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::RollingWindowPriceChangeStatistics; + use crate::http::{request::Request, Method}; + + #[test] + fn market_rolling_window_price_change_statistics_convert_to_request_test() { + let request: Request = RollingWindowPriceChangeStatistics::new() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/ticker".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("symbols".to_owned(), "[\"BTCUSDT\",\"BNBBTC\"]".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/ticker_price.rs b/src/market/ticker_price.rs new file mode 100644 index 0000000..58ccbc2 --- /dev/null +++ b/src/market/ticker_price.rs @@ -0,0 +1,101 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/ticker/price` +/// +/// Latest price for a symbol or symbols. +/// +/// * If the symbol is not sent, prices for all symbols will be returned in an array. +/// +/// Weight(IP): +/// * `1` for a single symbol; +/// * `2` when the symbol parameter is omitted; +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::ticker_price().symbol("BNBUSDT").symbols(vec!["BTCUSDT","BNBBTC"]); +/// ``` +pub struct TickerPrice { + symbol: Option, + symbols: Option>, +} + +impl TickerPrice { + pub fn new() -> Self { + Self { + symbol: None, + symbols: None, + } + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn symbols(mut self, symbols: Vec<&str>) -> Self { + self.symbols = Some(symbols.iter().map(|s| s.to_string()).collect()); + self + } +} + +impl From for Request { + fn from(request: TickerPrice) -> Request { + let mut params = vec![]; + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(symbols) = request.symbols { + params.push(( + "symbols".to_owned(), + format!("[\"{}\"]", symbols.join("\",\"")), + )); + } + + Request { + path: "/api/v3/ticker/price".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +impl Default for TickerPrice { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::TickerPrice; + use crate::http::{request::Request, Method}; + + #[test] + fn market_ticker_price_convert_to_request_test() { + let request: Request = TickerPrice::new() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/ticker/price".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("symbols".to_owned(), "[\"BTCUSDT\",\"BNBBTC\"]".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/ticker_twenty_four_hr.rs b/src/market/ticker_twenty_four_hr.rs new file mode 100644 index 0000000..bd3e813 --- /dev/null +++ b/src/market/ticker_twenty_four_hr.rs @@ -0,0 +1,101 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/ticker/24hr` +/// +/// 24 hour rolling window price change statistics. Careful when accessing this with no symbol. +/// +/// * If the symbol is not sent, tickers for all symbols will be returned in an array. +/// +/// Weight(IP): +/// * `1` for a single symbol; +/// * `40` when the symbol parameter is omitted; +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::ticker_twenty_four_hr().symbol("BNBUSDT").symbols(vec!["BTCUSDT","BNBBTC"]); +/// ``` +pub struct Ticker24hr { + symbol: Option, + symbols: Option>, +} + +impl Ticker24hr { + pub fn new() -> Self { + Self { + symbol: None, + symbols: None, + } + } + + pub fn symbol(mut self, symbol: &str) -> Self { + self.symbol = Some(symbol.to_owned()); + self + } + + pub fn symbols(mut self, symbols: Vec<&str>) -> Self { + self.symbols = Some(symbols.iter().map(|s| s.to_string()).collect()); + self + } +} + +impl From for Request { + fn from(request: Ticker24hr) -> Request { + let mut params = vec![]; + + if let Some(symbol) = request.symbol { + params.push(("symbol".to_owned(), symbol)); + } + + if let Some(symbols) = request.symbols { + params.push(( + "symbols".to_owned(), + format!("[\"{}\"]", symbols.join("\",\"")), + )); + } + + Request { + path: "/api/v3/ticker/24hr".to_owned(), + method: Method::Get, + params, + credentials: None, + sign: false, + } + } +} + +impl Default for Ticker24hr { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::Ticker24hr; + use crate::http::{request::Request, Method}; + + #[test] + fn market_ticker_twenty_four_hr_convert_to_request_test() { + let request: Request = Ticker24hr::new() + .symbol("BNBUSDT") + .symbols(vec!["BTCUSDT", "BNBBTC"]) + .into(); + + assert_eq!( + request, + Request { + path: "/api/v3/ticker/24hr".to_owned(), + credentials: None, + method: Method::Get, + params: vec![ + ("symbol".to_owned(), "BNBUSDT".to_string()), + ("symbols".to_owned(), "[\"BTCUSDT\",\"BNBBTC\"]".to_string()), + ], + sign: false + } + ); + } +} diff --git a/src/market/time.rs b/src/market/time.rs new file mode 100644 index 0000000..1341f17 --- /dev/null +++ b/src/market/time.rs @@ -0,0 +1,64 @@ +use crate::http::{request::Request, Method}; + +/// `GET /api/v3/time` +/// +/// Test connectivity to the Rest API and get the current server time. +/// +/// Weight(IP): 1 +/// +/// # Example +/// +/// ``` +/// use binance_spot_connector_rust::market; +/// +/// let request = market::time(); +/// ``` +pub struct Time {} + +impl Time { + pub fn new() -> Self { + Self {} + } +} + +impl From