2021-01-23 16:13:37 +00:00
|
|
|
use std::fmt;
|
|
|
|
use std::fmt::{Display, Formatter};
|
2021-01-19 21:30:01 +00:00
|
|
|
use std::hash::{Hash, Hasher};
|
2021-01-16 11:43:16 +00:00
|
|
|
|
2021-01-24 20:53:33 +00:00
|
|
|
use crate::connectors::Exchange;
|
2021-01-13 08:57:46 +00:00
|
|
|
use crate::currency::SymbolPair;
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
/***************
|
|
|
|
* 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
|
|
|
|
***************/
|
|
|
|
|
2021-01-19 21:30:01 +00:00
|
|
|
#[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<OrderBookEntry>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OrderBook {
|
|
|
|
pub fn new(pair: SymbolPair) -> Self {
|
|
|
|
OrderBook {
|
|
|
|
pair,
|
|
|
|
entries: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_entries(mut self, entries: Vec<OrderBookEntry>) -> 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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2021-01-22 15:37:53 +00:00
|
|
|
pub struct ActiveOrder {
|
2021-01-23 16:13:37 +00:00
|
|
|
pub(crate) exchange: Exchange,
|
2021-01-22 16:08:40 +00:00
|
|
|
pub(crate) id: u64,
|
|
|
|
pub(crate) group_id: Option<u64>,
|
2021-01-23 16:13:37 +00:00
|
|
|
pub(crate) client_id: Option<u64>,
|
2021-01-22 16:08:40 +00:00
|
|
|
pub(crate) symbol: SymbolPair,
|
2021-01-23 16:13:37 +00:00
|
|
|
pub(crate) current_form: OrderForm,
|
2021-01-22 16:08:40 +00:00
|
|
|
pub(crate) creation_timestamp: u64,
|
|
|
|
pub(crate) update_timestamp: u64,
|
2021-01-13 08:57:46 +00:00
|
|
|
}
|
|
|
|
|
2021-01-22 15:37:53 +00:00
|
|
|
impl Hash for ActiveOrder {
|
2021-01-16 11:43:16 +00:00
|
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
2021-01-23 16:13:37 +00:00
|
|
|
state.write(&self.id.to_le_bytes());
|
2021-01-16 11:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 11:44:59 +00:00
|
|
|
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 {}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub enum TradingPlatform {
|
|
|
|
Exchange,
|
|
|
|
Derivative,
|
|
|
|
Funding,
|
|
|
|
Margin,
|
2021-01-13 08:57:46 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl TradingPlatform {
|
|
|
|
pub fn as_str(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
TradingPlatform::Exchange => "Exchange",
|
|
|
|
TradingPlatform::Derivative => "Derivative",
|
|
|
|
TradingPlatform::Funding => "Funding",
|
|
|
|
TradingPlatform::Margin => "Margin",
|
2021-01-15 10:49:44 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
}
|
2021-01-15 10:49:44 +00:00
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl Display for TradingPlatform {
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{}", self.as_str())
|
2021-01-15 10:49:44 +00:00
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
}
|
2021-01-15 10:49:44 +00:00
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
|
|
pub enum OrderKind {
|
|
|
|
Limit {
|
|
|
|
price: f64,
|
|
|
|
amount: f64,
|
|
|
|
},
|
|
|
|
Market {
|
|
|
|
amount: f64,
|
|
|
|
},
|
|
|
|
Stop {
|
|
|
|
price: f64,
|
|
|
|
amount: f64,
|
|
|
|
},
|
|
|
|
StopLimit {
|
|
|
|
price: f64,
|
|
|
|
amount: f64,
|
|
|
|
limit_price: f64,
|
|
|
|
},
|
|
|
|
TrailingStop {
|
|
|
|
distance: f64,
|
|
|
|
amount: f64,
|
|
|
|
},
|
|
|
|
FillOrKill {
|
|
|
|
price: f64,
|
|
|
|
amount: f64,
|
|
|
|
},
|
|
|
|
ImmediateOrCancel {
|
|
|
|
price: f64,
|
|
|
|
amount: f64,
|
|
|
|
},
|
|
|
|
}
|
2021-01-24 20:53:33 +00:00
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl OrderKind {
|
|
|
|
pub fn as_str(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
OrderKind::Limit { .. } => "Limit",
|
|
|
|
OrderKind::Market { .. } => "Market",
|
|
|
|
OrderKind::Stop { .. } => "Stop",
|
|
|
|
OrderKind::StopLimit { .. } => "Stop Limit",
|
|
|
|
OrderKind::TrailingStop { .. } => "Trailing Stop",
|
|
|
|
OrderKind::FillOrKill { .. } => "Fill or Kill",
|
|
|
|
OrderKind::ImmediateOrCancel { .. } => "Immediate or Cancel",
|
2021-01-15 10:49:44 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
}
|
2021-01-15 10:49:44 +00:00
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl Display for OrderKind {
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
OrderKind::Limit { price, amount } => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
|
|
|
self.as_str(),
|
|
|
|
price,
|
|
|
|
amount
|
|
|
|
)
|
|
|
|
}
|
|
|
|
OrderKind::Market { amount } => {
|
|
|
|
write!(f, "[{} | Amount: {:0.5}]", self.as_str(), amount)
|
|
|
|
}
|
|
|
|
OrderKind::Stop { price, amount } => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
|
|
|
self.as_str(),
|
|
|
|
price,
|
|
|
|
amount
|
|
|
|
)
|
|
|
|
}
|
|
|
|
OrderKind::StopLimit {
|
|
|
|
price,
|
|
|
|
amount,
|
|
|
|
limit_price,
|
|
|
|
} => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"[{} | Price: {:0.5}, Amount: {:0.5}, Limit Price: {:0.5}]",
|
|
|
|
self.as_str(),
|
|
|
|
price,
|
|
|
|
amount,
|
|
|
|
limit_price
|
|
|
|
)
|
|
|
|
}
|
|
|
|
OrderKind::TrailingStop { distance, amount } => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"[{} | Distance: {:0.5}, Amount: {:0.5}]",
|
|
|
|
self.as_str(),
|
|
|
|
distance,
|
|
|
|
amount
|
|
|
|
)
|
|
|
|
}
|
|
|
|
OrderKind::FillOrKill { price, amount } => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
|
|
|
self.as_str(),
|
|
|
|
price,
|
|
|
|
amount
|
|
|
|
)
|
|
|
|
}
|
|
|
|
OrderKind::ImmediateOrCancel { price, amount } => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
|
|
|
self.as_str(),
|
|
|
|
price,
|
|
|
|
amount
|
|
|
|
)
|
2021-01-15 10:49:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
}
|
2021-01-15 10:49:44 +00:00
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct OrderForm {
|
|
|
|
pair: SymbolPair,
|
|
|
|
kind: OrderKind,
|
|
|
|
platform: TradingPlatform,
|
|
|
|
}
|
2021-01-16 11:43:16 +00:00
|
|
|
|
2021-01-23 16:13:37 +00:00
|
|
|
impl OrderForm {
|
|
|
|
pub fn new(pair: SymbolPair, order_kind: OrderKind, platform: TradingPlatform) -> Self {
|
|
|
|
Self {
|
|
|
|
pair,
|
|
|
|
kind: order_kind,
|
|
|
|
platform,
|
|
|
|
}
|
2021-01-16 11:43:16 +00:00
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
|
2021-01-16 11:43:16 +00:00
|
|
|
pub fn pair(&self) -> &SymbolPair {
|
|
|
|
&self.pair
|
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
|
|
|
|
pub fn kind(&self) -> OrderKind {
|
|
|
|
self.kind
|
2021-01-16 11:43:16 +00:00
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
|
|
|
|
pub fn platform(&self) -> &TradingPlatform {
|
|
|
|
&self.platform
|
2021-01-16 11:43:16 +00:00
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
|
|
|
|
pub fn amount(&self) -> f64 {
|
|
|
|
match self.kind {
|
|
|
|
OrderKind::Limit { amount, .. } => amount,
|
|
|
|
OrderKind::Market { amount } => amount,
|
|
|
|
OrderKind::Stop { amount, .. } => amount,
|
|
|
|
OrderKind::StopLimit { amount, .. } => amount,
|
|
|
|
OrderKind::TrailingStop { amount, .. } => amount,
|
|
|
|
OrderKind::FillOrKill { amount, .. } => amount,
|
|
|
|
OrderKind::ImmediateOrCancel { amount, .. } => amount,
|
|
|
|
}
|
2021-01-16 11:43:16 +00:00
|
|
|
}
|
2021-01-23 16:13:37 +00:00
|
|
|
|
|
|
|
pub fn price(&self) -> Option<f64> {
|
|
|
|
match self.kind {
|
|
|
|
OrderKind::Limit { price, .. } => Some(price),
|
|
|
|
OrderKind::Market { .. } => None,
|
|
|
|
OrderKind::Stop { price, .. } => Some(price),
|
|
|
|
OrderKind::StopLimit { price, .. } => Some(price),
|
|
|
|
OrderKind::TrailingStop { .. } => None,
|
|
|
|
OrderKind::FillOrKill { price, .. } => Some(price),
|
|
|
|
OrderKind::ImmediateOrCancel { price, .. } => Some(price),
|
|
|
|
}
|
2021-01-16 11:43:16 +00:00
|
|
|
}
|
2021-01-15 10:49:44 +00:00
|
|
|
}
|
|
|
|
|
2021-01-14 12:42:23 +00:00
|
|
|
/***************
|
|
|
|
* Positions
|
|
|
|
***************/
|
|
|
|
|
2021-01-16 11:43:16 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2021-01-13 08:57:46 +00:00
|
|
|
pub struct Position {
|
|
|
|
pair: SymbolPair,
|
|
|
|
state: PositionState,
|
|
|
|
profit_state: Option<PositionProfitState>,
|
|
|
|
amount: f64,
|
|
|
|
base_price: f64,
|
|
|
|
pl: f64,
|
|
|
|
pl_perc: f64,
|
|
|
|
price_liq: f64,
|
|
|
|
position_id: u64,
|
|
|
|
creation_date: Option<u64>,
|
|
|
|
creation_update: Option<u64>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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<u64>) -> Self {
|
|
|
|
self.creation_date = creation_date;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_creation_update(mut self, creation_update: Option<u64>) -> Self {
|
|
|
|
self.creation_update = creation_update;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_profit_state(mut self, profit_state: Option<PositionProfitState>) -> Self {
|
|
|
|
self.profit_state = profit_state;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-01-24 15:42:54 +00:00
|
|
|
pub fn update_profit_loss(&mut self, best_offer: f64, fee_perc: f64) {
|
2021-01-25 13:16:45 +00:00
|
|
|
let (base_price, delta) = {
|
|
|
|
if self.is_short() {
|
|
|
|
let base_price = self.base_price * (1.0 - fee_perc / 100.0);
|
|
|
|
let delta = base_price - best_offer;
|
2021-01-24 15:42:54 +00:00
|
|
|
|
2021-01-25 13:16:45 +00:00
|
|
|
(base_price, delta)
|
|
|
|
} else {
|
2021-01-25 13:54:25 +00:00
|
|
|
let base_price = self.base_price * (1.0 + fee_perc / 100.0);
|
2021-01-25 13:16:45 +00:00
|
|
|
let delta = best_offer - base_price;
|
|
|
|
|
|
|
|
(base_price, delta)
|
|
|
|
}
|
2021-01-24 15:42:54 +00:00
|
|
|
};
|
|
|
|
|
2021-01-25 13:16:45 +00:00
|
|
|
let profit_loss = delta * self.amount.abs();
|
2021-01-25 13:54:25 +00:00
|
|
|
let profit_loss_percentage = delta / base_price * 100.0;
|
2021-01-24 15:42:54 +00:00
|
|
|
|
2021-01-25 12:28:01 +00:00
|
|
|
self.pl = profit_loss;
|
2021-01-24 15:42:54 +00:00
|
|
|
self.pl_perc = profit_loss_percentage;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_profit_loss(mut self, profit_loss: f64) -> Self {
|
|
|
|
self.pl = profit_loss;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-01-13 08:57:46 +00:00
|
|
|
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
|
|
|
|
}
|
2021-01-16 11:43:16 +00:00
|
|
|
pub fn id(&self) -> u64 {
|
2021-01-13 08:57:46 +00:00
|
|
|
self.position_id
|
|
|
|
}
|
|
|
|
pub fn profit_state(&self) -> Option<PositionProfitState> {
|
|
|
|
self.profit_state
|
|
|
|
}
|
|
|
|
pub fn creation_date(&self) -> Option<u64> {
|
|
|
|
self.creation_date
|
|
|
|
}
|
|
|
|
pub fn creation_update(&self) -> Option<u64> {
|
|
|
|
self.creation_update
|
|
|
|
}
|
2021-01-16 11:43:16 +00:00
|
|
|
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<H: Hasher>(&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()
|
|
|
|
}
|
2021-01-13 08:57:46 +00:00
|
|
|
}
|
2021-01-24 20:53:33 +00:00
|
|
|
|
2021-01-16 11:43:16 +00:00
|
|
|
impl Eq for Position {}
|
2021-01-13 08:57:46 +00:00
|
|
|
|
|
|
|
#[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,
|
|
|
|
}
|