Browse Source

Merge pull request #82 from comit-network/refactor-cfd-model

Refactor Cfd to be based on Order
no-contract-setup-message
Daniel Karzel 3 years ago
committed by GitHub
parent
commit
0a2e43ed64
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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. 63
      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);

63
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<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