2021-01-23 13:44:08 +00:00
|
|
|
use std::convert::{TryFrom, TryInto};
|
2021-01-26 17:13:14 +00:00
|
|
|
use std::error::Error;
|
2021-01-23 13:44:08 +00:00
|
|
|
use std::fmt::{Debug, Formatter};
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2021-01-06 21:17:01 +00:00
|
|
|
use async_trait::async_trait;
|
|
|
|
use bitfinex::api::Bitfinex;
|
2021-01-23 16:13:37 +00:00
|
|
|
use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse};
|
2021-01-06 21:17:01 +00:00
|
|
|
use bitfinex::ticker::TradingPairTicker;
|
2021-01-26 11:15:04 +00:00
|
|
|
use futures_retry::{FutureRetry, RetryPolicy, StreamRetryExt};
|
|
|
|
use futures_util::task::FutureObj;
|
|
|
|
use log::trace;
|
2021-01-26 17:13:14 +00:00
|
|
|
use tokio::macros::support::Future;
|
2021-01-26 11:15:04 +00:00
|
|
|
use tokio::time::Duration;
|
2021-01-06 21:17:01 +00:00
|
|
|
|
2021-01-14 18:56:31 +00:00
|
|
|
use crate::currency::SymbolPair;
|
2021-01-19 21:29:02 +00:00
|
|
|
use crate::models::{
|
2021-01-22 15:37:53 +00:00
|
|
|
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState,
|
2021-01-23 16:13:37 +00:00
|
|
|
PriceTicker, TradingPlatform,
|
2021-01-19 21:29:02 +00:00
|
|
|
};
|
2021-01-06 21:17:01 +00:00
|
|
|
use crate::BoxError;
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
|
|
pub enum Exchange {
|
|
|
|
Bitfinex,
|
|
|
|
}
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
2021-01-23 16:13:37 +00:00
|
|
|
pub enum ExchangeDetails {
|
2021-01-26 16:19:10 +00:00
|
|
|
Bitfinex { api_key: String, api_secret: String },
|
2021-01-13 09:24:59 +00:00
|
|
|
}
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
/// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it,
|
2021-01-13 09:24:59 +00:00
|
|
|
/// because it already uses an [`Arc`] internally.
|
2021-01-14 12:42:23 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2021-01-13 09:24:59 +00:00
|
|
|
pub struct Client {
|
2021-01-25 13:17:13 +00:00
|
|
|
exchange: Exchange,
|
2021-01-26 16:19:10 +00:00
|
|
|
inner: Arc<Box<dyn Connector>>,
|
2021-01-13 09:24:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
2021-01-23 16:13:37 +00:00
|
|
|
pub fn new(exchange: &ExchangeDetails) -> Self {
|
2021-01-25 13:17:13 +00:00
|
|
|
match exchange {
|
2021-01-23 16:13:37 +00:00
|
|
|
ExchangeDetails::Bitfinex {
|
2021-01-26 16:19:10 +00:00
|
|
|
api_key,
|
|
|
|
api_secret,
|
2021-01-25 13:17:13 +00:00
|
|
|
} => Self {
|
|
|
|
exchange: Exchange::Bitfinex,
|
2021-01-26 16:19:10 +00:00
|
|
|
inner: Arc::new(Box::new((BitfinexConnector::new(api_key, api_secret)))),
|
2021-01-25 13:17:13 +00:00
|
|
|
},
|
2021-01-13 09:24:59 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-14 12:42:23 +00:00
|
|
|
|
2021-01-14 18:36:56 +00:00
|
|
|
pub async fn active_positions(
|
|
|
|
&self,
|
|
|
|
pair: &SymbolPair,
|
|
|
|
) -> Result<Option<Vec<Position>>, BoxError> {
|
2021-01-24 15:42:54 +00:00
|
|
|
// retrieving open positions and order book to calculate effective profit/loss
|
|
|
|
let (positions, order_book) = tokio::join!(
|
2021-01-26 16:19:10 +00:00
|
|
|
self.inner.active_positions(pair),
|
|
|
|
self.inner.order_book(pair)
|
2021-01-24 15:42:54 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let (mut positions, order_book) = (positions?, order_book?);
|
|
|
|
let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid());
|
|
|
|
|
|
|
|
// updating positions with effective profit/loss
|
2021-01-24 21:12:06 +00:00
|
|
|
// TODO: change fee with account's taker fee
|
2021-01-24 20:53:33 +00:00
|
|
|
positions.iter_mut().flatten().for_each(|x| {
|
2021-01-24 15:42:54 +00:00
|
|
|
if x.is_short() {
|
|
|
|
x.update_profit_loss(best_ask, 0.2);
|
|
|
|
} else {
|
|
|
|
x.update_profit_loss(best_bid, 0.2);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(positions)
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
|
2021-01-26 16:19:10 +00:00
|
|
|
self.inner.current_prices(pair).await
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
|
|
|
|
2021-01-22 15:37:53 +00:00
|
|
|
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ActiveOrder>, BoxError> {
|
2021-01-26 17:13:14 +00:00
|
|
|
Ok(self
|
|
|
|
.inner
|
|
|
|
.active_orders(pair)
|
|
|
|
.await?
|
|
|
|
.into_iter()
|
|
|
|
.filter(|x| &x.symbol == pair)
|
|
|
|
.collect())
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
pub async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
|
2021-01-26 16:19:10 +00:00
|
|
|
self.inner.submit_order(order).await
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
2021-01-19 21:29:02 +00:00
|
|
|
|
|
|
|
pub async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError> {
|
2021-01-26 16:19:10 +00:00
|
|
|
self.inner.order_book(pair).await
|
2021-01-19 21:29:02 +00:00
|
|
|
}
|
2021-01-23 13:44:08 +00:00
|
|
|
|
|
|
|
pub async fn cancel_order(&self, order: &ActiveOrder) -> Result<ActiveOrder, BoxError> {
|
2021-01-26 16:19:10 +00:00
|
|
|
self.inner.cancel_order(order).await
|
2021-01-23 13:44:08 +00:00
|
|
|
}
|
2021-01-11 17:16:44 +00:00
|
|
|
}
|
|
|
|
|
2021-01-06 21:17:01 +00:00
|
|
|
#[async_trait]
|
2021-01-14 12:42:23 +00:00
|
|
|
pub trait Connector: Send + Sync {
|
2021-01-15 10:40:36 +00:00
|
|
|
fn name(&self) -> String;
|
2021-01-14 18:36:56 +00:00
|
|
|
async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError>;
|
2021-01-06 21:17:01 +00:00
|
|
|
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
|
2021-01-19 21:29:02 +00:00
|
|
|
async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError>;
|
2021-01-22 15:37:53 +00:00
|
|
|
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ActiveOrder>, BoxError>;
|
2021-01-23 16:13:37 +00:00
|
|
|
async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError>;
|
2021-01-23 13:44:08 +00:00
|
|
|
async fn cancel_order(&self, order: &ActiveOrder) -> Result<ActiveOrder, BoxError>;
|
2021-01-06 21:17:01 +00:00
|
|
|
}
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
impl Debug for dyn Connector {
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
2021-01-15 10:40:36 +00:00
|
|
|
write!(f, "{}", self.name())
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**************
|
|
|
|
* BITFINEX
|
|
|
|
**************/
|
|
|
|
|
2021-01-13 09:04:58 +00:00
|
|
|
pub struct BitfinexConnector {
|
2021-01-06 21:17:01 +00:00
|
|
|
bfx: Bitfinex,
|
|
|
|
}
|
|
|
|
|
2021-01-13 09:04:58 +00:00
|
|
|
impl BitfinexConnector {
|
2021-01-15 10:40:36 +00:00
|
|
|
const AFFILIATE_CODE: &'static str = "XPebOgHxA";
|
|
|
|
|
2021-01-26 11:15:04 +00:00
|
|
|
fn handle_small_nonce_error(e: BoxError) -> RetryPolicy<BoxError> {
|
|
|
|
if e.to_string().contains("nonce: small") {
|
|
|
|
return RetryPolicy::WaitRetry(Duration::from_millis(1));
|
|
|
|
}
|
|
|
|
return RetryPolicy::ForwardError(e);
|
|
|
|
}
|
|
|
|
|
2021-01-06 21:17:01 +00:00
|
|
|
pub fn new(api_key: &str, api_secret: &str) -> Self {
|
2021-01-13 09:04:58 +00:00
|
|
|
BitfinexConnector {
|
2021-01-06 21:17:01 +00:00
|
|
|
bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
fn format_trading_pair(pair: &SymbolPair) -> String {
|
2021-01-14 12:42:23 +00:00
|
|
|
if pair.to_string().to_lowercase().contains("test") {
|
|
|
|
format!("{}:{}", pair.base(), pair.quote())
|
|
|
|
} else {
|
|
|
|
format!("{}{}", pair.base(), pair.quote())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl Connector for BitfinexConnector {
|
2021-01-15 10:40:36 +00:00
|
|
|
fn name(&self) -> String {
|
|
|
|
"Bitfinex".into()
|
|
|
|
}
|
|
|
|
|
2021-01-14 18:36:56 +00:00
|
|
|
async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError> {
|
2021-01-26 11:15:04 +00:00
|
|
|
let (active_positions, _) = FutureRetry::new(
|
|
|
|
move || self.bfx.positions.active_positions(),
|
|
|
|
BitfinexConnector::handle_small_nonce_error,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.map_err(|(e, attempts)| e)?;
|
2021-01-14 12:42:23 +00:00
|
|
|
|
2021-01-14 18:36:56 +00:00
|
|
|
let positions: Vec<_> = active_positions
|
2021-01-14 12:42:23 +00:00
|
|
|
.into_iter()
|
|
|
|
.filter_map(|x| x.try_into().ok())
|
|
|
|
.filter(|x: &Position| x.pair() == pair)
|
2021-01-14 18:36:56 +00:00
|
|
|
.collect();
|
|
|
|
|
2021-01-26 11:15:04 +00:00
|
|
|
trace!("\t[PositionManager] Retrieved positions for {}", pair);
|
|
|
|
Ok((!positions.is_empty()).then_some(positions))
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
|
2021-01-23 16:13:37 +00:00
|
|
|
let symbol_name = BitfinexConnector::format_trading_pair(pair);
|
|
|
|
|
|
|
|
let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(symbol_name).await?;
|
2021-01-14 12:42:23 +00:00
|
|
|
|
|
|
|
Ok(ticker)
|
|
|
|
}
|
|
|
|
|
2021-01-24 13:41:18 +00:00
|
|
|
async fn active_orders(&self, _: &SymbolPair) -> Result<Vec<ActiveOrder>, BoxError> {
|
2021-01-26 11:15:04 +00:00
|
|
|
let (response, _) = FutureRetry::new(
|
|
|
|
move || self.bfx.orders.active_orders(),
|
|
|
|
BitfinexConnector::handle_small_nonce_error,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.map_err(|(e, attempts)| e)?;
|
2021-01-24 13:41:18 +00:00
|
|
|
|
|
|
|
Ok(response.iter().map(Into::into).collect())
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
|
|
|
|
let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair()));
|
|
|
|
|
|
|
|
let order_form = match order.kind() {
|
|
|
|
OrderKind::Limit { price, amount } => {
|
|
|
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
|
|
}
|
|
|
|
OrderKind::Market { amount } => {
|
|
|
|
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
|
|
|
|
}
|
|
|
|
OrderKind::Stop { price, amount } => {
|
|
|
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
|
|
}
|
|
|
|
OrderKind::StopLimit {
|
|
|
|
price,
|
|
|
|
amount,
|
|
|
|
limit_price,
|
|
|
|
} => bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
|
|
.with_price_aux_limit(limit_price)?,
|
|
|
|
OrderKind::TrailingStop { distance, amount } => {
|
|
|
|
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
|
|
|
|
.with_price_trailing(distance)?
|
|
|
|
}
|
|
|
|
OrderKind::FillOrKill { price, amount } => {
|
|
|
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
|
|
}
|
|
|
|
OrderKind::ImmediateOrCancel { price, amount } => {
|
|
|
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.with_meta(OrderMeta::new(
|
|
|
|
BitfinexConnector::AFFILIATE_CODE.to_string(),
|
|
|
|
));
|
2021-01-14 12:42:23 +00:00
|
|
|
|
2021-01-26 11:15:04 +00:00
|
|
|
// retry to submit the order until it succeeds.
|
|
|
|
// the function may fail due to concurrent signed requests
|
|
|
|
// parsed in different times by the server
|
|
|
|
let response = {
|
|
|
|
loop {
|
|
|
|
match self.bfx.orders.submit_order(&order_form).await {
|
|
|
|
Ok(response) => break response,
|
|
|
|
Err(e) => {
|
|
|
|
if !e.to_string().contains("nonce: small") {
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
tokio::time::sleep(Duration::from_nanos(1)).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2021-01-19 21:29:02 +00:00
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
Ok((&response).try_into()?)
|
2021-01-19 21:29:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError> {
|
2021-01-23 16:13:37 +00:00
|
|
|
let symbol_name = BitfinexConnector::format_trading_pair(pair);
|
|
|
|
|
2021-01-26 11:15:04 +00:00
|
|
|
let response = {
|
|
|
|
loop {
|
|
|
|
match self
|
|
|
|
.bfx
|
|
|
|
.book
|
|
|
|
.trading_pair(symbol_name.clone(), bitfinex::book::BookPrecision::P0)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(response) => break response,
|
|
|
|
Err(e) => {
|
|
|
|
if !e.to_string().contains("nonce: small") {
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
tokio::time::sleep(Duration::from_nanos(1)).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2021-01-19 21:29:02 +00:00
|
|
|
|
2021-01-26 11:15:04 +00:00
|
|
|
let entries = response
|
2021-01-19 21:29:02 +00:00
|
|
|
.into_iter()
|
|
|
|
.map(|x| OrderBookEntry::Trading {
|
|
|
|
price: x.price,
|
|
|
|
count: x.count as u64,
|
|
|
|
amount: x.amount,
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Ok(OrderBook::new(pair.clone()).with_entries(entries))
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
2021-01-23 13:44:08 +00:00
|
|
|
|
|
|
|
async fn cancel_order(&self, order: &ActiveOrder) -> Result<ActiveOrder, BoxError> {
|
2021-01-23 16:13:37 +00:00
|
|
|
let cancel_form = order.into();
|
2021-01-23 13:44:08 +00:00
|
|
|
|
2021-01-26 11:15:04 +00:00
|
|
|
let response = {
|
|
|
|
loop {
|
|
|
|
match self.bfx.orders.cancel_order(&cancel_form).await {
|
|
|
|
Ok(response) => break response,
|
|
|
|
Err(e) => {
|
|
|
|
if !e.to_string().contains("nonce: small") {
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
tokio::time::sleep(Duration::from_nanos(1)).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((&response).try_into()?)
|
2021-01-23 16:13:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&ActiveOrder> for CancelOrderForm {
|
|
|
|
fn from(o: &ActiveOrder) -> Self {
|
|
|
|
Self::from_id(o.id)
|
2021-01-23 13:44:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl TryFrom<&bitfinex::orders::OrderResponse> for ActiveOrder {
|
2021-01-23 13:44:08 +00:00
|
|
|
type Error = BoxError;
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
fn try_from(response: &OrderResponse) -> Result<Self, Self::Error> {
|
2021-01-23 13:44:08 +00:00
|
|
|
Ok(Self {
|
2021-01-23 16:13:37 +00:00
|
|
|
exchange: Exchange::Bitfinex,
|
2021-01-23 13:44:08 +00:00
|
|
|
id: response.id(),
|
|
|
|
group_id: response.gid(),
|
2021-01-23 16:13:37 +00:00
|
|
|
client_id: Some(response.cid()),
|
2021-01-23 13:44:08 +00:00
|
|
|
symbol: SymbolPair::from_str(response.symbol())?,
|
2021-01-23 16:13:37 +00:00
|
|
|
current_form: OrderForm::new(
|
|
|
|
SymbolPair::from_str(response.symbol())?,
|
|
|
|
response.into(),
|
|
|
|
response.into(),
|
|
|
|
),
|
|
|
|
creation_timestamp: 0,
|
|
|
|
update_timestamp: 0,
|
2021-01-23 13:44:08 +00:00
|
|
|
})
|
|
|
|
}
|
2021-01-06 21:17:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TryInto<Position> for bitfinex::positions::Position {
|
|
|
|
type Error = BoxError;
|
|
|
|
|
|
|
|
fn try_into(self) -> Result<Position, Self::Error> {
|
|
|
|
let state = {
|
|
|
|
if self.status().to_lowercase().contains("active") {
|
|
|
|
PositionState::Open
|
|
|
|
} else {
|
|
|
|
PositionState::Closed
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Position::new(
|
2021-01-22 16:09:17 +00:00
|
|
|
SymbolPair::from_str(self.symbol())?,
|
2021-01-06 21:17:01 +00:00
|
|
|
state,
|
|
|
|
self.amount(),
|
|
|
|
self.base_price(),
|
|
|
|
self.pl(),
|
|
|
|
self.pl_perc(),
|
|
|
|
self.price_liq(),
|
|
|
|
self.position_id(),
|
|
|
|
)
|
|
|
|
.with_creation_date(self.mts_create())
|
|
|
|
.with_creation_update(self.mts_update()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl From<&OrderForm> for bitfinex::orders::OrderKind {
|
|
|
|
fn from(o: &OrderForm) -> Self {
|
|
|
|
match o.platform() {
|
|
|
|
TradingPlatform::Exchange => match o.kind() {
|
|
|
|
OrderKind::Limit { .. } => bitfinex::orders::OrderKind::ExchangeLimit,
|
|
|
|
OrderKind::Market { .. } => bitfinex::orders::OrderKind::ExchangeMarket,
|
|
|
|
OrderKind::Stop { .. } => bitfinex::orders::OrderKind::ExchangeStop,
|
|
|
|
OrderKind::StopLimit { .. } => bitfinex::orders::OrderKind::ExchangeStopLimit,
|
|
|
|
OrderKind::TrailingStop { .. } => bitfinex::orders::OrderKind::ExchangeTrailingStop,
|
|
|
|
OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok,
|
|
|
|
OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc,
|
|
|
|
},
|
|
|
|
TradingPlatform::Margin => match o.kind() {
|
|
|
|
OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit,
|
|
|
|
OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market,
|
|
|
|
OrderKind::Stop { .. } => bitfinex::orders::OrderKind::Stop,
|
|
|
|
OrderKind::StopLimit { .. } => bitfinex::orders::OrderKind::StopLimit,
|
|
|
|
OrderKind::TrailingStop { .. } => bitfinex::orders::OrderKind::TrailingStop,
|
|
|
|
OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::Fok,
|
|
|
|
OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::Ioc,
|
|
|
|
},
|
|
|
|
_ => unimplemented!(),
|
2021-01-11 17:16:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl From<&bitfinex::orders::OrderResponse> for TradingPlatform {
|
|
|
|
fn from(response: &OrderResponse) -> Self {
|
|
|
|
match response.order_type() {
|
|
|
|
bitfinex::orders::OrderKind::Limit
|
|
|
|
| bitfinex::orders::OrderKind::Market
|
|
|
|
| bitfinex::orders::OrderKind::StopLimit
|
|
|
|
| bitfinex::orders::OrderKind::Stop
|
|
|
|
| bitfinex::orders::OrderKind::TrailingStop
|
|
|
|
| bitfinex::orders::OrderKind::Fok
|
|
|
|
| bitfinex::orders::OrderKind::Ioc => Self::Margin,
|
|
|
|
_ => Self::Exchange,
|
2021-01-22 16:09:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-24 13:41:18 +00:00
|
|
|
impl From<&bitfinex::orders::ActiveOrder> for TradingPlatform {
|
|
|
|
fn from(response: &bitfinex::orders::ActiveOrder) -> Self {
|
|
|
|
match response.order_type() {
|
|
|
|
bitfinex::orders::OrderKind::Limit
|
|
|
|
| bitfinex::orders::OrderKind::Market
|
|
|
|
| bitfinex::orders::OrderKind::StopLimit
|
|
|
|
| bitfinex::orders::OrderKind::Stop
|
|
|
|
| bitfinex::orders::OrderKind::TrailingStop
|
|
|
|
| bitfinex::orders::OrderKind::Fok
|
|
|
|
| bitfinex::orders::OrderKind::Ioc => Self::Margin,
|
|
|
|
_ => Self::Exchange,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl From<&bitfinex::orders::OrderResponse> for OrderKind {
|
|
|
|
fn from(response: &OrderResponse) -> Self {
|
|
|
|
match response.order_type() {
|
|
|
|
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
|
|
|
|
Self::Limit {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
|
|
|
|
Self::Market {
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
|
|
|
|
Self::Stop {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::StopLimit
|
|
|
|
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
2021-01-24 13:41:18 +00:00
|
|
|
limit_price: response.price_aux_limit().expect("Limit price not found!"),
|
|
|
|
},
|
|
|
|
bitfinex::orders::OrderKind::TrailingStop
|
|
|
|
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
|
|
|
|
distance: response.price_trailing().expect("Distance not found!"),
|
|
|
|
amount: response.amount(),
|
|
|
|
},
|
|
|
|
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
|
|
|
|
Self::FillOrKill {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
|
|
|
|
Self::ImmediateOrCancel {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&bitfinex::orders::ActiveOrder> for OrderKind {
|
|
|
|
fn from(response: &bitfinex::orders::ActiveOrder) -> Self {
|
|
|
|
match response.order_type() {
|
|
|
|
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
|
|
|
|
Self::Limit {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
|
|
|
|
Self::Market {
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
|
|
|
|
Self::Stop {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::StopLimit
|
|
|
|
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
limit_price: response.price_aux_limit().expect("Limit price not found!"),
|
2021-01-23 16:13:37 +00:00
|
|
|
},
|
|
|
|
bitfinex::orders::OrderKind::TrailingStop
|
|
|
|
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
|
2021-01-24 13:41:18 +00:00
|
|
|
distance: response.price_trailing().expect("Distance not found!"),
|
2021-01-23 16:13:37 +00:00
|
|
|
amount: response.amount(),
|
|
|
|
},
|
|
|
|
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
|
|
|
|
Self::FillOrKill {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
|
|
|
|
Self::ImmediateOrCancel {
|
|
|
|
price: response.price(),
|
|
|
|
amount: response.amount(),
|
|
|
|
}
|
|
|
|
}
|
2021-01-16 11:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-24 13:41:18 +00:00
|
|
|
impl From<&bitfinex::orders::ActiveOrder> for ActiveOrder {
|
|
|
|
fn from(order: &bitfinex::orders::ActiveOrder) -> Self {
|
|
|
|
let pair = SymbolPair::from_str(&order.symbol()).expect("Invalid symbol!");
|
|
|
|
|
|
|
|
Self {
|
|
|
|
exchange: Exchange::Bitfinex,
|
|
|
|
id: order.id(),
|
|
|
|
group_id: order.group_id().map(|x| x as u64),
|
|
|
|
client_id: Some(order.client_id()),
|
|
|
|
symbol: pair.clone(),
|
|
|
|
current_form: OrderForm::new(pair, order.into(), order.into()),
|
|
|
|
creation_timestamp: order.creation_timestamp(),
|
|
|
|
update_timestamp: order.update_timestamp(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
impl From<TradingPairTicker> for PriceTicker {
|
|
|
|
fn from(t: TradingPairTicker) -> Self {
|
|
|
|
Self {
|
|
|
|
bid: t.bid,
|
|
|
|
bid_size: t.bid_size,
|
|
|
|
ask: t.ask,
|
|
|
|
ask_size: t.ask_size,
|
|
|
|
daily_change: t.daily_change,
|
|
|
|
daily_change_perc: t.daily_change_perc,
|
|
|
|
last_price: t.last_price,
|
|
|
|
volume: t.volume,
|
|
|
|
high: t.high,
|
|
|
|
low: t.low,
|
|
|
|
}
|
2021-01-06 21:17:01 +00:00
|
|
|
}
|
|
|
|
}
|