From a1354c2862f9e1ea79663e100c03ad79fbe90b66 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 23 Jan 2021 11:46:39 +0000 Subject: [PATCH] implemented Default for FastOrderStrategy. FastOrderStrategy closes position with a Market order if threshold is overridden --- rustybot/src/events.rs | 11 ++- rustybot/src/managers.rs | 194 ++++++++++++++++++++++++++++++--------- rustybot/src/strategy.rs | 70 ++++++++++++-- 3 files changed, 217 insertions(+), 58 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 5ef4766..44e57d6 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -4,7 +4,7 @@ use std::future::Future; use tokio::task::JoinHandle; use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager}; -use crate::models::{Position, PositionProfitState}; +use crate::models::{OrderKind, Position, PositionProfitState}; use tokio::sync::oneshot; #[derive(Debug)] @@ -15,8 +15,13 @@ pub struct ActorMessage { #[derive(Debug)] pub enum Message { - Update { tick: u64 }, - ClosePosition { position: Position }, + Update { + tick: u64, + }, + ClosePosition { + position: Position, + order_kind: OrderKind, + }, OpenPosition, } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index f02e196..dbbc0d6 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -353,12 +353,19 @@ impl OrderManagerHandle { Ok(recv.await?) } - pub async fn close_position(&mut self, position: Position) -> Result { + pub async fn close_position( + &mut self, + position: Position, + order_kind: OrderKind, + ) -> Result { let (send, recv) = oneshot::channel(); self.sender .send(ActorMessage { - message: Message::ClosePosition { position }, + message: Message::ClosePosition { + position, + order_kind, + }, respond_to: send, }) .await?; @@ -398,7 +405,10 @@ impl OrderManager { Message::Update { .. } => { self.update(); } - Message::ClosePosition { position, .. } => self.close_position(&position).await?, + Message::ClosePosition { + position, + order_kind, + } => self.close_position(&position, &order_kind).await?, _ => {} }; @@ -407,87 +417,177 @@ impl OrderManager { Ok(()) } - pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { + pub async fn close_position( + &mut self, + position: &Position, + order_kind: &OrderKind, + ) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); - info!("Closing position #{}", position.id()); + debug!("Closing position #{}", position.id()); + + debug!("Getting current prices..."); + let order_book = self.client.order_book(&self.pair).await?; // checking if the position has an open order. // If so, the strategy method is called, otherwise we open // an undercut limit order at the best current price. match open_order { Some(open_order) => { - info!("There is an open order. Calling strategy."); + debug!("There is an open order. Calling strategy."); + let (_, messages) = + self.strategy + .on_position_close(open_order, position, &order_book)?; - self.tracked_positions = self - .strategy - .on_position_close(open_order, &self.tracked_positions); + if let Some(messages) = messages { + for m in messages { + match m { + Message::ClosePosition { + position, + order_kind, + } => { + // TODO FIXME + match order_kind { + OrderKind::Market => { + self.submit_market_order(1.0, position.amount().neg()) + .await?; + } + _ => {} + } + } + _ => { + debug!("Received unsupported message from order strategy. Unimplemented.") + } + } + } + } } None => { - info!("Getting current prices..."); - let order_book = self.client.order_book(&self.pair).await?; + let closing_price = self.best_closing_price(&position, &order_book); - info!("Calculating best closing price..."); - let closing_price = self.best_closing_price(&position, &order_book)?; - - info!("Submitting order..."); - // submitting order - let order_form = OrderForm::new( - &self.pair, - closing_price, - position.amount().neg(), - OrderKind::Limit, - ); - - match self.client.submit_order(order_form).await { - Err(e) => error!("Could not submit order: {}", e), - Ok(o) => { - self.tracked_positions.insert(position.id(), o); - info!("Done!"); + let active_order = match order_kind { + OrderKind::Limit => { + self.submit_limit_order(closing_price, position.amount().neg()) + .await? + } + OrderKind::ExchangeLimit => { + self.submit_exchange_limit_order(closing_price, position.amount().neg()) + .await? + } + OrderKind::Market => { + self.submit_market_order(closing_price, position.amount().neg()) + .await? + } + OrderKind::ExchangeMarket => { + self.submit_exchange_market_order(closing_price, position.amount().neg()) + .await? + } + _ => { + unimplemented!() } }; + + self.tracked_positions.insert(position.id(), active_order); } } Ok(()) } + pub async fn submit_limit_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting exchange limit order of {} {}...", + amount, + self.pair.base() + ); + let order_form = OrderForm::new(&self.pair, closing_price, amount, OrderKind::Limit); + + Ok(self.client.submit_order(order_form).await?) + } + + pub async fn submit_exchange_limit_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting exchange limit order of {} {}...", + amount, + self.pair.base() + ); + let order_form = + OrderForm::new(&self.pair, closing_price, amount, OrderKind::ExchangeLimit); + + Ok(self.client.submit_order(order_form).await?) + } + + pub async fn submit_market_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting market order of {} {}...", + amount, + self.pair.base() + ); + let order_form = OrderForm::new(&self.pair, closing_price, amount, OrderKind::Market); + + Ok(self.client.submit_order(order_form).await?) + } + + pub async fn submit_exchange_market_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting market order of {} {}...", + amount, + self.pair.base() + ); + let order_form = + OrderForm::new(&self.pair, closing_price, amount, OrderKind::ExchangeMarket); + + Ok(self.client.submit_order(order_form).await?) + } + pub fn update(&self) -> Result { // TODO: implement me Ok((None, None)) } - pub fn best_closing_price( - &self, - position: &Position, - order_book: &OrderBook, - ) -> Result { + pub fn best_closing_price(&self, position: &Position, order_book: &OrderBook) -> f64 { 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 = { - let intermediate_price = { + let closing_price = { + let closing_price = { if position.is_short() { - bid + delta + bid - delta } else { - ask - delta + ask + delta } }; if avg > 9999.0 { if position.is_short() { - intermediate_price.ceil() + closing_price.ceil() } else { - intermediate_price.floor() + closing_price.floor() } } else { - intermediate_price + closing_price } }; - Ok(price) + closing_price } } @@ -496,7 +596,6 @@ pub struct PairManager { price_manager: PriceManagerHandle, order_manager: OrderManagerHandle, position_manager: PositionManagerHandle, - // dispatcher: Dispatcher, } impl PairManager { @@ -507,7 +606,7 @@ impl PairManager { order_manager: OrderManagerHandle::new( pair.clone(), client.clone(), - Box::new(FastOrderStrategy {}), + Box::new(FastOrderStrategy::default()), ), position_manager: PositionManagerHandle::new( pair.clone(), @@ -527,8 +626,13 @@ impl PairManager { if let Some(messages) = opt_pos_messages { for m in messages { match m { - Message::ClosePosition { position } => { - self.order_manager.close_position(position).await?; + Message::ClosePosition { + position, + order_kind, + } => { + self.order_manager + .close_position(position, order_kind) + .await?; } _ => {} } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 0ca1981..985a4ed 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,12 +1,16 @@ -use dyn_clone::DynClone; -use log::debug; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; +use dyn_clone::DynClone; +use log::{debug, info}; +use tokio::sync::oneshot; + use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; -use crate::models::{ActiveOrder, OrderForm, Position, PositionProfitState}; -use tokio::sync::oneshot; +use crate::models::{ + ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, +}; +use crate::BoxError; /*************** * DEFINITIONS @@ -38,8 +42,9 @@ pub trait OrderStrategy: DynClone + Send { fn on_position_close( &self, order: &ActiveOrder, - tracked_positions: &HashMap, - ) -> TrackedPositionsMap; + open_position: &Position, + order_book: &OrderBook, + ) -> Result<(Option>, Option>), BoxError>; } impl Debug for dyn OrderStrategy { @@ -106,6 +111,7 @@ impl PositionStrategy for TrailingStop { debug!("Inserting close position message..."); messages.push(Message::ClosePosition { position: position.clone(), + order_kind: OrderKind::Limit, }); PositionProfitState::Critical } @@ -181,7 +187,23 @@ impl PositionStrategy for TrailingStop { } #[derive(Clone, Debug)] -pub struct FastOrderStrategy {} +pub struct FastOrderStrategy { + // threshold (%) for which we trigger a market order + // to close an open position + threshold: f64, +} + +impl Default for FastOrderStrategy { + fn default() -> Self { + Self { threshold: 0.2 } + } +} + +impl FastOrderStrategy { + pub fn new(threshold: f64) -> Self { + Self { threshold } + } +} impl OrderStrategy for FastOrderStrategy { fn name(&self) -> String { @@ -195,8 +217,36 @@ impl OrderStrategy for FastOrderStrategy { fn on_position_close( &self, order: &ActiveOrder, - tracked_positions: &HashMap, - ) -> TrackedPositionsMap { - unimplemented!() + active_position: &Position, + order_book: &OrderBook, + ) -> Result<(Option>, Option>), BoxError> { + let mut messages = vec![]; + + // long + let offer_comparison = { + if order.amount > 0.0 { + order_book.highest_bid() + } else { + order_book.lowest_ask() + } + }; + + // if the best offer is higher than our threshold, + // ask the manager to close the position with a market order + let delta = (1.0 - (offer_comparison / order.price)) * 100.0; + + debug!( + "Offer comp: {} | Our offer: {} | Current delta: {}", + offer_comparison, order.price, delta + ); + + if delta > self.threshold { + messages.push(Message::ClosePosition { + position: active_position.clone(), + order_kind: OrderKind::Market, + }) + } + + Ok((None, (!messages.is_empty()).then_some(messages))) } }