@@ -2,9 +2,9 @@ use std::cmp::min;
22
33use ethers:: types:: { I256 , U256 } ;
44use 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
99impl 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 ----\n iter {:#?}" , 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