|
|
@ -1,14 +1,13 @@ |
|
|
|
use crate::connection::ConnectionStatus; |
|
|
|
use crate::model::cfd::{OrderId, Role, SettlementKind, UpdateCfdProposal, UpdateCfdProposals}; |
|
|
|
use crate::model::cfd::{OrderId, Role, UpdateCfdProposals}; |
|
|
|
use crate::model::{Leverage, Position, Timestamp, TradingPair}; |
|
|
|
use crate::projection::{CfdOrder, Identity, Price, Quote, Usd}; |
|
|
|
use crate::tx::{self, TxUrl}; |
|
|
|
use crate::projection::{self, CfdAction, CfdOrder, CfdState, Identity, Price, Quote, Usd}; |
|
|
|
use crate::{bitmex_price_feed, model}; |
|
|
|
use bdk::bitcoin::{Amount, Network, SignedAmount}; |
|
|
|
use rocket::request::FromParam; |
|
|
|
use rocket::response::stream::Event; |
|
|
|
use rust_decimal::Decimal; |
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
use serde::Serialize; |
|
|
|
use time::OffsetDateTime; |
|
|
|
use tokio::sync::watch; |
|
|
|
|
|
|
@ -37,33 +36,12 @@ pub struct Cfd { |
|
|
|
pub actions: Vec<CfdAction>, |
|
|
|
pub state_transition_timestamp: i64, |
|
|
|
|
|
|
|
pub details: CfdDetails, |
|
|
|
pub details: projection::CfdDetails, |
|
|
|
|
|
|
|
#[serde(with = "::time::serde::timestamp")] |
|
|
|
pub expiry_timestamp: OffsetDateTime, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize)] |
|
|
|
pub struct CfdDetails { |
|
|
|
tx_url_list: Vec<TxUrl>, |
|
|
|
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_btc::opt")] |
|
|
|
payout: Option<Amount>, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, derive_more::Display, Clone, Serialize, Deserialize, PartialEq)] |
|
|
|
#[serde(rename_all = "camelCase")] |
|
|
|
pub enum CfdAction { |
|
|
|
AcceptOrder, |
|
|
|
RejectOrder, |
|
|
|
Commit, |
|
|
|
Settle, |
|
|
|
AcceptSettlement, |
|
|
|
RejectSettlement, |
|
|
|
RollOver, |
|
|
|
AcceptRollOver, |
|
|
|
RejectRollOver, |
|
|
|
} |
|
|
|
|
|
|
|
impl<'v> FromParam<'v> for CfdAction { |
|
|
|
type Error = serde_plain::Error; |
|
|
|
|
|
|
@ -73,29 +51,6 @@ impl<'v> FromParam<'v> for CfdAction { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize)] |
|
|
|
pub enum CfdState { |
|
|
|
OutgoingOrderRequest, |
|
|
|
IncomingOrderRequest, |
|
|
|
Accepted, |
|
|
|
Rejected, |
|
|
|
ContractSetup, |
|
|
|
PendingOpen, |
|
|
|
Open, |
|
|
|
PendingCommit, |
|
|
|
PendingCet, |
|
|
|
PendingClose, |
|
|
|
OpenCommitted, |
|
|
|
IncomingSettlementProposal, |
|
|
|
OutgoingSettlementProposal, |
|
|
|
IncomingRollOverProposal, |
|
|
|
OutgoingRollOverProposal, |
|
|
|
Closed, |
|
|
|
PendingRefund, |
|
|
|
Refunded, |
|
|
|
SetupFailed, |
|
|
|
} |
|
|
|
|
|
|
|
pub trait ToSseEvent { |
|
|
|
fn to_sse_event(&self) -> Event; |
|
|
|
} |
|
|
@ -155,12 +110,7 @@ impl ToSseEvent for CfdsWithAuxData { |
|
|
|
}); |
|
|
|
|
|
|
|
let pending_proposal = self.pending_proposals.get(&cfd.order.id); |
|
|
|
let state = to_cfd_state(&cfd.state, pending_proposal); |
|
|
|
|
|
|
|
let details = CfdDetails { |
|
|
|
tx_url_list: tx::to_tx_url_list(cfd.state.clone(), network), |
|
|
|
payout: cfd.payout(), |
|
|
|
}; |
|
|
|
let state = projection::to_cfd_state(&cfd.state, pending_proposal); |
|
|
|
|
|
|
|
Cfd { |
|
|
|
order_id: cfd.order.id, |
|
|
@ -173,14 +123,14 @@ impl ToSseEvent for CfdsWithAuxData { |
|
|
|
profit_btc, |
|
|
|
profit_in_percent: profit_in_percent.round_dp(1).to_string(), |
|
|
|
state: state.clone(), |
|
|
|
actions: available_actions(state, cfd.role()), |
|
|
|
actions: projection::available_actions(state, cfd.role()), |
|
|
|
state_transition_timestamp: cfd.state.get_transition_timestamp().seconds(), |
|
|
|
|
|
|
|
// TODO: Depending on the state the margin might be set (i.e. in Open we save it
|
|
|
|
// in the DB internally) and does not have to be calculated
|
|
|
|
margin: cfd.margin().expect("margin to be available"), |
|
|
|
margin_counterparty: cfd.counterparty_margin().expect("margin to be available"), |
|
|
|
details, |
|
|
|
details: projection::to_cfd_details(cfd, network), |
|
|
|
expiry_timestamp: match cfd.expiry_timestamp() { |
|
|
|
None => cfd.order.oracle_event_id.timestamp(), |
|
|
|
Some(timestamp) => timestamp, |
|
|
@ -236,86 +186,12 @@ impl ToSseEvent for ConnectionStatus { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn to_cfd_state( |
|
|
|
cfd_state: &model::cfd::CfdState, |
|
|
|
proposal_status: Option<&UpdateCfdProposal>, |
|
|
|
) -> CfdState { |
|
|
|
match proposal_status { |
|
|
|
Some(UpdateCfdProposal::Settlement { |
|
|
|
direction: SettlementKind::Outgoing, |
|
|
|
.. |
|
|
|
}) => CfdState::OutgoingSettlementProposal, |
|
|
|
Some(UpdateCfdProposal::Settlement { |
|
|
|
direction: SettlementKind::Incoming, |
|
|
|
.. |
|
|
|
}) => CfdState::IncomingSettlementProposal, |
|
|
|
Some(UpdateCfdProposal::RollOverProposal { |
|
|
|
direction: SettlementKind::Outgoing, |
|
|
|
.. |
|
|
|
}) => CfdState::OutgoingRollOverProposal, |
|
|
|
Some(UpdateCfdProposal::RollOverProposal { |
|
|
|
direction: SettlementKind::Incoming, |
|
|
|
.. |
|
|
|
}) => CfdState::IncomingRollOverProposal, |
|
|
|
None => match cfd_state { |
|
|
|
// Filled in collaborative close in Open means that we're awaiting
|
|
|
|
// a collaborative closure
|
|
|
|
model::cfd::CfdState::Open { |
|
|
|
collaborative_close: Some(_), |
|
|
|
.. |
|
|
|
} => CfdState::PendingClose, |
|
|
|
model::cfd::CfdState::OutgoingOrderRequest { .. } => CfdState::OutgoingOrderRequest, |
|
|
|
model::cfd::CfdState::IncomingOrderRequest { .. } => CfdState::IncomingOrderRequest, |
|
|
|
model::cfd::CfdState::Accepted { .. } => CfdState::Accepted, |
|
|
|
model::cfd::CfdState::Rejected { .. } => CfdState::Rejected, |
|
|
|
model::cfd::CfdState::ContractSetup { .. } => CfdState::ContractSetup, |
|
|
|
model::cfd::CfdState::PendingOpen { .. } => CfdState::PendingOpen, |
|
|
|
model::cfd::CfdState::Open { .. } => CfdState::Open, |
|
|
|
model::cfd::CfdState::OpenCommitted { .. } => CfdState::OpenCommitted, |
|
|
|
model::cfd::CfdState::PendingRefund { .. } => CfdState::PendingRefund, |
|
|
|
model::cfd::CfdState::Refunded { .. } => CfdState::Refunded, |
|
|
|
model::cfd::CfdState::SetupFailed { .. } => CfdState::SetupFailed, |
|
|
|
model::cfd::CfdState::PendingCommit { .. } => CfdState::PendingCommit, |
|
|
|
model::cfd::CfdState::PendingCet { .. } => CfdState::PendingCet, |
|
|
|
model::cfd::CfdState::Closed { .. } => CfdState::Closed, |
|
|
|
}, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
impl ToSseEvent for Quote { |
|
|
|
fn to_sse_event(&self) -> Event { |
|
|
|
Event::json(self).event("quote") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn available_actions(state: CfdState, role: Role) -> Vec<CfdAction> { |
|
|
|
match (state, role) { |
|
|
|
(CfdState::IncomingOrderRequest { .. }, Role::Maker) => { |
|
|
|
vec![CfdAction::AcceptOrder, CfdAction::RejectOrder] |
|
|
|
} |
|
|
|
(CfdState::IncomingSettlementProposal { .. }, Role::Maker) => { |
|
|
|
vec![CfdAction::AcceptSettlement, CfdAction::RejectSettlement] |
|
|
|
} |
|
|
|
(CfdState::IncomingRollOverProposal { .. }, Role::Maker) => { |
|
|
|
vec![CfdAction::AcceptRollOver, CfdAction::RejectRollOver] |
|
|
|
} |
|
|
|
// If there is an outgoing settlement proposal already, user can't
|
|
|
|
// initiate new one
|
|
|
|
(CfdState::OutgoingSettlementProposal { .. }, Role::Maker) => { |
|
|
|
vec![CfdAction::Commit] |
|
|
|
} |
|
|
|
// User is awaiting collaborative close, commit is left as a safeguard
|
|
|
|
(CfdState::PendingClose { .. }, _) => { |
|
|
|
vec![CfdAction::Commit] |
|
|
|
} |
|
|
|
(CfdState::Open { .. }, Role::Taker) => { |
|
|
|
vec![CfdAction::RollOver, CfdAction::Commit, CfdAction::Settle] |
|
|
|
} |
|
|
|
(CfdState::Open { .. }, Role::Maker) => vec![CfdAction::Commit], |
|
|
|
_ => vec![], |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
mod tests { |
|
|
|
use super::*; |
|
|
|