internal order modeling overhaul
This commit is contained in:
parent
a1d905ebea
commit
5b84c99703
@ -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,
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user