Browse Source

Refactor Cfd to be based on Order

Flattening the Cfd out in the internal mode has caused duplication and confusion.
This refactor puts the Order into the Cfd to make relations clearer.

Note that once we have multiple leverage the leverage and liquidation price will have to move into the Cfd because they depend on the user's choice then.
This should be trivial to do.
no-contract-setup-message
Daniel Karzel 3 years ago
parent
commit
b60c55d031
No known key found for this signature in database GPG Key ID: 30C3FC2E438ADB6E
  1. 44
      daemon/sqlx-data.json
  2. 86
      daemon/src/db.rs
  3. 5
      daemon/src/maker_cfd_actor.rs
  4. 10
      daemon/src/model.rs
  5. 61
      daemon/src/model/cfd.rs
  6. 4
      daemon/src/routes_maker.rs
  7. 6
      daemon/src/taker_cfd_actor.rs
  8. 7
      daemon/src/taker_inc_message_actor.rs
  9. 12
      daemon/src/to_sse_event.rs

44
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
]
}

86
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<Sqlite>,
origin: Origin,
) -> anyhow::Result<()> {
pub async fn insert_order(order: &Order, conn: &mut PoolConnection<Sqlite>) -> 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<Sqlite>) -> 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<Sqlite>) -> 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<Sqlite>) -> 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();

5
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);

10
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);

61
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 {
@ -41,7 +47,8 @@ pub struct Order {
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<Self> {
pub fn from_default_with_price(price: Usd, origin: Origin) -> Result<Self> {
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<Amount> {
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(

4
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<CfdNewOrderRequest>,
cfd_actor_inbox: &State<mpsc::UnboundedSender<maker_cfd_actor::Command>>,
) -> Result<status::Accepted<()>, status::BadRequest<String>> {
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);

6
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();
}

7
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();

12
daemon/src/to_sse_event.rs

@ -63,12 +63,12 @@ impl ToSseEvent for Vec<model::cfd::Cfd> {
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,

Loading…
Cancel
Save