Skip to content

Commit

Permalink
chore: Rework maintenance margin for liquidation calculation
Browse files Browse the repository at this point in the history
Introduces the maintenance margin in replacement of the incorrect implementation of a margin call.

Also introduces the bankruptcy liquidation price, which is a liquidation price with a maintenance margin of 0%.
  • Loading branch information
holzeis committed Apr 5, 2024
1 parent 82f894e commit cc80eb8
Show file tree
Hide file tree
Showing 22 changed files with 299 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ whitelist_enabled = false
# Default testnet maker
whitelisted_makers = ["035eccdd1f05c65b433cf38e3b2597e33715e0392cb14d183e812f1319eb7b6794"]
min_quantity = 1
margin_call_percentage = 0.1
maintenance_margin = 0.1

[ln_dlc]
off_chain_sync_interval = 5
Expand Down
2 changes: 1 addition & 1 deletion coordinator/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub struct NodeSettings {
// At times, we want to disallow opening new positions (e.g. before
// scheduled upgrade)
pub allow_opening_positions: bool,
pub margin_call_percentage: f32,
pub maintenance_margin: f32,
}

#[derive(Clone)]
Expand Down
261 changes: 148 additions & 113 deletions coordinator/src/node/liquidated_positions.rs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions coordinator/src/orderbook/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ pub async fn websocket_connection(stream: WebSocket, state: Arc<AppState>) {
let liquidity_options =
db::liquidity_options::get_all(&mut conn).unwrap_or_default();

let (min_quantity, margin_call_percentage) = {
let (min_quantity, maintenance_margin) = {
let settings = state.settings.read().await;
(settings.min_quantity, settings.margin_call_percentage)
(settings.min_quantity, settings.maintenance_margin)
};

if let Err(e) = local_sender
.send(Message::Authenticated(TenTenOneConfig {
liquidity_options,
min_quantity,
margin_call_percentage,
maintenance_margin,
}))
.await
{
Expand Down
15 changes: 8 additions & 7 deletions coordinator/src/payout_curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use payout_curve::ROUNDING_PERCENT;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;
use tracing::instrument;
use trade::cfd::calculate_long_liquidation_price;
use trade::cfd::calculate_short_liquidation_price;
use trade::cfd::calculate_long_bankruptcy_price;
use trade::cfd::calculate_short_bankruptcy_price;
use trade::cfd::BTCUSD_MAX_PRICE;
use trade::ContractSymbol;
use trade::Direction;
Expand Down Expand Up @@ -168,7 +168,8 @@ fn build_inverse_payout_function(
Ok((payout_function, rounding_intervals))
}

/// Returns the liquidation price for `(coordinator, maker)`
/// Returns the liquidation price for `(coordinator, maker)` with a maintenance margin of 0%. also
/// known as the bankruptcy price.
fn get_liquidation_prices(
initial_price: Decimal,
coordinator_direction: Direction,
Expand All @@ -177,12 +178,12 @@ fn get_liquidation_prices(
) -> (Decimal, Decimal) {
let (coordinator_liquidation_price, trader_liquidation_price) = match coordinator_direction {
Direction::Long => (
calculate_long_liquidation_price(leverage_coordinator, initial_price),
calculate_short_liquidation_price(leverage_trader, initial_price),
calculate_long_bankruptcy_price(leverage_coordinator, initial_price),
calculate_short_bankruptcy_price(leverage_trader, initial_price),
),
Direction::Short => (
calculate_short_liquidation_price(leverage_coordinator, initial_price),
calculate_long_liquidation_price(leverage_trader, initial_price),
calculate_short_bankruptcy_price(leverage_coordinator, initial_price),
calculate_long_bankruptcy_price(leverage_trader, initial_price),
),
};
(coordinator_liquidation_price, trader_liquidation_price)
Expand Down
15 changes: 9 additions & 6 deletions coordinator/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,12 @@ pub struct Settings {
/// A list of makers who are allowed to post limit orders. This is to prevent spam.
pub whitelisted_makers: Vec<PublicKey>,

/// The min quantity that we accept to be traded with.
pub min_quantity: u64,

pub margin_call_percentage: f32,
/// The maintenance margin in percent, defining the required margin in the position. If the
/// margin drops below that the position gets liquidated.
pub maintenance_margin: f32,
}

impl Settings {
Expand Down Expand Up @@ -102,7 +105,7 @@ impl Settings {
pub fn to_node_settings(&self) -> NodeSettings {
NodeSettings {
allow_opening_positions: self.new_positions_enabled,
margin_call_percentage: self.margin_call_percentage,
maintenance_margin: self.maintenance_margin,
}
}

Expand All @@ -123,7 +126,7 @@ impl Settings {
whitelist_enabled: file.whitelist_enabled,
whitelisted_makers: file.whitelisted_makers,
min_quantity: file.min_quantity,
margin_call_percentage: file.margin_call_percentage,
maintenance_margin: file.maintenance_margin,
}
}
}
Expand All @@ -146,7 +149,7 @@ pub struct SettingsFile {
whitelisted_makers: Vec<PublicKey>,

min_quantity: u64,
margin_call_percentage: f32,
maintenance_margin: f32,
}

impl From<Settings> for SettingsFile {
Expand All @@ -162,7 +165,7 @@ impl From<Settings> for SettingsFile {
whitelist_enabled: false,
whitelisted_makers: value.whitelisted_makers,
min_quantity: value.min_quantity,
margin_call_percentage: value.margin_call_percentage,
maintenance_margin: value.maintenance_margin,
}
}
}
Expand Down Expand Up @@ -194,7 +197,7 @@ mod tests {
)
.unwrap()],
min_quantity: 1,
margin_call_percentage: 0.1,
maintenance_margin: 0.1,
};

let serialized = toml::to_string_pretty(&original).unwrap();
Expand Down
65 changes: 22 additions & 43 deletions coordinator/src/trade/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,36 +529,24 @@ impl TradeExecutor {
coordinator_leverage: f32,
stable: bool,
) -> Result<()> {
let trader_liquidation_price = trader_liquidation_price(trade_params);

let price = trade_params.average_execution_price();
let coordinator_liquidation_price = coordinator_liquidation_price(
let maintenance_margin = { self.node.settings.read().await.maintenance_margin };
let maintenance_margin =
Decimal::try_from(maintenance_margin).expect("to fit into decimal");

let trader_liquidation_price = liquidation_price(
price,
Decimal::try_from(coordinator_leverage).expect("to fit into decimal"),
trade_params.direction.opposite(),
trade_params.direction,
maintenance_margin,
);
let coordinator_liquidation_price =
Decimal::try_from(coordinator_liquidation_price).expect("to fit into decimal");
let trader_liquidation_price =
Decimal::try_from(trader_liquidation_price).expect("to fit into decimal");

let margin_call_percentage = { self.node.settings.read().await.margin_call_percentage };
let margin_call_percentage =
Decimal::try_from(margin_call_percentage).expect("to fit into decimal");

let (coordinator_liquidation_price, trader_liquidation_price) = match trade_params.direction
{
Direction::Short => (
coordinator_liquidation_price
+ coordinator_liquidation_price * margin_call_percentage,
trader_liquidation_price - trader_liquidation_price * margin_call_percentage,
),
Direction::Long => (
coordinator_liquidation_price
- coordinator_liquidation_price * margin_call_percentage,
trader_liquidation_price + trader_liquidation_price * margin_call_percentage,
),
};
let coordinator_liquidation_price = liquidation_price(
price,
Decimal::try_from(coordinator_leverage).expect("to fit into decimal"),
trade_params.direction.opposite(),
maintenance_margin,
);

let margin_coordinator = margin_coordinator(trade_params, coordinator_leverage);
let margin_trader = margin_trader(trade_params);
Expand Down Expand Up @@ -811,29 +799,20 @@ fn margin_coordinator(trade_params: &TradeParams, coordinator_leverage: f32) ->
)
}

fn trader_liquidation_price(trade_params: &TradeParams) -> f32 {
let price = trade_params.average_execution_price();
let leverage = Decimal::try_from(trade_params.leverage).expect("to fit into decimal");

match trade_params.direction {
Direction::Long => calculate_long_liquidation_price(leverage, price),
Direction::Short => calculate_short_liquidation_price(leverage, price),
}
.to_f32()
.expect("to fit into f32")
}

fn coordinator_liquidation_price(
fn liquidation_price(
price: Decimal,
coordinator_leverage: Decimal,
direction: Direction,
) -> f32 {
maintenance_margin: Decimal,
) -> Decimal {
match direction {
Direction::Long => calculate_long_liquidation_price(coordinator_leverage, price),
Direction::Short => calculate_short_liquidation_price(coordinator_leverage, price),
Direction::Long => {
calculate_long_liquidation_price(coordinator_leverage, price, maintenance_margin)
}
Direction::Short => {
calculate_short_liquidation_price(coordinator_leverage, price, maintenance_margin)
}
}
.to_f32()
.expect("to fit into f32")
}

pub fn coordinator_leverage_for_trade(_counterparty_peer_id: &PublicKey) -> Result<f32> {
Expand Down
2 changes: 1 addition & 1 deletion crates/commons/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub struct TenTenOneConfig {
// The liquidity options for onboarding
pub liquidity_options: Vec<LiquidityOption>,
pub min_quantity: u64,
pub margin_call_percentage: f32,
pub maintenance_margin: f32,
}

#[derive(Serialize, Clone, Deserialize, Debug)]
Expand Down
16 changes: 8 additions & 8 deletions crates/payout_curve/examples/payout_curve_csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::fs::File;
use std::ops::Mul;
use trade::cfd::calculate_long_liquidation_price;
use trade::cfd::calculate_long_bankruptcy_price;
use trade::cfd::calculate_margin;
use trade::cfd::calculate_pnl;
use trade::cfd::calculate_short_liquidation_price;
use trade::cfd::calculate_short_bankruptcy_price;
use trade::Direction;

/// The example below will export the computed payout curve and how it should look like as CSV.
Expand All @@ -35,12 +35,12 @@ fn main() -> Result<()> {
let leverage_long = 2.0;

let price_params = {
let short_liquidation_price = calculate_short_liquidation_price(
let short_liquidation_price = calculate_short_bankruptcy_price(
Decimal::from_f32(leverage_short).expect("to be able to parse f32"),
initial_price,
);

let long_liquidation_price = calculate_long_liquidation_price(
let long_liquidation_price = calculate_long_bankruptcy_price(
Decimal::from_f32(leverage_long).expect("to be able to parse f32"),
initial_price,
);
Expand Down Expand Up @@ -289,8 +289,8 @@ pub fn should_payouts_as_csv_short(
let file = File::create(csv_path)?;
let mut wtr = csv::WriterBuilder::new().delimiter(b';').from_writer(file);

let long_liquidation_price = calculate_long_liquidation_price(leverage_long, initial_price);
let short_liquidation_price = calculate_short_liquidation_price(leverage_short, initial_price);
let long_liquidation_price = calculate_long_bankruptcy_price(leverage_long, initial_price);
let short_liquidation_price = calculate_short_bankruptcy_price(leverage_short, initial_price);

wtr.write_record(["price", "payout_offer", "trader"])?;
wtr.write_record(&[0.to_string(), total_collateral.to_string(), 0.to_string()])?;
Expand Down Expand Up @@ -394,8 +394,8 @@ pub fn should_payouts_as_csv_long(
let file = File::create(csv_path)?;
let mut wtr = csv::WriterBuilder::new().delimiter(b';').from_writer(file);

let long_liquidation_price = calculate_long_liquidation_price(leverage_long, initial_price);
let short_liquidation_price = calculate_short_liquidation_price(leverage_short, initial_price);
let long_liquidation_price = calculate_long_bankruptcy_price(leverage_long, initial_price);
let short_liquidation_price = calculate_short_bankruptcy_price(leverage_short, initial_price);

wtr.write_record(["price", "payout_offer", "trader"])?;
wtr.write_record(&[
Expand Down
16 changes: 8 additions & 8 deletions crates/payout_curve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,9 @@ mod tests {
use rust_decimal_macros::dec;
use std::fs::File;
use std::ops::Mul;
use trade::cfd::calculate_long_liquidation_price;
use trade::cfd::calculate_long_bankruptcy_price;
use trade::cfd::calculate_margin;
use trade::cfd::calculate_short_liquidation_price;
use trade::cfd::calculate_short_bankruptcy_price;

/// set this to true to export test data to csv files
/// An example gnuplot file has been provided in [`payout_curve.gp`]
Expand Down Expand Up @@ -542,11 +542,11 @@ mod tests {
Amount::from_sat(calculate_margin(initial_price, quantity, short_leverage));
let collateral_reserve_offer = Amount::from_sat(300_000);

let long_liquidation_price = calculate_long_liquidation_price(
let long_liquidation_price = calculate_long_bankruptcy_price(
Decimal::from_f32(long_leverage).expect("to fit into f32"),
initial_price,
);
let short_liquidation_price = calculate_short_liquidation_price(
let short_liquidation_price = calculate_short_bankruptcy_price(
Decimal::from_f32(short_leverage).expect("to fit into f32"),
initial_price,
);
Expand Down Expand Up @@ -681,11 +681,11 @@ mod tests {

let collateral_reserve_offer = Amount::from_sat(155);

let long_liquidation_price = calculate_long_liquidation_price(
let long_liquidation_price = calculate_long_bankruptcy_price(
Decimal::from_f32(long_leverage).expect("to fit into f32"),
initial_price,
);
let short_liquidation_price = calculate_short_liquidation_price(
let short_liquidation_price = calculate_short_bankruptcy_price(
Decimal::from_f32(short_leverage).expect("to fit into f32"),
initial_price,
);
Expand Down Expand Up @@ -986,11 +986,11 @@ mod tests {
Amount::from_sat(collateral_reserve)
};

let long_liquidation_price = calculate_long_liquidation_price(
let long_liquidation_price = calculate_long_bankruptcy_price(
Decimal::from_f32(long_leverage).expect("to fit into f32"),
initial_price,
);
let short_liquidation_price = calculate_short_liquidation_price(
let short_liquidation_price = calculate_short_bankruptcy_price(
Decimal::from_f32(short_leverage).expect("to fit into f32"),
initial_price,
);
Expand Down
Loading

0 comments on commit cc80eb8

Please sign in to comment.