core/rustybot/src/connectors.rs

326 lines
10 KiB
Rust
Raw Normal View History

2021-01-06 21:17:01 +00:00
use async_trait::async_trait;
use bitfinex::api::Bitfinex;
2021-01-22 15:37:53 +00:00
use bitfinex::orders::OrderMeta;
2021-01-06 21:17:01 +00:00
use bitfinex::ticker::TradingPairTicker;
use log::debug;
use std::convert::TryInto;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
2021-01-06 21:17:01 +00:00
2021-01-14 18:56:31 +00:00
use crate::currency::SymbolPair;
use crate::models::{
2021-01-22 15:37:53 +00:00
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, 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 {
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-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,
} => BitfinexConnector::new(&api_key, &api_secret),
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
}
2021-01-22 15:37:53 +00:00
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ActiveOrder>, BoxError> {
2021-01-14 12:42:23 +00:00
self.inner.active_orders(pair).await
}
2021-01-22 15:37:53 +00:00
pub async fn submit_order(&self, order: OrderForm) -> Result<ActiveOrder, BoxError> {
2021-01-16 11:43:16 +00:00
self.inner.submit_order(order).await
2021-01-14 12:42:23 +00:00
}
pub async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError> {
self.inner.order_book(pair).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 {
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>;
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>;
async fn submit_order(&self, order: OrderForm) -> 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 {
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,
affiliate_code: Option<String>,
// account_info: String,
// ledger: String,
}
2021-01-13 09:04:58 +00:00
impl BitfinexConnector {
const AFFILIATE_CODE: &'static str = "XPebOgHxA";
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: Some(BitfinexConnector::AFFILIATE_CODE.into()),
2021-01-06 21:17:01 +00:00
}
}
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 {
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-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)
}
2021-01-22 15:37:53 +00:00
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ActiveOrder>, BoxError> {
2021-01-14 12:42:23 +00:00
unimplemented!()
}
2021-01-22 15:37:53 +00:00
async fn submit_order(&self, order: OrderForm) -> Result<ActiveOrder, BoxError> {
// TODO: change trading pair formatting. awful.
2021-01-14 12:42:23 +00:00
let order_form = match &self.affiliate_code {
2021-01-16 11:43:16 +00:00
Some(affiliate_code) => bitfinex::orders::OrderForm::new(
format!("t{}", self.format_trading_pair(order.pair())),
2021-01-16 11:43:16 +00:00
*order.price(),
*order.amount(),
order.kind().into(),
)
.with_meta(OrderMeta::new(affiliate_code.clone())),
None => bitfinex::orders::OrderForm::new(
format!("t{}", self.format_trading_pair(order.pair())),
2021-01-16 11:43:16 +00:00
*order.price(),
*order.amount(),
order.kind().into(),
),
2021-01-14 12:42:23 +00:00
};
let response = self.bfx.orders.submit_order(&order_form).await?;
2021-01-22 15:37:53 +00:00
Ok(ActiveOrder {
id: 1,
group_id: None,
client_id: 0,
symbol: "".to_string(),
creation_timestamp: 0,
update_timestamp: 0,
amount: 0.0,
amount_original: 0.0,
order_type: "".to_string(),
previous_order_type: None,
flags: None,
order_status: None,
price: 0.0,
price_avg: 0.0,
price_trailing: None,
price_aux_limit: None,
notify: 0,
hidden: 0,
placed_id: None,
})
}
async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError> {
let x = self
.bfx
.book
2021-01-22 15:37:53 +00:00
.trading_pair(
self.format_trading_pair(&pair),
bitfinex::book::BookPrecision::P0,
)
.await?;
let entries = x
.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-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-16 11:43:16 +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
}
}
2021-01-16 11:43:16 +00:00
2021-01-22 15:37:53 +00:00
impl From<bitfinex::orders::ActiveOrder> for ActiveOrder {
fn from(o: bitfinex::orders::ActiveOrder) -> Self {
2021-01-16 11:43:16 +00:00
Self {
id: o.id,
group_id: o.group_id,
client_id: o.client_id,
symbol: o.symbol,
creation_timestamp: o.creation_timestamp,
update_timestamp: o.update_timestamp,
amount: o.amount,
amount_original: o.amount_original,
order_type: o.order_type,
previous_order_type: o.previous_order_type,
flags: o.flags,
order_status: o.order_status,
price: o.price,
price_avg: o.price_avg,
price_trailing: o.price_trailing,
price_aux_limit: o.price_aux_limit,
notify: o.notify,
hidden: o.hidden,
placed_id: o.placed_id,
}
}
}