Browse Source

Trim the Usd and Price precision only before sending to the UI feed

Create newtypes for Price and Usd that are sent to the UI with 2 digits precision.

Store and send higher precision prices between maker and taker; currently 24
digits, it can be changed with one constant.

Round percents visible in the UI to single digit.
burn-down-handle
Mariusz Klochowicz 3 years ago
parent
commit
837ed28308
No known key found for this signature in database GPG Key ID: 470C865699C8D4D
  1. 116
      daemon/src/model.rs
  2. 99
      daemon/src/to_sse_event.rs

116
daemon/src/model.rs

@ -25,7 +25,7 @@ pub enum Error {
NegativePrice, NegativePrice,
} }
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Usd(Decimal); pub struct Usd(Decimal);
impl Usd { impl Usd {
@ -40,6 +40,11 @@ impl Usd {
pub fn try_into_f64(&self) -> Result<f64> { pub fn try_into_f64(&self) -> Result<f64> {
self.0.to_f64().context("Could not fit decimal into f64") self.0.to_f64().context("Could not fit decimal into f64")
} }
#[must_use]
pub fn into_decimal(self) -> Decimal {
self.0
}
} }
impl fmt::Display for Usd { impl fmt::Display for Usd {
@ -48,26 +53,6 @@ impl fmt::Display for Usd {
} }
} }
impl Serialize for Usd {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
<Decimal as Serialize>::serialize(&self.0.round_dp(2), serializer)
}
}
impl<'de> Deserialize<'de> for Usd {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let dec = <Decimal as Deserialize>::deserialize(deserializer)?.round_dp(2);
Ok(Usd(dec))
}
}
impl str::FromStr for Usd { impl str::FromStr for Usd {
type Err = anyhow::Error; type Err = anyhow::Error;
@ -77,7 +62,7 @@ impl str::FromStr for Usd {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Price(Decimal); pub struct Price(Decimal);
impl Price { impl Price {
@ -100,25 +85,10 @@ impl Price {
pub fn try_into_f64(&self) -> Result<f64> { pub fn try_into_f64(&self) -> Result<f64> {
self.0.to_f64().context("Could not fit decimal into f64") self.0.to_f64().context("Could not fit decimal into f64")
} }
}
impl Serialize for Price {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
<Decimal as Serialize>::serialize(&self.0.round_dp(2), serializer)
}
}
impl<'de> Deserialize<'de> for Price {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let dec = <Decimal as Deserialize>::deserialize(deserializer)?.round_dp(2);
Ok(Price(dec)) #[must_use]
pub fn into_decimal(self) -> Decimal {
self.0
} }
} }
@ -137,7 +107,7 @@ impl str::FromStr for Price {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct InversePrice(Decimal); pub struct InversePrice(Decimal);
impl InversePrice { impl InversePrice {
@ -162,26 +132,6 @@ impl InversePrice {
} }
} }
impl Serialize for InversePrice {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
<Decimal as Serialize>::serialize(&self.0.round_dp(2), serializer)
}
}
impl<'de> Deserialize<'de> for InversePrice {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let dec = <Decimal as Deserialize>::deserialize(deserializer)?.round_dp(2);
Ok(InversePrice(dec))
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, sqlx::Type)] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, sqlx::Type)]
pub struct Leverage(u8); pub struct Leverage(u8);
@ -400,32 +350,19 @@ impl Div<Leverage> for Leverage {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct Percent(Decimal); pub struct Percent(Decimal);
impl fmt::Display for Percent { impl Percent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[must_use]
self.0.round_dp(2).fmt(f) pub fn round_dp(self, digits: u32) -> Self {
Self(self.0.round_dp(digits))
} }
} }
impl Serialize for Percent { impl fmt::Display for Percent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
where self.0.round_dp(2).fmt(f)
S: serde::Serializer,
{
<Decimal as Serialize>::serialize(&self.0.round_dp(2), serializer)
}
}
impl<'de> Deserialize<'de> for Percent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let dec = <Decimal as Deserialize>::deserialize(deserializer)?.round_dp(2);
Ok(Percent(dec))
} }
} }
@ -587,25 +524,10 @@ impl Timestamp {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
use serde_test::{assert_de_tokens, assert_ser_tokens, Token};
use time::macros::datetime; use time::macros::datetime;
use super::*; use super::*;
#[test]
fn usd_serializes_with_only_cents() {
let usd = Usd::new(dec!(1000.12345));
assert_ser_tokens(&usd, &[Token::Str("1000.12")]);
}
#[test]
fn usd_deserializes_trims_precision() {
let usd = Usd::new(dec!(1000.12));
assert_de_tokens(&usd, &[Token::Str("1000.12345")]);
}
#[test] #[test]
fn to_olivia_url() { fn to_olivia_url() {
let url = BitMexPriceEventId::with_20_digits(datetime!(2021-09-23 10:00:00).assume_utc()) let url = BitMexPriceEventId::with_20_digits(datetime!(2021-09-23 10:00:00).assume_utc())

99
daemon/src/to_sse_event.rs

@ -1,7 +1,7 @@
use crate::model::cfd::{ use crate::model::cfd::{
Dlc, OrderId, Payout, Role, SettlementKind, UpdateCfdProposal, UpdateCfdProposals, Dlc, OrderId, Payout, Role, SettlementKind, UpdateCfdProposal, UpdateCfdProposals,
}; };
use crate::model::{Leverage, Position, Price, Timestamp, TradingPair, Usd}; use crate::model::{Leverage, Position, Timestamp, TradingPair};
use crate::{bitmex_price_feed, model}; use crate::{bitmex_price_feed, model};
use bdk::bitcoin::{Amount, Network, SignedAmount, Txid}; use bdk::bitcoin::{Amount, Network, SignedAmount, Txid};
use rocket::request::FromParam; use rocket::request::FromParam;
@ -12,6 +12,64 @@ use std::convert::TryInto;
use time::OffsetDateTime; use time::OffsetDateTime;
use tokio::sync::watch; use tokio::sync::watch;
#[derive(Debug, Clone)]
pub struct Usd {
inner: model::Usd,
}
impl Usd {
fn new(usd: model::Usd) -> Self {
Self {
inner: model::Usd::new(usd.into_decimal().round_dp(2)),
}
}
}
impl From<model::Usd> for Usd {
fn from(usd: model::Usd) -> Self {
Self::new(usd)
}
}
impl Serialize for Usd {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
<Decimal as Serialize>::serialize(&self.inner.into_decimal(), serializer)
}
}
#[derive(Debug, Clone)]
pub struct Price {
inner: model::Price,
}
impl Price {
fn new(price: model::Price) -> Self {
Self {
inner: model::Price::new(price.into_decimal().round_dp(2)).expect(
"rounding a valid price to 2 decimal places should still result in a valid price",
),
}
}
}
impl From<model::Price> for Price {
fn from(price: model::Price) -> Self {
Self::new(price)
}
}
impl Serialize for Price {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
<Decimal as Serialize>::serialize(&self.inner.into_decimal(), serializer)
}
}
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Cfd { pub struct Cfd {
pub order_id: OrderId, pub order_id: OrderId,
@ -183,7 +241,7 @@ pub trait ToSseEvent {
/// by UI /// by UI
pub struct CfdsWithAuxData { pub struct CfdsWithAuxData {
pub cfds: Vec<model::cfd::Cfd>, pub cfds: Vec<model::cfd::Cfd>,
pub current_price: Price, pub current_price: model::Price,
pub pending_proposals: UpdateCfdProposals, pub pending_proposals: UpdateCfdProposals,
pub network: Network, pub network: Network,
} }
@ -242,14 +300,14 @@ impl ToSseEvent for CfdsWithAuxData {
Cfd { Cfd {
order_id: cfd.order.id, order_id: cfd.order.id,
initial_price: cfd.order.price, initial_price: cfd.order.price.into(),
leverage: cfd.order.leverage, leverage: cfd.order.leverage,
trading_pair: cfd.order.trading_pair.clone(), trading_pair: cfd.order.trading_pair.clone(),
position: cfd.position(), position: cfd.position(),
liquidation_price: cfd.order.liquidation_price, liquidation_price: cfd.order.liquidation_price.into(),
quantity_usd: cfd.quantity_usd, quantity_usd: cfd.quantity_usd.into(),
profit_btc, profit_btc,
profit_in_percent: profit_in_percent.to_string(), profit_in_percent: profit_in_percent.round_dp(1).to_string(),
state: state.clone(), state: state.clone(),
actions: available_actions(state, cfd.role()), actions: available_actions(state, cfd.role()),
state_transition_timestamp: cfd.state.get_transition_timestamp().seconds(), state_transition_timestamp: cfd.state.get_transition_timestamp().seconds(),
@ -274,11 +332,11 @@ impl ToSseEvent for Option<model::cfd::Order> {
id: order.id, id: order.id,
trading_pair: order.trading_pair, trading_pair: order.trading_pair,
position: order.position, position: order.position,
price: order.price, price: order.price.into(),
min_quantity: order.min_quantity, min_quantity: order.min_quantity.into(),
max_quantity: order.max_quantity, max_quantity: order.max_quantity.into(),
leverage: order.leverage, leverage: order.leverage,
liquidation_price: order.liquidation_price, liquidation_price: order.liquidation_price.into(),
creation_timestamp: order.creation_timestamp, creation_timestamp: order.creation_timestamp,
settlement_time_interval_in_secs: order settlement_time_interval_in_secs: order
.settlement_time_interval_hours .settlement_time_interval_hours
@ -417,8 +475,8 @@ pub struct Quote {
impl ToSseEvent for bitmex_price_feed::Quote { impl ToSseEvent for bitmex_price_feed::Quote {
fn to_sse_event(&self) -> Event { fn to_sse_event(&self) -> Event {
let quote = Quote { let quote = Quote {
bid: self.bid, bid: self.bid.into(),
ask: self.ask, ask: self.ask.into(),
last_updated_at: self.timestamp, last_updated_at: self.timestamp,
}; };
Event::json(&quote).event("quote") Event::json(&quote).event("quote")
@ -457,6 +515,9 @@ fn available_actions(state: CfdState, role: Role) -> Vec<CfdAction> {
mod tests { mod tests {
use super::*; use super::*;
use rust_decimal_macros::dec;
use serde_test::{assert_ser_tokens, Token};
#[test] #[test]
fn state_snapshot_test() { fn state_snapshot_test() {
// Make sure to update the UI after changing this test! // Make sure to update the UI after changing this test!
@ -484,4 +545,18 @@ mod tests {
let json = serde_json::to_string(&CfdState::SetupFailed).unwrap(); let json = serde_json::to_string(&CfdState::SetupFailed).unwrap();
assert_eq!(json, "\"SetupFailed\""); assert_eq!(json, "\"SetupFailed\"");
} }
#[test]
fn usd_serializes_with_only_cents() {
let usd = Usd::new(model::Usd::new(dec!(1000.12345)));
assert_ser_tokens(&usd, &[Token::Str("1000.12")]);
}
#[test]
fn price_serializes_with_only_cents() {
let price = Price::new(model::Price::new(dec!(1000.12345)).unwrap());
assert_ser_tokens(&price, &[Token::Str("1000.12")]);
}
} }

Loading…
Cancel
Save