diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index c8f84dd..b332147 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use log::{debug, error, info}; use tokio::time::delay_for; -use crate::connectors::{Client, ExchangeKind}; +use crate::connectors::{Client, ExchangeDetails}; use crate::currency::{Symbol, SymbolPair}; use crate::events::Event; use crate::managers::{ExchangeManager, OrderManager, PositionManager, PriceManager}; @@ -21,7 +21,7 @@ pub struct BfxBot { impl BfxBot { pub fn new( - exchanges: Vec, + exchanges: Vec, trading_symbols: Vec, quote: Symbol, tick_duration: Duration, diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index b3702af..06b4756 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; -use bitfinex::orders::{OrderMeta, OrderResponse}; +use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse}; use bitfinex::ticker::TradingPairTicker; use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; use log::debug; @@ -13,12 +13,17 @@ use log::debug; use crate::currency::SymbolPair; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, - PriceTicker, + PriceTicker, TradingPlatform, }; use crate::BoxError; +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum Exchange { + Bitfinex, +} + #[derive(Eq, PartialEq, Hash, Clone, Debug)] -pub enum ExchangeKind { +pub enum ExchangeDetails { Bitfinex { api_key: String, api_secret: String }, } @@ -26,14 +31,14 @@ pub enum ExchangeKind { /// because it already uses an [`Arc`] internally. #[derive(Clone, Debug)] pub struct Client { - exchange: ExchangeKind, + exchange: ExchangeDetails, inner: Arc>, } impl Client { - pub fn new(exchange: &ExchangeKind) -> Self { + pub fn new(exchange: &ExchangeDetails) -> Self { let inner = match &exchange { - ExchangeKind::Bitfinex { + ExchangeDetails::Bitfinex { api_key, api_secret, } => BitfinexConnector::new(&api_key, &api_secret), @@ -60,7 +65,7 @@ impl Client { self.inner.active_orders(pair).await } - pub async fn submit_order(&self, order: OrderForm) -> Result { + pub async fn submit_order(&self, order: &OrderForm) -> Result { self.inner.submit_order(order).await } @@ -80,7 +85,7 @@ pub trait Connector: Send + Sync { async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn order_book(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; - async fn submit_order(&self, order: OrderForm) -> Result; + async fn submit_order(&self, order: &OrderForm) -> Result; async fn cancel_order(&self, order: &ActiveOrder) -> Result; } @@ -111,7 +116,7 @@ impl BitfinexConnector { } } - fn format_trading_pair(&self, pair: &SymbolPair) -> String { + fn format_trading_pair(pair: &SymbolPair) -> String { if pair.to_string().to_lowercase().contains("test") { format!("{}:{}", pair.base(), pair.quote()) } else { @@ -143,11 +148,9 @@ impl Connector for BitfinexConnector { } async fn current_prices(&self, pair: &SymbolPair) -> Result { - let ticker: TradingPairTicker = self - .bfx - .ticker - .trading_pair(self.format_trading_pair(pair)) - .await?; + let symbol_name = BitfinexConnector::format_trading_pair(pair); + + let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(symbol_name).await?; Ok(ticker) } @@ -156,37 +159,52 @@ impl Connector for BitfinexConnector { unimplemented!() } - async fn submit_order(&self, order: OrderForm) -> Result { - // TODO: change trading pair formatting. awful. - let order_form = match &self.affiliate_code { - Some(affiliate_code) => bitfinex::orders::OrderForm::new( - format!("t{}", self.format_trading_pair(order.pair())), - *order.price(), - *order.amount(), - order.kind().into(), - ) - .with_meta(OrderMeta::new(affiliate_code.clone())), - None => bitfinex::orders::OrderForm::new( - format!("t{}", self.format_trading_pair(order.pair())), - *order.price(), - *order.amount(), - order.kind().into(), - ), - }; + async fn submit_order(&self, order: &OrderForm) -> Result { + let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair())); + + let order_form = match order.kind() { + OrderKind::Limit { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::Market { amount } => { + bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) + } + OrderKind::Stop { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::StopLimit { + price, + amount, + limit_price, + } => bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + .with_price_aux_limit(limit_price)?, + OrderKind::TrailingStop { distance, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) + .with_price_trailing(distance)? + } + OrderKind::FillOrKill { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::ImmediateOrCancel { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + } + .with_meta(OrderMeta::new( + BitfinexConnector::AFFILIATE_CODE.to_string(), + )); let response = self.bfx.orders.submit_order(&order_form).await?; - Ok(response.try_into()?) + Ok((&response).try_into()?) } async fn order_book(&self, pair: &SymbolPair) -> Result { + let symbol_name = BitfinexConnector::format_trading_pair(pair); + let x = self .bfx .book - .trading_pair( - self.format_trading_pair(&pair), - bitfinex::book::BookPrecision::P0, - ) + .trading_pair(symbol_name, bitfinex::book::BookPrecision::P0) .await?; let entries = x @@ -202,39 +220,35 @@ impl Connector for BitfinexConnector { } async fn cancel_order(&self, order: &ActiveOrder) -> Result { - let date = DateTime::::from_utc( - NaiveDateTime::from_timestamp(order.update_timestamp as i64, 0), - Utc, - ); - let cancel_form = bitfinex::orders::CancelOrderForm::new(order.id, order.client_id, date); + let cancel_form = order.into(); - Ok(self - .bfx - .orders - .cancel_order(&cancel_form) - .await? - .try_into()?) + Ok((&self.bfx.orders.cancel_order(&cancel_form).await?).try_into()?) } } -impl TryFrom for ActiveOrder { +impl From<&ActiveOrder> for CancelOrderForm { + fn from(o: &ActiveOrder) -> Self { + Self::from_id(o.id) + } +} + +impl TryFrom<&bitfinex::orders::OrderResponse> for ActiveOrder { type Error = BoxError; - fn try_from(response: OrderResponse) -> Result { + fn try_from(response: &OrderResponse) -> Result { Ok(Self { + exchange: Exchange::Bitfinex, id: response.id(), group_id: response.gid(), - client_id: response.cid(), + client_id: Some(response.cid()), symbol: SymbolPair::from_str(response.symbol())?, - creation_timestamp: response.mts_create(), - update_timestamp: response.mts_update(), - amount: response.amount(), - amount_original: response.amount_orig(), - order_type: (&response.order_type()).into(), - previous_order_type: response.prev_order_type().map(|x| (&x).into()), - price: response.price(), - price_avg: response.price_avg(), - hidden: response.hidden(), + current_form: OrderForm::new( + SymbolPair::from_str(response.symbol())?, + response.into(), + response.into(), + ), + creation_timestamp: 0, + update_timestamp: 0, }) } } @@ -266,62 +280,90 @@ impl TryInto for bitfinex::positions::Position { } } -impl From<&OrderKind> for bitfinex::orders::OrderKind { - fn from(o: &OrderKind) -> Self { - match o { - OrderKind::Limit => Self::Limit, - OrderKind::ExchangeLimit => Self::ExchangeLimit, - OrderKind::Market => Self::Market, - OrderKind::ExchangeMarket => Self::ExchangeMarket, - OrderKind::Stop => Self::Stop, - OrderKind::ExchangeStop => Self::ExchangeStop, - OrderKind::StopLimit => Self::StopLimit, - OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, - OrderKind::TrailingStop => Self::TrailingStop, - OrderKind::Fok => Self::Fok, - OrderKind::ExchangeFok => Self::ExchangeFok, - OrderKind::Ioc => Self::Ioc, - OrderKind::ExchangeIoc => Self::ExchangeIoc, +impl From<&OrderForm> for bitfinex::orders::OrderKind { + fn from(o: &OrderForm) -> Self { + match o.platform() { + TradingPlatform::Exchange => match o.kind() { + OrderKind::Limit { .. } => bitfinex::orders::OrderKind::ExchangeLimit, + OrderKind::Market { .. } => bitfinex::orders::OrderKind::ExchangeMarket, + OrderKind::Stop { .. } => bitfinex::orders::OrderKind::ExchangeStop, + OrderKind::StopLimit { .. } => bitfinex::orders::OrderKind::ExchangeStopLimit, + OrderKind::TrailingStop { .. } => bitfinex::orders::OrderKind::ExchangeTrailingStop, + OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok, + OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc, + }, + TradingPlatform::Margin => match o.kind() { + OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit, + OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market, + OrderKind::Stop { .. } => bitfinex::orders::OrderKind::Stop, + OrderKind::StopLimit { .. } => bitfinex::orders::OrderKind::StopLimit, + OrderKind::TrailingStop { .. } => bitfinex::orders::OrderKind::TrailingStop, + OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::Fok, + OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::Ioc, + }, + _ => unimplemented!(), } } } -impl From<&bitfinex::orders::OrderKind> for OrderKind { - fn from(o: &bitfinex::orders::OrderKind) -> Self { - match o { - bitfinex::orders::OrderKind::Limit => OrderKind::Limit, - bitfinex::orders::OrderKind::ExchangeLimit => OrderKind::ExchangeLimit, - bitfinex::orders::OrderKind::Market => OrderKind::Market, - bitfinex::orders::OrderKind::ExchangeMarket => OrderKind::ExchangeMarket, - bitfinex::orders::OrderKind::Stop => OrderKind::Stop, - bitfinex::orders::OrderKind::ExchangeStop => OrderKind::ExchangeStop, - bitfinex::orders::OrderKind::StopLimit => OrderKind::StopLimit, - bitfinex::orders::OrderKind::ExchangeStopLimit => OrderKind::ExchangeStopLimit, - bitfinex::orders::OrderKind::TrailingStop => OrderKind::TrailingStop, - bitfinex::orders::OrderKind::Fok => OrderKind::Fok, - bitfinex::orders::OrderKind::ExchangeFok => OrderKind::ExchangeFok, - bitfinex::orders::OrderKind::Ioc => OrderKind::Ioc, - bitfinex::orders::OrderKind::ExchangeIoc => OrderKind::ExchangeIoc, +impl From<&bitfinex::orders::OrderResponse> for TradingPlatform { + fn from(response: &OrderResponse) -> Self { + match response.order_type() { + bitfinex::orders::OrderKind::Limit + | bitfinex::orders::OrderKind::Market + | bitfinex::orders::OrderKind::StopLimit + | bitfinex::orders::OrderKind::Stop + | bitfinex::orders::OrderKind::TrailingStop + | bitfinex::orders::OrderKind::Fok + | bitfinex::orders::OrderKind::Ioc => Self::Margin, + _ => Self::Exchange, } } } -impl From for bitfinex::orders::OrderKind { - fn from(o: OrderKind) -> Self { - match o { - OrderKind::Limit => Self::Limit, - OrderKind::ExchangeLimit => Self::ExchangeLimit, - OrderKind::Market => Self::Market, - OrderKind::ExchangeMarket => Self::ExchangeMarket, - OrderKind::Stop => Self::Stop, - OrderKind::ExchangeStop => Self::ExchangeStop, - OrderKind::StopLimit => Self::StopLimit, - OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, - OrderKind::TrailingStop => Self::TrailingStop, - OrderKind::Fok => Self::Fok, - OrderKind::ExchangeFok => Self::ExchangeFok, - OrderKind::Ioc => Self::Ioc, - OrderKind::ExchangeIoc => Self::ExchangeIoc, +impl From<&bitfinex::orders::OrderResponse> for OrderKind { + fn from(response: &OrderResponse) -> Self { + match response.order_type() { + bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => { + Self::Limit { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => { + Self::Market { + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => { + Self::Stop { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::StopLimit + | bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit { + price: response.price(), + amount: response.amount(), + limit_price: response.price_aux_limit(), + }, + bitfinex::orders::OrderKind::TrailingStop + | bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop { + distance: response.price_trailing(), + amount: response.amount(), + }, + bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => { + Self::FillOrKill { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => { + Self::ImmediateOrCancel { + price: response.price(), + amount: response.amount(), + } + } } } } diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 44e57d6..2a23e0a 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::{OrderKind, Position, PositionProfitState}; +use crate::models::{OrderForm, OrderKind, Position, PositionProfitState}; use tokio::sync::oneshot; #[derive(Debug)] @@ -20,7 +20,7 @@ pub enum Message { }, ClosePosition { position: Position, - order_kind: OrderKind, + order_form: OrderForm, }, OpenPosition, } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index cdc9d7e..20e0bc3 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -10,10 +10,12 @@ use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::oneshot; -use crate::connectors::{Client, ExchangeKind}; +use crate::connectors::{Client, ExchangeDetails}; use crate::currency::SymbolPair; use crate::events::{ActorMessage, Event, Message}; -use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker}; +use crate::models::{ + ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform, +}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; @@ -356,7 +358,7 @@ impl OrderManagerHandle { pub async fn close_position( &mut self, position: Position, - order_kind: OrderKind, + order_form: OrderForm, ) -> Result { let (send, recv) = oneshot::channel(); @@ -364,7 +366,7 @@ impl OrderManagerHandle { .send(ActorMessage { message: Message::ClosePosition { position, - order_kind, + order_form, }, respond_to: send, }) @@ -407,8 +409,8 @@ impl OrderManager { } Message::ClosePosition { position, - order_kind, - } => self.close_position(&position, &order_kind).await?, + order_form, + } => self.close_position(&position, &order_form).await?, _ => {} }; @@ -420,12 +422,11 @@ impl OrderManager { pub async fn close_position( &mut self, position: &Position, - order_kind: &OrderKind, + order_form: &OrderForm, ) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); debug!("Closing position #{}", position.id()); - debug!("Getting current prices..."); let order_book = self.client.order_book(&self.pair).await?; @@ -442,24 +443,11 @@ impl OrderManager { if let Some(messages) = messages { for m in messages { match m { - Message::ClosePosition { - position, - order_kind, - } => { - // TODO FIXME - match order_kind { - OrderKind::Market => { - info!("Closing open order with a market order."); - if let Ok(_) = self - .submit_market_order(1.0, position.amount().neg()) - .await - { - // cancel active order - info!("Cancelling open order #{}", open_order.id); - self.client.cancel_order(open_order).await?; - } - } - _ => {} + Message::ClosePosition { order_form, .. } => { + info!("Closing open order with a {} order", order_form.kind()); + if let Ok(_) = self.client.submit_order(&order_form).await { + info!("Cancelling open order #{}", open_order.id); + self.client.cancel_order(open_order).await?; } } _ => { @@ -472,27 +460,18 @@ impl OrderManager { None => { let closing_price = self.best_closing_price(&position, &order_book); - 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!() - } - }; + // TODO: hardocoded platform to Margin! + let order_form = OrderForm::new( + self.pair.clone(), + OrderKind::Limit { + price: closing_price, + amount: position.amount().neg(), + }, + TradingPlatform::Margin, + ); + + info!("Submitting {} order", order_form.kind()); + let active_order = self.client.submit_order(&order_form).await?; self.tracked_positions.insert(position.id(), active_order); } @@ -501,68 +480,6 @@ impl OrderManager { Ok(()) } - pub async fn submit_limit_order( - &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( - &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( - &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( - &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)) @@ -635,10 +552,10 @@ impl PairManager { match m { Message::ClosePosition { position, - order_kind, + order_form, } => { self.order_manager - .close_position(position, order_kind) + .close_position(position, order_form) .await?; } _ => {} @@ -651,13 +568,13 @@ impl PairManager { } pub struct ExchangeManager { - kind: ExchangeKind, + kind: ExchangeDetails, pair_managers: Vec, client: Client, } impl ExchangeManager { - pub fn new(kind: &ExchangeKind, pairs: &Vec) -> Self { + pub fn new(kind: &ExchangeDetails, pairs: &Vec) -> Self { let client = Client::new(kind); let pair_managers = pairs .into_iter() diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 5c89a44..1c5713d 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,9 +1,11 @@ -use std::fmt::Display; +use std::fmt; +use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use chrono::{DateTime, TimeZone}; use float_cmp::ApproxEq; +use crate::connectors::{Exchange, ExchangeDetails}; use crate::currency::SymbolPair; use crate::BoxError; @@ -108,24 +110,19 @@ impl OrderBook { #[derive(Clone, Debug)] pub struct ActiveOrder { + pub(crate) exchange: Exchange, pub(crate) id: u64, pub(crate) group_id: Option, - pub(crate) client_id: u64, + pub(crate) client_id: Option, pub(crate) symbol: SymbolPair, + pub(crate) current_form: OrderForm, 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()) + state.write(&self.id.to_le_bytes()); } } @@ -137,118 +134,195 @@ impl PartialEq for ActiveOrder { 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(Debug, Clone, Copy)] +pub enum TradingPlatform { + Exchange, + Derivative, + Funding, + Margin, } -#[derive(Clone, Debug)] +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, + 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, + }, +} +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, 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 + ) + } + } + } +} + +#[derive(Debug, Clone)] 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, + kind: OrderKind, + platform: TradingPlatform, } 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 new(pair: SymbolPair, order_kind: OrderKind, platform: TradingPlatform) -> Self { + Self { + pair, + kind: order_kind, + platform, } } - 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 pair(&self) -> &SymbolPair { + &self.pair } pub fn kind(&self) -> OrderKind { self.kind } - pub fn pair(&self) -> &SymbolPair { - &self.pair + + pub fn platform(&self) -> &TradingPlatform { + &self.platform } - pub fn price(&self) -> &f64 { - &self.price + + 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, + } } - 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 + + pub fn price(&self) -> Option { + 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), + } } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index b954bd8..9c5c759 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; +use std::ops::Neg; use dyn_clone::DynClone; use log::{debug, info}; @@ -9,6 +10,7 @@ use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, + TradingPlatform, }; use crate::BoxError; @@ -111,7 +113,14 @@ impl PositionStrategy for TrailingStop { debug!("Inserting close position message..."); messages.push(Message::ClosePosition { position: position.clone(), - order_kind: OrderKind::Limit, + order_form: OrderForm::new( + position.pair().clone(), + OrderKind::Limit { + price: 0.0, + amount: position.amount(), + }, + TradingPlatform::Margin, + ), }); PositionProfitState::Critical } @@ -195,7 +204,7 @@ pub struct FastOrderStrategy { impl Default for FastOrderStrategy { fn default() -> Self { - Self { threshold: 0.2 } + Self { threshold: 0.05 } } } @@ -224,7 +233,7 @@ impl OrderStrategy for FastOrderStrategy { // long let offer_comparison = { - if order.amount > 0.0 { + if order.current_form.amount() > 0.0 { order_book.highest_bid() } else { order_book.lowest_ask() @@ -233,12 +242,22 @@ impl OrderStrategy for FastOrderStrategy { // 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; + let order_price = order + .current_form + .price() + .ok_or("The active order does not have a price!")?; + let delta = (1.0 - (offer_comparison / order_price)) * 100.0; if delta > self.threshold { messages.push(Message::ClosePosition { position: active_position.clone(), - order_kind: OrderKind::Market, + order_form: OrderForm::new( + order.symbol.clone(), + OrderKind::Market { + amount: order.current_form.amount(), + }, + order.current_form.platform().clone(), + ), }) }