diff --git a/daemon/sqlx-data.json b/daemon/sqlx-data.json index bd4298a..9aab65e 100644 --- a/daemon/sqlx-data.json +++ b/daemon/sqlx-data.json @@ -28,8 +28,8 @@ ] } }, - "7941da81b43abcaa82d51bbfecaed51f13fc637dc611cebb87eea638ba055a42": { - "query": "\n select\n cfds.id as cfd_id,\n orders.uuid as order_id,\n orders.initial_price as initial_price,\n orders.leverage as leverage,\n orders.trading_pair as trading_pair,\n orders.position as position,\n orders.origin as origin,\n orders.liquidation_price as liquidation_price,\n cfds.quantity_usd as quantity_usd,\n cfd_states.state as state\n from cfds as cfds\n inner join orders as orders on cfds.order_id = orders.id\n inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id\n where cfd_states.state in (\n select\n state\n from cfd_states\n where cfd_id = cfds.id\n order by id desc\n limit 1\n )\n ", + "57c0eb6669321997352d87372431788aa039dd1898ca0b11ba4600f743ff4d93": { + "query": "\n select\n cfds.id as cfd_id,\n orders.uuid as order_id,\n orders.initial_price as price,\n orders.min_quantity as min_quantity,\n orders.max_quantity as max_quantity,\n orders.leverage as leverage,\n orders.trading_pair as trading_pair,\n orders.position as position,\n orders.origin as origin,\n orders.liquidation_price as liquidation_price,\n orders.creation_timestamp as creation_timestamp,\n orders.term as term,\n cfds.quantity_usd as quantity_usd,\n cfd_states.state as state\n from cfds as cfds\n inner join orders as orders on cfds.order_id = orders.id\n inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id\n where cfd_states.state in (\n select\n state\n from cfd_states\n where cfd_id = cfds.id\n order by id desc\n limit 1\n )\n ", "describe": { "columns": [ { @@ -43,43 +43,63 @@ "type_info": "Text" }, { - "name": "initial_price", + "name": "price", "ordinal": 2, "type_info": "Text" }, { - "name": "leverage", + "name": "min_quantity", "ordinal": 3, + "type_info": "Text" + }, + { + "name": "max_quantity", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "leverage", + "ordinal": 5, "type_info": "Int64" }, { "name": "trading_pair", - "ordinal": 4, + "ordinal": 6, "type_info": "Text" }, { "name": "position", - "ordinal": 5, + "ordinal": 7, "type_info": "Text" }, { "name": "origin", - "ordinal": 6, + "ordinal": 8, "type_info": "Text" }, { "name": "liquidation_price", - "ordinal": 7, + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "creation_timestamp", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "term", + "ordinal": 11, "type_info": "Text" }, { "name": "quantity_usd", - "ordinal": 8, + "ordinal": 12, "type_info": "Text" }, { "name": "state", - "ordinal": 9, + "ordinal": 13, "type_info": "Text" } ], @@ -96,6 +116,10 @@ false, false, false, + false, + false, + false, + false, false ] } diff --git a/daemon/src/db.rs b/daemon/src/db.rs index 8cc1509..1b0159a 100644 --- a/daemon/src/db.rs +++ b/daemon/src/db.rs @@ -1,29 +1,19 @@ -use crate::model::cfd::{Cfd, CfdState, Order, OrderId}; +use crate::model::cfd::{Cfd, CfdState, Order, OrderId, Origin}; use crate::model::{Leverage, Position}; use anyhow::Context; use rocket_db_pools::sqlx; -use serde::{Deserialize, Serialize}; + use sqlx::pool::PoolConnection; use sqlx::{Acquire, Sqlite, SqlitePool}; use std::convert::TryInto; use std::mem; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum Origin { - Ours, - Theirs, -} - pub async fn run_migrations(pool: &SqlitePool) -> anyhow::Result<()> { sqlx::migrate!("./migrations").run(pool).await?; Ok(()) } -pub async fn insert_order( - order: &Order, - conn: &mut PoolConnection, - origin: Origin, -) -> anyhow::Result<()> { +pub async fn insert_order(order: &Order, conn: &mut PoolConnection) -> anyhow::Result<()> { let uuid = serde_json::to_string(&order.id).unwrap(); let trading_pair = serde_json::to_string(&order.trading_pair).unwrap(); let position = serde_json::to_string(&order.position).unwrap(); @@ -34,7 +24,7 @@ pub async fn insert_order( let liquidation_price = serde_json::to_string(&order.liquidation_price).unwrap(); let creation_timestamp = serde_json::to_string(&order.creation_timestamp).unwrap(); let term = serde_json::to_string(&order.term).unwrap(); - let origin = serde_json::to_string(&origin).unwrap(); + let origin = serde_json::to_string(&order.origin).unwrap(); sqlx::query!( r#" @@ -95,6 +85,7 @@ pub async fn load_order_by_id( let liquidation_price = serde_json::from_str(row.liquidation_price.as_str()).unwrap(); let creation_timestamp = serde_json::from_str(row.creation_timestamp.as_str()).unwrap(); let term = serde_json::from_str(row.term.as_str()).unwrap(); + let origin = serde_json::from_str(row.origin.as_str()).unwrap(); Ok(Order { id: uuid, @@ -107,13 +98,14 @@ pub async fn load_order_by_id( liquidation_price, creation_timestamp, term, + origin, }) } pub async fn insert_cfd(cfd: Cfd, conn: &mut PoolConnection) -> anyhow::Result<()> { let mut tx = conn.begin().await?; - let order_uuid = serde_json::to_string(&cfd.order_id)?; + let order_uuid = serde_json::to_string(&cfd.order.id)?; let order_row = sqlx::query!( r#" select * from orders where uuid = ?; @@ -258,12 +250,16 @@ pub async fn load_all_cfds(conn: &mut PoolConnection) -> anyhow::Result< select cfds.id as cfd_id, orders.uuid as order_id, - orders.initial_price as initial_price, + orders.initial_price as price, + orders.min_quantity as min_quantity, + orders.max_quantity as max_quantity, orders.leverage as leverage, orders.trading_pair as trading_pair, orders.position as position, orders.origin as origin, orders.liquidation_price as liquidation_price, + orders.creation_timestamp as creation_timestamp, + orders.term as term, cfds.quantity_usd as quantity_usd, cfd_states.state as state from cfds as cfds @@ -286,28 +282,36 @@ pub async fn load_all_cfds(conn: &mut PoolConnection) -> anyhow::Result< .iter() .map(|row| { let order_id = serde_json::from_str(row.order_id.as_str()).unwrap(); - let initial_price = serde_json::from_str(row.initial_price.as_str()).unwrap(); - let leverage = Leverage(row.leverage.try_into().unwrap()); let trading_pair = serde_json::from_str(row.trading_pair.as_str()).unwrap(); + let position: Position = serde_json::from_str(row.position.as_str()).unwrap(); + let price = serde_json::from_str(row.price.as_str()).unwrap(); + let min_quantity = serde_json::from_str(row.min_quantity.as_str()).unwrap(); + let max_quantity = serde_json::from_str(row.max_quantity.as_str()).unwrap(); + let leverage = Leverage(row.leverage.try_into().unwrap()); let liquidation_price = serde_json::from_str(row.liquidation_price.as_str()).unwrap(); - let quantity = serde_json::from_str(row.quantity_usd.as_str()).unwrap(); - let latest_state = serde_json::from_str(row.state.as_str()).unwrap(); - + let creation_timestamp = serde_json::from_str(row.creation_timestamp.as_str()).unwrap(); + let term = serde_json::from_str(row.term.as_str()).unwrap(); let origin: Origin = serde_json::from_str(row.origin.as_str()).unwrap(); - let position: Position = serde_json::from_str(row.position.as_str()).unwrap(); - let position = match origin { - Origin::Ours => position, - Origin::Theirs => position.counter_position(), - }; + let quantity = serde_json::from_str(row.quantity_usd.as_str()).unwrap(); + let latest_state = serde_json::from_str(row.state.as_str()).unwrap(); - Cfd { - order_id, - initial_price, - leverage, + let order = Order { + id: order_id, trading_pair, position, + price, + min_quantity, + max_quantity, + leverage, liquidation_price, + creation_timestamp, + term, + origin, + }; + + Cfd { + order, quantity_usd: quantity, state: latest_state, } @@ -337,10 +341,8 @@ mod tests { let pool = setup_test_db().await; let mut conn = pool.acquire().await.unwrap(); - let order = Order::from_default_with_price(Usd(dec!(10000))).unwrap(); - insert_order(&order, &mut conn, Origin::Theirs) - .await - .unwrap(); + let order = Order::from_default_with_price(Usd(dec!(10000)), Origin::Theirs).unwrap(); + insert_order(&order, &mut conn).await.unwrap(); let order_loaded = load_order_by_id(order.id, &mut conn).await.unwrap(); @@ -352,7 +354,7 @@ mod tests { let pool = setup_test_db().await; let mut conn = pool.acquire().await.unwrap(); - let order = Order::from_default_with_price(Usd(dec!(10000))).unwrap(); + let order = Order::from_default_with_price(Usd(dec!(10000)), Origin::Theirs).unwrap(); let cfd = Cfd::new( order.clone(), Usd(dec!(1000)), @@ -361,13 +363,10 @@ mod tests { transition_timestamp: SystemTime::now(), }, }, - Position::Buy, ); // the order ahs to exist in the db in order to be able to insert the cfd - insert_order(&order, &mut conn, Origin::Theirs) - .await - .unwrap(); + insert_order(&order, &mut conn).await.unwrap(); insert_cfd(cfd.clone(), &mut conn).await.unwrap(); let cfds_from_db = load_all_cfds(&mut conn).await.unwrap(); @@ -380,7 +379,7 @@ mod tests { let pool = setup_test_db().await; let mut conn = pool.acquire().await.unwrap(); - let order = Order::from_default_with_price(Usd(dec!(10000))).unwrap(); + let order = Order::from_default_with_price(Usd(dec!(10000)), Origin::Theirs).unwrap(); let mut cfd = Cfd::new( order.clone(), Usd(dec!(1000)), @@ -389,13 +388,10 @@ mod tests { transition_timestamp: SystemTime::now(), }, }, - Position::Buy, ); // the order ahs to exist in the db in order to be able to insert the cfd - insert_order(&order, &mut conn, Origin::Theirs) - .await - .unwrap(); + insert_order(&order, &mut conn).await.unwrap(); insert_cfd(cfd.clone(), &mut conn).await.unwrap(); cfd.state = CfdState::Accepted { @@ -403,7 +399,7 @@ mod tests { transition_timestamp: SystemTime::now(), }, }; - insert_new_cfd_state_by_order_id(cfd.order_id, cfd.state, &mut conn) + insert_new_cfd_state_by_order_id(cfd.order.id, cfd.state, &mut conn) .await .unwrap(); diff --git a/daemon/src/maker_cfd_actor.rs b/daemon/src/maker_cfd_actor.rs index 622f0ba..829b8ec 100644 --- a/daemon/src/maker_cfd_actor.rs +++ b/daemon/src/maker_cfd_actor.rs @@ -1,6 +1,6 @@ use std::time::SystemTime; -use crate::db::{insert_cfd, insert_order, load_all_cfds, load_order_by_id, Origin}; +use crate::db::{insert_cfd, insert_order, load_all_cfds, load_order_by_id}; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::{TakerId, Usd}; use crate::wire::SetupMsg; @@ -101,7 +101,6 @@ where transition_timestamp: SystemTime::now(), }, }, - current_order.position, ); insert_cfd(cfd, &mut conn).await.unwrap(); @@ -125,7 +124,7 @@ where maker_cfd_actor::Command::NewOrder(order) => { // 1. Save to DB let mut conn = db.acquire().await.unwrap(); - insert_order(&order, &mut conn, Origin::Ours).await.unwrap(); + insert_order(&order, &mut conn).await.unwrap(); // 2. Update actor state to current order current_order_id.replace(order.id); diff --git a/daemon/src/model.rs b/daemon/src/model.rs index ea5da1f..8968b19 100644 --- a/daemon/src/model.rs +++ b/daemon/src/model.rs @@ -69,16 +69,6 @@ pub enum Position { Sell, } -impl Position { - #[allow(dead_code)] - pub fn counter_position(&self) -> Self { - match self { - Position::Buy => Position::Sell, - Position::Sell => Position::Buy, - } - } -} - #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct TakerId(Uuid); diff --git a/daemon/src/model/cfd.rs b/daemon/src/model/cfd.rs index 45cedab..de09799 100644 --- a/daemon/src/model/cfd.rs +++ b/daemon/src/model/cfd.rs @@ -26,6 +26,12 @@ impl Display for OrderId { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum Origin { + Ours, + Theirs, +} + /// A concrete order created by a maker for a taker #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Order { @@ -37,11 +43,12 @@ pub struct Order { pub price: Usd, // TODO: [post-MVP] Representation of the contract size; at the moment the contract size is - // always 1 USD + // always 1 USD pub min_quantity: Usd, pub max_quantity: Usd, - // TODO: [post-MVP] Allow different values + // TODO: [post-MVP] - Once we have multiple leverage we will have to move leverage and + // liquidation_price into the CFD and add a calculation endpoint for the taker buy screen pub leverage: Leverage, pub liquidation_price: Usd, @@ -49,11 +56,13 @@ pub struct Order { /// The duration that will be used for calculating the settlement timestamp pub term: Duration, + + pub origin: Origin, } #[allow(dead_code)] // Only one binary and the tests use this. impl Order { - pub fn from_default_with_price(price: Usd) -> Result { + pub fn from_default_with_price(price: Usd, origin: Origin) -> Result { let leverage = Leverage(5); let maintenance_margin_rate = dec!(0.005); let liquidation_price = @@ -70,6 +79,7 @@ impl Order { position: Position::Sell, creation_timestamp: SystemTime::now(), term: Duration::from_secs(60 * 60 * 8), // 8 hours + origin, }) } pub fn with_min_quantity(mut self, min_quantity: Usd) -> Order { @@ -240,53 +250,50 @@ impl Display for CfdState { /// Represents a cfd (including state) #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Cfd { - pub order_id: OrderId, - pub initial_price: Usd, - - pub leverage: Leverage, - pub trading_pair: TradingPair, - pub position: Position, - pub liquidation_price: Usd, - + pub order: Order, pub quantity_usd: Usd, - pub state: CfdState, + /* TODO: Leverage is currently derived from the Order, but the actual leverage should be + * stored in the Cfd once there is multiple choices of leverage */ } impl Cfd { - pub fn new(cfd_order: Order, quantity: Usd, state: CfdState, position: Position) -> Self { + pub fn new(order: Order, quantity: Usd, state: CfdState) -> Self { Cfd { - order_id: cfd_order.id, - initial_price: cfd_order.price, - leverage: cfd_order.leverage, - trading_pair: cfd_order.trading_pair, - position, - liquidation_price: cfd_order.liquidation_price, + order, quantity_usd: quantity, state, } } pub fn calc_margin(&self) -> Result { - let margin = match self.position { + let margin = match self.position() { Position::Buy => { - calculate_buy_margin(self.initial_price, self.quantity_usd, self.leverage)? + calculate_buy_margin(self.order.price, self.quantity_usd, self.order.leverage)? } - Position::Sell => calculate_sell_margin(self.initial_price, self.quantity_usd)?, + Position::Sell => calculate_sell_margin(self.order.price, self.quantity_usd)?, }; Ok(margin) } pub fn calc_profit(&self, current_price: Usd) -> Result<(Amount, Usd)> { - let profit = calculate_profit( - self.initial_price, - current_price, - dec!(0.005), - Usd(dec!(0.1)), - )?; + let profit = + calculate_profit(self.order.price, current_price, dec!(0.005), Usd(dec!(0.1)))?; Ok(profit) } + + pub fn position(&self) -> Position { + match self.order.origin { + Origin::Ours => self.order.position.clone(), + + // If the order is not our own we take the counter-position in the CFD + Origin::Theirs => match self.order.position { + Position::Buy => Position::Sell, + Position::Sell => Position::Buy, + }, + } + } } fn calculate_profit( diff --git a/daemon/src/routes_maker.rs b/daemon/src/routes_maker.rs index ca438db..22af97c 100644 --- a/daemon/src/routes_maker.rs +++ b/daemon/src/routes_maker.rs @@ -1,5 +1,5 @@ use crate::maker_cfd_actor; -use crate::model::cfd::{Cfd, Order}; +use crate::model::cfd::{Cfd, Order, Origin}; use crate::model::Usd; use crate::to_sse_event::ToSseEvent; use anyhow::Result; @@ -67,7 +67,7 @@ pub async fn post_sell_order( order: Json, cfd_actor_inbox: &State>, ) -> Result, status::BadRequest> { - let order = Order::from_default_with_price(order.price) + let order = Order::from_default_with_price(order.price, Origin::Ours) .map_err(|e| status::BadRequest(Some(e.to_string())))? .with_min_quantity(order.min_quantity) .with_max_quantity(order.max_quantity); diff --git a/daemon/src/taker_cfd_actor.rs b/daemon/src/taker_cfd_actor.rs index b47315a..d0cfb1c 100644 --- a/daemon/src/taker_cfd_actor.rs +++ b/daemon/src/taker_cfd_actor.rs @@ -1,6 +1,5 @@ use crate::db::{ insert_cfd, insert_new_cfd_state_by_order_id, insert_order, load_all_cfds, load_order_by_id, - Origin, }; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::Usd; @@ -66,7 +65,6 @@ where transition_timestamp: SystemTime::now(), }, }, - current_order.position.counter_position(), ); insert_cfd(cfd, &mut conn).await.unwrap(); @@ -80,9 +78,7 @@ where } Command::NewOrder(Some(order)) => { let mut conn = db.acquire().await.unwrap(); - insert_order(&order, &mut conn, Origin::Theirs) - .await - .unwrap(); + insert_order(&order, &mut conn).await.unwrap(); order_feed_actor_inbox.send(Some(order)).unwrap(); } diff --git a/daemon/src/taker_inc_message_actor.rs b/daemon/src/taker_inc_message_actor.rs index a5c3ec5..0ad962d 100644 --- a/daemon/src/taker_inc_message_actor.rs +++ b/daemon/src/taker_inc_message_actor.rs @@ -1,3 +1,4 @@ +use crate::model::cfd::Origin; use crate::{taker_cfd_actor, wire}; use futures::{Future, StreamExt}; use tokio::net::tcp::OwnedReadHalf; @@ -18,7 +19,11 @@ pub fn new( async move { while let Some(message) = messages.next().await { match message { - Ok(wire::MakerToTaker::CurrentOrder(order)) => { + Ok(wire::MakerToTaker::CurrentOrder(mut order)) => { + if let Some(order) = order.as_mut() { + order.origin = Origin::Theirs; + } + cfd_actor .send(taker_cfd_actor::Command::NewOrder(order)) .unwrap(); diff --git a/daemon/src/to_sse_event.rs b/daemon/src/to_sse_event.rs index fbecd47..1028876 100644 --- a/daemon/src/to_sse_event.rs +++ b/daemon/src/to_sse_event.rs @@ -63,12 +63,12 @@ impl ToSseEvent for Vec { let (profit_btc, profit_usd) = cfd.calc_profit(current_price).unwrap(); Cfd { - order_id: cfd.order_id, - initial_price: cfd.initial_price, - leverage: cfd.leverage, - trading_pair: cfd.trading_pair.clone(), - position: cfd.position.clone(), - liquidation_price: cfd.liquidation_price, + order_id: cfd.order.id, + initial_price: cfd.order.price, + 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, profit_btc, profit_usd,