Skip to content

Commit d9d280e

Browse files
committed
new fn and test
1 parent 9af83c8 commit d9d280e

File tree

1 file changed

+102
-19
lines changed
  • crates/hyperdrive-math/src/short

1 file changed

+102
-19
lines changed

crates/hyperdrive-math/src/short/max.rs

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use std::cmp::min;
22

33
use ethers::types::{I256, U256};
44
use eyre::{eyre, Result};
5-
use fixedpointmath::{fixed, fixed_i256, FixedPoint};
5+
use fixedpointmath::{fixed, FixedPoint};
66

7-
use crate::{base, calculate_effective_share_reserves, short::open, State, YieldSpace};
7+
use crate::{calculate_effective_share_reserves, State, YieldSpace};
88

99
impl State {
1010
/// Calculates the minimum price that the pool can support.
@@ -60,6 +60,28 @@ impl State {
6060
Ok(spot_price * (fixed!(1e18) - weight) + min_price * weight)
6161
}
6262

63+
/// Use a conservative short price to calculate an estimate of the bonds
64+
/// that would be shorte given a base deposit amount.
65+
///
66+
/// By assuming that the LP principal is $p' * \Delta y$, we can calculate
67+
/// a closed form approximation fo the inverse of opening a short. The
68+
/// approximated deposit amount is guaranteed to be less than the provided
69+
/// base deposit amount.
70+
pub fn calculate_approximate_short_bonds_given_deposit(
71+
&self,
72+
base_deposit_amount: FixedPoint<U256>,
73+
open_vault_share_price: FixedPoint<U256>,
74+
) -> Result<FixedPoint<U256>> {
75+
let close_vault_share_price = open_vault_share_price.max(self.vault_share_price());
76+
let conservative_price = self.calculate_conservative_short_price(base_deposit_amount)?;
77+
let bond_amount = base_deposit_amount
78+
/ ((close_vault_share_price / open_vault_share_price)
79+
+ self.flat_fee()
80+
+ (self.curve_fee() * (fixed!(1e18) - self.calculate_spot_price()?))
81+
- conservative_price);
82+
Ok(bond_amount)
83+
}
84+
6385
/// Use SGD with rate reduction to find the amount of bonds shorted for a
6486
/// given base deposit amount.
6587
pub fn calculate_short_bonds_given_deposit(
@@ -76,18 +98,13 @@ impl State {
7698
let min_learning_rate = fixed!(1e2);
7799

78100
// Start with a conservative estimate of the bonds shorted & base paid.
79-
// Assuming the LP short principal is equal to some relized price times
101+
// By approximating the LP short principal as some relized price times
80102
// the bond amount, we can calculate a lower bound using a conservative
81103
// price.
82-
let conservative_price = self.calculate_conservative_short_price(base_deposit_amount)?;
83-
println!("conservative_price = {:#?}", conservative_price);
84-
let close_vault_share_price = open_vault_share_price.max(self.vault_share_price());
85-
let mut last_good_bond_amount = base_deposit_amount
86-
/ ((close_vault_share_price / open_vault_share_price)
87-
+ self.flat_fee()
88-
+ (self.curve_fee() * (fixed!(1e18) - self.calculate_spot_price()?))
89-
- conservative_price);
90-
104+
let mut last_good_bond_amount = self.calculate_approximate_short_bonds_given_deposit(
105+
base_deposit_amount,
106+
open_vault_share_price,
107+
)?;
91108
let mut last_good_base_amount =
92109
self.calculate_open_short(last_good_bond_amount, open_vault_share_price)?;
93110
println!("base_deposit_amount = {:#?}", base_deposit_amount);
@@ -135,12 +152,12 @@ impl State {
135152
)?;
136153
println!("base_amount_derivative={:#?}", base_amount_derivative);
137154
// Calculate the new bond amount.
138-
// FIXME: swap x & y so it's bonds & base
139-
// The update rule is: x_1 = x_0 - \eta * L(y,y_t) * dL(y,y_t)/dx,
140-
// where x is the deposit in base, \eta is the learning rate, y is
141-
// open_short(x), y_t is the target deposit, and L is (y_t - y).
142-
// The derivative of L(y,y_t) wrt x is -base_amount_derivative,
143-
// so we add here instead of subtracting a negative.
155+
// The update rule is: y_1 = y_0 - \eta * L(x,x_t) * dL(x,x_t)/dy,
156+
// where y is the bond amount, x is the base deposit returned by
157+
// calculate_open_short(y), x_t is the target deposit, \eta is the
158+
// learning rate, and L is the loss (x_t - x). The derivative of
159+
// L(x, x_t) wrt y is -base_amount_derivative, so we add here
160+
// instead of subtracting a negative.
144161
let new_bond_amount =
145162
last_good_bond_amount + learning_rate * error * base_amount_derivative;
146163
println!("new_bond_amount={:#?}", new_bond_amount);
@@ -720,6 +737,72 @@ mod tests {
720737
preamble::{get_max_short, initialize_pool_with_random_state},
721738
};
722739

740+
#[tokio::test]
741+
async fn fuzz_calculate_approximate_short_bonds_given_deposit() -> Result<()> {
742+
let mut rng = thread_rng();
743+
for iter in 0..*FAST_FUZZ_RUNS {
744+
println!("\n----\niter {:#?}", iter);
745+
let state = rng.gen::<State>();
746+
let open_vault_share_price = rng.gen_range(fixed!(1e5)..=state.vault_share_price());
747+
let checkpoint_exposure = {
748+
let value = rng.gen_range(fixed!(0)..=FixedPoint::from(U256::from(U128::MAX)));
749+
if rng.gen() {
750+
-I256::try_from(value)?
751+
} else {
752+
I256::try_from(value)?
753+
}
754+
};
755+
// Get the short trade in bonds.
756+
let max_short_trade = match get_max_short(state.clone(), checkpoint_exposure, None) {
757+
Ok(max_short_trade) => max_short_trade,
758+
Err(_) => continue,
759+
};
760+
let target_bond_amount =
761+
rng.gen_range(state.minimum_transaction_amount()..=max_short_trade);
762+
println!("target_bond_amount {:#?}", target_bond_amount);
763+
// The typical flow is to open a short, which receives bonds and
764+
// returns base.
765+
let target_base_amount =
766+
state.calculate_open_short(target_bond_amount, open_vault_share_price)?;
767+
println!("target_base_amount {:#?}", target_base_amount);
768+
// We approximately invert this flow, so we receive base and return
769+
// bonds.
770+
let approximate_bond_amount = state.calculate_approximate_short_bonds_given_deposit(
771+
target_base_amount,
772+
open_vault_share_price,
773+
)?;
774+
println!("approximate_bond_amount {:#?}", approximate_bond_amount);
775+
// We want to make sure that the approximation is safe, so the
776+
// approximate amount should be less than or equal to the target.
777+
assert!(
778+
target_bond_amount >= approximate_bond_amount,
779+
"{:#?} not >= {:#?}",
780+
target_bond_amount,
781+
approximate_bond_amount
782+
);
783+
// As a final sanity check, lets make sure we can open a short
784+
// with this approximate bond amount, and again check that the
785+
// resulting deposit is less than the target deposit.
786+
match state.calculate_open_short(approximate_bond_amount, open_vault_share_price) {
787+
Ok(approximate_base_amount) => {
788+
println!("approximate_base_amount {:#?}", approximate_base_amount);
789+
assert!(
790+
target_base_amount >= approximate_base_amount,
791+
"{:#?} not >= {:#?}",
792+
target_base_amount,
793+
approximate_base_amount,
794+
);
795+
}
796+
Err(_) => assert!(
797+
false,
798+
"Failed to run calculate_open_short with the approximate bond amount = {:#?}.",
799+
approximate_bond_amount
800+
),
801+
};
802+
}
803+
Ok(())
804+
}
805+
723806
#[tokio::test]
724807
async fn fuzz_calculate_short_bonds_given_deposit() -> Result<()> {
725808
let test_tolerance = fixed!(1e9);
@@ -728,7 +811,7 @@ mod tests {
728811
for fuzz_iter in 0..*SLOW_FUZZ_RUNS {
729812
println!("fuzz_iter {:#?}", fuzz_iter);
730813
let state = rng.gen::<State>();
731-
let open_vault_share_price = rng.gen_range(fixed!(0)..=state.vault_share_price());
814+
let open_vault_share_price = rng.gen_range(fixed!(1e5)..=state.vault_share_price());
732815
let checkpoint_exposure = {
733816
let value = rng.gen_range(fixed!(0)..=FixedPoint::from(U256::from(U128::MAX)));
734817
if rng.gen() {

0 commit comments

Comments
 (0)