Browse Source

Merge #315

315: Lock in profit on collaborative close price r=da-kami a=da-kami

fixes #312 

Once we have collaborative close present in the state(s) we lock in the profit based on the price of the collaborative close.
Note: Currently no automated validation on the maker side if the given amounts and price of collaborative settlement are sane.

Co-authored-by: Daniel Karzel <daniel@comit.network>
refactor/no-log-handler
bors[bot] 3 years ago
committed by GitHub
parent
commit
d979610056
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      daemon/src/maker_cfd.rs
  2. 50
      daemon/src/model/cfd.rs
  3. 4
      daemon/src/monitor.rs
  4. 7
      daemon/src/taker_cfd.rs
  5. 1
      daemon/src/wire.rs

14
daemon/src/maker_cfd.rs

@ -4,8 +4,8 @@ use crate::db::{
};
use crate::maker_inc_connections::TakerCommand;
use crate::model::cfd::{
Attestation, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin,
Role, RollOverProposal, SettlementKind, SettlementProposal, TimestampedTransaction,
Attestation, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, CollaborativeSettlement, Dlc,
Order, OrderId, Origin, Role, RollOverProposal, SettlementKind, SettlementProposal,
UpdateCfdProposal, UpdateCfdProposals,
};
use crate::model::{TakerId, Usd};
@ -304,7 +304,11 @@ impl Actor {
let (tx, sig_maker) = dlc.close_transaction(proposal)?;
cfd.handle(CfdStateChangeEvent::ProposalSigned(
TimestampedTransaction::new(tx.clone(), dlc.script_pubkey_for(cfd.role())),
CollaborativeSettlement::new(
tx.clone(),
dlc.script_pubkey_for(cfd.role()),
proposal.price,
),
))?;
insert_new_cfd_state_by_order_id(cfd.order.id, &cfd.state, &mut conn).await?;
self.cfd_feed_actor_inbox
@ -1077,6 +1081,7 @@ impl Handler<TakerStreamMessage> for Actor {
timestamp,
taker,
maker,
price,
} => {
log_error!(self.handle_propose_settlement(
taker_id,
@ -1084,7 +1089,8 @@ impl Handler<TakerStreamMessage> for Actor {
order_id,
timestamp,
taker,
maker
maker,
price
}
))
}

50
daemon/src/model/cfd.rs

@ -217,7 +217,7 @@ pub enum CfdState {
common: CfdStateCommon,
dlc: Dlc,
attestation: Option<Attestation>,
collaborative_close: Option<TimestampedTransaction>,
collaborative_close: Option<CollaborativeSettlement>,
},
/// The commit transaction was published but it not final yet
@ -289,7 +289,7 @@ pub enum CfdState {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Payout {
CollaborativeClose(TimestampedTransaction),
CollaborativeClose(CollaborativeSettlement),
Cet(Attestation),
}
@ -407,7 +407,7 @@ impl CfdState {
self.get_common().transition_timestamp
}
pub fn get_collaborative_close(&self) -> Option<TimestampedTransaction> {
pub fn get_collaborative_close(&self) -> Option<CollaborativeSettlement> {
match self {
CfdState::Open {
collaborative_close,
@ -486,6 +486,7 @@ pub struct SettlementProposal {
pub timestamp: SystemTime,
pub taker: Amount,
pub maker: Amount,
pub price: Usd,
}
/// Proposed collaborative settlement
@ -546,17 +547,16 @@ impl Cfd {
}
pub fn profit(&self, current_price: Usd) -> Result<(SignedAmount, Percent)> {
// TODO: We should use the payout curve here and not just the current price!
// TODO: Use the collab settlement if there was one
let current_price = if let Some(attestation) = self.attestation() {
attestation.price()
} else {
current_price
let closing_price = match (self.attestation(), self.collaborative_close()) {
(Some(_attestation), Some(collaborative_close)) => collaborative_close.price,
(None, Some(collaborative_close)) => collaborative_close.price,
(Some(attestation), None) => attestation.price(),
(None, None) => current_price,
};
let (p_n_l, p_n_l_percent) = calculate_profit(
self.order.price,
current_price,
closing_price,
self.quantity_usd,
self.margin()?,
self.position(),
@ -569,18 +569,20 @@ impl Cfd {
let payout_curve =
payout_curve::calculate(self.order.price, self.quantity_usd, self.order.leverage)?;
let current_price = current_price.try_into_u64()?;
let payout = payout_curve
.iter()
.find(|&x| x.digits().range().contains(&current_price))
.context("find current price on the payout curve")?;
let payout = {
let current_price = current_price.try_into_u64()?;
payout_curve
.iter()
.find(|&x| x.digits().range().contains(&current_price))
.context("find current price on the payout curve")?
};
let settlement = SettlementProposal {
order_id: self.order.id,
timestamp: SystemTime::now(),
taker: *payout.taker_amount(),
maker: *payout.maker_amount(),
price: current_price,
};
Ok(settlement)
@ -1168,7 +1170,7 @@ impl Cfd {
}
}
pub fn collaborative_close(&self) -> Option<TimestampedTransaction> {
pub fn collaborative_close(&self) -> Option<CollaborativeSettlement> {
match self.state.clone() {
CfdState::Open {
collaborative_close: Some(collaborative_close),
@ -1235,13 +1237,13 @@ pub enum CfdStateChangeEvent {
OracleAttestation(Attestation),
CetSent,
/// Settlement proposal was signed by the taker and sent to the maker
ProposalSigned(TimestampedTransaction),
ProposalSigned(CollaborativeSettlement),
}
/// Returns the Profit/Loss (P/L) as Bitcoin. Losses are capped by the provided margin
fn calculate_profit(
initial_price: Usd,
current_price: Usd,
closing_price: Usd,
quantity: Usd,
margin: Amount,
position: Position,
@ -1253,7 +1255,7 @@ fn calculate_profit(
.checked_div(initial_price.0)
.context("Calculating inverse of initial_price resulted in overflow")?;
let current_price_inverse = dec!(1)
.checked_div(current_price.0)
.checked_div(closing_price.0)
.context("Calculating inverse of current_price resulted in overflow")?;
// calculate profit/loss (P and L) in BTC
@ -1710,15 +1712,16 @@ pub struct RevokedCommit {
/// timestamp as it could have occured for different reasons (like a new
/// attestation in Open state)
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TimestampedTransaction {
pub struct CollaborativeSettlement {
pub tx: Transaction,
pub timestamp: SystemTime,
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")]
payout: Amount,
price: Usd,
}
impl TimestampedTransaction {
pub fn new(tx: Transaction, own_script_pubkey: Script) -> Self {
impl CollaborativeSettlement {
pub fn new(tx: Transaction, own_script_pubkey: Script, price: Usd) -> Self {
// Falls back to Amount::ZERO in case we don't find an output that matches out script pubkey
// The assumption is, that this can happen for cases where we were liuqidated
let payout = match tx
@ -1740,6 +1743,7 @@ impl TimestampedTransaction {
tx,
timestamp: SystemTime::now(),
payout,
price,
}
}

4
daemon/src/monitor.rs

@ -1,4 +1,4 @@
use crate::model::cfd::{CetStatus, Cfd, CfdState, Dlc, OrderId, TimestampedTransaction};
use crate::model::cfd::{CetStatus, Cfd, CfdState, CollaborativeSettlement, Dlc, OrderId};
use crate::model::BitMexPriceEventId;
use crate::oracle::Attestation;
use crate::{log_error, model, oracle};
@ -84,7 +84,7 @@ impl Actor<bdk::electrum_client::Client> {
actor.monitor_commit_refund_timelock(&params, cfd.order.id);
actor.monitor_refund_finality(&params,cfd.order.id);
if let Some(TimestampedTransaction { tx, ..}
if let Some(CollaborativeSettlement { tx, ..}
) = cfd.state.get_collaborative_close() {
let close_params = (tx.txid(),
tx.output.first().expect("have output").script_pubkey.clone());

7
daemon/src/taker_cfd.rs

@ -3,8 +3,8 @@ use crate::db::{
load_cfd_by_order_id, load_cfds_by_oracle_event_id, load_order_by_id,
};
use crate::model::cfd::{
Attestation, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin,
Role, RollOverProposal, SettlementKind, SettlementProposal, TimestampedTransaction,
Attestation, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, CollaborativeSettlement, Dlc,
Order, OrderId, Origin, Role, RollOverProposal, SettlementKind, SettlementProposal,
UpdateCfdProposal, UpdateCfdProposals,
};
use crate::model::{BitMexPriceEventId, Usd};
@ -204,6 +204,7 @@ impl Actor {
timestamp: proposal.timestamp,
taker: proposal.taker,
maker: proposal.maker,
price: proposal.price,
})
.await?;
Ok(())
@ -370,7 +371,7 @@ impl Actor {
.await?;
cfd.handle(CfdStateChangeEvent::ProposalSigned(
TimestampedTransaction::new(tx, dlc.script_pubkey_for(cfd.role())),
CollaborativeSettlement::new(tx, dlc.script_pubkey_for(cfd.role()), proposal.price),
))?;
insert_new_cfd_state_by_order_id(cfd.order.id, &cfd.state, &mut conn).await?;

1
daemon/src/wire.rs

@ -31,6 +31,7 @@ pub enum TakerToMaker {
taker: Amount,
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_btc")]
maker: Amount,
price: Usd,
},
InitiateSettlement {
order_id: OrderId,

Loading…
Cancel
Save