669 lines
16 KiB
Rust
669 lines
16 KiB
Rust
use std::fmt;
|
|
use std::fmt::{Display, Formatter};
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
use dyn_clone::clone_box;
|
|
|
|
use crate::connectors::Exchange;
|
|
use crate::currency::{Symbol, SymbolPair};
|
|
use crate::strategy::OrderStrategy;
|
|
|
|
/***************
|
|
* 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<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))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum OrderFee {
|
|
Maker(f64),
|
|
Taker(f64),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OrderDetails {
|
|
exchange: Exchange,
|
|
pair: SymbolPair,
|
|
platform: TradingPlatform,
|
|
kind: OrderKind,
|
|
execution_timestamp: u64,
|
|
id: u64,
|
|
}
|
|
|
|
impl OrderDetails {
|
|
pub fn new(
|
|
exchange: Exchange,
|
|
id: u64,
|
|
pair: SymbolPair,
|
|
platform: TradingPlatform,
|
|
kind: OrderKind,
|
|
execution_timestamp: u64,
|
|
) -> Self {
|
|
OrderDetails {
|
|
exchange,
|
|
pair,
|
|
platform,
|
|
kind,
|
|
execution_timestamp,
|
|
id,
|
|
}
|
|
}
|
|
|
|
pub fn id(&self) -> u64 {
|
|
self.id
|
|
}
|
|
|
|
pub fn pair(&self) -> &SymbolPair {
|
|
&self.pair
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ActiveOrder {
|
|
exchange: Exchange,
|
|
id: u64,
|
|
group_id: Option<u64>,
|
|
client_id: Option<u64>,
|
|
pair: SymbolPair,
|
|
order_form: OrderForm,
|
|
creation_timestamp: u64,
|
|
update_timestamp: u64,
|
|
strategy: Option<Box<dyn OrderStrategy>>,
|
|
}
|
|
|
|
impl ActiveOrder {
|
|
pub fn new(
|
|
exchange: Exchange,
|
|
id: u64,
|
|
pair: SymbolPair,
|
|
order_form: OrderForm,
|
|
creation_timestamp: u64,
|
|
update_timestamp: u64,
|
|
) -> Self {
|
|
Self {
|
|
exchange,
|
|
id,
|
|
group_id: None,
|
|
client_id: None,
|
|
pair,
|
|
order_form,
|
|
creation_timestamp,
|
|
update_timestamp,
|
|
strategy: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_group_id(mut self, group_id: Option<u64>) -> Self {
|
|
self.group_id = group_id;
|
|
self
|
|
}
|
|
|
|
pub fn with_client_id(mut self, client_id: Option<u64>) -> Self {
|
|
self.client_id = client_id;
|
|
self
|
|
}
|
|
|
|
pub fn with_strategy(mut self, strategy: Option<Box<dyn OrderStrategy>>) -> Self {
|
|
self.strategy = strategy;
|
|
self
|
|
}
|
|
|
|
pub fn with_leverage(mut self, leverage: Option<f64>) -> Self {
|
|
self.order_form = self.order_form.with_leverage(leverage);
|
|
self
|
|
}
|
|
|
|
pub fn exchange(&self) -> Exchange {
|
|
self.exchange
|
|
}
|
|
pub fn id(&self) -> u64 {
|
|
self.id
|
|
}
|
|
pub fn group_id(&self) -> Option<u64> {
|
|
self.group_id
|
|
}
|
|
pub fn client_id(&self) -> Option<u64> {
|
|
self.client_id
|
|
}
|
|
pub fn pair(&self) -> &SymbolPair {
|
|
&self.pair
|
|
}
|
|
pub fn order_form(&self) -> &OrderForm {
|
|
&self.order_form
|
|
}
|
|
pub fn creation_timestamp(&self) -> u64 {
|
|
self.creation_timestamp
|
|
}
|
|
pub fn update_timestamp(&self) -> u64 {
|
|
self.update_timestamp
|
|
}
|
|
pub fn strategy(&self) -> &Option<Box<dyn OrderStrategy>> {
|
|
&self.strategy
|
|
}
|
|
}
|
|
|
|
impl Hash for ActiveOrder {
|
|
fn hash<H: Hasher>(&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 {}
|
|
|
|
impl Clone for ActiveOrder {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
exchange: self.exchange,
|
|
id: self.id,
|
|
group_id: self.group_id,
|
|
client_id: self.client_id,
|
|
pair: self.pair.clone(),
|
|
order_form: self.order_form.clone(),
|
|
creation_timestamp: self.creation_timestamp,
|
|
update_timestamp: self.update_timestamp,
|
|
strategy: self.strategy.as_ref().map(|x| clone_box(&**x))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
|
pub enum TradingPlatform {
|
|
Exchange,
|
|
Derivative,
|
|
Funding,
|
|
Margin,
|
|
}
|
|
|
|
impl TradingPlatform {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
TradingPlatform::Exchange => "Exchange",
|
|
TradingPlatform::Derivative => "Derivative",
|
|
TradingPlatform::Funding => "Funding",
|
|
TradingPlatform::Margin => "Margin",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for TradingPlatform {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.as_str())
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum OrderKind {
|
|
Limit { price: f64 },
|
|
Market,
|
|
Stop { price: f64 },
|
|
StopLimit { stop_price: f64, limit_price: f64 },
|
|
TrailingStop { distance: f64 },
|
|
FillOrKill { price: f64 },
|
|
ImmediateOrCancel { price: f64 },
|
|
}
|
|
|
|
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",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for OrderKind {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
OrderKind::Limit { price } => {
|
|
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price, )
|
|
}
|
|
OrderKind::Market => {
|
|
write!(f, "[{}]", self.as_str())
|
|
}
|
|
OrderKind::Stop { price } => {
|
|
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price, )
|
|
}
|
|
OrderKind::StopLimit { stop_price, limit_price } => {
|
|
write!(
|
|
f,
|
|
"[{} | Stop: {:0.5}, Limit: {:0.5}]",
|
|
self.as_str(),
|
|
stop_price,
|
|
limit_price
|
|
)
|
|
}
|
|
OrderKind::TrailingStop { distance } => {
|
|
write!(f, "[{} | Distance: {:0.5}]", self.as_str(), distance, )
|
|
}
|
|
OrderKind::FillOrKill { price } => {
|
|
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price, )
|
|
}
|
|
OrderKind::ImmediateOrCancel { price } => {
|
|
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price, )
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct OrderForm {
|
|
pair: SymbolPair,
|
|
kind: OrderKind,
|
|
platform: TradingPlatform,
|
|
amount: f64,
|
|
leverage: Option<f64>,
|
|
metadata: Option<OrderMetadata>,
|
|
}
|
|
|
|
impl OrderForm {
|
|
pub fn new(
|
|
pair: SymbolPair,
|
|
order_kind: OrderKind,
|
|
platform: TradingPlatform,
|
|
amount: f64,
|
|
) -> Self {
|
|
Self {
|
|
pair,
|
|
kind: order_kind,
|
|
platform,
|
|
amount,
|
|
leverage: None,
|
|
metadata: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_leverage(mut self, leverage: Option<f64>) -> Self {
|
|
self.leverage = leverage;
|
|
self
|
|
}
|
|
|
|
pub fn with_metadata(mut self, metadata: Option<OrderMetadata>) -> Self {
|
|
self.metadata = metadata;
|
|
self
|
|
}
|
|
|
|
pub fn pair(&self) -> &SymbolPair {
|
|
&self.pair
|
|
}
|
|
|
|
pub fn kind(&self) -> OrderKind {
|
|
self.kind
|
|
}
|
|
|
|
pub fn platform(&self) -> &TradingPlatform {
|
|
&self.platform
|
|
}
|
|
|
|
pub fn amount(&self) -> f64 {
|
|
self.amount
|
|
}
|
|
|
|
pub fn price(&self) -> Option<f64> {
|
|
match self.kind {
|
|
OrderKind::Limit { price, .. } => Some(price),
|
|
OrderKind::Market { .. } => None,
|
|
OrderKind::Stop { price, .. } => Some(price),
|
|
OrderKind::StopLimit { stop_price: price, .. } => Some(price),
|
|
OrderKind::TrailingStop { .. } => None,
|
|
OrderKind::FillOrKill { price, .. } => Some(price),
|
|
OrderKind::ImmediateOrCancel { price, .. } => Some(price),
|
|
}
|
|
}
|
|
|
|
pub fn leverage(&self) -> Option<f64> {
|
|
self.leverage
|
|
}
|
|
|
|
pub fn metadata(&self) -> &Option<OrderMetadata> {
|
|
&self.metadata
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OrderMetadata {
|
|
position_id: Option<u64>,
|
|
strategy: Option<Box<dyn OrderStrategy>>,
|
|
}
|
|
|
|
impl Clone for OrderMetadata {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
position_id: self.position_id.clone(),
|
|
strategy: self.strategy.as_ref().map(|x| clone_box(&**x)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl OrderMetadata {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
position_id: None,
|
|
strategy: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_position_id(mut self, position_id: Option<u64>) -> Self {
|
|
self.position_id = position_id;
|
|
self
|
|
}
|
|
|
|
pub fn with_strategy(mut self, strategy: Option<Box<dyn OrderStrategy>>) -> Self {
|
|
self.strategy = strategy;
|
|
self
|
|
}
|
|
|
|
pub fn position_id(&self) -> Option<u64> {
|
|
self.position_id
|
|
}
|
|
pub fn cloned_strategy(&self) -> Option<Box<dyn OrderStrategy>> {
|
|
match &self.strategy {
|
|
None => { None }
|
|
Some(strategy) => {
|
|
Some(clone_box(&**strategy))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************
|
|
* Positions
|
|
***************/
|
|
|
|
#[derive(Clone, Debug)]
|
|
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>,
|
|
platform: TradingPlatform,
|
|
leverage: f64,
|
|
}
|
|
|
|
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,
|
|
platform: TradingPlatform,
|
|
leverage: f64,
|
|
) -> Self {
|
|
Position {
|
|
pair,
|
|
state,
|
|
amount,
|
|
base_price,
|
|
pl,
|
|
pl_perc,
|
|
price_liq,
|
|
position_id,
|
|
creation_date: None,
|
|
creation_update: None,
|
|
profit_state: None,
|
|
platform,
|
|
leverage,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pub fn update_profit_loss(&mut self, best_offer: f64, fee_perc: f64) {
|
|
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;
|
|
|
|
(base_price, delta)
|
|
} else {
|
|
let base_price = self.base_price * (1.0 + fee_perc / 100.0);
|
|
let delta = best_offer - base_price;
|
|
|
|
(base_price, delta)
|
|
}
|
|
};
|
|
|
|
let profit_loss = delta * self.amount.abs();
|
|
let profit_loss_percentage = delta / base_price * 100.0;
|
|
|
|
self.pl = profit_loss;
|
|
self.pl_perc = profit_loss_percentage;
|
|
}
|
|
|
|
pub fn with_profit_loss(mut self, profit_loss: f64) -> Self {
|
|
self.pl = profit_loss;
|
|
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<PositionProfitState> {
|
|
self.profit_state
|
|
}
|
|
pub fn creation_date(&self) -> Option<u64> {
|
|
self.creation_date
|
|
}
|
|
pub fn creation_update(&self) -> Option<u64> {
|
|
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()
|
|
}
|
|
pub fn platform(&self) -> TradingPlatform {
|
|
self.platform
|
|
}
|
|
pub fn leverage(&self) -> f64 {
|
|
self.leverage
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
impl Eq for Position {}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
|
pub enum PositionProfitState {
|
|
Critical,
|
|
Loss,
|
|
BreakEven,
|
|
MinimumProfit,
|
|
Profit,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
|
pub enum PositionState {
|
|
Closed,
|
|
Open,
|
|
}
|
|
|
|
pub enum WalletKind {
|
|
Exchange,
|
|
Margin,
|
|
Funding,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Trade {
|
|
pub trade_id: u64,
|
|
pub pair: SymbolPair,
|
|
pub execution_timestamp: u64,
|
|
pub price: f64,
|
|
pub amount: f64,
|
|
pub fee: OrderFee,
|
|
pub fee_currency: Symbol,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum TradingFees {
|
|
Maker {
|
|
platform: TradingPlatform,
|
|
percentage: f64,
|
|
},
|
|
Taker {
|
|
platform: TradingPlatform,
|
|
percentage: f64,
|
|
},
|
|
}
|