@ -1,4 +1,4 @@
use crate ::model ::cfd ::{ OrderId , Role } ;
use crate ::model ::cfd ::{ OrderId , Role , SettlementProposals } ;
use crate ::model ::{ Leverage , Position , TradingPair , Usd } ;
use crate ::{ bitmex_price_feed , model } ;
use bdk ::bitcoin ::{ Amount , SignedAmount } ;
@ -7,6 +7,7 @@ use rocket::response::stream::Event;
use rust_decimal ::Decimal ;
use serde ::{ Deserialize , Serialize } ;
use std ::time ::{ SystemTime , UNIX_EPOCH } ;
use tokio ::sync ::watch ;
#[ derive(Debug, Clone, Serialize) ]
pub struct Cfd {
@ -33,12 +34,14 @@ pub struct Cfd {
}
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq) ]
#[ serde(rename_all = " lower case" ) ]
#[ serde(rename_all = " camelC ase " ) ]
pub enum CfdAction {
Accept ,
Reject ,
AcceptOrder ,
RejectOrder ,
Commit ,
Settle ,
AcceptSettlement ,
RejectSettlement ,
}
impl < 'v > FromParam < 'v > for CfdAction {
@ -61,6 +64,8 @@ pub enum CfdState {
Open ,
PendingCommit ,
OpenCommitted ,
IncomingSettlementProposal ,
OutgoingSettlementProposal ,
MustRefund ,
Refunded ,
SetupFailed ,
@ -89,13 +94,76 @@ pub trait ToSseEvent {
fn to_sse_event ( & self ) -> Event ;
}
/// Intermediate struct to able to piggy-back current price along with cfds
pub struct CfdsWithCurrentPrice {
/// Intermediate struct to able to piggy-back additional information along with
/// cfds, so we can avoid a 1:1 mapping between the states in the model and seen
/// by UI
pub struct CfdsWithAuxData {
pub cfds : Vec < model ::cfd ::Cfd > ,
pub current_price : Usd ,
pub settlement_proposals : SettlementProposals ,
}
impl ToSseEvent for CfdsWithCurrentPrice {
enum SettlementProposalStatus {
Incoming ,
Outgoing ,
None ,
}
impl CfdsWithAuxData {
pub fn new (
rx_cfds : & watch ::Receiver < Vec < model ::cfd ::Cfd > > ,
rx_quote : & watch ::Receiver < bitmex_price_feed ::Quote > ,
rx_settlement : & watch ::Receiver < SettlementProposals > ,
role : Role ,
) -> Self {
let quote = rx_quote . borrow ( ) . clone ( ) ;
let current_price = match role {
Role ::Maker = > quote . for_maker ( ) ,
Role ::Taker = > quote . for_taker ( ) ,
} ;
let settlement_proposals = rx_settlement . borrow ( ) . clone ( ) ;
// Test whether the correct settlement proposals were sent
match settlement_proposals {
SettlementProposals ::Incoming ( _ ) = > {
if role = = Role ::Taker {
panic ! ( "Taker should never receive incoming settlement proposals" ) ;
}
}
SettlementProposals ::Outgoing ( _ ) = > {
if role = = Role ::Maker {
panic ! ( "Maker should never receive outgoing settlement proposals" ) ;
}
}
}
CfdsWithAuxData {
cfds : rx_cfds . borrow ( ) . clone ( ) ,
current_price ,
settlement_proposals ,
}
}
/// Check whether given CFD has any active settlement proposals
fn settlement_proposal_status ( & self , cfd : & model ::cfd ::Cfd ) -> SettlementProposalStatus {
match & self . settlement_proposals {
SettlementProposals ::Incoming ( proposals ) = > {
if proposals . contains_key ( & cfd . order . id ) {
return SettlementProposalStatus ::Incoming ;
}
}
SettlementProposals ::Outgoing ( proposals ) = > {
if proposals . contains_key ( & cfd . order . id ) {
return SettlementProposalStatus ::Outgoing ;
}
}
}
SettlementProposalStatus ::None
}
}
impl ToSseEvent for CfdsWithAuxData {
// TODO: This conversion can fail, we might want to change the API
fn to_sse_event ( & self ) -> Event {
let current_price = self . current_price ;
@ -113,6 +181,8 @@ impl ToSseEvent for CfdsWithCurrentPrice {
( SignedAmount ::ZERO , Decimal ::ZERO . into ( ) )
} ) ;
let state = to_cfd_state ( & cfd . state , self . settlement_proposal_status ( cfd ) ) ;
Cfd {
order_id : cfd . order . id ,
initial_price : cfd . order . price ,
@ -123,8 +193,8 @@ impl ToSseEvent for CfdsWithCurrentPrice {
quantity_usd : cfd . quantity_usd ,
profit_btc ,
profit_in_percent : profit_in_percent . to_string ( ) ,
state : cfd . state . clone ( ) . into ( ) ,
actions : actions_for_state ( cfd . state . clone ( ) , cfd . role ( ) ) ,
state : state . clone ( ) ,
actions : available_a ctions ( state , cfd . role ( ) ) ,
state_transition_timestamp : cfd
. state
. get_transition_timestamp ( )
@ -186,9 +256,14 @@ impl ToSseEvent for model::WalletInfo {
}
}
impl From < model ::cfd ::CfdState > for CfdState {
fn from ( cfd_state : model ::cfd ::CfdState ) -> Self {
match cfd_state {
fn to_cfd_state (
cfd_state : & model ::cfd ::CfdState ,
proposal_status : SettlementProposalStatus ,
) -> CfdState {
match proposal_status {
SettlementProposalStatus ::Incoming = > CfdState ::IncomingSettlementProposal ,
SettlementProposalStatus ::Outgoing = > CfdState ::OutgoingSettlementProposal ,
SettlementProposalStatus ::None = > match cfd_state {
model ::cfd ::CfdState ::OutgoingOrderRequest { . . } = > CfdState ::OutgoingOrderRequest ,
model ::cfd ::CfdState ::IncomingOrderRequest { . . } = > CfdState ::IncomingOrderRequest ,
model ::cfd ::CfdState ::Accepted { . . } = > CfdState ::Accepted ,
@ -201,7 +276,7 @@ impl From<model::cfd::CfdState> for CfdState {
model ::cfd ::CfdState ::Refunded { . . } = > CfdState ::Refunded ,
model ::cfd ::CfdState ::SetupFailed { . . } = > CfdState ::SetupFailed ,
model ::cfd ::CfdState ::PendingCommit { . . } = > CfdState ::PendingCommit ,
}
} ,
}
}
@ -230,15 +305,23 @@ fn into_unix_secs(time: SystemTime) -> u64 {
. as_secs ( )
}
fn actions_for_state ( state : model ::cfd ::CfdState , role : Role ) -> Vec < CfdAction > {
fn available_a ctions ( state : CfdState , role : Role ) -> Vec < CfdAction > {
match ( state , role ) {
( model ::cfd ::CfdState ::IncomingOrderRequest { . . } , Role ::Maker ) = > {
vec ! [ CfdAction ::Accept , CfdAction ::Reject ]
( CfdState ::IncomingOrderRequest { . . } , Role ::Maker ) = > {
vec ! [ CfdAction ::AcceptOrder , CfdAction ::RejectOrder ]
}
( CfdState ::IncomingSettlementProposal { . . } , Role ::Maker ) = > {
vec ! [ CfdAction ::AcceptSettlement , CfdAction ::RejectSettlement ]
}
// If there is an outgoing settlement proposal already, user can't
// initiate new one
( CfdState ::OutgoingSettlementProposal { . . } , Role ::Maker ) = > {
vec ! [ CfdAction ::Commit ]
}
( model ::cfd ::CfdState ::Open { . . } , Role ::Taker ) = > {
( CfdState ::Open { . . } , Role ::Taker ) = > {
vec ! [ CfdAction ::Commit , CfdAction ::Settle ]
}
( model ::cfd ::CfdState ::Open { . . } , Role ::Maker ) = > vec ! [ CfdAction ::Commit ] ,
( CfdState ::Open { . . } , Role ::Maker ) = > vec ! [ CfdAction ::Commit ] ,
_ = > vec ! [ ] ,
}
}