rust #10

Merged
peperunas merged 127 commits from rust into master 2021-02-18 09:42:16 +00:00
3 changed files with 217 additions and 58 deletions
Showing only changes of commit a1354c2862 - Show all commits

View File

@ -4,7 +4,7 @@ use std::future::Future;
use tokio::task::JoinHandle;
use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager};
use crate::models::{Position, PositionProfitState};
use crate::models::{OrderKind, Position, PositionProfitState};
use tokio::sync::oneshot;
#[derive(Debug)]
@ -15,8 +15,13 @@ pub struct ActorMessage {
#[derive(Debug)]
pub enum Message {
Update { tick: u64 },
ClosePosition { position: Position },
Update {
tick: u64,
},
ClosePosition {
position: Position,
order_kind: OrderKind,
},
OpenPosition,
}

View File

@ -353,12 +353,19 @@ impl OrderManagerHandle {
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();
self.sender
.send(ActorMessage {
message: Message::ClosePosition { position },
message: Message::ClosePosition {
position,
order_kind,
},
respond_to: send,
})
.await?;
@ -398,7 +405,10 @@ impl OrderManager {
Message::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(())
}
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());
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.
// If so, the strategy method is called, otherwise we open
// an undercut limit order at the best current price.
match 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
.strategy
.on_position_close(open_order, &self.tracked_positions);
if let Some(messages) = messages {
for m in messages {
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 => {
info!("Getting current prices...");
let order_book = self.client.order_book(&self.pair).await?;
let closing_price = self.best_closing_price(&position, &order_book);
info!("Calculating best closing price...");
let closing_price = self.best_closing_price(&position, &order_book)?;
info!("Submitting order...");
// submitting order
let order_form = OrderForm::new(
&self.pair,
closing_price,
position.amount().neg(),
OrderKind::Limit,
);
match self.client.submit_order(order_form).await {
Err(e) => error!("Could not submit order: {}", e),
Ok(o) => {
self.tracked_positions.insert(position.id(), o);
info!("Done!");
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!()
}
};
self.tracked_positions.insert(position.id(), active_order);
}
}
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> {
// TODO: implement me
Ok((None, None))
}
pub fn best_closing_price(
&self,
position: &Position,
order_book: &OrderBook,
) -> Result<f64, BoxError> {
pub fn best_closing_price(&self, position: &Position, order_book: &OrderBook) -> f64 {
let ask = order_book.lowest_ask();
let bid = order_book.highest_bid();
let avg = (bid + ask) / 2.0;
let delta = (ask - bid) / 10.0;
let price = {
let intermediate_price = {
let closing_price = {
let closing_price = {
if position.is_short() {
bid + delta
bid - delta
} else {
ask - delta
ask + delta
}
};
if avg > 9999.0 {
if position.is_short() {
intermediate_price.ceil()
closing_price.ceil()
} else {
intermediate_price.floor()
closing_price.floor()
}
} else {
intermediate_price
closing_price
}
};
Ok(price)
closing_price
}
}
@ -496,7 +596,6 @@ pub struct PairManager {
price_manager: PriceManagerHandle,
order_manager: OrderManagerHandle,
position_manager: PositionManagerHandle,
// dispatcher: Dispatcher,
}
impl PairManager {
@ -507,7 +606,7 @@ impl PairManager {
order_manager: OrderManagerHandle::new(
pair.clone(),
client.clone(),
Box::new(FastOrderStrategy {}),
Box::new(FastOrderStrategy::default()),
),
position_manager: PositionManagerHandle::new(
pair.clone(),
@ -527,8 +626,13 @@ impl PairManager {
if let Some(messages) = opt_pos_messages {
for m in messages {
match m {
Message::ClosePosition { position } => {
self.order_manager.close_position(position).await?;
Message::ClosePosition {
position,
order_kind,
} => {
self.order_manager
.close_position(position, order_kind)
.await?;
}
_ => {}
}

View File

@ -1,12 +1,16 @@
use dyn_clone::DynClone;
use log::debug;
use std::collections::HashMap;
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::managers::{OrderManager, PositionManager, TrackedPositionsMap};
use crate::models::{ActiveOrder, OrderForm, Position, PositionProfitState};
use tokio::sync::oneshot;
use crate::models::{
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState,
};
use crate::BoxError;
/***************
* DEFINITIONS
@ -38,8 +42,9 @@ pub trait OrderStrategy: DynClone + Send {
fn on_position_close(
&self,
order: &ActiveOrder,
tracked_positions: &HashMap<u64, ActiveOrder>,
) -> TrackedPositionsMap;
open_position: &Position,
order_book: &OrderBook,
) -> Result<(Option<Vec<Event>>, Option<Vec<Message>>), BoxError>;
}
impl Debug for dyn OrderStrategy {
@ -106,6 +111,7 @@ impl PositionStrategy for TrailingStop {
debug!("Inserting close position message...");
messages.push(Message::ClosePosition {
position: position.clone(),
order_kind: OrderKind::Limit,
});
PositionProfitState::Critical
}
@ -181,7 +187,23 @@ impl PositionStrategy for TrailingStop {
}
#[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 {
fn name(&self) -> String {
@ -195,8 +217,36 @@ impl OrderStrategy for FastOrderStrategy {
fn on_position_close(
&self,
order: &ActiveOrder,
tracked_positions: &HashMap<u64, ActiveOrder>,
) -> TrackedPositionsMap {
unimplemented!()
active_position: &Position,
order_book: &OrderBook,
) -> 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)))
}
}