internal order modeling overhaul

This commit is contained in:
Giulio De Pasquale 2021-01-23 16:13:37 +00:00
parent a1d905ebea
commit 5b84c99703
6 changed files with 386 additions and 334 deletions

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use log::{debug, error, info}; use log::{debug, error, info};
use tokio::time::delay_for; use tokio::time::delay_for;
use crate::connectors::{Client, ExchangeKind}; use crate::connectors::{Client, ExchangeDetails};
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
use crate::events::Event; use crate::events::Event;
use crate::managers::{ExchangeManager, OrderManager, PositionManager, PriceManager}; use crate::managers::{ExchangeManager, OrderManager, PositionManager, PriceManager};
@ -21,7 +21,7 @@ pub struct BfxBot {
impl BfxBot { impl BfxBot {
pub fn new( pub fn new(
exchanges: Vec<ExchangeKind>, exchanges: Vec<ExchangeDetails>,
trading_symbols: Vec<Symbol>, trading_symbols: Vec<Symbol>,
quote: Symbol, quote: Symbol,
tick_duration: Duration, tick_duration: Duration,

View File

@ -5,7 +5,7 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use bitfinex::api::Bitfinex; use bitfinex::api::Bitfinex;
use bitfinex::orders::{OrderMeta, OrderResponse}; use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse};
use bitfinex::ticker::TradingPairTicker; use bitfinex::ticker::TradingPairTicker;
use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
use log::debug; use log::debug;
@ -13,12 +13,17 @@ use log::debug;
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::models::{ use crate::models::{
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState,
PriceTicker, PriceTicker, TradingPlatform,
}; };
use crate::BoxError; use crate::BoxError;
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Exchange {
Bitfinex,
}
#[derive(Eq, PartialEq, Hash, Clone, Debug)] #[derive(Eq, PartialEq, Hash, Clone, Debug)]
pub enum ExchangeKind { pub enum ExchangeDetails {
Bitfinex { api_key: String, api_secret: String }, Bitfinex { api_key: String, api_secret: String },
} }
@ -26,14 +31,14 @@ pub enum ExchangeKind {
/// because it already uses an [`Arc`] internally. /// because it already uses an [`Arc`] internally.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Client { pub struct Client {
exchange: ExchangeKind, exchange: ExchangeDetails,
inner: Arc<Box<dyn Connector>>, inner: Arc<Box<dyn Connector>>,
} }
impl Client { impl Client {
pub fn new(exchange: &ExchangeKind) -> Self { pub fn new(exchange: &ExchangeDetails) -> Self {
let inner = match &exchange { let inner = match &exchange {
ExchangeKind::Bitfinex { ExchangeDetails::Bitfinex {
api_key, api_key,
api_secret, api_secret,
} => BitfinexConnector::new(&api_key, &api_secret), } => BitfinexConnector::new(&api_key, &api_secret),
@ -60,7 +65,7 @@ impl Client {
self.inner.active_orders(pair).await self.inner.active_orders(pair).await
} }
pub async fn submit_order(&self, order: OrderForm) -> Result<ActiveOrder, BoxError> { pub async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
self.inner.submit_order(order).await self.inner.submit_order(order).await
} }
@ -80,7 +85,7 @@ pub trait Connector: Send + Sync {
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError>; async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError>;
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ActiveOrder>, BoxError>; async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ActiveOrder>, BoxError>;
async fn submit_order(&self, order: OrderForm) -> Result<ActiveOrder, BoxError>; async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError>;
async fn cancel_order(&self, order: &ActiveOrder) -> Result<ActiveOrder, BoxError>; async fn cancel_order(&self, order: &ActiveOrder) -> Result<ActiveOrder, BoxError>;
} }
@ -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") { if pair.to_string().to_lowercase().contains("test") {
format!("{}:{}", pair.base(), pair.quote()) format!("{}:{}", pair.base(), pair.quote())
} else { } else {
@ -143,11 +148,9 @@ impl Connector for BitfinexConnector {
} }
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> { async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> {
let ticker: TradingPairTicker = self let symbol_name = BitfinexConnector::format_trading_pair(pair);
.bfx
.ticker let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(symbol_name).await?;
.trading_pair(self.format_trading_pair(pair))
.await?;
Ok(ticker) Ok(ticker)
} }
@ -156,37 +159,52 @@ impl Connector for BitfinexConnector {
unimplemented!() unimplemented!()
} }
async fn submit_order(&self, order: OrderForm) -> Result<ActiveOrder, BoxError> { async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
// TODO: change trading pair formatting. awful. let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair()));
let order_form = match &self.affiliate_code {
Some(affiliate_code) => bitfinex::orders::OrderForm::new( let order_form = match order.kind() {
format!("t{}", self.format_trading_pair(order.pair())), OrderKind::Limit { price, amount } => {
*order.price(), bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
*order.amount(), }
order.kind().into(), OrderKind::Market { amount } => {
) bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
.with_meta(OrderMeta::new(affiliate_code.clone())), }
None => bitfinex::orders::OrderForm::new( OrderKind::Stop { price, amount } => {
format!("t{}", self.format_trading_pair(order.pair())), bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
*order.price(), }
*order.amount(), OrderKind::StopLimit {
order.kind().into(), 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?; 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<OrderBook, BoxError> { async fn order_book(&self, pair: &SymbolPair) -> Result<OrderBook, BoxError> {
let symbol_name = BitfinexConnector::format_trading_pair(pair);
let x = self let x = self
.bfx .bfx
.book .book
.trading_pair( .trading_pair(symbol_name, bitfinex::book::BookPrecision::P0)
self.format_trading_pair(&pair),
bitfinex::book::BookPrecision::P0,
)
.await?; .await?;
let entries = x let entries = x
@ -202,39 +220,35 @@ impl Connector for BitfinexConnector {
} }
async fn cancel_order(&self, order: &ActiveOrder) -> Result<ActiveOrder, BoxError> { async fn cancel_order(&self, order: &ActiveOrder) -> Result<ActiveOrder, BoxError> {
let date = DateTime::<Utc>::from_utc( let cancel_form = order.into();
NaiveDateTime::from_timestamp(order.update_timestamp as i64, 0),
Utc,
);
let cancel_form = bitfinex::orders::CancelOrderForm::new(order.id, order.client_id, date);
Ok(self Ok((&self.bfx.orders.cancel_order(&cancel_form).await?).try_into()?)
.bfx
.orders
.cancel_order(&cancel_form)
.await?
.try_into()?)
} }
} }
impl TryFrom<bitfinex::orders::OrderResponse> 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; type Error = BoxError;
fn try_from(response: OrderResponse) -> Result<Self, Self::Error> { fn try_from(response: &OrderResponse) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
exchange: Exchange::Bitfinex,
id: response.id(), id: response.id(),
group_id: response.gid(), group_id: response.gid(),
client_id: response.cid(), client_id: Some(response.cid()),
symbol: SymbolPair::from_str(response.symbol())?, symbol: SymbolPair::from_str(response.symbol())?,
creation_timestamp: response.mts_create(), current_form: OrderForm::new(
update_timestamp: response.mts_update(), SymbolPair::from_str(response.symbol())?,
amount: response.amount(), response.into(),
amount_original: response.amount_orig(), response.into(),
order_type: (&response.order_type()).into(), ),
previous_order_type: response.prev_order_type().map(|x| (&x).into()), creation_timestamp: 0,
price: response.price(), update_timestamp: 0,
price_avg: response.price_avg(),
hidden: response.hidden(),
}) })
} }
} }
@ -266,62 +280,90 @@ impl TryInto<Position> for bitfinex::positions::Position {
} }
} }
impl From<&OrderKind> for bitfinex::orders::OrderKind { impl From<&OrderForm> for bitfinex::orders::OrderKind {
fn from(o: &OrderKind) -> Self { fn from(o: &OrderForm) -> Self {
match o { match o.platform() {
OrderKind::Limit => Self::Limit, TradingPlatform::Exchange => match o.kind() {
OrderKind::ExchangeLimit => Self::ExchangeLimit, OrderKind::Limit { .. } => bitfinex::orders::OrderKind::ExchangeLimit,
OrderKind::Market => Self::Market, OrderKind::Market { .. } => bitfinex::orders::OrderKind::ExchangeMarket,
OrderKind::ExchangeMarket => Self::ExchangeMarket, OrderKind::Stop { .. } => bitfinex::orders::OrderKind::ExchangeStop,
OrderKind::Stop => Self::Stop, OrderKind::StopLimit { .. } => bitfinex::orders::OrderKind::ExchangeStopLimit,
OrderKind::ExchangeStop => Self::ExchangeStop, OrderKind::TrailingStop { .. } => bitfinex::orders::OrderKind::ExchangeTrailingStop,
OrderKind::StopLimit => Self::StopLimit, OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok,
OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc,
OrderKind::TrailingStop => Self::TrailingStop, },
OrderKind::Fok => Self::Fok, TradingPlatform::Margin => match o.kind() {
OrderKind::ExchangeFok => Self::ExchangeFok, OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit,
OrderKind::Ioc => Self::Ioc, OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market,
OrderKind::ExchangeIoc => Self::ExchangeIoc, 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 { impl From<&bitfinex::orders::OrderResponse> for TradingPlatform {
fn from(o: &bitfinex::orders::OrderKind) -> Self { fn from(response: &OrderResponse) -> Self {
match o { match response.order_type() {
bitfinex::orders::OrderKind::Limit => OrderKind::Limit, bitfinex::orders::OrderKind::Limit
bitfinex::orders::OrderKind::ExchangeLimit => OrderKind::ExchangeLimit, | bitfinex::orders::OrderKind::Market
bitfinex::orders::OrderKind::Market => OrderKind::Market, | bitfinex::orders::OrderKind::StopLimit
bitfinex::orders::OrderKind::ExchangeMarket => OrderKind::ExchangeMarket, | bitfinex::orders::OrderKind::Stop
bitfinex::orders::OrderKind::Stop => OrderKind::Stop, | bitfinex::orders::OrderKind::TrailingStop
bitfinex::orders::OrderKind::ExchangeStop => OrderKind::ExchangeStop, | bitfinex::orders::OrderKind::Fok
bitfinex::orders::OrderKind::StopLimit => OrderKind::StopLimit, | bitfinex::orders::OrderKind::Ioc => Self::Margin,
bitfinex::orders::OrderKind::ExchangeStopLimit => OrderKind::ExchangeStopLimit, _ => Self::Exchange,
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<OrderKind> for bitfinex::orders::OrderKind { impl From<&bitfinex::orders::OrderResponse> for OrderKind {
fn from(o: OrderKind) -> Self { fn from(response: &OrderResponse) -> Self {
match o { match response.order_type() {
OrderKind::Limit => Self::Limit, bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
OrderKind::ExchangeLimit => Self::ExchangeLimit, Self::Limit {
OrderKind::Market => Self::Market, price: response.price(),
OrderKind::ExchangeMarket => Self::ExchangeMarket, amount: response.amount(),
OrderKind::Stop => Self::Stop, }
OrderKind::ExchangeStop => Self::ExchangeStop, }
OrderKind::StopLimit => Self::StopLimit, bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, Self::Market {
OrderKind::TrailingStop => Self::TrailingStop, amount: response.amount(),
OrderKind::Fok => Self::Fok, }
OrderKind::ExchangeFok => Self::ExchangeFok, }
OrderKind::Ioc => Self::Ioc, bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
OrderKind::ExchangeIoc => Self::ExchangeIoc, 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(),
}
}
} }
} }
} }

View File

@ -4,7 +4,7 @@ use std::future::Future;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager}; use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager};
use crate::models::{OrderKind, Position, PositionProfitState}; use crate::models::{OrderForm, OrderKind, Position, PositionProfitState};
use tokio::sync::oneshot; use tokio::sync::oneshot;
#[derive(Debug)] #[derive(Debug)]
@ -20,7 +20,7 @@ pub enum Message {
}, },
ClosePosition { ClosePosition {
position: Position, position: Position,
order_kind: OrderKind, order_form: OrderForm,
}, },
OpenPosition, OpenPosition,
} }

View File

@ -10,10 +10,12 @@ use tokio::sync::mpsc::channel;
use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use crate::connectors::{Client, ExchangeKind}; use crate::connectors::{Client, ExchangeDetails};
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::events::{ActorMessage, Event, Message}; 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::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop};
use crate::BoxError; use crate::BoxError;
@ -356,7 +358,7 @@ impl OrderManagerHandle {
pub async fn close_position( pub async fn close_position(
&mut self, &mut self,
position: Position, position: Position,
order_kind: OrderKind, order_form: OrderForm,
) -> Result<OptionUpdate, BoxError> { ) -> Result<OptionUpdate, BoxError> {
let (send, recv) = oneshot::channel(); let (send, recv) = oneshot::channel();
@ -364,7 +366,7 @@ impl OrderManagerHandle {
.send(ActorMessage { .send(ActorMessage {
message: Message::ClosePosition { message: Message::ClosePosition {
position, position,
order_kind, order_form,
}, },
respond_to: send, respond_to: send,
}) })
@ -407,8 +409,8 @@ impl OrderManager {
} }
Message::ClosePosition { Message::ClosePosition {
position, position,
order_kind, order_form,
} => self.close_position(&position, &order_kind).await?, } => self.close_position(&position, &order_form).await?,
_ => {} _ => {}
}; };
@ -420,12 +422,11 @@ impl OrderManager {
pub async fn close_position( pub async fn close_position(
&mut self, &mut self,
position: &Position, position: &Position,
order_kind: &OrderKind, order_form: &OrderForm,
) -> Result<(), BoxError> { ) -> Result<(), BoxError> {
let open_order = self.tracked_positions.get(&position.id()); let open_order = self.tracked_positions.get(&position.id());
debug!("Closing position #{}", position.id()); debug!("Closing position #{}", position.id());
debug!("Getting current prices..."); debug!("Getting current prices...");
let order_book = self.client.order_book(&self.pair).await?; let order_book = self.client.order_book(&self.pair).await?;
@ -442,26 +443,13 @@ impl OrderManager {
if let Some(messages) = messages { if let Some(messages) = messages {
for m in messages { for m in messages {
match m { match m {
Message::ClosePosition { Message::ClosePosition { order_form, .. } => {
position, info!("Closing open order with a {} order", order_form.kind());
order_kind, if let Ok(_) = self.client.submit_order(&order_form).await {
} => {
// 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); info!("Cancelling open order #{}", open_order.id);
self.client.cancel_order(open_order).await?; self.client.cancel_order(open_order).await?;
} }
} }
_ => {}
}
}
_ => { _ => {
debug!("Received unsupported message from order strategy. Unimplemented.") debug!("Received unsupported message from order strategy. Unimplemented.")
} }
@ -472,27 +460,18 @@ impl OrderManager {
None => { None => {
let closing_price = self.best_closing_price(&position, &order_book); let closing_price = self.best_closing_price(&position, &order_book);
let active_order = match order_kind { // TODO: hardocoded platform to Margin!
OrderKind::Limit => { let order_form = OrderForm::new(
self.submit_limit_order(closing_price, position.amount().neg()) self.pair.clone(),
.await? OrderKind::Limit {
} price: closing_price,
OrderKind::ExchangeLimit => { amount: position.amount().neg(),
self.submit_exchange_limit_order(closing_price, position.amount().neg()) },
.await? TradingPlatform::Margin,
} );
OrderKind::Market => {
self.submit_market_order(closing_price, position.amount().neg()) info!("Submitting {} order", order_form.kind());
.await? let active_order = self.client.submit_order(&order_form).await?;
}
OrderKind::ExchangeMarket => {
self.submit_exchange_market_order(closing_price, position.amount().neg())
.await?
}
_ => {
unimplemented!()
}
};
self.tracked_positions.insert(position.id(), active_order); self.tracked_positions.insert(position.id(), active_order);
} }
@ -501,68 +480,6 @@ impl OrderManager {
Ok(()) Ok(())
} }
pub async fn submit_limit_order(
&self,
closing_price: f64,
amount: f64,
) -> Result<ActiveOrder, BoxError> {
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<ActiveOrder, BoxError> {
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<ActiveOrder, BoxError> {
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<ActiveOrder, BoxError> {
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<OptionUpdate, BoxError> { pub fn update(&self) -> Result<OptionUpdate, BoxError> {
// TODO: implement me // TODO: implement me
Ok((None, None)) Ok((None, None))
@ -635,10 +552,10 @@ impl PairManager {
match m { match m {
Message::ClosePosition { Message::ClosePosition {
position, position,
order_kind, order_form,
} => { } => {
self.order_manager self.order_manager
.close_position(position, order_kind) .close_position(position, order_form)
.await?; .await?;
} }
_ => {} _ => {}
@ -651,13 +568,13 @@ impl PairManager {
} }
pub struct ExchangeManager { pub struct ExchangeManager {
kind: ExchangeKind, kind: ExchangeDetails,
pair_managers: Vec<PairManager>, pair_managers: Vec<PairManager>,
client: Client, client: Client,
} }
impl ExchangeManager { impl ExchangeManager {
pub fn new(kind: &ExchangeKind, pairs: &Vec<SymbolPair>) -> Self { pub fn new(kind: &ExchangeDetails, pairs: &Vec<SymbolPair>) -> Self {
let client = Client::new(kind); let client = Client::new(kind);
let pair_managers = pairs let pair_managers = pairs
.into_iter() .into_iter()

View File

@ -1,9 +1,11 @@
use std::fmt::Display; use std::fmt;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use chrono::{DateTime, TimeZone}; use chrono::{DateTime, TimeZone};
use float_cmp::ApproxEq; use float_cmp::ApproxEq;
use crate::connectors::{Exchange, ExchangeDetails};
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::BoxError; use crate::BoxError;
@ -108,24 +110,19 @@ impl OrderBook {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ActiveOrder { pub struct ActiveOrder {
pub(crate) exchange: Exchange,
pub(crate) id: u64, pub(crate) id: u64,
pub(crate) group_id: Option<u64>, pub(crate) group_id: Option<u64>,
pub(crate) client_id: u64, pub(crate) client_id: Option<u64>,
pub(crate) symbol: SymbolPair, pub(crate) symbol: SymbolPair,
pub(crate) current_form: OrderForm,
pub(crate) creation_timestamp: u64, pub(crate) creation_timestamp: u64,
pub(crate) update_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<OrderKind>,
pub(crate) price: f64,
pub(crate) price_avg: f64,
pub(crate) hidden: bool,
} }
impl Hash for ActiveOrder { impl Hash for ActiveOrder {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&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 {} impl Eq for ActiveOrder {}
#[derive(Copy, Clone, Debug, Hash)] #[derive(Debug, Clone, Copy)]
pub enum OrderKind { pub enum TradingPlatform {
Limit, Exchange,
ExchangeLimit, Derivative,
Market, Funding,
ExchangeMarket, Margin,
Stop,
ExchangeStop,
StopLimit,
ExchangeStopLimit,
TrailingStop,
Fok,
ExchangeFok,
Ioc,
ExchangeIoc,
} }
#[derive(Clone, Debug)] impl TradingPlatform {
pub struct OrderForm { pub fn as_str(&self) -> &'static str {
/// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, match self {
/// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, TradingPlatform::Exchange => "Exchange",
/// TRAILING STOP, EXCHANGE TRAILING STOP, FOK, TradingPlatform::Derivative => "Derivative",
/// EXCHANGE FOK, IOC, EXCHANGE IOC TradingPlatform::Funding => "Funding",
kind: OrderKind, TradingPlatform::Margin => "Margin",
/// Symbol for desired pair }
pair: SymbolPair, }
/// Price of order }
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, price: f64,
/// Amount of order (positive for buy, negative for sell)
amount: f64, amount: f64,
/// Set the leverage for a derivative order, supported by derivative symbol orders only. },
/// The value should be between 1 and 100 inclusive. Market {
/// The field is optional, if omitted the default leverage value of 10 will be used. amount: f64,
leverage: Option<u32>, },
/// The trailing price for a trailing stop order Stop {
price_trailing: Option<String>, price: f64,
/// Auxiliary Limit price (for STOP LIMIT) amount: f64,
price_aux_limit: Option<String>, },
/// Time-In-Force: datetime for automatic order cancellation (ie. 2020-01-01 10:45:23) ) StopLimit {
tif: Option<String>, 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 {
pair: SymbolPair,
kind: OrderKind,
platform: TradingPlatform,
} }
impl OrderForm { impl OrderForm {
pub fn new(pair: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self { pub fn new(pair: SymbolPair, order_kind: OrderKind, platform: TradingPlatform) -> Self {
OrderForm { Self {
kind, pair,
pair: pair.clone(), kind: order_kind,
price, platform,
amount,
leverage: None,
price_trailing: None,
price_aux_limit: None,
tif: None,
} }
} }
pub fn with_leverage(mut self, leverage: u32) -> Self { pub fn pair(&self) -> &SymbolPair {
self.leverage = Some(leverage); &self.pair
self
}
pub fn with_price_trailing(mut self, trailing: f64) -> Result<Self, BoxError> {
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<Self, BoxError> {
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<T: TimeZone>(mut self, tif: DateTime<T>) -> Self
where
T::Offset: Display,
{
self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string());
self
} }
pub fn kind(&self) -> OrderKind { pub fn kind(&self) -> OrderKind {
self.kind 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<u32> {
self.leverage 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),
} }
pub fn price_trailing(&self) -> &Option<String> {
&self.price_trailing
}
pub fn price_aux_limit(&self) -> &Option<String> {
&self.price_aux_limit
}
pub fn tif(&self) -> &Option<String> {
&self.tif
} }
} }

View File

@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::ops::Neg;
use dyn_clone::DynClone; use dyn_clone::DynClone;
use log::{debug, info}; use log::{debug, info};
@ -9,6 +10,7 @@ use crate::events::{Event, EventKind, EventMetadata, Message};
use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap};
use crate::models::{ use crate::models::{
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState,
TradingPlatform,
}; };
use crate::BoxError; use crate::BoxError;
@ -111,7 +113,14 @@ impl PositionStrategy for TrailingStop {
debug!("Inserting close position message..."); debug!("Inserting close position message...");
messages.push(Message::ClosePosition { messages.push(Message::ClosePosition {
position: position.clone(), 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 PositionProfitState::Critical
} }
@ -195,7 +204,7 @@ pub struct FastOrderStrategy {
impl Default for FastOrderStrategy { impl Default for FastOrderStrategy {
fn default() -> Self { fn default() -> Self {
Self { threshold: 0.2 } Self { threshold: 0.05 }
} }
} }
@ -224,7 +233,7 @@ impl OrderStrategy for FastOrderStrategy {
// long // long
let offer_comparison = { let offer_comparison = {
if order.amount > 0.0 { if order.current_form.amount() > 0.0 {
order_book.highest_bid() order_book.highest_bid()
} else { } else {
order_book.lowest_ask() order_book.lowest_ask()
@ -233,12 +242,22 @@ impl OrderStrategy for FastOrderStrategy {
// if the best offer is higher than our threshold, // if the best offer is higher than our threshold,
// ask the manager to close the position with a market order // 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 { if delta > self.threshold {
messages.push(Message::ClosePosition { messages.push(Message::ClosePosition {
position: active_position.clone(), 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(),
),
}) })
} }