Skip to content

Commit

Permalink
Merge pull request #2352 from get10101/fix/issue-2288
Browse files Browse the repository at this point in the history
Generate own step payout function
  • Loading branch information
luckysori authored Apr 7, 2024
2 parents 86e9219 + 10e0cee commit 54a67c8
Show file tree
Hide file tree
Showing 9 changed files with 5,349 additions and 40,372 deletions.
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coordinator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ url = "2.3.1"
uuid = { version = "1.3.0", features = ["v4", "serde"] }

[dev-dependencies]
proptest = "1"
rust_decimal_macros = "1"
testcontainers = "0.14.0"
221 changes: 147 additions & 74 deletions coordinator/src/payout_curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ use dlc_manager::payout_curve::PayoutPoint;
use dlc_manager::payout_curve::PolynomialPayoutCurvePiece;
use dlc_manager::payout_curve::RoundingInterval;
use dlc_manager::payout_curve::RoundingIntervals;
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::BTCUSD_MAX_PRICE;
use trade::ContractSymbol;
use trade::Direction;

Expand Down Expand Up @@ -123,18 +121,6 @@ fn build_inverse_payout_function(
coordinator_direction,
)?;

// The payout curve generation code tends to shift the liquidation prices slightly.
let adjusted_long_liquidation_price = payout_points
.first()
.context("Empty payout points")?
.1
.event_outcome;
let adjusted_short_liquidation_price = payout_points
.last()
.context("Empty payout points")?
.0
.event_outcome;

