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,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Usd(Decimal);
impl Usd {
@ -40,6 +40,11 @@ impl Usd {
pub fn try_into_f64(&self) -> Result<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 {
@ -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 {
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);
impl Price {
@ -100,25 +85,10 @@ impl Price {
pub fn try_into_f64(&self) -> Result<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);
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)]
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);
impl fmt::Display for Percent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.round_dp(2).fmt(f)
impl Percent {
#[must_use]
pub fn round_dp(self, digits: u32) -> Self {
Self(self.0.round_dp(digits))
}
}
impl Serialize for Percent {
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 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))
impl fmt::Display for Percent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.round_dp(2).fmt(f)
}
}
@ -587,25 +524,10 @@ impl Timestamp {
#[cfg(test)]
mod tests {
use rust_decimal_macros::dec;
use serde_test::{assert_de_tokens, assert_ser_tokens, Token};
use time::macros::datetime;
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]
fn to_olivia_url() {
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::{
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 bdk::bitcoin::{Amount, Network, SignedAmount, Txid};
use rocket::request::FromParam;
@ -12,6 +12,64 @@ use std::convert::TryInto;
use time::OffsetDateTime;
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)]
pub struct Cfd {
pub order_id: OrderId,
@ -183,7 +241,7 @@ pub trait ToSseEvent {
/// by UI
pub struct CfdsWithAuxData {
pub cfds: Vec<model::cfd::Cfd>,
pub current_price: Price,
pub current_price: model::Price,
pub pending_proposals: UpdateCfdProposals,
pub network: Network,
}
@ -242,14 +300,14 @@ impl ToSseEvent for CfdsWithAuxData {
Cfd {
order_id: cfd.order.id,
initial_price: cfd.order.price,
initial_price: cfd.order.price.into(),
leverage: cfd.order.leverage,
trading_pair: cfd.order.trading_pair.clone(),
position: cfd.position(),
liquidation_price: cfd.order.liquidation_price,
quantity_usd: cfd.quantity_usd,
liquidation_price: cfd.order.liquidation_price.into(),
quantity_usd: cfd.quantity_usd.into(),
profit_btc,
profit_in_percent: profit_in_percent.to_string(),
profit_in_percent: profit_in_percent.round_dp(1).to_string(),
state: state.clone(),
actions: available_actions(state, cfd.role()),
state_transition_timestamp: cfd.state.get_transition_timestamp().seconds(),
@ -274,11 +332,11 @@ impl ToSseEvent for Option<model::cfd::Order> {
id: order.id,
trading_pair: order.trading_pair,
position: order.position,
price: order.price,
min_quantity: order.min_quantity,
max_quantity: order.max_quantity,
price: order.price.into(),
min_quantity: order.min_quantity.into(),
max_quantity: order.max_quantity.into(),
leverage: order.leverage,
liquidation_price: order.liquidation_price,
liquidation_price: order.liquidation_price.into(),
creation_timestamp: order.creation_timestamp,
settlement_time_interval_in_secs: order
.settlement_time_interval_hours
@ -417,8 +475,8 @@ pub struct Quote {
impl ToSseEvent for bitmex_price_feed::Quote {
fn to_sse_event(&self) -> Event {
let quote = Quote {
bid: self.bid,
ask: self.ask,
bid: self.bid.into(),
ask: self.ask.into(),
last_updated_at: self.timestamp,
};
Event::json(&quote).event("quote")
@ -457,6 +515,9 @@ fn available_actions(state: CfdState, role: Role) -> Vec<CfdAction> {
mod tests {
use super::*;
use rust_decimal_macros::dec;
use serde_test::{assert_ser_tokens, Token};
#[test]
fn state_snapshot_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();
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