From aa8231944362198bc4b7e07a1b892ffe7ed91cb8 Mon Sep 17 00:00:00 2001 From: Martynas Kazlauskas Date: Sat, 27 Mar 2021 21:55:03 +0200 Subject: [PATCH] Add get_account_fees() --- src/any_exchange.rs | 15 +++++++++++---- src/binance/mod.rs | 12 +++++++++++- src/binance/model/mod.rs | 8 +++++--- src/coinbase/client/account.rs | 6 +++++- src/coinbase/mod.rs | 15 ++++++++++++++- src/coinbase/model/mod.rs | 10 ++++++++++ src/exchange.rs | 10 ++++++---- src/model/mod.rs | 6 ++++++ src/nash/mod.rs | 10 +++++++++- src/shared.rs | 32 ++++++++++++++++++++++++++++++++ tests/binance/account.rs | 26 +++++++++++++++++++++----- tests/coinbase/account.rs | 11 +++++++++++ tests/nash/account.rs | 11 +++++++++++ 13 files changed, 152 insertions(+), 20 deletions(-) diff --git a/src/any_exchange.rs b/src/any_exchange.rs index bb3db096..06fe2f5a 100644 --- a/src/any_exchange.rs +++ b/src/any_exchange.rs @@ -19,10 +19,10 @@ use crate::{ }; use crate::{ model::{ - websocket::Subscription, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, - GetHistoricRatesRequest, GetHistoricTradesRequest, GetOrderHistoryRequest, GetOrderRequest, - GetPriceTickerRequest, OpenLimitOrderRequest, OpenMarketOrderRequest, Order, - OrderBookRequest, OrderBookResponse, OrderCanceled, Paginator, Ticker, Trade, + websocket::Subscription, AccountFees, Balance, CancelAllOrdersRequest, CancelOrderRequest, + Candle, GetHistoricRatesRequest, GetHistoricTradesRequest, GetOrderHistoryRequest, + GetOrderRequest, GetPriceTickerRequest, OpenLimitOrderRequest, OpenMarketOrderRequest, + Order, OrderBookRequest, OrderBookResponse, OrderCanceled, Paginator, Ticker, Trade, TradeHistoryRequest, }, shared::Result, @@ -165,6 +165,13 @@ impl ExchangeAccount for AnyExchange { Self::Coinbase(coinbase) => coinbase.get_account_balances(paginator).await, } } + async fn get_account_fees(&self) -> Result { + match self { + Self::Nash(nash) => nash.get_account_fees().await, + Self::Binance(binance) => binance.get_account_fees().await, + Self::Coinbase(coinbase) => coinbase.get_account_fees().await, + } + } async fn get_order(&self, req: &GetOrderRequest) -> Result { match self { Self::Nash(nash) => nash.get_order(req).await, diff --git a/src/binance/mod.rs b/src/binance/mod.rs index 5cd36266..227c34eb 100644 --- a/src/binance/mod.rs +++ b/src/binance/mod.rs @@ -1,6 +1,7 @@ pub mod client; pub mod model; mod transport; +use rust_decimal::prelude::*; use std::convert::TryFrom; use crate::{ @@ -10,7 +11,7 @@ use crate::{ exchange::{Exchange, ExchangeMarketData}, exchange_info::{ExchangeInfo, ExchangeInfoRetrieval, MarketPair, MarketPairHandle}, model::{ - AskBid, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, + AccountFees, AskBid, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, GetHistoricRatesRequest, GetHistoricTradesRequest, GetOrderHistoryRequest, GetOrderRequest, GetPriceTickerRequest, Interval, Liquidity, OpenLimitOrderRequest, OpenMarketOrderRequest, Order, OrderBookRequest, OrderBookResponse, OrderCanceled, OrderStatus, OrderType, @@ -279,6 +280,15 @@ impl ExchangeAccount for Binance { .map(|v| v.balances.into_iter().map(Into::into).collect()) } + async fn get_account_fees(&self) -> Result { + // Binance returns 10 for 0.1% + let coefficient = Decimal::from_i32(10000).unwrap(); + self.client.get_account().await.map(|v| AccountFees { + maker: v.maker_commission / coefficient, + taker: v.taker_commission / coefficient, + }) + } + async fn get_order(&self, req: &GetOrderRequest) -> Result { let pair = req.market_pair.clone().ok_or_else(|| { OpenLimitsError::MissingParameter("market_pair parameter is required.".to_string()) diff --git a/src/binance/model/mod.rs b/src/binance/model/mod.rs index 20ffa4fa..b35293e6 100644 --- a/src/binance/model/mod.rs +++ b/src/binance/model/mod.rs @@ -1,5 +1,5 @@ pub mod websocket; -use crate::shared::{string_to_decimal, string_to_opt_decimal}; +use crate::shared::{f32_to_decimal, string_to_decimal, string_to_opt_decimal}; use serde::{Deserialize, Serialize}; use rust_decimal::prelude::Decimal; @@ -30,8 +30,10 @@ pub struct ExchangeInformation { #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct AccountInformation { - pub maker_commission: f32, - pub taker_commission: f32, + #[serde(with = "f32_to_decimal")] + pub maker_commission: Decimal, + #[serde(with = "f32_to_decimal")] + pub taker_commission: Decimal, pub buyer_commission: f32, pub seller_commission: f32, pub can_trade: bool, diff --git a/src/coinbase/client/account.rs b/src/coinbase/client/account.rs index 5968f6e9..05b48ce5 100644 --- a/src/coinbase/client/account.rs +++ b/src/coinbase/client/account.rs @@ -1,7 +1,7 @@ use super::BaseClient; use crate::{ coinbase::model::{ - Account, CancelAllOrders, CancelOrder, Fill, GetFillsReq, GetOrderRequest, Order, + Account, CancelAllOrders, CancelOrder, Fees, Fill, GetFillsReq, GetOrderRequest, Order, OrderRequest, OrderRequestMarketType, OrderRequestType, OrderSide, OrderTimeInForce, Paginator, }, @@ -16,6 +16,10 @@ impl BaseClient { self.transport.signed_get("/accounts", paginator).await } + pub async fn get_fees(&self) -> Result { + self.transport.signed_get::<_, ()>("/fees", None).await + } + pub async fn get_orders(&self, params: Option<&GetOrderRequest>) -> Result> { self.transport.signed_get::<_, _>("/orders", params).await } diff --git a/src/coinbase/mod.rs b/src/coinbase/mod.rs index 2437a23c..0d097b1f 100644 --- a/src/coinbase/mod.rs +++ b/src/coinbase/mod.rs @@ -10,7 +10,7 @@ use crate::{ exchange_info::ExchangeInfo, exchange_info::{ExchangeInfoRetrieval, MarketPair, MarketPairHandle}, model::{ - AskBid, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, + AccountFees, AskBid, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, GetHistoricRatesRequest, GetHistoricTradesRequest, GetOrderHistoryRequest, GetOrderRequest, GetPriceTickerRequest, Interval, Liquidity, OpenLimitOrderRequest, OpenMarketOrderRequest, Order, OrderBookRequest, OrderBookResponse, OrderCanceled, OrderStatus, OrderType, @@ -160,6 +160,15 @@ impl From> for OrderBookResponse { } } +impl From for AccountFees { + fn from(fees: model::Fees) -> Self { + Self { + maker: fees.maker_fee_rate, + taker: fees.taker_fee_rate, + } + } +} + impl From for AskBid { fn from(bids: model::BookRecordL2) -> Self { Self { @@ -293,6 +302,10 @@ impl ExchangeAccount for Coinbase { .map(|v| v.into_iter().map(Into::into).collect()) } + async fn get_account_fees(&self) -> Result { + self.client.get_fees().await.map(Into::into) + } + async fn get_order(&self, req: &GetOrderRequest) -> Result { let id = req.id.clone(); diff --git a/src/coinbase/model/mod.rs b/src/coinbase/model/mod.rs index fb49bbd5..2e866eb0 100644 --- a/src/coinbase/model/mod.rs +++ b/src/coinbase/model/mod.rs @@ -43,6 +43,16 @@ pub struct Account { pub profile_id: String, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Fees { + #[serde(with = "string_to_decimal")] + pub maker_fee_rate: Decimal, + #[serde(with = "string_to_decimal")] + pub taker_fee_rate: Decimal, + #[serde(with = "string_to_decimal")] + pub usd_volume: Decimal, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Candle { pub time: u64, diff --git a/src/exchange.rs b/src/exchange.rs index d468dee1..6d42c6d5 100644 --- a/src/exchange.rs +++ b/src/exchange.rs @@ -3,10 +3,11 @@ use async_trait::async_trait; use crate::exchange_info::ExchangeInfoRetrieval; use crate::{ model::{ - Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, GetHistoricRatesRequest, - GetHistoricTradesRequest, GetOrderHistoryRequest, GetOrderRequest, GetPriceTickerRequest, - OpenLimitOrderRequest, OpenMarketOrderRequest, Order, OrderBookRequest, OrderBookResponse, - OrderCanceled, Paginator, Ticker, Trade, TradeHistoryRequest, + AccountFees, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, + GetHistoricRatesRequest, GetHistoricTradesRequest, GetOrderHistoryRequest, GetOrderRequest, + GetPriceTickerRequest, OpenLimitOrderRequest, OpenMarketOrderRequest, Order, + OrderBookRequest, OrderBookResponse, OrderCanceled, Paginator, Ticker, Trade, + TradeHistoryRequest, }, shared::Result, }; @@ -47,5 +48,6 @@ pub trait ExchangeAccount { async fn get_order_history(&self, req: &GetOrderHistoryRequest) -> Result>; async fn get_trade_history(&self, req: &TradeHistoryRequest) -> Result>; async fn get_account_balances(&self, paginator: Option) -> Result>; + async fn get_account_fees(&self) -> Result; async fn get_order(&self, req: &GetOrderRequest) -> Result; } diff --git a/src/model/mod.rs b/src/model/mod.rs index 60804a17..aa852ac6 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -187,6 +187,12 @@ pub enum Liquidity { Taker, } +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct AccountFees { + pub maker: Decimal, + pub taker: Decimal, +} + #[derive(Serialize, Deserialize, Default)] pub struct TradeHistoryRequest { pub market_pair: Option, diff --git a/src/nash/mod.rs b/src/nash/mod.rs index 053aae3b..a3e52f51 100644 --- a/src/nash/mod.rs +++ b/src/nash/mod.rs @@ -11,7 +11,7 @@ use crate::{ model::websocket::OpenLimitsWebSocketMessage, model::{ websocket::{Subscription, WebSocketResponse}, - AskBid, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, + AccountFees, AskBid, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle, GetHistoricRatesRequest, GetHistoricTradesRequest, GetOrderHistoryRequest, GetOrderRequest, GetPriceTickerRequest, Interval, Liquidity, OpenLimitOrderRequest, OpenMarketOrderRequest, Order, OrderBookRequest, OrderBookResponse, OrderCanceled, OrderStatus, OrderType, @@ -217,6 +217,14 @@ impl ExchangeAccount for Nash { Ok(balances) } + async fn get_account_fees(&self) -> Result { + Err(OpenLimitsError::MissingImplementation( + MissingImplementationContent { + message: "'get_account_fees' for nash is not implemented".to_string(), + }, + )) + } + async fn get_all_open_orders(&self) -> Result> { let req = nash_protocol::protocol::list_account_orders::ListAccountOrdersRequest { market: Default::default(), diff --git a/src/shared.rs b/src/shared.rs index c1af6ff0..9c16e180 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,5 +1,37 @@ pub type Result = std::result::Result; +pub mod f32_to_decimal { + use std::fmt; + + use rust_decimal::prelude::*; + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &T, serializer: S) -> Result + where + T: fmt::Display, + S: Serializer, + { + serializer.collect_str(value) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum F32ToDecimal { + F32(f32), + } + + let F32ToDecimal::F32(val) = F32ToDecimal::deserialize(deserializer)?; + Decimal::from_f32(val).ok_or(de::Error::custom(format!( + "Failed to convert {} to Decimal", + val + ))) + } +} + pub mod string_to_decimal { use std::fmt; diff --git a/tests/binance/account.rs b/tests/binance/account.rs index 8f803da6..d440fcb0 100644 --- a/tests/binance/account.rs +++ b/tests/binance/account.rs @@ -1,6 +1,8 @@ use dotenv::dotenv; use std::env; +use openlimits::exchange::ExchangeMarketData; +use openlimits::model::GetPriceTickerRequest; use openlimits::{ binance::Binance, binance::BinanceCredentials, @@ -12,8 +14,6 @@ use openlimits::{ }, }; use rust_decimal::prelude::Decimal; -use openlimits::exchange::ExchangeMarketData; -use openlimits::model::GetPriceTickerRequest; #[tokio::test] #[ignore] @@ -179,8 +179,13 @@ async fn get_order_history() { } async fn get_price(exchange: &Binance, pair: &str) -> Decimal { - let get_price_ticker_request = GetPriceTickerRequest { market_pair: pair.to_string() }; - let ticker = exchange.get_price_ticker(&get_price_ticker_request).await.expect("Couldn't get ticker."); + let get_price_ticker_request = GetPriceTickerRequest { + market_pair: pair.to_string(), + }; + let ticker = exchange + .get_price_ticker(&get_price_ticker_request) + .await + .expect("Couldn't get ticker."); ticker.price.expect("Couldn't get price.") } @@ -214,7 +219,18 @@ async fn get_account_balances() { let resp = exchange .get_account_balances(None) .await - .expect("Couldn't get acount balances."); + .expect("Couldn't get account balances."); + println!("{:?}", resp); +} + +#[tokio::test] +async fn get_account_fees() { + let exchange = init().await; + + let resp = exchange + .get_account_fees() + .await + .expect("couldn't get account fees."); println!("{:?}", resp); } diff --git a/tests/coinbase/account.rs b/tests/coinbase/account.rs index c6b2092c..ea8c69fc 100644 --- a/tests/coinbase/account.rs +++ b/tests/coinbase/account.rs @@ -201,6 +201,17 @@ async fn get_account_balances() { println!("{:?}", resp); } +#[tokio::test] +async fn get_account_fees() { + let exchange = init().await; + + let resp = exchange + .get_account_fees() + .await + .expect("couldn't get account fees."); + println!("{:?}", resp); +} + #[tokio::test] async fn get_trade_history() { let exchange = init().await; diff --git a/tests/nash/account.rs b/tests/nash/account.rs index ea4ca62d..413ca862 100644 --- a/tests/nash/account.rs +++ b/tests/nash/account.rs @@ -232,6 +232,17 @@ async fn get_account_balances() { println!("{:?}", resp); } +#[tokio::test] +async fn get_account_fees() { + let exchange = init().await; + + let resp = exchange + .get_account_fees() + .await + .expect("couldn't get account fees."); + println!("{:?}", resp); +} + #[tokio::test] async fn get_trade_history() { let exchange = init().await;