2021-01-14 18:56:31 +00:00
|
|
|
use std::convert::TryInto;
|
|
|
|
use std::fmt::{Debug, Formatter};
|
2021-01-14 12:42:23 +00:00
|
|
|
use std::sync::Arc;
|
2021-01-06 21:17:01 +00:00
|
|
|
|
|
|
|
use async_trait::async_trait;
|
|
|
|
use bitfinex::api::Bitfinex;
|
2021-01-11 17:16:44 +00:00
|
|
|
use bitfinex::orders::{OrderForm, OrderMeta};
|
2021-01-06 21:17:01 +00:00
|
|
|
use bitfinex::ticker::TradingPairTicker;
|
|
|
|
|
2021-01-14 18:56:31 +00:00
|
|
|
use crate::currency::SymbolPair;
|
2021-01-14 12:42:23 +00:00
|
|
|
use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker};
|
2021-01-06 21:17:01 +00:00
|
|
|
use crate::BoxError;
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
#[derive(Eq, PartialEq, Hash, Clone, Debug)]
|
2021-01-11 17:16:44 +00:00
|
|
|
pub enum ExchangeKind {
|
2021-01-13 09:24:59 +00:00
|
|
|
Bitfinex {
|
|
|
|
api_key: String,
|
|
|
|
api_secret: String,
|
|
|
|
affiliate_code: Option<String>,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
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-14 12:42:23 +00:00
|
|
|
exchange: ExchangeKind,
|
2021-01-13 09:24:59 +00:00
|
|
|
inner: Arc<Box<dyn Connector>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
2021-01-14 12:42:23 +00:00
|
|
|
pub fn new(exchange: &ExchangeKind) -> Self {
|
|
|
|
let inner = match &exchange {
|
2021-01-13 09:24:59 +00:00
|
|
|
ExchangeKind::Bitfinex {
|
|
|
|
api_key,
|
|
|
|
api_secret,
|
|
|
|
affiliate_code,
|
2021-01-14 12:42:23 +00:00
|
|
|
} => BitfinexConnector::new(&api_key, &api_secret)
|
|
|
|
.with_affiliate_code(affiliate_code.clone()),
|
2021-01-13 09:24:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Client {
|
2021-01-14 12:42:23 +00:00
|
|
|
exchange: exchange.clone(),
|
2021-01-13 09:24:59 +00:00
|
|
|
inner: Arc::new(Box::new(inner)),
|
|
|
|
}
|
|
|
|
}
|
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-14 12:42:23 +00:00
|
|
|
self.inner.active_positions(pair).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
|
|
|
|
self.inner.current_prices(pair).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
|
|
|
|
self.inner.active_orders(pair).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn submit_order(
|
|
|
|
&self,
|
|
|
|
pair: &SymbolPair,
|
|
|
|
amount: f64,
|
|
|
|
price: f64,
|
|
|
|
kind: &OrderKind,
|
|
|
|
) -> Result<(), BoxError> {
|
|
|
|
self.inner.submit_order(pair, amount, price, kind).await
|
|
|
|
}
|
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-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>;
|
|
|
|
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError>;
|
2021-01-11 17:16:44 +00:00
|
|
|
async fn submit_order(
|
|
|
|
&self,
|
|
|
|
pair: &SymbolPair,
|
|
|
|
amount: f64,
|
|
|
|
price: f64,
|
|
|
|
kind: &OrderKind,
|
|
|
|
) -> Result<(), 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 {
|
|
|
|
write!(f, "Connector")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**************
|
|
|
|
* BITFINEX
|
|
|
|
**************/
|
|
|
|
|
2021-01-13 09:04:58 +00:00
|
|
|
pub struct BitfinexConnector {
|
2021-01-06 21:17:01 +00:00
|
|
|
bfx: Bitfinex,
|
|
|
|
affiliate_code: Option<String>,
|
|
|
|
// account_info: String,
|
|
|
|
// ledger: String,
|
|
|
|
}
|
|
|
|
|
2021-01-13 09:04:58 +00:00
|
|
|
impl BitfinexConnector {
|
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())),
|
|
|
|
affiliate_code: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_affiliate_code(mut self, affiliate_code: Option<String>) -> Self {
|
|
|
|
self.affiliate_code = affiliate_code;
|
|
|
|
self
|
|
|
|
}
|
2021-01-14 12:42:23 +00:00
|
|
|
|
|
|
|
fn format_trading_pair(&self, pair: &SymbolPair) -> String {
|
|
|
|
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-14 18:36:56 +00:00
|
|
|
async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError> {
|
2021-01-14 12:42:23 +00:00
|
|
|
let active_positions = self.bfx.positions.active_positions().await?;
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
if positions.is_empty() {
|
|
|
|
Ok(None)
|
|
|
|
} else {
|
|
|
|
Ok(Some(positions))
|
|
|
|
}
|
2021-01-14 12:42:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
|
|
|
|
let ticker: TradingPairTicker = self
|
|
|
|
.bfx
|
|
|
|
.ticker
|
|
|
|
.trading_pair(self.format_trading_pair(pair))
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(ticker)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> {
|
|
|
|
unimplemented!()
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn submit_order(
|
|
|
|
&self,
|
|
|
|
pair: &SymbolPair,
|
|
|
|
amount: f64,
|
|
|
|
price: f64,
|
|
|
|
kind: &OrderKind,
|
|
|
|
) -> Result<(), BoxError> {
|
|
|
|
let order_form = match &self.affiliate_code {
|
|
|
|
Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into())
|
|
|
|
.with_meta(OrderMeta::new(affiliate_code.clone())),
|
|
|
|
None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(self.bfx.orders.submit_order(&order_form).await?)
|
|
|
|
}
|
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(
|
|
|
|
self.symbol().try_into()?,
|
|
|
|
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-11 17:16:44 +00:00
|
|
|
impl From<&OrderKind> for bitfinex::orders::OrderKind {
|
|
|
|
fn from(o: &OrderKind) -> Self {
|
|
|
|
match o {
|
|
|
|
OrderKind::Limit => Self::Limit,
|
|
|
|
OrderKind::ExchangeLimit => Self::ExchangeLimit,
|
|
|
|
OrderKind::Market => Self::Market,
|
|
|
|
OrderKind::ExchangeMarket => Self::ExchangeMarket,
|
|
|
|
OrderKind::Stop => Self::Stop,
|
|
|
|
OrderKind::ExchangeStop => Self::ExchangeStop,
|
|
|
|
OrderKind::StopLimit => Self::StopLimit,
|
|
|
|
OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit,
|
|
|
|
OrderKind::TrailingStop => Self::TrailingStop,
|
|
|
|
OrderKind::Fok => Self::Fok,
|
|
|
|
OrderKind::ExchangeFok => Self::ExchangeFok,
|
|
|
|
OrderKind::Ioc => Self::Ioc,
|
|
|
|
OrderKind::ExchangeIoc => Self::ExchangeIoc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|