implemented Default for FastOrderStrategy. FastOrderStrategy closes position with a Market order if threshold is overridden
This commit is contained in:
parent
47f17efcfb
commit
a1354c2862
@ -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::{Position, PositionProfitState};
|
use crate::models::{OrderKind, Position, PositionProfitState};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -15,8 +15,13 @@ pub struct ActorMessage {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Update { tick: u64 },
|
Update {
|
||||||
ClosePosition { position: Position },
|
tick: u64,
|
||||||
|
},
|
||||||
|
ClosePosition {
|
||||||
|
position: Position,
|
||||||
|
order_kind: OrderKind,
|
||||||
|
},
|
||||||
OpenPosition,
|
OpenPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,12 +353,19 @@ impl OrderManagerHandle {
|
|||||||
Ok(recv.await?)
|
Ok(recv.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn close_position(&mut self, position: Position) -> Result<OptionUpdate, BoxError> {
|
pub async fn close_position(
|
||||||
|
&mut self,
|
||||||
|
position: Position,
|
||||||
|
order_kind: OrderKind,
|
||||||
|
) -> Result<OptionUpdate, BoxError> {
|
||||||
let (send, recv) = oneshot::channel();
|
let (send, recv) = oneshot::channel();
|
||||||
|
|
||||||
self.sender
|
self.sender
|
||||||
.send(ActorMessage {
|
.send(ActorMessage {
|
||||||
message: Message::ClosePosition { position },
|
message: Message::ClosePosition {
|
||||||
|
position,
|
||||||
|
order_kind,
|
||||||
|
},
|
||||||
respond_to: send,
|
respond_to: send,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -398,7 +405,10 @@ impl OrderManager {
|
|||||||
Message::Update { .. } => {
|
Message::Update { .. } => {
|
||||||
self.update();
|
self.update();
|
||||||
}
|
}
|
||||||
Message::ClosePosition { position, .. } => self.close_position(&position).await?,
|
Message::ClosePosition {
|
||||||
|
position,
|
||||||
|
order_kind,
|
||||||
|
} => self.close_position(&position, &order_kind).await?,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -407,87 +417,177 @@ impl OrderManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> {
|
pub async fn close_position(
|
||||||
|
&mut self,
|
||||||
|
position: &Position,
|
||||||
|
order_kind: &OrderKind,
|
||||||
|
) -> Result<(), BoxError> {
|
||||||
let open_order = self.tracked_positions.get(&position.id());
|
let open_order = self.tracked_positions.get(&position.id());
|
||||||
|
|
||||||
info!("Closing position #{}", position.id());
|
debug!("Closing position #{}", position.id());
|
||||||
|
|
||||||
|
debug!("Getting current prices...");
|
||||||
|
let order_book = self.client.order_book(&self.pair).await?;
|
||||||
|
|
||||||
// checking if the position has an open order.
|
// checking if the position has an open order.
|
||||||
// If so, the strategy method is called, otherwise we open
|
// If so, the strategy method is called, otherwise we open
|
||||||
// an undercut limit order at the best current price.
|
// an undercut limit order at the best current price.
|
||||||
match open_order {
|
match open_order {
|
||||||
Some(open_order) => {
|
Some(open_order) => {
|
||||||
info!("There is an open order. Calling strategy.");
|
debug!("There is an open order. Calling strategy.");
|
||||||
|
let (_, messages) =
|
||||||
|
self.strategy
|
||||||
|
.on_position_close(open_order, position, &order_book)?;
|
||||||
|
|
||||||
self.tracked_positions = self
|
if let Some(messages) = messages {
|
||||||
.strategy
|
for m in messages {
|
||||||
.on_position_close(open_order, &self.tracked_positions);
|
match m {
|
||||||
|
Message::ClosePosition {
|
||||||
|
position,
|
||||||
|
order_kind,
|
||||||
|
} => {
|
||||||
|
// TODO FIXME
|
||||||
|
match order_kind {
|
||||||
|
OrderKind::Market => {
|
||||||
|
self.submit_market_order(1.0, position.amount().neg())
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("Received unsupported message from order strategy. Unimplemented.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
info!("Getting current prices...");
|
let closing_price = self.best_closing_price(&position, &order_book);
|
||||||
let order_book = self.client.order_book(&self.pair).await?;
|
|
||||||
|
|
||||||
info!("Calculating best closing price...");
|
let active_order = match order_kind {
|
||||||
let closing_price = self.best_closing_price(&position, &order_book)?;
|
OrderKind::Limit => {
|
||||||
|
self.submit_limit_order(closing_price, position.amount().neg())
|
||||||
info!("Submitting order...");
|
.await?
|
||||||
// submitting order
|
}
|
||||||
let order_form = OrderForm::new(
|
OrderKind::ExchangeLimit => {
|
||||||
&self.pair,
|
self.submit_exchange_limit_order(closing_price, position.amount().neg())
|
||||||
closing_price,
|
.await?
|
||||||
position.amount().neg(),
|
}
|
||||||
OrderKind::Limit,
|
OrderKind::Market => {
|
||||||
);
|
self.submit_market_order(closing_price, position.amount().neg())
|
||||||
|
.await?
|
||||||
match self.client.submit_order(order_form).await {
|
}
|
||||||
Err(e) => error!("Could not submit order: {}", e),
|
OrderKind::ExchangeMarket => {
|
||||||
Ok(o) => {
|
self.submit_exchange_market_order(closing_price, position.amount().neg())
|
||||||
self.tracked_positions.insert(position.id(), o);
|
.await?
|
||||||
info!("Done!");
|
}
|
||||||
|
_ => {
|
||||||
|
unimplemented!()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.tracked_positions.insert(position.id(), active_order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn submit_limit_order(
|
||||||
|
&mut 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(
|
||||||
|
&mut 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(
|
||||||
|
&mut 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(
|
||||||
|
&mut 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn best_closing_price(
|
pub fn best_closing_price(&self, position: &Position, order_book: &OrderBook) -> f64 {
|
||||||
&self,
|
|
||||||
position: &Position,
|
|
||||||
order_book: &OrderBook,
|
|
||||||
) -> Result<f64, BoxError> {
|
|
||||||
let ask = order_book.lowest_ask();
|
let ask = order_book.lowest_ask();
|
||||||
let bid = order_book.highest_bid();
|
let bid = order_book.highest_bid();
|
||||||
let avg = (bid + ask) / 2.0;
|
let avg = (bid + ask) / 2.0;
|
||||||
let delta = (ask - bid) / 10.0;
|
let delta = (ask - bid) / 10.0;
|
||||||
|
|
||||||
let price = {
|
let closing_price = {
|
||||||
let intermediate_price = {
|
let closing_price = {
|
||||||
if position.is_short() {
|
if position.is_short() {
|
||||||
bid + delta
|
bid - delta
|
||||||
} else {
|
} else {
|
||||||
ask - delta
|
ask + delta
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if avg > 9999.0 {
|
if avg > 9999.0 {
|
||||||
if position.is_short() {
|
if position.is_short() {
|
||||||
intermediate_price.ceil()
|
closing_price.ceil()
|
||||||
} else {
|
} else {
|
||||||
intermediate_price.floor()
|
closing_price.floor()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
intermediate_price
|
closing_price
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(price)
|
closing_price
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,7 +596,6 @@ pub struct PairManager {
|
|||||||
price_manager: PriceManagerHandle,
|
price_manager: PriceManagerHandle,
|
||||||
order_manager: OrderManagerHandle,
|
order_manager: OrderManagerHandle,
|
||||||
position_manager: PositionManagerHandle,
|
position_manager: PositionManagerHandle,
|
||||||
// dispatcher: Dispatcher,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PairManager {
|
impl PairManager {
|
||||||
@ -507,7 +606,7 @@ impl PairManager {
|
|||||||
order_manager: OrderManagerHandle::new(
|
order_manager: OrderManagerHandle::new(
|
||||||
pair.clone(),
|
pair.clone(),
|
||||||
client.clone(),
|
client.clone(),
|
||||||
Box::new(FastOrderStrategy {}),
|
Box::new(FastOrderStrategy::default()),
|
||||||
),
|
),
|
||||||
position_manager: PositionManagerHandle::new(
|
position_manager: PositionManagerHandle::new(
|
||||||
pair.clone(),
|
pair.clone(),
|
||||||
@ -527,8 +626,13 @@ impl PairManager {
|
|||||||
if let Some(messages) = opt_pos_messages {
|
if let Some(messages) = opt_pos_messages {
|
||||||
for m in messages {
|
for m in messages {
|
||||||
match m {
|
match m {
|
||||||
Message::ClosePosition { position } => {
|
Message::ClosePosition {
|
||||||
self.order_manager.close_position(position).await?;
|
position,
|
||||||
|
order_kind,
|
||||||
|
} => {
|
||||||
|
self.order_manager
|
||||||
|
.close_position(position, order_kind)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
use dyn_clone::DynClone;
|
|
||||||
use log::debug;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
use log::{debug, info};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use crate::events::{Event, EventKind, EventMetadata, Message};
|
use crate::events::{Event, EventKind, EventMetadata, Message};
|
||||||
use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap};
|
use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap};
|
||||||
use crate::models::{ActiveOrder, OrderForm, Position, PositionProfitState};
|
use crate::models::{
|
||||||
use tokio::sync::oneshot;
|
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState,
|
||||||
|
};
|
||||||
|
use crate::BoxError;
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
* DEFINITIONS
|
* DEFINITIONS
|
||||||
@ -38,8 +42,9 @@ pub trait OrderStrategy: DynClone + Send {
|
|||||||
fn on_position_close(
|
fn on_position_close(
|
||||||
&self,
|
&self,
|
||||||
order: &ActiveOrder,
|
order: &ActiveOrder,
|
||||||
tracked_positions: &HashMap<u64, ActiveOrder>,
|
open_position: &Position,
|
||||||
) -> TrackedPositionsMap;
|
order_book: &OrderBook,
|
||||||
|
) -> Result<(Option<Vec<Event>>, Option<Vec<Message>>), BoxError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn OrderStrategy {
|
impl Debug for dyn OrderStrategy {
|
||||||
@ -106,6 +111,7 @@ 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,
|
||||||
});
|
});
|
||||||
PositionProfitState::Critical
|
PositionProfitState::Critical
|
||||||
}
|
}
|
||||||
@ -181,7 +187,23 @@ impl PositionStrategy for TrailingStop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FastOrderStrategy {}
|
pub struct FastOrderStrategy {
|
||||||
|
// threshold (%) for which we trigger a market order
|
||||||
|
// to close an open position
|
||||||
|
threshold: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FastOrderStrategy {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { threshold: 0.2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FastOrderStrategy {
|
||||||
|
pub fn new(threshold: f64) -> Self {
|
||||||
|
Self { threshold }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl OrderStrategy for FastOrderStrategy {
|
impl OrderStrategy for FastOrderStrategy {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
@ -195,8 +217,36 @@ impl OrderStrategy for FastOrderStrategy {
|
|||||||
fn on_position_close(
|
fn on_position_close(
|
||||||
&self,
|
&self,
|
||||||
order: &ActiveOrder,
|
order: &ActiveOrder,
|
||||||
tracked_positions: &HashMap<u64, ActiveOrder>,
|
active_position: &Position,
|
||||||
) -> TrackedPositionsMap {
|
order_book: &OrderBook,
|
||||||
unimplemented!()
|
) -> Result<(Option<Vec<Event>>, Option<Vec<Message>>), BoxError> {
|
||||||
|
let mut messages = vec![];
|
||||||
|
|
||||||
|
// long
|
||||||
|
let offer_comparison = {
|
||||||
|
if order.amount > 0.0 {
|
||||||
|
order_book.highest_bid()
|
||||||
|
} else {
|
||||||
|
order_book.lowest_ask()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if the best offer is higher than our threshold,
|
||||||
|
// ask the manager to close the position with a market order
|
||||||
|
let delta = (1.0 - (offer_comparison / order.price)) * 100.0;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Offer comp: {} | Our offer: {} | Current delta: {}",
|
||||||
|
offer_comparison, order.price, delta
|
||||||
|
);
|
||||||
|
|
||||||
|
if delta > self.threshold {
|
||||||
|
messages.push(Message::ClosePosition {
|
||||||
|
position: active_position.clone(),
|
||||||
|
order_kind: OrderKind::Market,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((None, (!messages.is_empty()).then_some(messages)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user