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 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<ExchangeKind>,
exchanges: Vec<ExchangeDetails>,
trading_symbols: Vec<Symbol>,
quote: Symbol,
tick_duration: Duration,

View File

@ -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<Box<dyn Connector>>,
}
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<ActiveOrder, BoxError> {
pub async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
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 order_book(&self, pair: &SymbolPair) -> Result<OrderBook, 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>;
}
@ -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<TradingPairTicker, BoxError> {
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<ActiveOrder, BoxError> {
// 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<ActiveOrder, BoxError> {
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<OrderBook, BoxError> {
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<ActiveOrder, BoxError> {
let date = DateTime::<Utc>::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<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;
fn try_from(response: OrderResponse) -> Result<Self, Self::Error> {
fn try_from(response: &OrderResponse) -> Result<Self, Self::Error> {
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<Position> 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<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<&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(),
}
}
}
}
}

View File

@ -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,
}

View File

@ -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<OptionUpdate, BoxError> {
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,26 +443,13 @@ 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
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?;
}
}
_ => {}
}
}
_ => {
debug!("Received unsupported message from order strategy. Unimplemented.")
}
@ -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<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> {
// 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<PairManager>,
client: Client,
}
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 pair_managers = pairs
.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 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<u64>,
pub(crate) client_id: u64,
pub(crate) client_id: Option<u64>,
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<OrderKind>,
pub(crate) price: f64,
pub(crate) price_avg: f64,
pub(crate) hidden: bool,
}
impl Hash for ActiveOrder {
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 {}
#[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)]
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
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 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<u32>,
/// The trailing price for a trailing stop order
price_trailing: Option<String>,
/// Auxiliary Limit price (for STOP LIMIT)
price_aux_limit: Option<String>,
/// Time-In-Force: datetime for automatic order cancellation (ie. 2020-01-01 10:45:23) )
tif: Option<String>,
},
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 {
pair: SymbolPair,
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<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 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<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::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(),
),
})
}