let mut pieces = vec![];
for (lower, upper) in payout_points {
let lower_range = PolynomialPayoutCurvePiece::new(vec![
Expand All @@ -155,14 +141,12 @@ fn build_inverse_payout_function(
let payout_function =
PayoutFunction::new(pieces).context("could not create payout function")?;

let rounding_intervals = {
let total_margin = coordinator_margin + trader_margin;

create_rounding_intervals(
total_margin,
adjusted_long_liquidation_price,
adjusted_short_liquidation_price,
)
let rounding_intervals = RoundingIntervals {
intervals: vec![RoundingInterval {
begin_interval: 0,
// No rounding needed because we are giving `rust-dlc` a step function already.
rounding_mod: 1,
}],
};

Ok((payout_function, rounding_intervals))
Expand All @@ -188,58 +172,11 @@ fn get_liquidation_prices(
(coordinator_liquidation_price, trader_liquidation_price)
}

pub fn create_rounding_intervals(
total_margin: u64,
long_liquidation_price: u64,
short_liquidation_price: u64,
) -> RoundingIntervals {
let liquidation_diff = short_liquidation_price
.checked_sub(long_liquidation_price)
.expect("short liquidation to be higher than long liquidation");
let low_price = long_liquidation_price + liquidation_diff / 10;
let high_price = short_liquidation_price - liquidation_diff / 10;

let mut intervals = vec![
RoundingInterval {
begin_interval: 0,
// No rounding.
rounding_mod: 1,
},
// HACK: We decrease the rounding here to prevent `rust-dlc` from rounding under the long
// liquidation price _payout_.
RoundingInterval {
begin_interval: long_liquidation_price,
rounding_mod: (total_margin as f32 * ROUNDING_PERCENT * 0.1) as u64,
},
RoundingInterval {
begin_interval: low_price,
rounding_mod: (total_margin as f32 * ROUNDING_PERCENT) as u64,
},
];

if short_liquidation_price < BTCUSD_MAX_PRICE {
intervals.push(
// HACK: We decrease the rounding here to prevent `rust-dlc` from rounding over the
// short liquidation price _payout_.
RoundingInterval {
begin_interval: high_price,
rounding_mod: (total_margin as f32 * ROUNDING_PERCENT * 0.1) as u64,
},
);
intervals.push(RoundingInterval {
begin_interval: short_liquidation_price,
// No rounding.
rounding_mod: 1,
})
}

RoundingIntervals { intervals }
}

#[cfg(test)]
mod tests {
use super::*;
use commons::order_matching_fee_taker;
use proptest::prelude::*;
use rust_decimal_macros::dec;
use trade::cfd::calculate_margin;

Expand Down Expand Up @@ -279,9 +216,11 @@ mod tests {

let range_payouts = match descriptor {
ContractDescriptor::Enum(_) => unreachable!(),
ContractDescriptor::Numerical(numerical) => {
numerical.get_range_payouts(total_collateral).unwrap()
}
ContractDescriptor::Numerical(numerical) => numerical
.get_range_payouts(
total_collateral + coordinator_collateral_reserve + trader_collateral_reserve,
)
.unwrap(),
};

let max_price = 2usize.pow(20);
Expand All @@ -298,6 +237,140 @@ mod tests {
}
}

#[test]
fn payout_function_respects_collateral_reserve() {
let initial_price = dec!(28_251);
let quantity = 500.0;
let leverage_coordinator = 2.0;
let coordinator_margin = calculate_margin(initial_price, quantity, leverage_coordinator);

let leverage_trader = 2.0;
let trader_margin = calculate_margin(initial_price, quantity, leverage_trader);

let coordinator_direction = Direction::Short;

let coordinator_collateral_reserve = 2_120_386;
let trader_collateral_reserve = 5_115_076;

let total_collateral = coordinator_margin
+ trader_margin
+ coordinator_collateral_reserve
+ trader_collateral_reserve;

let symbol = ContractSymbol::BtcUsd;

let descriptor = build_contract_descriptor(
initial_price,
coordinator_margin,
trader_margin,
leverage_coordinator,
leverage_trader,
coordinator_direction,
coordinator_collateral_reserve,
trader_collateral_reserve,
quantity,
symbol,
)
.unwrap();

let range_payouts = match descriptor {
ContractDescriptor::Enum(_) => unreachable!(),
ContractDescriptor::Numerical(numerical) => {
numerical.get_range_payouts(total_collateral).unwrap()
}
};

let liquidation_payout_offer = range_payouts
.iter()
.min_by(|a, b| a.payout.offer.cmp(&b.payout.offer))
.unwrap()
.payout
.offer;

assert_eq!(liquidation_payout_offer, coordinator_collateral_reserve);

let liquidation_payout_accept = range_payouts
.iter()
.min_by(|a, b| a.payout.accept.cmp(&b.payout.accept))
.unwrap()
.payout
.accept;

assert_eq!(liquidation_payout_accept, trader_collateral_reserve);
}

proptest! {
#[test]
fn payout_function_always_respects_reserves(
quantity in 1.0f32..10_000.0,
initial_price in 20_000u32..80_000,
leverage_coordinator in 1u32..5,
leverage_trader in 1u32..5,
is_coordinator_long in proptest::bool::ANY,
collateral_reserve_coordinator in 0u64..1_000_000,
collateral_reserve_trader in 0u64..1_000_000,
) {
let initial_price = Decimal::from(initial_price);
let leverage_coordinator = leverage_coordinator as f32;
let leverage_trader = leverage_trader as f32;

let margin_coordinator = calculate_margin(initial_price, quantity, leverage_coordinator);
let margin_trader = calculate_margin(initial_price, quantity, leverage_trader);

let coordinator_direction = if is_coordinator_long {
Direction::Long
} else {
Direction::Short
};

let total_collateral = margin_coordinator
+ margin_trader
+ collateral_reserve_coordinator
+ collateral_reserve_trader;

let symbol = ContractSymbol::BtcUsd;

let descriptor = build_contract_descriptor(
initial_price,
margin_coordinator,
margin_trader,
leverage_coordinator,
leverage_trader,
coordinator_direction,
collateral_reserve_coordinator,
collateral_reserve_trader,
quantity,
symbol,
)
.unwrap();

let range_payouts = match descriptor {
ContractDescriptor::Enum(_) => unreachable!(),
ContractDescriptor::Numerical(numerical) => numerical
.get_range_payouts(total_collateral)
.unwrap(),
};

let liquidation_payout_offer = range_payouts
.iter()
.min_by(|a, b| a.payout.offer.cmp(&b.payout.offer))
.unwrap()
.payout
.offer;

assert_eq!(liquidation_payout_offer, collateral_reserve_coordinator);

let liquidation_payout_accept = range_payouts
.iter()
.min_by(|a, b| a.payout.accept.cmp(&b.payout.accept))
.unwrap()
.payout
.accept;

assert_eq!(liquidation_payout_accept, collateral_reserve_trader);
}
}

#[test]
fn calculate_liquidation_price_coordinator_long() {
let initial_price = dec!(30_000);
Expand Down Expand Up @@ -335,7 +408,7 @@ mod tests {
}

#[test]
fn build_contract_descriptor_dont_panic() {
fn build_contract_descriptor_does_not_panic() {
let initial_price = dec!(36404.5);
let quantity = 20.0;
let leverage_coordinator = 2.0;
Expand Down
1 change: 1 addition & 0 deletions crates/payout_curve/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ trade = { path = "../trade" }
[dev-dependencies]
csv = "1.3.0"
dlc-manager = { version = "0.4.0", features = ["use-serde"] }
insta = "1"
proptest = "1"
rust_decimal_macros = "1"
tracing = "0.1.37"
Expand Down
Loading

0 comments on commit 54a67c8

Please sign in to comment.