use std::fmt::Display; use std::hash::{Hash, Hasher}; use chrono::{DateTime, TimeZone}; use float_cmp::ApproxEq; use crate::currency::SymbolPair; use crate::BoxError; /*************** * Prices ***************/ #[derive(Copy, Clone, Debug)] pub struct PriceTicker { pub bid: f64, pub bid_size: f64, pub ask: f64, pub ask_size: f64, pub daily_change: f64, pub daily_change_perc: f64, pub last_price: f64, pub volume: f64, pub high: f64, pub low: f64, } /*************** * Orders ***************/ #[derive(Debug)] pub enum OrderBookEntry { Trading { price: f64, count: u64, amount: f64, }, Funding { rate: f64, period: u64, count: u64, amount: f64, }, } #[derive(Debug)] pub struct OrderBook { pair: SymbolPair, entries: Vec, } impl OrderBook { pub fn new(pair: SymbolPair) -> Self { OrderBook { pair, entries: Vec::new(), } } pub fn with_entries(mut self, entries: Vec) -> Self { self.entries = entries; self } // TODO: distinguish between trading and funding pub fn bids(&self) -> Vec<&OrderBookEntry> { self.entries .iter() .filter(|x| match x { OrderBookEntry::Trading { amount, .. } => amount > &0.0, OrderBookEntry::Funding { amount, .. } => amount < &0.0, }) .collect() } // TODO: distinguish between trading and funding pub fn asks(&self) -> Vec<&OrderBookEntry> { self.entries .iter() .filter(|x| match x { OrderBookEntry::Trading { amount, .. } => amount < &0.0, OrderBookEntry::Funding { amount, .. } => amount > &0.0, }) .collect() } pub fn highest_bid(&self) -> f64 { self.bids() .iter() .map(|x| match x { OrderBookEntry::Trading { price, .. } => price, OrderBookEntry::Funding { rate, .. } => rate, }) .fold(f64::NEG_INFINITY, |a, &b| a.max(b)) } pub fn lowest_ask(&self) -> f64 { self.asks() .iter() .map(|x| match x { OrderBookEntry::Trading { price, .. } => price, OrderBookEntry::Funding { rate, .. } => rate, }) .fold(f64::INFINITY, |a, &b| a.min(b)) } } #[derive(Clone, Debug)] pub struct ActiveOrder { pub(crate) id: u64, pub(crate) group_id: Option, pub(crate) client_id: u64, pub(crate) symbol: SymbolPair, pub(crate) creation_timestamp: u64, pub(crate) update_timestamp: u64, pub(crate) amount: f64, pub(crate) amount_original: f64, pub(crate) order_type: OrderKind, pub(crate) previous_order_type: Option, pub(crate) price: f64, pub(crate) price_avg: f64, pub(crate) hidden: bool, } impl Hash for ActiveOrder { fn hash(&self, state: &mut H) { state.write(&self.id.to_le_bytes()) } } impl PartialEq for ActiveOrder { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.client_id == other.client_id && self.group_id == other.group_id } } impl Eq for ActiveOrder {} #[derive(Copy, Clone, Debug, Hash)] pub enum OrderKind { Limit, ExchangeLimit, Market, ExchangeMarket, Stop, ExchangeStop, StopLimit, ExchangeStopLimit, TrailingStop, Fok, ExchangeFok, Ioc, ExchangeIoc, } #[derive(Clone, Debug)] pub struct OrderForm { /// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, /// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, /// TRAILING STOP, EXCHANGE TRAILING STOP, FOK, /// EXCHANGE FOK, IOC, EXCHANGE IOC kind: OrderKind, /// Symbol for desired pair pair: SymbolPair, /// Price of order price: f64, /// Amount of order (positive for buy, negative for sell) amount: f64, /// Set the leverage for a derivative order, supported by derivative symbol orders only. /// The value should be between 1 and 100 inclusive. /// The field is optional, if omitted the default leverage value of 10 will be used. leverage: Option, /// The trailing price for a trailing stop order price_trailing: Option, /// Auxiliary Limit price (for STOP LIMIT) price_aux_limit: Option, /// Time-In-Force: datetime for automatic order cancellation (ie. 2020-01-01 10:45:23) ) tif: Option, } impl OrderForm { pub fn new(pair: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self { OrderForm { kind, pair: pair.clone(), price, amount, leverage: None, price_trailing: None, price_aux_limit: None, tif: None, } } pub fn with_leverage(mut self, leverage: u32) -> Self { self.leverage = Some(leverage); self } pub fn with_price_trailing(mut self, trailing: f64) -> Result { match self.kind { OrderKind::TrailingStop => { self.price_trailing = Some(trailing.to_string()); Ok(self) } _ => Err("Invalid order type.".into()), } } pub fn with_price_aux_limit(mut self, limit: f64) -> Result { match self.kind { OrderKind::StopLimit | OrderKind::ExchangeStopLimit => { self.price_aux_limit = Some(limit.to_string()); Ok(self) } _ => Err("Invalid order type.".into()), } } pub fn with_tif(mut self, tif: DateTime) -> Self where T::Offset: Display, { self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string()); self } pub fn kind(&self) -> OrderKind { self.kind } pub fn pair(&self) -> &SymbolPair { &self.pair } pub fn price(&self) -> &f64 { &self.price } pub fn amount(&self) -> &f64 { &self.amount } pub fn leverage(&self) -> Option { self.leverage } pub fn price_trailing(&self) -> &Option { &self.price_trailing } pub fn price_aux_limit(&self) -> &Option { &self.price_aux_limit } pub fn tif(&self) -> &Option { &self.tif } } /*************** * Positions ***************/ #[derive(Clone, Debug)] pub struct Position { pair: SymbolPair, state: PositionState, profit_state: Option, amount: f64, base_price: f64, pl: f64, pl_perc: f64, price_liq: f64, position_id: u64, creation_date: Option, creation_update: Option, } impl Position { pub fn new( pair: SymbolPair, state: PositionState, amount: f64, base_price: f64, pl: f64, pl_perc: f64, price_liq: f64, position_id: u64, ) -> Self { Position { pair, state, amount, base_price, pl, pl_perc, price_liq, position_id, creation_date: None, creation_update: None, profit_state: None, } } pub fn with_creation_date(mut self, creation_date: Option) -> Self { self.creation_date = creation_date; self } pub fn with_creation_update(mut self, creation_update: Option) -> Self { self.creation_update = creation_update; self } pub fn with_profit_state(mut self, profit_state: Option) -> Self { self.profit_state = profit_state; self } pub fn pair(&self) -> &SymbolPair { &self.pair } pub fn state(&self) -> PositionState { self.state } pub fn amount(&self) -> f64 { self.amount } pub fn base_price(&self) -> f64 { self.base_price } pub fn pl(&self) -> f64 { self.pl } pub fn pl_perc(&self) -> f64 { self.pl_perc } pub fn price_liq(&self) -> f64 { self.price_liq } pub fn id(&self) -> u64 { self.position_id } pub fn profit_state(&self) -> Option { self.profit_state } pub fn creation_date(&self) -> Option { self.creation_date } pub fn creation_update(&self) -> Option { self.creation_update } pub fn is_short(&self) -> bool { self.amount.is_sign_negative() } pub fn is_long(&self) -> bool { self.amount.is_sign_positive() } } impl Hash for Position { fn hash(&self, state: &mut H) { state.write(&self.id().to_le_bytes()) } } impl PartialEq for Position { fn eq(&self, other: &Self) -> bool { self.id() == other.id() } } impl Eq for Position {} #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum PositionProfitState { Critical, Loss, BreakEven, MinimumProfit, Profit, } impl PositionProfitState { fn color(self) -> String { match self { PositionProfitState::Critical | PositionProfitState::Loss => "red", PositionProfitState::BreakEven => "yellow", PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green", } .into() } } #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum PositionState { Closed, Open, }