Skip to content

Commit

Permalink
feat(coordinator): Apply funding fee events to consider liquidation
Browse files Browse the repository at this point in the history
  • Loading branch information
luckysori committed May 29, 2024
1 parent 3ce7e2f commit e39b523
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 157 deletions.
4 changes: 0 additions & 4 deletions coordinator/src/db/funding_fee_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,6 @@ pub(crate) fn get_for_active_trader_positions(
}

/// Get the unpaid [`funding_fee::FundingFeeEvent`]s for a trader position.
///
/// TODO: Use outstanding fees when:
///
/// - Deciding if positions need to be liquidated.
pub(crate) fn get_outstanding_fees(
conn: &mut PgConnection,
trader_pubkey: PublicKey,
Expand Down
14 changes: 14 additions & 0 deletions coordinator/src/funding_fee.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::db;
use crate::decimal_from_f32;
use crate::message::OrderbookMessage;
use crate::FundingFee;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use bitcoin::secp256k1::PublicKey;
use bitcoin::Amount;
use bitcoin::SignedAmount;
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
Expand Down Expand Up @@ -289,6 +291,18 @@ struct Index {
_reference: String,
}

pub fn funding_fee_from_funding_fee_events(events: &[FundingFeeEvent]) -> FundingFee {
let funding_fee_amount = events
.iter()
.fold(SignedAmount::ZERO, |acc, e| acc + e.amount);

match funding_fee_amount.to_sat() {
0 => FundingFee::Zero,
n if n.is_positive() => FundingFee::TraderPays(Amount::from_sat(n.unsigned_abs())),
n => FundingFee::CoordinatorPays(Amount::from_sat(n.unsigned_abs())),
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
49 changes: 49 additions & 0 deletions coordinator/src/node/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::dlc_protocol;
use crate::dlc_protocol::DlcProtocolType;
use crate::node::Node;
use crate::position::models::PositionState;
use crate::FundingFee;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
Expand Down Expand Up @@ -291,6 +292,54 @@ impl Node {
Ok(())
}

pub fn apply_funding_fee_to_channel(
&self,
dlc_channel_id: DlcChannelId,
funding_fee: FundingFee,
) -> Result<(Amount, Amount)> {
let collateral_reserve_coordinator =
self.inner.get_dlc_channel_usable_balance(&dlc_channel_id)?;
let collateral_reserve_trader = self
.inner
.get_dlc_channel_usable_balance_counterparty(&dlc_channel_id)?;

let reserves = match funding_fee {
FundingFee::Zero => (collateral_reserve_coordinator, collateral_reserve_trader),
FundingFee::CoordinatorPays(funding_fee) => {
let funding_fee = funding_fee.to_signed().expect("to fit");

let collateral_reserve_trader =
collateral_reserve_trader.to_signed().expect("to fit");
let new_collateral_reserve_trader = collateral_reserve_trader + funding_fee;
let new_collateral_reserve_trader =
new_collateral_reserve_trader.to_unsigned().expect("to fit");

(
collateral_reserve_coordinator,
new_collateral_reserve_trader,
)
}
FundingFee::TraderPays(funding_fee) => {
let funding_fee = funding_fee.to_signed().expect("to fit");

let collateral_reserve_coordinator =
collateral_reserve_coordinator.to_signed().expect("to fit");
let new_collateral_reserve_coordinator =
collateral_reserve_coordinator + funding_fee;
let new_collateral_reserve_coordinator = new_collateral_reserve_coordinator
.to_unsigned()
.expect("to fit");

(
new_collateral_reserve_coordinator,
collateral_reserve_trader,
)
}
};

Ok(reserves)
}

fn handle_closing_event(&self, conn: &mut PgConnection, channel: &Channel) -> Result<()> {
// If a channel is set to closing it means the buffer transaction got broadcasted,
// which will only happen if the channel got force closed while the
Expand Down
15 changes: 13 additions & 2 deletions coordinator/src/node/liquidated_positions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::db;
use crate::funding_fee::funding_fee_from_funding_fee_events;
use crate::node::Node;
use crate::orderbook;
use crate::orderbook::db::orders;
Expand Down Expand Up @@ -43,9 +44,18 @@ async fn check_if_positions_need_to_get_liquidated(
let best_current_price =
orderbook::db::orders::get_best_price(&mut conn, ContractSymbol::BtcUsd)?;

let maintenance_margin_rate =
{ Decimal::try_from(node.settings.read().await.maintenance_margin_rate).expect("to fit") };

for position in open_positions {
// TODO: These liquidation prices do not consider the outstanding funding fee events, so
// they are not quite right for the party that owes the fees.
// Update position based on the outstanding funding fee events _before_ considering
// liquidation.
let funding_fee_events =
db::funding_fee_events::get_outstanding_fees(&mut conn, position.trader, position.id)?;

let funding_fee = funding_fee_from_funding_fee_events(&funding_fee_events);

let position = position.apply_funding_fee(funding_fee, maintenance_margin_rate);

let coordinator_liquidation_price =
Decimal::try_from(position.coordinator_liquidation_price).expect("to fit into decimal");
Expand All @@ -57,6 +67,7 @@ async fn check_if_positions_need_to_get_liquidated(
&best_current_price,
trader_liquidation_price,
);

let coordinator_liquidation = check_if_position_needs_to_get_liquidated(
position.trader_direction.opposite(),
&best_current_price,
Expand Down
40 changes: 10 additions & 30 deletions coordinator/src/node/rollover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@ use crate::db::positions;
use crate::decimal_from_f32;
use crate::dlc_protocol;
use crate::dlc_protocol::RolloverParams;
use crate::funding_fee::funding_fee_from_funding_fee_events;
use crate::node::Node;
use crate::notifications::Notification;
use crate::notifications::NotificationKind;
use crate::payout_curve::build_contract_descriptor;
use crate::position::models::Position;
use crate::position::models::PositionState;
use crate::FundingFee;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use bitcoin::secp256k1::PublicKey;
use bitcoin::Amount;
use bitcoin::Network;
use bitcoin::SignedAmount;
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
use diesel::r2d2::PooledConnection;
Expand Down Expand Up @@ -183,12 +181,6 @@ impl Node {

let next_expiry = commons::calculate_next_expiry(OffsetDateTime::now_utc(), network);

let collateral_reserve_coordinator =
self.inner.get_dlc_channel_usable_balance(dlc_channel_id)?;
let collateral_reserve_trader = self
.inner
.get_dlc_channel_usable_balance_counterparty(dlc_channel_id)?;

let (oracle_pk, contract_tx_fee_rate) = {
let old_contract = self.inner.get_contract_by_dlc_channel_id(dlc_channel_id)?;

Expand Down Expand Up @@ -227,28 +219,11 @@ impl Node {
let funding_fee_events =
db::funding_fee_events::get_outstanding_fees(conn, trader_pubkey, position.id)?;

let funding_fee_amount = funding_fee_events
.iter()
.fold(SignedAmount::ZERO, |acc, e| acc + e.amount);

let funding_fee_event_ids = funding_fee_events
.iter()
.map(|event| event.id)
.collect::<Vec<_>>();

let funding_fee = match funding_fee_amount.to_sat() {
0 => FundingFee::Zero,
n if n.is_positive() => FundingFee::TraderPays(Amount::from_sat(n.unsigned_abs())),
n => FundingFee::CoordinatorPays(Amount::from_sat(n.unsigned_abs())),
};
let funding_fee = funding_fee_from_funding_fee_events(&funding_fee_events);

let (position, collateral_reserve_coordinator, collateral_reserve_trader) = position
.apply_funding_fee_to_position(
collateral_reserve_coordinator,
collateral_reserve_trader,
funding_fee,
maintenance_margin_rate,
);
let position = position.apply_funding_fee(funding_fee, maintenance_margin_rate);
let (collateral_reserve_coordinator, collateral_reserve_trader) =
self.apply_funding_fee_to_channel(*dlc_channel_id, funding_fee)?;

let Position {
coordinator_margin: margin_coordinator,
Expand Down Expand Up @@ -309,6 +284,11 @@ impl Node {
None => None,
};

let funding_fee_event_ids = funding_fee_events
.iter()
.map(|event| event.id)
.collect::<Vec<_>>();

let funding_fee_events = funding_fee_events
.into_iter()
.map(xxi_node::message_handler::FundingFeeEvent::from)
Expand Down
57 changes: 13 additions & 44 deletions coordinator/src/position/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,38 +203,28 @@ impl Position {
)
}

pub fn apply_funding_fee_to_position(
#[must_use]
pub fn apply_funding_fee(
self,
collateral_reserve_coordinator: Amount,
collateral_reserve_trader: Amount,
funding_fee: FundingFee,
maintenance_margin_rate: Decimal,
) -> (Self, Amount, Amount) {
) -> Self {
let quantity = decimal_from_f32(self.quantity);
let average_entry_price = decimal_from_f32(self.average_entry_price);

match funding_fee {
FundingFee::Zero => (
self,
collateral_reserve_coordinator,
collateral_reserve_trader,
),
FundingFee::Zero => self,
FundingFee::CoordinatorPays(funding_fee) => {
let funding_fee = funding_fee.to_signed().expect("to fit");

let coordinator_margin = self.coordinator_margin.to_signed().expect("to fit");
let new_coordinator_margin = coordinator_margin - funding_fee;
let new_coordinator_margin = new_coordinator_margin.to_unsigned().expect("to fit");

let collateral_reserve_trader =
collateral_reserve_trader.to_signed().expect("to fit");
let new_collateral_reserve_trader = collateral_reserve_trader + funding_fee;
let new_collateral_reserve_trader =
new_collateral_reserve_trader.to_unsigned().expect("to fit");
let new_coordinator_margin =
new_coordinator_margin.to_unsigned().unwrap_or(Amount::ZERO);

let new_coordinator_leverage =
calculate_leverage(quantity, new_coordinator_margin, average_entry_price)
.expect("valid leverage");
calculate_leverage(quantity, new_coordinator_margin, average_entry_price);

let new_coordinator_liquidation_price = match self.trader_direction {
Direction::Long => calculate_short_liquidation_price(
Expand All @@ -249,39 +239,24 @@ impl Position {
),
};

let position = Self {
Self {
coordinator_margin: new_coordinator_margin,
coordinator_leverage: f32_from_decimal(new_coordinator_leverage),
coordinator_liquidation_price: f32_from_decimal(
new_coordinator_liquidation_price,
),
..self
};

(
position,
collateral_reserve_coordinator,
new_collateral_reserve_trader,
)
}
}
FundingFee::TraderPays(funding_fee) => {
let funding_fee = funding_fee.to_signed().expect("to fit");

let margin_trader = self.trader_margin.to_signed().expect("to fit");
let new_trader_margin = margin_trader - funding_fee;
let new_trader_margin = new_trader_margin.to_unsigned().expect("to fit");

let collateral_reserve_coordinator =
collateral_reserve_coordinator.to_signed().expect("to fit");
let new_collateral_reserve_coordinator =
collateral_reserve_coordinator + funding_fee;
let new_collateral_reserve_coordinator = new_collateral_reserve_coordinator
.to_unsigned()
.expect("to fit");
let new_trader_margin = new_trader_margin.to_unsigned().unwrap_or(Amount::ZERO);

let new_trader_leverage =
calculate_leverage(quantity, new_trader_margin, average_entry_price)
.expect("valid leverage");
calculate_leverage(quantity, new_trader_margin, average_entry_price);

let new_trader_liquidation_price = match self.trader_direction {
Direction::Long => calculate_long_liquidation_price(
Expand All @@ -296,18 +271,12 @@ impl Position {
),
};

let position = Self {
Self {
trader_margin: new_trader_margin,
trader_leverage: f32_from_decimal(new_trader_leverage),
trader_liquidation_price: f32_from_decimal(new_trader_liquidation_price),
..self
};

(
position,
new_collateral_reserve_coordinator,
collateral_reserve_trader,
)
}
}
}
}
Expand Down
Loading

0 comments on commit e39b523

Please sign in to comment.