binance-rs-async 1.1.4

Async Rust Library for the Binance API
Documentation
use crate::client::*;
use crate::errors::*;
use crate::rest_model::*;
use crate::util::*;
use serde_json::from_str;
use std::collections::BTreeMap;

static API_V3_ACCOUNT: &str = "/api/v3/account";
static API_V3_OPEN_ORDERS: &str = "/api/v3/openOrders";
static API_V3_ALL_ORDERS: &str = "/api/v3/allOrders";
static API_V3_MYTRADES: &str = "/api/v3/myTrades";
static API_V3_ORDER: &str = "/api/v3/order";
/// Endpoint for test orders.
/// Orders issued to this endpoint are validated, but not sent into the matching engine.
static API_V3_ORDER_TEST: &str = "/api/v3/order/test";

/// Account API access, full example provided in examples/binance_endpoints.rs
#[derive(Clone)]
pub struct Account {
    pub client: Client,
    pub recv_window: u64,
}

/// Order Request
/// perform an order for the account
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OrderRequest {
    pub symbol: String,
    pub side: OrderSide,
    #[serde(rename = "type")]
    pub order_type: OrderType,
    pub time_in_force: Option<TimeInForce>,
    pub quantity: Option<f64>,
    pub quote_order_qty: Option<f64>,
    pub price: Option<f64>,
    /// A unique id for the order, automatically generated if not sent.
    pub new_client_order_id: Option<String>,
    /// Used with stop loss, stop loss limit, take profit and take profit limit order types.
    pub stop_price: Option<f64>,
    /// Used with limit, stop loss limit and take profit limit to create an iceberg order.
    pub iceberg_qty: Option<f64>,
    /// Set the response json, market and limit default to full others to ack.
    pub new_order_resp_type: Option<OrderResponse>,
    /// Cannot be greater than 60000
    pub recv_window: Option<u64>,
}

impl OrderRequest {
    fn valid(&self) -> Result<()> {
        if self.iceberg_qty.is_some() && self.time_in_force != Some(TimeInForce::GTC) {
            return Err(Error::InvalidOrderError {
                msg: "Time in force has to be GTC for iceberg orders".to_string(),
            });
        }
        Ok(())
    }
}

/// Order Cancellation Request
/// perform an order cancellation for the account
/// only works if the parameters match an active order
/// either order_id (binance side id) or orig_client_order_id (id originally given by the client) must be set
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OrderCancellation {
    pub symbol: String,
    pub order_id: Option<u64>,
    pub orig_client_order_id: Option<String>,
    /// Used to uniquely identify this cancel. Automatically generated by default.
    pub new_client_order_id: Option<String>,
    /// Cannot be greater than 60000
    pub recv_window: Option<u64>,
}

/// Order Status Request
/// perform an order status request for the account
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OrderStatusRequest {
    pub symbol: String,
    pub order_id: Option<u64>,
    pub orig_client_order_id: Option<String>,
    /// Cannot be greater than 60000
    pub recv_window: Option<u64>,
}

/// Order Status Request
/// perform a query on all orders for the account
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OrdersQuery {
    pub symbol: String,
    pub order_id: Option<u64>,
    pub start_time: Option<u64>,
    pub end_time: Option<u64>,
    /// Default 500 max 1000
    pub limit: Option<u32>,
    /// Cannot be greater than 60000
    pub recv_window: Option<u64>,
}

impl Account {
    /// General account information
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let account = tokio_test::block_on(account.get_account());
    /// assert!(account.is_ok(), "{:?}", account);
    /// ```
    pub async fn get_account(&self) -> Result<AccountInformation> {
        let parameters: BTreeMap<String, String> = BTreeMap::new();

        let request = build_signed_request(parameters, self.recv_window)?;
        let data = self.client.get_signed(API_V3_ACCOUNT, &request).await?;
        let account_info: AccountInformation = from_str(data.as_str())?;

        Ok(account_info)
    }

    /// Account balance for a single asset
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let balance = tokio_test::block_on(account.get_balance("BTC"));
    /// assert!(balance.is_ok(), "{:?}", balance);
    /// ```
    pub async fn get_balance<S>(&self, asset: S) -> Result<Balance>
    where
        S: Into<String>,
    {
        match self.get_account().await {
            Ok(account) => {
                let cmp_asset = asset.into();
                for balance in account.balances {
                    if balance.asset == cmp_asset {
                        return Ok(balance);
                    }
                }
                Err(Error::Msg("Asset not found".to_string()))
            }
            Err(e) => Err(e),
        }
    }

