-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
300: Amend model to provide the prices from the orderbook r=klochowicz a=klochowicz The orderbook can query the orders to provide the "best" orders for a given contract symbol. I had to deal with the situation when there's no price (either bid or ask). For the simplicity of wiring things together, I'm only piping through the BTCUSD best prices into Flutter for now. PR for Flutter is incoming soon (excluded from this commit, as I'm still working on that part) - in the meantime it would be good to get some feedback on this backend code. There was a bit of complexity that I had to tackle because: - there might be no orders for particular direction (which means there's no bid or ask price from orderbook), - initial misunderstandings on how orderbooks work (thanks `@da-kami` for clarification on that). Co-authored-by: Mariusz Klochowicz <mariusz@klochowicz.com>
- Loading branch information
Showing
7 changed files
with
252 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
use crate::Order; | ||
use crate::ToPrimitive; | ||
use rust_decimal::Decimal; | ||
use serde::Deserialize; | ||
use serde::Serialize; | ||
use std::collections::HashMap; | ||
use trade::ContractSymbol; | ||
use trade::Direction; | ||
|
||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)] | ||
pub struct Price { | ||
pub bid: Option<Decimal>, | ||
pub ask: Option<Decimal>, | ||
} | ||
|
||
pub type Prices = HashMap<ContractSymbol, Price>; | ||
|
||
/// Best prices across all current orders for given ContractSymbol in the orderbook | ||
/// Taken orders are not included in the average | ||
pub fn best_current_price(current_orders: &[Order]) -> Prices { | ||
let mut prices = HashMap::new(); | ||
let mut add_price_for_symbol = |symbol| { | ||
prices.insert( | ||
symbol, | ||
Price { | ||
bid: best_bid_price(current_orders, symbol), | ||
ask: best_ask_price(current_orders, symbol), | ||
}, | ||
); | ||
}; | ||
add_price_for_symbol(ContractSymbol::BtcUsd); | ||
prices | ||
} | ||
|
||
/// Best price (highest) of all long (buy) orders in the orderbook | ||
fn best_bid_price(current_orders: &[Order], symbol: ContractSymbol) -> Option<Decimal> { | ||
best_price_for(current_orders, Direction::Long, symbol) | ||
} | ||
|
||
/// Best price (lowest) of all short (sell) orders in the orderbook | ||
fn best_ask_price(current_orders: &[Order], symbol: ContractSymbol) -> Option<Decimal> { | ||
best_price_for(current_orders, Direction::Short, symbol) | ||
} | ||
|
||
fn best_price_for( | ||
current_orders: &[Order], | ||
direction: Direction, | ||
symbol: ContractSymbol, | ||
) -> Option<Decimal> { | ||
assert_eq!( | ||
symbol, | ||
ContractSymbol::BtcUsd, | ||
"only btcusd supported for now" | ||
); | ||
let use_max = direction == Direction::Long; | ||
current_orders | ||
.iter() | ||
.filter(|order| !order.taken && order.direction == direction) | ||
.map(|order| order.price.to_f64().expect("to represent decimal as f64")) | ||
// get the best price | ||
.fold(None, |acc, x| match acc { | ||
Some(y) => Some(if use_max { x.max(y) } else { x.min(y) }), | ||
None => Some(x), | ||
})? | ||
.try_into() | ||
.ok() | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::price::best_ask_price; | ||
use crate::price::best_bid_price; | ||
use crate::Order; | ||
use crate::OrderType; | ||
use rust_decimal::Decimal; | ||
use rust_decimal_macros::dec; | ||
use secp256k1::PublicKey; | ||
use std::str::FromStr; | ||
use time::OffsetDateTime; | ||
use trade::ContractSymbol; | ||
use trade::Direction; | ||
use uuid::Uuid; | ||
use ContractSymbol::BtcUsd; | ||
|
||
fn dummy_public_key() -> PublicKey { | ||
PublicKey::from_str("02bd998ebd176715fe92b7467cf6b1df8023950a4dd911db4c94dfc89cc9f5a655") | ||
.unwrap() | ||
} | ||
|
||
fn dummy_order(price: Decimal, direction: Direction, taken: bool) -> Order { | ||
Order { | ||
id: Uuid::new_v4(), | ||
price, | ||
trader_id: dummy_public_key(), | ||
taken, | ||
direction, | ||
quantity: 100.into(), | ||
order_type: OrderType::Market, | ||
timestamp: OffsetDateTime::now_utc(), | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_best_bid_price() { | ||
let current_orders = vec![ | ||
dummy_order(dec!(10_000), Direction::Long, false), | ||
dummy_order(dec!(30_000), Direction::Long, false), | ||
dummy_order(dec!(500_000), Direction::Long, true), // taken | ||
dummy_order(dec!(50_000), Direction::Short, false), // wrong direction | ||
]; | ||
assert_eq!(best_bid_price(¤t_orders, BtcUsd), Some(dec!(30_000))); | ||
} | ||
|
||
#[test] | ||
fn test_best_ask_price() { | ||
let current_orders = vec![ | ||
dummy_order(dec!(10_000), Direction::Short, false), | ||
dummy_order(dec!(30_000), Direction::Short, false), | ||
// ignored in the calculations - this order is taken | ||
dummy_order(dec!(5_000), Direction::Short, true), | ||
// ignored in the calculations - it's the bid price | ||
dummy_order(dec!(50_000), Direction::Long, false), | ||
]; | ||
assert_eq!(best_ask_price(¤t_orders, BtcUsd), Some(dec!(10_000))); | ||
} | ||
|
||
#[test] | ||
fn test_no_price() { | ||
let all_orders_taken = vec![ | ||
dummy_order(dec!(10_000), Direction::Short, true), | ||
dummy_order(dec!(30_000), Direction::Long, true), | ||
]; | ||
|
||
assert_eq!(best_ask_price(&all_orders_taken, BtcUsd), None); | ||
assert_eq!(best_bid_price(&all_orders_taken, BtcUsd), None); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters