diff --git a/daemon/src/projection.rs b/daemon/src/projection.rs index ede1e52..a589f15 100644 --- a/daemon/src/projection.rs +++ b/daemon/src/projection.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; -use crate::bitmex_price_feed::Quote; -use crate::model::TakerId; +use crate::model::{TakerId, Timestamp}; +use crate::{bitmex_price_feed, model}; use crate::{Cfd, Order, UpdateCfdProposals}; +use rust_decimal::Decimal; +use serde::Serialize; use tokio::sync::watch; use xtra_productivity::xtra_productivity; @@ -11,19 +13,19 @@ pub struct Actor { } pub struct Feeds { - pub cfds: watch::Receiver>, pub order: watch::Receiver>, + pub cfds: watch::Receiver>, pub quote: watch::Receiver, pub settlements: watch::Receiver, pub connected_takers: watch::Receiver>, } impl Actor { - pub fn new(init_cfds: Vec, init_quote: Quote) -> (Self, Feeds) { + pub fn new(init_cfds: Vec, init_quote: bitmex_price_feed::Quote) -> (Self, Feeds) { let (tx_cfds, rx_cfds) = watch::channel(init_cfds); let (tx_order, rx_order) = watch::channel(None); let (tx_update_cfd_feed, rx_update_cfd_feed) = watch::channel(HashMap::new()); - let (tx_quote, rx_quote) = watch::channel(init_quote); + let (tx_quote, rx_quote) = watch::channel(init_quote.into()); let (tx_connected_takers, rx_connected_takers) = watch::channel(Vec::new()); ( @@ -68,8 +70,8 @@ impl Actor { fn handle(&mut self, msg: Update>) { let _ = self.tx.order.send(msg.0); } - fn handle(&mut self, msg: Update) { - let _ = self.tx.quote.send(msg.0); + fn handle(&mut self, msg: Update) { + let _ = self.tx.quote.send(msg.0.into()); } fn handle(&mut self, msg: Update) { let _ = self.tx.settlements.send(msg.0); @@ -80,3 +82,120 @@ impl Actor { } impl xtra::Actor for Actor {} + +/// Types + +#[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 for Usd { + fn from(usd: model::Usd) -> Self { + Self::new(usd) + } +} + +impl Serialize for Usd { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ::serialize(&self.inner.into_decimal(), serializer) + } +} + +impl Serialize for Price { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ::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 for Price { + fn from(price: model::Price) -> Self { + Self::new(price) + } +} + +// TODO: Remove this after CfdsWithAuxData is removed +impl From for model::Price { + fn from(price: Price) -> Self { + price.inner + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct Quote { + bid: Price, + ask: Price, + last_updated_at: Timestamp, +} + +impl From for Quote { + fn from(quote: bitmex_price_feed::Quote) -> Self { + Quote { + bid: quote.bid.into(), + ask: quote.ask.into(), + last_updated_at: quote.timestamp, + } + } +} + +// TODO: Remove this after CfdsWithAuxData is removed +impl From for bitmex_price_feed::Quote { + fn from(quote: Quote) -> Self { + Self { + timestamp: quote.last_updated_at, + bid: quote.bid.into(), + ask: quote.ask.into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use rust_decimal_macros::dec; + use serde_test::{assert_ser_tokens, Token}; + + #[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")]); + } +} diff --git a/daemon/src/routes_taker.rs b/daemon/src/routes_taker.rs index 0fc32ea..994322b 100644 --- a/daemon/src/routes_taker.rs +++ b/daemon/src/routes_taker.rs @@ -5,7 +5,7 @@ use daemon::model::{Leverage, Price, Usd, WalletInfo}; use daemon::projection::Feeds; use daemon::routes::EmbeddedFileExt; use daemon::to_sse_event::{CfdAction, CfdsWithAuxData, ToSseEvent}; -use daemon::{taker_cfd, wallet}; +use daemon::{bitmex_price_feed, taker_cfd, wallet}; use http_api_problem::{HttpApiProblem, StatusCode}; use rocket::http::{ContentType, Status}; use rocket::response::stream::EventStream; @@ -152,7 +152,8 @@ pub async fn post_cfd_action( } CfdAction::Commit => cfd_action_channel.send(Commit { order_id: id }), CfdAction::Settle => { - let current_price = feeds.quote.borrow().for_taker(); + let quote: bitmex_price_feed::Quote = feeds.quote.borrow().clone().into(); + let current_price = quote.for_taker(); cfd_action_channel.send(ProposeSettlement { order_id: id, current_price, diff --git a/daemon/src/to_sse_event.rs b/daemon/src/to_sse_event.rs index 32be7c6..b7570fb 100644 --- a/daemon/src/to_sse_event.rs +++ b/daemon/src/to_sse_event.rs @@ -3,6 +3,7 @@ use crate::model::cfd::{ Dlc, OrderId, Payout, Role, SettlementKind, UpdateCfdProposal, UpdateCfdProposals, }; use crate::model::{Leverage, Position, TakerId, Timestamp, TradingPair}; +use crate::projection::{Price, Quote, Usd}; use crate::{bitmex_price_feed, model}; use bdk::bitcoin::{Amount, Network, SignedAmount, Txid}; use rocket::request::FromParam; @@ -13,64 +14,6 @@ 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 for Usd { - fn from(usd: model::Usd) -> Self { - Self::new(usd) - } -} - -impl Serialize for Usd { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - ::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 for Price { - fn from(price: model::Price) -> Self { - Self::new(price) - } -} - -impl Serialize for Price { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - ::serialize(&self.inner.into_decimal(), serializer) - } -} - #[derive(Debug, Clone, Serialize)] pub struct Cfd { pub order_id: OrderId, @@ -250,12 +193,12 @@ pub struct CfdsWithAuxData { impl CfdsWithAuxData { pub fn new( rx_cfds: &watch::Receiver>, - rx_quote: &watch::Receiver, + rx_quote: &watch::Receiver, rx_updates: &watch::Receiver, role: Role, network: Network, ) -> Self { - let quote = rx_quote.borrow().clone(); + let quote: bitmex_price_feed::Quote = rx_quote.borrow().clone().into(); let current_price = match role { Role::Maker => quote.for_maker(), Role::Taker => quote.for_taker(), @@ -487,21 +430,9 @@ fn to_tx_url_list(state: model::cfd::CfdState, network: Network) -> Vec { } } -#[derive(Debug, Clone, Serialize)] -pub struct Quote { - bid: Price, - ask: Price, - last_updated_at: Timestamp, -} - -impl ToSseEvent for bitmex_price_feed::Quote { +impl ToSseEvent for Quote { fn to_sse_event(&self) -> Event { - let quote = Quote { - bid: self.bid.into(), - ask: self.ask.into(), - last_updated_at: self.timestamp, - }; - Event::json("e).event("quote") + Event::json(self).event("quote") } } @@ -537,9 +468,6 @@ fn available_actions(state: CfdState, role: Role) -> Vec { 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! @@ -567,18 +495,4 @@ 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")]); - } }