use error_chain::bail;
use crate::util::build_signed_request;
use crate::model::{
AccountInformation, Balance, Empty, Order, OrderCanceled, TradeHistory, Transaction,
};
use crate::client::Client;
use crate::errors::Result;
use std::collections::BTreeMap;
use std::fmt::Display;
use crate::api::API;
use crate::api::Spot;
#[derive(Clone)]
pub struct Account {
pub client: Client,
pub recv_window: u64,
}
struct OrderRequest {
pub symbol: String,
pub qty: f64,
pub price: f64,
pub stop_price: Option<f64>,
pub order_side: OrderSide,
pub order_type: OrderType,
pub time_in_force: TimeInForce,
pub new_client_order_id: Option<String>,
}
struct OrderQuoteQuantityRequest {
pub symbol: String,
pub quote_order_qty: f64,
pub price: f64,
pub order_side: OrderSide,
pub order_type: OrderType,
pub time_in_force: TimeInForce,
pub new_client_order_id: Option<String>,
}
pub enum OrderType {
Limit,
Market,
StopLossLimit,
}
impl Display for OrderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Limit => write!(f, "LIMIT"),
Self::Market => write!(f, "MARKET"),
Self::StopLossLimit => write!(f, "STOP_LOSS_LIMIT"),
}
}
}
pub enum OrderSide {
Buy,
Sell,
}
impl Display for OrderSide {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Buy => write!(f, "BUY"),
Self::Sell => write!(f, "SELL"),
}
}
}
#[allow(clippy::all)]
pub enum TimeInForce {
GTC,
IOC,
FOK,
}
impl Display for TimeInForce {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::GTC => write!(f, "GTC"),
Self::IOC => write!(f, "IOC"),
Self::FOK => write!(f, "FOK"),
}
}
}
impl Account {
pub fn get_account(&self) -> Result<AccountInformation> {
let request = build_signed_request(BTreeMap::new(), self.recv_window)?;
self.client
.get_signed(API::Spot(Spot::Account), Some(request))
}
pub fn get_balance<S>(&self, asset: S) -> Result<Balance>
where
S: Into<String>,
{
match self.get_account() {
Ok(account) => {
let cmp_asset = asset.into();
for balance in account.balances {
if balance.asset == cmp_asset {
return Ok(balance);
}
}
bail!("Asset not found");
}
Err(e) => Err(e),
}
}
pub 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)?;
self.client
.get_signed(API::Spot(Spot::OpenOrders), Some(request))
}
pub fn get_all_open_orders(&self) -> Result<Vec<Order>> {
let parameters: BTreeMap<String, String> = BTreeMap::new();
let request = build_signed_request(parameters, self.recv_window)?;
self.client
.get_signed(API::Spot(Spot::OpenOrders), Some(request))
}
pub fn cancel_all_open_orders<S>(&self, symbol: S) -> Result<Vec<OrderCanceled>>
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)?;
self.client
.delete_signed(API::Spot(Spot::OpenOrders), Some(request))
}
pub fn order_status<S>(&self, symbol: S, order_id: u64) -> Result<Order>
where
S: Into<String>,
{
let mut parameters: BTreeMap<String, String> = BTreeMap::new();
parameters.insert("symbol".into(), symbol.into());
parameters.insert("orderId".into(), order_id.to_string());
let request = build_signed_request(parameters, self.recv_window)?;
self.client
.get_signed(API::Spot(Spot::Order), Some(request))
}
pub fn test_order_status<S>(&self, symbol: S, order_id: u64) -> Result<()>
where
S: Into<String>,
{
let mut parameters: BTreeMap<String, String> = BTreeMap::new();
parameters.insert("symbol".into(), symbol.into());
parameters.insert("orderId".into(), order_id.to_string());
let request = build_signed_request(parameters, self.recv_window)?;
self.client
.get_signed::<Empty>(API::Spot(Spot::OrderTest), Some(request))
.map(|_| ())
}
pub fn limit_buy<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let buy = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: None,
order_side: OrderSide::Buy,
order_type: OrderType::Limit,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(buy);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_limit_buy<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let buy = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: None,
order_side: OrderSide::Buy,
order_type: OrderType::Limit,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(buy);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn limit_sell<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: None,
order_side: OrderSide::Sell,
order_type: OrderType::Limit,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_limit_sell<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: None,
order_side: OrderSide::Sell,
order_type: OrderType::Limit,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn market_buy<S, F>(&self, symbol: S, qty: F) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let buy = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price: 0.0,
stop_price: None,
order_side: OrderSide::Buy,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(buy);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_market_buy<S, F>(&self, symbol: S, qty: F) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let buy = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price: 0.0,
stop_price: None,
order_side: OrderSide::Buy,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(buy);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn market_buy_using_quote_quantity<S, F>(
&self, symbol: S, quote_order_qty: F,
) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let buy = OrderQuoteQuantityRequest {
symbol: symbol.into(),
quote_order_qty: quote_order_qty.into(),
price: 0.0,
order_side: OrderSide::Buy,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_quote_quantity_order(buy);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_market_buy_using_quote_quantity<S, F>(
&self, symbol: S, quote_order_qty: F,
) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let buy = OrderQuoteQuantityRequest {
symbol: symbol.into(),
quote_order_qty: quote_order_qty.into(),
price: 0.0,
order_side: OrderSide::Buy,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_quote_quantity_order(buy);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn market_sell<S, F>(&self, symbol: S, qty: F) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price: 0.0,
stop_price: None,
order_side: OrderSide::Sell,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_market_sell<S, F>(&self, symbol: S, qty: F) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price: 0.0,
stop_price: None,
order_side: OrderSide::Sell,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn market_sell_using_quote_quantity<S, F>(
&self, symbol: S, quote_order_qty: F,
) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderQuoteQuantityRequest {
symbol: symbol.into(),
quote_order_qty: quote_order_qty.into(),
price: 0.0,
order_side: OrderSide::Sell,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_quote_quantity_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_market_sell_using_quote_quantity<S, F>(
&self, symbol: S, quote_order_qty: F,
) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderQuoteQuantityRequest {
symbol: symbol.into(),
quote_order_qty: quote_order_qty.into(),
price: 0.0,
order_side: OrderSide::Sell,
order_type: OrderType::Market,
time_in_force: TimeInForce::GTC,
new_client_order_id: None,
};
let order = self.build_quote_quantity_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn stop_limit_buy_order<S, F>(
&self, symbol: S, qty: F, price: f64, stop_price: f64, time_in_force: TimeInForce,
) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: Some(stop_price),
order_side: OrderSide::Buy,
order_type: OrderType::StopLossLimit,
time_in_force,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_stop_limit_buy_order<S, F>(
&self, symbol: S, qty: F, price: f64, stop_price: f64, time_in_force: TimeInForce,
) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: Some(stop_price),
order_side: OrderSide::Buy,
order_type: OrderType::StopLossLimit,
time_in_force,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn stop_limit_sell_order<S, F>(
&self, symbol: S, qty: F, price: f64, stop_price: f64, time_in_force: TimeInForce,
) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: Some(stop_price),
order_side: OrderSide::Sell,
order_type: OrderType::StopLossLimit,
time_in_force,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
pub fn test_stop_limit_sell_order<S, F>(
&self, symbol: S, qty: F, price: f64, stop_price: f64, time_in_force: TimeInForce,
) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price: Some(stop_price),
order_side: OrderSide::Sell,
order_type: OrderType::StopLossLimit,
time_in_force,
new_client_order_id: None,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
#[allow(clippy::too_many_arguments)]
pub fn custom_order<S, F>(
&self, symbol: S, qty: F, price: f64, stop_price: Option<f64>, order_side: OrderSide,
order_type: OrderType, time_in_force: TimeInForce, new_client_order_id: Option<String>,
) -> Result<Transaction>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price,
order_side,
order_type,
time_in_force,
new_client_order_id,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client.post_signed(API::Spot(Spot::Order), request)
}
#[allow(clippy::too_many_arguments)]
pub fn test_custom_order<S, F>(
&self, symbol: S, qty: F, price: f64, stop_price: Option<f64>, order_side: OrderSide,
order_type: OrderType, time_in_force: TimeInForce, new_client_order_id: Option<String>,
) -> Result<()>
where
S: Into<String>,
F: Into<f64>,
{
let sell = OrderRequest {
symbol: symbol.into(),
qty: qty.into(),
price,
stop_price,
order_side,
order_type,
time_in_force,
new_client_order_id,
};
let order = self.build_order(sell);
let request = build_signed_request(order, self.recv_window)?;
self.client
.post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
.map(|_| ())
}
pub fn cancel_order<S>(&self, symbol: S, order_id: u64) -> Result<OrderCanceled>
where
S: Into<String>,
{
let mut parameters: BTreeMap<String, String> = BTreeMap::new();
parameters.insert("symbol".into(), symbol.into());
parameters.insert("orderId".into(), order_id.to_string());
let request = build_signed_request(parameters, self.recv_window)?;
self.client
.delete_signed(API::Spot(Spot::Order), Some(request))
}
pub fn cancel_order_with_client_id<S>(
&self, symbol: S, orig_client_order_id: String,
) -> Result<OrderCanceled>
where
S: Into<String>,
{
let mut parameters: BTreeMap<String, String> = BTreeMap::new();
parameters.insert("symbol".into(), symbol.into());
parameters.insert("origClientOrderId".into(), orig_client_order_id);
let request = build_signed_request(parameters, self.recv_window)?;
self.client
.delete_signed(API::Spot(Spot::Order), Some(request))
}
pub fn test_cancel_order<S>(&self, symbol: S, order_id: u64) -> Result<()>
where
S: Into<String>,
{
let mut parameters: BTreeMap<String, String> = BTreeMap::new();
parameters.insert("symbol".into(), symbol.into());
parameters.insert("orderId".into(), order_id.to_string());
let request = build_signed_request(parameters, self.recv_window)?;
self.client
.delete_signed::<Empty>(API::Spot(Spot::OrderTest), Some(request))
.map(|_| ())
}
pub 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)?;
self.client
.get_signed(API::Spot(Spot::MyTrades), Some(request))
}
fn build_order(&self, order: OrderRequest) -> BTreeMap<String, String> {
let mut order_parameters: BTreeMap<String, String> = BTreeMap::new();
order_parameters.insert("symbol".into(), order.symbol);
order_parameters.insert("side".into(), order.order_side.to_string());
order_parameters.insert("type".into(), order.order_type.to_string());
order_parameters.insert("quantity".into(), order.qty.to_string());
if let Some(stop_price) = order.stop_price {
order_parameters.insert("stopPrice".into(), stop_price.to_string());
}
if order.price != 0.0 {
order_parameters.insert("price".into(), order.price.to_string());
order_parameters.insert("timeInForce".into(), order.time_in_force.to_string());
}
if let Some(client_order_id) = order.new_client_order_id {
order_parameters.insert("newClientOrderId".into(), client_order_id);
}
order_parameters
}
fn build_quote_quantity_order(
&self, order: OrderQuoteQuantityRequest,
) -> BTreeMap<String, String> {
let mut order_parameters: BTreeMap<String, String> = BTreeMap::new();
order_parameters.insert("symbol".into(), order.symbol);
order_parameters.insert("side".into(), order.order_side.to_string());
order_parameters.insert("type".into(), order.order_type.to_string());
order_parameters.insert("quoteOrderQty".into(), order.quote_order_qty.to_string());
if order.price != 0.0 {
order_parameters.insert("price".into(), order.price.to_string());
order_parameters.insert("timeInForce".into(), order.time_in_force.to_string());
}
if let Some(client_order_id) = order.new_client_order_id {
order_parameters.insert("newClientOrderId".into(), client_order_id);
}
order_parameters
}
}