implemented orderbook structs, modified algorithm to calculate best price when closing orders
This commit is contained in:
parent
945f5f63c1
commit
e6cb512a17
10
rustybot/Cargo.lock
generated
10
rustybot/Cargo.lock
generated
@ -301,6 +301,15 @@ dependencies = [
|
|||||||
"log 0.4.11",
|
"log 0.4.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@ -1132,6 +1141,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"fern",
|
"fern",
|
||||||
|
"float-cmp",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log 0.4.11",
|
"log 0.4.11",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -18,3 +18,4 @@ log = "0.4"
|
|||||||
fern = {version = "0.6", features = ["colored"]}
|
fern = {version = "0.6", features = ["colored"]}
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
|
float-cmp = "0.8"
|
@ -13,7 +13,7 @@ use tokio::sync::oneshot;
|
|||||||
use crate::connectors::{Client, ExchangeKind};
|
use crate::connectors::{Client, ExchangeKind};
|
||||||
use crate::currency::SymbolPair;
|
use crate::currency::SymbolPair;
|
||||||
use crate::events::{ActorMessage, Event, Message};
|
use crate::events::{ActorMessage, Event, Message};
|
||||||
use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker};
|
use crate::models::{ExecutedOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker};
|
||||||
use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop};
|
use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop};
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
|
|
||||||
@ -88,6 +88,8 @@ impl PriceManager {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message.respond_to.send((None, None));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +202,9 @@ impl PositionManagerHandle {
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(recv.await?)
|
let response = recv.await?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,16 +255,14 @@ impl PositionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
pub async fn update(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
||||||
debug!("Updating {}", self.pair);
|
// debug!("Updating {}", self.pair);
|
||||||
|
|
||||||
let opt_active_positions = self.client.active_positions(&self.pair).await?;
|
let opt_active_positions = self.client.active_positions(&self.pair).await?;
|
||||||
let mut events = vec![];
|
|
||||||
|
|
||||||
self.current_tick = tick;
|
self.current_tick = tick;
|
||||||
|
|
||||||
// we assume there is only ONE active position per pair
|
// we assume there is only ONE active position per pair
|
||||||
match opt_active_positions {
|
match opt_active_positions {
|
||||||
// no open positions, no events and no order forms returned
|
// no open positions, no events and no messages returned
|
||||||
None => return Ok((None, None)),
|
None => return Ok((None, None)),
|
||||||
|
|
||||||
Some(positions) => {
|
Some(positions) => {
|
||||||
@ -271,24 +273,25 @@ impl PositionManager {
|
|||||||
.next()
|
.next()
|
||||||
{
|
{
|
||||||
// no open positions for our pair, setting active position to none
|
// no open positions for our pair, setting active position to none
|
||||||
None => self.active_position = None,
|
None => {
|
||||||
|
self.active_position = None;
|
||||||
|
return Ok((None, None));
|
||||||
|
}
|
||||||
|
|
||||||
// applying strategy to open position and saving into struct
|
// applying strategy to open position and saving into struct
|
||||||
Some(position) => {
|
Some(position) => {
|
||||||
let (position_after_strategy, strategy_events, _) =
|
let (position_after_strategy, strategy_events, strategy_messages) =
|
||||||
self.strategy.on_new_tick(position, &self);
|
self.strategy.on_new_tick(position, &self);
|
||||||
|
|
||||||
events.extend(strategy_events);
|
|
||||||
|
|
||||||
self.positions_history
|
self.positions_history
|
||||||
.insert(self.current_tick(), position_after_strategy.clone());
|
.insert(self.current_tick(), position_after_strategy.clone());
|
||||||
self.active_position = Some(position_after_strategy);
|
self.active_position = Some(position_after_strategy);
|
||||||
|
|
||||||
|
return Ok((strategy_events, strategy_messages));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((None, None))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position_previous_tick(&self, id: u64, tick: Option<u64>) -> Option<&Position> {
|
pub fn position_previous_tick(&self, id: u64, tick: Option<u64>) -> Option<&Position> {
|
||||||
@ -374,8 +377,6 @@ pub struct OrderManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OrderManager {
|
impl OrderManager {
|
||||||
const UNDERCUT_PERC: f64 = 0.005;
|
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
receiver: Receiver<ActorMessage>,
|
receiver: Receiver<ActorMessage>,
|
||||||
pair: SymbolPair,
|
pair: SymbolPair,
|
||||||
@ -401,13 +402,15 @@ impl OrderManager {
|
|||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
msg.respond_to.send((None, None));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> {
|
pub async fn close_position(&mut self, position: &Position) -> 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());
|
info!("Closing position #{}", position.id());
|
||||||
|
|
||||||
// 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
|
||||||
@ -422,9 +425,10 @@ impl OrderManager {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
info!("Getting current prices...");
|
info!("Getting current prices...");
|
||||||
let current_prices = self.client.current_prices(&self.pair).await?;
|
let order_book = self.client.order_book(&self.pair).await?;
|
||||||
|
|
||||||
info!("Calculating best closing price...");
|
info!("Calculating best closing price...");
|
||||||
let closing_price = self.best_closing_price(&position, ¤t_prices)?;
|
let closing_price = self.best_closing_price(&position, &order_book)?;
|
||||||
|
|
||||||
info!("Submitting order...");
|
info!("Submitting order...");
|
||||||
// submitting order
|
// submitting order
|
||||||
@ -449,23 +453,41 @@ impl OrderManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self) -> Result<OptionUpdate, BoxError> {
|
pub fn update(&self) -> Result<OptionUpdate, BoxError> {
|
||||||
unimplemented!()
|
// TODO: implement me
|
||||||
|
Ok((None, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn best_closing_price(
|
pub fn best_closing_price(
|
||||||
&self,
|
&self,
|
||||||
position: &Position,
|
position: &Position,
|
||||||
price_ticker: &TradingPairTicker,
|
order_book: &OrderBook,
|
||||||
) -> Result<f64, BoxError> {
|
) -> Result<f64, BoxError> {
|
||||||
|
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 price = {
|
||||||
|
let intermediate_price = {
|
||||||
if position.is_short() {
|
if position.is_short() {
|
||||||
price_ticker.ask
|
bid + delta
|
||||||
} else {
|
} else {
|
||||||
price_ticker.bid
|
ask - delta
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(price * (1.0 - OrderManager::UNDERCUT_PERC))
|
if avg > 9999.0 {
|
||||||
|
if position.is_short() {
|
||||||
|
intermediate_price.ceil()
|
||||||
|
} else {
|
||||||
|
intermediate_price.floor()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
intermediate_price
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(price)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,9 +514,29 @@ impl PairManager {
|
|||||||
client.clone(),
|
client.clone(),
|
||||||
Box::new(TrailingStop::new()),
|
Box::new(TrailingStop::new()),
|
||||||
),
|
),
|
||||||
// dispatcher: Dispatcher::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> {
|
||||||
|
let (opt_price_events, opt_price_messages) = self.price_manager.update(tick).await?;
|
||||||
|
let (opt_pos_events, opt_pos_messages) = self.position_manager.update(tick).await?;
|
||||||
|
let (opt_order_events, opt_order_messages) = self.order_manager.update(tick).await?;
|
||||||
|
|
||||||
|
// TODO: to move into Handler?
|
||||||
|
|
||||||
|
if let Some(messages) = opt_pos_messages {
|
||||||
|
for m in messages {
|
||||||
|
match m {
|
||||||
|
Message::ClosePosition { position } => {
|
||||||
|
self.order_manager.close_position(position).await?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExchangeManager {
|
pub struct ExchangeManager {
|
||||||
@ -518,36 +560,56 @@ impl ExchangeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_managers(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> {
|
||||||
let (price_opt_events, price_opt_signals) = self.update_price_managers(tick).await?;
|
|
||||||
let (pos_opt_events, pos_opt_signals) = self.update_position_managers(tick).await?;
|
|
||||||
|
|
||||||
debug!("{:?}", pos_opt_signals);
|
|
||||||
|
|
||||||
Ok((pos_opt_events, price_opt_signals))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update_position_managers(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
|
||||||
let mut futures: FuturesUnordered<_> = self
|
let mut futures: FuturesUnordered<_> = self
|
||||||
.pair_managers
|
.pair_managers
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|x| x.position_manager.update(tick))
|
.map(|x| x.update_managers(tick))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
while let Some(x) = futures.next().await {}
|
// execute the futures
|
||||||
|
while let Some(_) = futures.next().await {}
|
||||||
|
|
||||||
Ok((None, None))
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_price_managers(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
// async fn update_position_managers(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
||||||
let mut futures: FuturesUnordered<_> = self
|
// let mut res_events = Vec::new();
|
||||||
.pair_managers
|
// let mut res_messages = Vec::new();
|
||||||
.iter_mut()
|
//
|
||||||
.map(|x| x.price_manager.update(tick))
|
// let mut futures: FuturesUnordered<_> = self
|
||||||
.collect();
|
// .pair_managers
|
||||||
|
// .iter_mut()
|
||||||
while let Some(x) = futures.next().await {}
|
// .map(|x| x.position_manager.update(tick))
|
||||||
|
// .collect();
|
||||||
Ok((None, None))
|
//
|
||||||
}
|
// while let Some(future_result) = futures.next().await {
|
||||||
|
// let (opt_events, opt_messages) = future_result?;
|
||||||
|
//
|
||||||
|
// if let Some(events) = opt_events {
|
||||||
|
// res_events.extend(events);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if let Some(messages) = opt_messages {
|
||||||
|
// res_messages.extend(messages);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Ok((
|
||||||
|
// (!res_events.is_empty()).then_some(res_events),
|
||||||
|
// (!res_messages.is_empty()).then_some(res_messages),
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// async fn update_price_managers(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
||||||
|
// let mut futures: FuturesUnordered<_> = self
|
||||||
|
// .pair_managers
|
||||||
|
// .iter_mut()
|
||||||
|
// .map(|x| x.price_manager.update(tick))
|
||||||
|
// .collect();
|
||||||
|
//
|
||||||
|
// while let Some(x) = futures.next().await {}
|
||||||
|
//
|
||||||
|
// Ok((None, None))
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use chrono::{DateTime, TimeZone};
|
use chrono::{DateTime, TimeZone};
|
||||||
|
|
||||||
use crate::currency::SymbolPair;
|
use crate::currency::SymbolPair;
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
use std::hash::{Hash, Hasher};
|
use float_cmp::ApproxEq;
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
* Prices
|
* Prices
|
||||||
@ -28,6 +29,83 @@ pub struct PriceTicker {
|
|||||||
* Orders
|
* Orders
|
||||||
***************/
|
***************/
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OrderBookEntry {
|
||||||
|
Trading {
|
||||||
|
price: f64,
|
||||||
|
count: u64,
|
||||||
|
amount: f64,
|
||||||
|
},
|
||||||
|
Funding {
|
||||||
|
rate: f64,
|
||||||
|
period: u64,
|
||||||
|
count: u64,
|
||||||
|
amount: f64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OrderBook {
|
||||||
|
pair: SymbolPair,
|
||||||
|
entries: Vec<OrderBookEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrderBook {
|
||||||
|
pub fn new(pair: SymbolPair) -> Self {
|
||||||
|
OrderBook {
|
||||||
|
pair,
|
||||||
|
entries: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_entries(mut self, entries: Vec<OrderBookEntry>) -> Self {
|
||||||
|
self.entries = entries;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: distinguish between trading and funding
|
||||||
|
pub fn bids(&self) -> Vec<&OrderBookEntry> {
|
||||||
|
self.entries
|
||||||
|
.iter()
|
||||||
|
.filter(|x| match x {
|
||||||
|
OrderBookEntry::Trading { amount, .. } => amount > &0.0,
|
||||||
|
OrderBookEntry::Funding { amount, .. } => amount < &0.0,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: distinguish between trading and funding
|
||||||
|
pub fn asks(&self) -> Vec<&OrderBookEntry> {
|
||||||
|
self.entries
|
||||||
|
.iter()
|
||||||
|
.filter(|x| match x {
|
||||||
|
OrderBookEntry::Trading { amount, .. } => amount < &0.0,
|
||||||
|
OrderBookEntry::Funding { amount, .. } => amount > &0.0,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn highest_bid(&self) -> f64 {
|
||||||
|
self.bids()
|
||||||
|
.iter()
|
||||||
|
.map(|x| match x {
|
||||||
|
OrderBookEntry::Trading { price, .. } => price,
|
||||||
|
OrderBookEntry::Funding { rate, .. } => rate,
|
||||||
|
})
|
||||||
|
.fold(f64::NEG_INFINITY, |a, &b| a.max(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lowest_ask(&self) -> f64 {
|
||||||
|
self.asks()
|
||||||
|
.iter()
|
||||||
|
.map(|x| match x {
|
||||||
|
OrderBookEntry::Trading { price, .. } => price,
|
||||||
|
OrderBookEntry::Funding { rate, .. } => rate,
|
||||||
|
})
|
||||||
|
.fold(f64::INFINITY, |a, &b| a.min(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ExecutedOrder {
|
pub struct ExecutedOrder {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
@ -74,7 +152,7 @@ pub enum OrderKind {
|
|||||||
ExchangeIoc,
|
ExchangeIoc,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OrderForm {
|
pub struct OrderForm {
|
||||||
/// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET,
|
/// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET,
|
||||||
/// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT,
|
/// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT,
|
||||||
|
Loading…
Reference in New Issue
Block a user