    /// All currently open orders for a single symbol
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let orders = tokio_test::block_on(account.get_open_orders("BTCUSDT"));
    /// assert!(orders.is_ok(), "{:?}", orders);
    /// ```
    pub async fn get_open_orders<S>(&self, symbol: S) -> Result<Vec<Order>>
    where
        S: Into<String>,
    {
        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
        parameters.insert("symbol".into(), symbol.into());

        let request = build_signed_request(parameters, self.recv_window)?;
        let data = self.client.get_signed(API_V3_OPEN_ORDERS, &request).await?;
        let order: Vec<Order> = from_str(data.as_str())?;

        Ok(order)
    }

    /// All orders for the account
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let query = OrdersQuery {
    ///     symbol: "BTCUSDT".to_string(),
    ///     order_id: None,
    ///     start_time: None,
    ///     end_time: None,
    ///     limit: None,
    ///     recv_window: None,
    /// };
    /// let orders = tokio_test::block_on(account.get_all_orders(query));
    /// assert!(orders.is_ok(), "{:?}", orders);
    /// ```
    pub async fn get_all_orders(&self, query: OrdersQuery) -> Result<Vec<Order>> {
        let recv_window = query.recv_window.unwrap_or(self.recv_window);
        let request = build_signed_request_p(query, recv_window)?;
        let data = self.client.get_signed(API_V3_ALL_ORDERS, &request).await?;
        let order: Vec<Order> = from_str(data.as_str())?;

        Ok(order)
    }

    /// All currently open orders for the account
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let orders = tokio_test::block_on(account.get_all_open_orders());
    /// assert!(orders.is_ok(), "{:?}", orders);
    /// ```
    pub async fn get_all_open_orders(&self) -> Result<Vec<Order>> {
        let request = build_signed_request(BTreeMap::new(), self.recv_window)?;
        let data = self.client.get_signed(API_V3_OPEN_ORDERS, &request).await?;
        let order: Vec<Order> = from_str(data.as_str())?;

        Ok(order)
    }

    /// Cancels all currently open orders of specified symbol for the account
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let canceled_orders = tokio_test::block_on(account.cancel_all_open_orders());
    /// assert!(canceled_orders.is_ok(), "{:?}", canceled_orders);
    /// ```
    pub async fn cancel_all_open_orders<S>(&self, symbol: S) -> Result<Vec<Order>>
    where
        S: Into<String>,
    {
        let mut params: BTreeMap<String, String> = BTreeMap::new();
        params.insert("symbol".into(), symbol.into());
        let request = build_signed_request(params, self.recv_window)?;
        let data = self.client.delete_signed(API_V3_OPEN_ORDERS, &request).await?;
        let order: Vec<Order> = from_str(data.as_str())?;
        Ok(order)
    }

    /// Check an order's status
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let query = OrderStatusRequest {
    ///     symbol: "BTCUSDT".to_string(),
    ///     order_id: Some(1),
    ///     orig_client_order_id: Some("my_id".to_string()),
    ///     recv_window: None
    /// };
    /// let order = tokio_test::block_on(account.order_status(query));
    /// assert!(order.is_ok(), "{:?}", order);
    /// ```
    pub async fn order_status(&self, osr: OrderStatusRequest) -> Result<Order> {
        let recv_window = osr.recv_window.unwrap_or(self.recv_window);
        let request = build_signed_request_p(osr, recv_window)?;
        let data = self.client.get_signed(API_V3_ORDER, &request).await?;
        let order: Order = from_str(data.as_str())?;

        Ok(order)
    }

    /// Place a test status order
    ///
    /// This order is sandboxed: it is validated, but not sent to the matching engine.
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let query = OrderStatusRequest {
    ///     symbol: "BTCUSDT".to_string(),
    ///     order_id: Some(1),
    ///     orig_client_order_id: Some("my_id".to_string()),
    ///     recv_window: None
    /// };
    /// let resp = tokio_test::block_on(account.test_order_status(query));
    /// assert!(resp.is_ok(), "{:?}", resp);
    /// ```
    pub async fn test_order_status(&self, osr: OrderStatusRequest) -> Result<TestResponse> {
        let recv_window = osr.recv_window.unwrap_or(self.recv_window);
        let request = build_signed_request_p(osr, recv_window)?;
        let data = self.client.get_signed(API_V3_ORDER_TEST, &request).await?;
        let tr: TestResponse = from_str(data.as_str())?;

        Ok(tr)
    }

    /// Place an order
    /// Returns the Transaction if Ok
    /// This methods validates the order request before sending, making sure it complies with Binance rules
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*, rest_model::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let limit_buy = OrderRequest {
    ///         symbol: "BTCUSDT".to_string(),
    ///         quantity: Some(10.0),
    ///         price: Some(0.014000),
    ///         order_type: OrderType::Limit,
    ///         side: OrderSide::Buy,
    ///         time_in_force: Some(TimeInForce::FOK),
    ///         ..OrderRequest::default()
    ///     };
    /// let transaction = tokio_test::block_on(account.place_order(limit_buy));
    /// assert!(transaction.is_ok(), "{:?}", transaction);
    /// ```
    pub async fn place_order(&self, order: OrderRequest) -> Result<Transaction> {
        let _ = order.valid()?;
        let recv_window = order.recv_window.unwrap_or(self.recv_window);
        let request = build_signed_request_p(order, recv_window)?;
        let data = self.client.post_signed(API_V3_ORDER, &request).await?;
        let transaction: Transaction = from_str(data.as_str())?;

        Ok(transaction)
    }

    /// Place a test order
    ///
    /// Despite being a test, this order is still validated before calls
    /// This order is sandboxed: it is validated, but not sent to the matching engine.
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*, rest_model::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let limit_buy = OrderRequest {
    ///         symbol: "BTCUSDT".to_string(),
    ///         quantity: Some(10.0),
    ///         price: Some(0.014000),
    ///         order_type: OrderType::Limit,
    ///         side: OrderSide::Buy,
    ///         time_in_force: Some(TimeInForce::FOK),
    ///         ..OrderRequest::default()
    ///     };
    /// let resp = tokio_test::block_on(account.place_test_order(limit_buy));
    /// assert!(resp.is_ok(), "{:?}", resp);
    /// ```
    pub async fn place_test_order(&self, order: OrderRequest) -> Result<TestResponse> {
        let _ = order.valid()?;
        let recv_window = order.recv_window.unwrap_or(self.recv_window);
        let request = build_signed_request_p(order, recv_window)?;
        let data = self.client.post_signed(API_V3_ORDER_TEST, &request).await?;
        let tr: TestResponse = from_str(data.as_str())?;
        Ok(tr)
    }

    /// Place a cancellation order
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let query = OrderCancellation {
    ///     symbol: "BTCUSDT".to_string(),
    ///     order_id: Some(1),
    ///     orig_client_order_id: Some("my_id".to_string()),
    ///     new_client_order_id: None,
    ///     recv_window: None
    /// };
    /// let canceled = tokio_test::block_on(account.cancel_order(query));
    /// assert!(canceled.is_ok(), "{:?}", canceled);
    /// ```
    pub async fn cancel_order(&self, o: OrderCancellation) -> Result<OrderCanceled> {
        let recv_window = o.recv_window.unwrap_or(self.recv_window);
        let request = build_signed_request_p(o, recv_window)?;
        let data = self.client.delete_signed(API_V3_ORDER, &request).await?;
        let order_canceled: OrderCanceled = from_str(data.as_str())?;

        Ok(order_canceled)
    }

    /// Place a test cancel order
    ///
    /// This order is sandboxed: it is validated, but not sent to the matching engine.
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let query = OrderCancellation {
    ///     symbol: "BTCUSDT".to_string(),
    ///     order_id: Some(1),
    ///     orig_client_order_id: Some("my_id".to_string()),
    ///     new_client_order_id: None,
    ///     recv_window: None
    /// };
    /// let response = tokio_test::block_on(account.test_cancel_order(query));
    /// assert!(response.is_ok(), "{:?}", response);
    /// ```
    pub async fn test_cancel_order(&self, o: OrderCancellation) -> Result<TestResponse> {
        let recv_window = o.recv_window.unwrap_or(self.recv_window);
        let request = build_signed_request_p(o, recv_window)?;
        let data = self.client.delete_signed(API_V3_ORDER_TEST, &request).await?;
        let tr: TestResponse = from_str(data.as_str())?;

        Ok(tr)
    }

    /// Trade history
    /// # Examples
    /// ```rust,no_run
    /// use binance::{api::*, account::*, config::*};
    /// let account: Account = Binance::new_with_env(&Config::testnet());
    /// let trade_history = tokio_test::block_on(account.trade_history("BTCUSDT"));
    /// assert!(trade_history.is_ok(), "{:?}", trade_history);
    /// ```
    pub async fn trade_history<S>(&self, symbol: S) -> Result<Vec<TradeHistory>>
    where
        S: Into<String>,
    {
        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
        parameters.insert("symbol".into(), symbol.into());

        let request = build_signed_request(parameters, self.recv_window)?;
        let data = self.client.get_signed(API_V3_MYTRADES, &request).await?;
        let trade_history: Vec<TradeHistory> = from_str(data.as_str())?;

        Ok(trade_history)
    }
}