diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 92cc2d1..d1015ad 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -301,6 +301,15 @@ dependencies = [ "log 0.4.11", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1132,6 +1141,7 @@ dependencies = [ "chrono", "dyn-clone", "fern", + "float-cmp", "futures-util", "log 0.4.11", "regex", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index eecc05d..fe7f9b2 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -17,4 +17,5 @@ dyn-clone = "1" log = "0.4" fern = {version = "0.6", features = ["colored"]} chrono = "0.4" -byteorder = "1" \ No newline at end of file +byteorder = "1" +float-cmp = "0.8" \ No newline at end of file diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index baa3de3..53ba11e 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -13,7 +13,7 @@ use tokio::sync::oneshot; use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; use crate::events::{ActorMessage, Event, Message}; -use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; +use crate::models::{ExecutedOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; @@ -88,6 +88,8 @@ impl PriceManager { _ => {} } + message.respond_to.send((None, None)); + Ok(()) } @@ -200,7 +202,9 @@ impl PositionManagerHandle { }) .await?; - Ok(recv.await?) + let response = recv.await?; + + Ok(response) } } @@ -251,16 +255,14 @@ impl PositionManager { } pub async fn update(&mut self, tick: u64) -> Result { - debug!("Updating {}", self.pair); + // debug!("Updating {}", self.pair); let opt_active_positions = self.client.active_positions(&self.pair).await?; - let mut events = vec![]; - self.current_tick = tick; // we assume there is only ONE active position per pair match opt_active_positions { - // no open positions, no events and no order forms returned + // no open positions, no events and no messages returned None => return Ok((None, None)), Some(positions) => { @@ -271,24 +273,25 @@ impl PositionManager { .next() { // no open positions for our pair, setting active position to none - None => self.active_position = None, + None => { + self.active_position = None; + return Ok((None, None)); + } // applying strategy to open position and saving into struct Some(position) => { - let (position_after_strategy, strategy_events, _) = + let (position_after_strategy, strategy_events, strategy_messages) = self.strategy.on_new_tick(position, &self); - events.extend(strategy_events); - self.positions_history .insert(self.current_tick(), position_after_strategy.clone()); self.active_position = Some(position_after_strategy); + + return Ok((strategy_events, strategy_messages)); } } } }; - - Ok((None, None)) } pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { @@ -374,8 +377,6 @@ pub struct OrderManager { } impl OrderManager { - const UNDERCUT_PERC: f64 = 0.005; - pub fn new( receiver: Receiver, pair: SymbolPair, @@ -401,13 +402,15 @@ impl OrderManager { _ => {} }; + msg.respond_to.send((None, None)); + Ok(()) } pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); - info!("Closing position {}", position.id()); + info!("Closing position #{}", position.id()); // checking if the position has an open order. // If so, the strategy method is called, otherwise we open @@ -422,9 +425,10 @@ impl OrderManager { } None => { info!("Getting current prices..."); - let current_prices = self.client.current_prices(&self.pair).await?; + let order_book = self.client.order_book(&self.pair).await?; + info!("Calculating best closing price..."); - let closing_price = self.best_closing_price(&position, ¤t_prices)?; + let closing_price = self.best_closing_price(&position, &order_book)?; info!("Submitting order..."); // submitting order @@ -449,23 +453,41 @@ impl OrderManager { } pub fn update(&self) -> Result { - unimplemented!() + // TODO: implement me + Ok((None, None)) } pub fn best_closing_price( &self, position: &Position, - price_ticker: &TradingPairTicker, + order_book: &OrderBook, ) -> Result { + let ask = order_book.lowest_ask(); + let bid = order_book.highest_bid(); + let avg = (bid + ask) / 2.0; + let delta = (ask - bid) / 10.0; + let price = { - if position.is_short() { - price_ticker.ask + let intermediate_price = { + if position.is_short() { + bid + delta + } else { + ask - delta + } + }; + + if avg > 9999.0 { + if position.is_short() { + intermediate_price.ceil() + } else { + intermediate_price.floor() + } } else { - price_ticker.bid + intermediate_price } }; - Ok(price * (1.0 - OrderManager::UNDERCUT_PERC)) + Ok(price) } } @@ -492,9 +514,29 @@ impl PairManager { client.clone(), Box::new(TrailingStop::new()), ), - // dispatcher: Dispatcher::new(), } } + + pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { + let (opt_price_events, opt_price_messages) = self.price_manager.update(tick).await?; + let (opt_pos_events, opt_pos_messages) = self.position_manager.update(tick).await?; + let (opt_order_events, opt_order_messages) = self.order_manager.update(tick).await?; + + // TODO: to move into Handler? + + if let Some(messages) = opt_pos_messages { + for m in messages { + match m { + Message::ClosePosition { position } => { + self.order_manager.close_position(position).await?; + } + _ => {} + } + } + } + + Ok(()) + } } pub struct ExchangeManager { @@ -518,36 +560,56 @@ impl ExchangeManager { } } - pub async fn update_managers(&mut self, tick: u64) -> Result { - let (price_opt_events, price_opt_signals) = self.update_price_managers(tick).await?; - let (pos_opt_events, pos_opt_signals) = self.update_position_managers(tick).await?; - - debug!("{:?}", pos_opt_signals); - - Ok((pos_opt_events, price_opt_signals)) - } - - async fn update_position_managers(&mut self, tick: u64) -> Result { + pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { let mut futures: FuturesUnordered<_> = self .pair_managers .iter_mut() - .map(|x| x.position_manager.update(tick)) + .map(|x| x.update_managers(tick)) .collect(); - while let Some(x) = futures.next().await {} + // execute the futures + while let Some(_) = futures.next().await {} - Ok((None, None)) + Ok(()) } - async fn update_price_managers(&mut self, tick: u64) -> Result { - let mut futures: FuturesUnordered<_> = self - .pair_managers - .iter_mut() - .map(|x| x.price_manager.update(tick)) - .collect(); - - while let Some(x) = futures.next().await {} - - Ok((None, None)) - } + // async fn update_position_managers(&mut self, tick: u64) -> Result { + // let mut res_events = Vec::new(); + // let mut res_messages = Vec::new(); + // + // let mut futures: FuturesUnordered<_> = self + // .pair_managers + // .iter_mut() + // .map(|x| x.position_manager.update(tick)) + // .collect(); + // + // while let Some(future_result) = futures.next().await { + // let (opt_events, opt_messages) = future_result?; + // + // if let Some(events) = opt_events { + // res_events.extend(events); + // } + // + // if let Some(messages) = opt_messages { + // res_messages.extend(messages); + // } + // } + // + // Ok(( + // (!res_events.is_empty()).then_some(res_events), + // (!res_messages.is_empty()).then_some(res_messages), + // )) + // } + // + // async fn update_price_managers(&mut self, tick: u64) -> Result { + // let mut futures: FuturesUnordered<_> = self + // .pair_managers + // .iter_mut() + // .map(|x| x.price_manager.update(tick)) + // .collect(); + // + // while let Some(x) = futures.next().await {} + // + // Ok((None, None)) + // } } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index e2b53cb..10fa256 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,10 +1,11 @@ use std::fmt::Display; +use std::hash::{Hash, Hasher}; use chrono::{DateTime, TimeZone}; use crate::currency::SymbolPair; use crate::BoxError; -use std::hash::{Hash, Hasher}; +use float_cmp::ApproxEq; /*************** * Prices @@ -28,6 +29,83 @@ pub struct PriceTicker { * 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 ExecutedOrder { pub id: i64, @@ -74,7 +152,7 @@ pub enum OrderKind { ExchangeIoc, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct OrderForm { /// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, /// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT,