From 280cc4d3453a45ddba3db6e74d3af39b2d6bbcea Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 7 Nov 2023 06:48:35 -0500 Subject: [PATCH 01/23] bigz/amm-reservation-price-offset --- programs/drift/src/controller/amm.rs | 9 +++ .../drift/src/controller/insurance/tests.rs | 4 +- programs/drift/src/math/amm_spread.rs | 56 ++++++++++++++++++- programs/drift/src/math/amm_spread/tests.rs | 31 ++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index ddae482a8..0580959b2 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -197,6 +197,15 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.long_spread = long_spread; amm.short_spread = short_spread; + // TODO + // amm.reservation_price_offset = amm_spread::calculate_reservation_price_offset( + // amm.last_24h_avg_funding_rate, + // amm.base_asset_amount_with_amm, + // amm.min_order_size, + // amm.historical_oracle_data.last_oracle_price_twap_5min, + // amm.last_mark_price_twap_5min, + // )?; + update_spread_reserves(amm)?; Ok((long_spread, short_spread)) diff --git a/programs/drift/src/controller/insurance/tests.rs b/programs/drift/src/controller/insurance/tests.rs index 33e9fb84a..21034a8e2 100644 --- a/programs/drift/src/controller/insurance/tests.rs +++ b/programs/drift/src/controller/insurance/tests.rs @@ -1534,7 +1534,7 @@ fn test_transfer_protocol_owned_stake() { .unwrap(); if_balance -= amount_returned; - assert_eq!(amount_returned, (99500000000) as u64); + assert_eq!(amount_returned, (99500000000_u64) as u64); assert_eq!(spot_market.insurance_fund.user_shares, 0); assert_eq!(spot_market.insurance_fund.total_shares, 21105203599); @@ -1597,7 +1597,7 @@ fn test_transfer_protocol_owned_stake() { let mut expected_if_stake_2 = InsuranceFundStake::new(Pubkey::default(), 0, 0); expected_if_stake_2 - .increase_if_shares(21105203599 as u128, &spot_market) + .increase_if_shares(21105203599_u128, &spot_market) .unwrap(); assert_eq!(user_stats_2.if_staked_quote_asset_amount, 99500000000); diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index 19f8dcffb..e212be22d 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -11,7 +11,7 @@ use crate::math::constants::{ AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO_I128, AMM_TO_QUOTE_PRECISION_RATIO_I128, BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_I128, BID_ASK_SPREAD_PRECISION_U128, DEFAULT_LARGE_BID_ASK_FACTOR, DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT, - MAX_BID_ASK_INVENTORY_SKEW_FACTOR, PEG_PRECISION, PERCENTAGE_PRECISION, + FUNDING_RATE_BUFFER, MAX_BID_ASK_INVENTORY_SKEW_FACTOR, PEG_PRECISION, PERCENTAGE_PRECISION, PERCENTAGE_PRECISION_U64, PRICE_PRECISION, PRICE_PRECISION_I128, }; use crate::math::safe_math::SafeMath; @@ -448,6 +448,8 @@ pub fn calculate_spread_reserves( PositionDirection::Short => amm.short_spread, }; + // spread = spread.safe_add(amm.reservation_price_offset)?; + let quote_asset_reserve_delta = if spread > 0 { amm.quote_asset_reserve .safe_div(BID_ASK_SPREAD_PRECISION_U128 / (spread.cast::()? / 2))? @@ -473,3 +475,55 @@ pub fn calculate_spread_reserves( Ok((base_asset_reserve, quote_asset_reserve)) } + +#[allow(clippy::comparison_chain)] +pub fn calculate_reservation_price_offset( + last_24h_avg_funding_rate: i64, + base_inventory: i128, + min_order_size: u64, + oracle_twap: i64, + mark_twap: u64, +) -> DriftResult { + let mut offset: i64 = 0; + let max_offset: i64 = (PERCENTAGE_PRECISION_I128 / 400).cast()?; // 25 bps + let base_inventory_threshold: i128 = (min_order_size * 5).cast()?; + // calculate quote denominated market premium + let mark_premium_fast: i64 = mark_twap.cast::()?.safe_sub(oracle_twap)?; + + // convert last_24h_avg_funding_rate to quote denominated premium + let mark_premium_slow: i64 = last_24h_avg_funding_rate + .safe_div(FUNDING_RATE_BUFFER.cast()?)? + .safe_mul(24)?; + + // only apply when inventory is consistent with recent and 24h market premium + if mark_premium_fast > 0 && mark_premium_slow > 0 && base_inventory > base_inventory_threshold { + offset = mark_premium_fast.min(mark_premium_slow); + validate!( + offset > 0, + ErrorCode::InvalidAmmDetected, + "offset non-positive {}", + offset + )?; + } + if mark_premium_fast < 0 && mark_premium_slow < 0 && base_inventory < -base_inventory_threshold + { + offset = mark_premium_fast.max(mark_premium_slow); + validate!( + offset < 0, + ErrorCode::InvalidAmmDetected, + "offset non-negative {}", + offset + )?; + } + + let clamped_offset = offset.clamp(-max_offset, max_offset); + + validate!( + clamped_offset.abs() <= max_offset, + ErrorCode::InvalidAmmDetected, + "clamp offset failed {}", + clamped_offset + )?; + + clamped_offset.cast() +} diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index b946b56c0..fc93a4a78 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -37,6 +37,37 @@ mod test { assert_eq!(s, 104); } + #[test] + fn calculate_reservation_offset_tests() { + let res = calculate_reservation_price_offset(0, 0, 0, 0, 0).unwrap(); + assert_eq!(res, 0); + + let res = + calculate_reservation_price_offset(43_000, 10, 1, 4216 * 10000, 4218 * 10000).unwrap(); + assert_eq!(res, 1032); + + let res = + calculate_reservation_price_offset(-43_000, 10, 1, 4216 * 10000, 4218 * 10000).unwrap(); + assert_eq!(res, 0); // none, wrong 24h_avg sign + + let res = calculate_reservation_price_offset(-43_000, -10, 1, 4216 * 10000, 4218 * 10000) + .unwrap(); + assert_eq!(res, 0); // none, wrong 24h_avg / base inventory sign + + let res = calculate_reservation_price_offset(-43_000, -10, 1, 4216 * 10000, 4214 * 10000) + .unwrap(); + assert_eq!(res, -1032); // flipped + + let res = calculate_reservation_price_offset(10_000_000, 10, 1, 4216 * 10000, 4223 * 10000) + .unwrap(); + assert_eq!(res, 2500); // upper bound + + let res = + calculate_reservation_price_offset(-10_000_000, -10, 1, 4216 * 10000, 4123 * 10000) + .unwrap(); + assert_eq!(res, -2500); // lower bound + } + #[test] fn calculate_spread_tests() { let base_spread = 1000; // .1% From abaa09c951cf9ac9d322e5f779b359a70dd93f70 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:22:25 -0500 Subject: [PATCH 02/23] start spread shrinkage based on calculate_reservation_price_offset --- programs/drift/src/controller/amm.rs | 28 +++-- programs/drift/src/math/amm_spread.rs | 43 +++++-- programs/drift/src/math/amm_spread/tests.rs | 122 +++++++++++++++++--- 3 files changed, 156 insertions(+), 37 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index 0580959b2..d459862e1 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -16,7 +16,7 @@ use crate::math::amm_spread::{calculate_spread_reserves, get_spread_reserves}; use crate::math::casting::Cast; use crate::math::constants::{ CONCENTRATION_PRECISION, FEE_POOL_TO_REVENUE_POOL_THRESHOLD, K_BPS_UPDATE_SCALE, - MAX_CONCENTRATION_COEFFICIENT, MAX_K_BPS_INCREASE, MAX_SQRT_K, + MAX_CONCENTRATION_COEFFICIENT, MAX_K_BPS_INCREASE, MAX_SQRT_K, PERCENTAGE_PRECISION, }; use crate::math::cp_curve::get_update_k_result; use crate::math::repeg::get_total_fee_lower_bound; @@ -167,6 +167,23 @@ pub fn update_spread_reserves(amm: &mut AMM) -> DriftResult { } pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u32)> { + let max_offset = + (amm.max_spread.cast::()? / 10).max(PERCENTAGE_PRECISION.cast::()? / 1000); + + let _reservation_price_offset = if amm.curve_update_intensity > 0 { + amm_spread::calculate_reservation_price_offset( + reserve_price, + amm.last_24h_avg_funding_rate, + amm.base_asset_amount_with_amm, + amm.min_order_size, + amm.historical_oracle_data.last_oracle_price_twap_5min, + amm.last_mark_price_twap_5min, + max_offset, + )? + } else { + 0 + }; + let (long_spread, short_spread) = if amm.curve_update_intensity > 0 { amm_spread::calculate_spread( amm.base_spread, @@ -188,6 +205,7 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.long_intensity_volume, amm.short_intensity_volume, amm.volume_24h, + // reservation_price_offset, )? } else { let half_base_spread = amm.base_spread.safe_div(2)?; @@ -198,13 +216,7 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.short_spread = short_spread; // TODO - // amm.reservation_price_offset = amm_spread::calculate_reservation_price_offset( - // amm.last_24h_avg_funding_rate, - // amm.base_asset_amount_with_amm, - // amm.min_order_size, - // amm.historical_oracle_data.last_oracle_price_twap_5min, - // amm.last_mark_price_twap_5min, - // )?; + // amm.reservation_price_offset = reservation_price_offset; update_spread_reserves(amm)?; diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index e212be22d..a6e05e5f7 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -12,15 +12,14 @@ use crate::math::constants::{ BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_I128, BID_ASK_SPREAD_PRECISION_U128, DEFAULT_LARGE_BID_ASK_FACTOR, DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT, FUNDING_RATE_BUFFER, MAX_BID_ASK_INVENTORY_SKEW_FACTOR, PEG_PRECISION, PERCENTAGE_PRECISION, - PERCENTAGE_PRECISION_U64, PRICE_PRECISION, PRICE_PRECISION_I128, + PERCENTAGE_PRECISION_I128, PERCENTAGE_PRECISION_U64, PRICE_PRECISION, PRICE_PRECISION_I128, + PRICE_PRECISION_I64, }; use crate::math::safe_math::SafeMath; use crate::state::perp_market::AMM; use crate::validate; -use super::constants::PERCENTAGE_PRECISION_I128; - #[cfg(test)] mod tests; @@ -317,6 +316,7 @@ pub fn calculate_spread( long_intensity_volume: u64, short_intensity_volume: u64, volume_24h: u64, + // reservation_price_offset: i32, ) -> DriftResult<(u32, u32)> { let (long_vol_spread, short_vol_spread) = calculate_long_short_vol_spread( last_oracle_conf_pct, @@ -328,8 +328,10 @@ pub fn calculate_spread( volume_24h, )?; - let mut long_spread = max((base_spread / 2) as u64, long_vol_spread); - let mut short_spread = max((base_spread / 2) as u64, short_vol_spread); + let half_base_spread_u64 = (base_spread / 2) as u64; + + let mut long_spread = max(half_base_spread_u64, long_vol_spread); + let mut short_spread = max(half_base_spread_u64, short_vol_spread); let max_target_spread = max_spread .cast::()? @@ -377,6 +379,19 @@ pub fn calculate_spread( .safe_div(BID_ASK_SPREAD_PRECISION)?; } + // if reservation_price_offset != 0 { + // let spread_shrinkage = reservation_price_offset.abs().cast::()?.safe_div(2)?; + // if reservation_price_offset > 0 { + // long_spread = long_spread + // .saturating_sub(spread_shrinkage) + // .max(half_base_spread_u64); + // } else { + // short_spread = short_spread + // .saturating_sub(spread_shrinkage) + // .max(half_base_spread_u64); + // } + // } + if total_fee_minus_distributions <= 0 { long_spread = long_spread .safe_mul(DEFAULT_LARGE_BID_ASK_FACTOR)? @@ -478,14 +493,15 @@ pub fn calculate_spread_reserves( #[allow(clippy::comparison_chain)] pub fn calculate_reservation_price_offset( + reserve_price: u64, last_24h_avg_funding_rate: i64, base_inventory: i128, min_order_size: u64, oracle_twap: i64, mark_twap: u64, + max_offset: i64, ) -> DriftResult { let mut offset: i64 = 0; - let max_offset: i64 = (PERCENTAGE_PRECISION_I128 / 400).cast()?; // 25 bps let base_inventory_threshold: i128 = (min_order_size * 5).cast()?; // calculate quote denominated market premium let mark_premium_fast: i64 = mark_twap.cast::()?.safe_sub(oracle_twap)?; @@ -516,14 +532,19 @@ pub fn calculate_reservation_price_offset( )?; } - let clamped_offset = offset.clamp(-max_offset, max_offset); + // TODO: convert quote offset to percent offset + let offset_pct: i64 = offset + .safe_mul(PRICE_PRECISION_I64)? + .safe_div(reserve_price.cast()?)?; + + let clamped_offset_pct = offset_pct.clamp(-max_offset, max_offset); validate!( - clamped_offset.abs() <= max_offset, + clamped_offset_pct.abs() <= max_offset, ErrorCode::InvalidAmmDetected, - "clamp offset failed {}", - clamped_offset + "clamp offset pct failed {}", + clamped_offset_pct )?; - clamped_offset.cast() + clamped_offset_pct.cast() } diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index fc93a4a78..f3b80e71c 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -39,33 +39,119 @@ mod test { #[test] fn calculate_reservation_offset_tests() { - let res = calculate_reservation_price_offset(0, 0, 0, 0, 0).unwrap(); - assert_eq!(res, 0); + let rev_price = 4216 * 10000; + let max_offset: i64 = 2500; // 25 bps - let res = - calculate_reservation_price_offset(43_000, 10, 1, 4216 * 10000, 4218 * 10000).unwrap(); - assert_eq!(res, 1032); + let res = calculate_reservation_price_offset(rev_price, 0, 0, 0, 0, 0, max_offset).unwrap(); + assert_eq!(res, 0); - let res = - calculate_reservation_price_offset(-43_000, 10, 1, 4216 * 10000, 4218 * 10000).unwrap(); + let res = calculate_reservation_price_offset( + rev_price, + 430_000_000, + 10, + 1, + 4216 * 10000, + 4217 * 10000, + max_offset, + ) + .unwrap(); + assert_eq!(res, 237); // 1 penny divergence + let res = calculate_reservation_price_offset( + rev_price, + 430_000_000, + 10, + 1, + 4216 * 10000, + 4219 * 10000, + max_offset, + ) + .unwrap(); + assert_eq!(res, 237 * 3); // 3 penny divergence + + let res = calculate_reservation_price_offset( + rev_price, + -43_000_000, + 10, + 1, + 4216 * 10000, + 4218 * 10000, + max_offset, + ) + .unwrap(); assert_eq!(res, 0); // none, wrong 24h_avg sign - let res = calculate_reservation_price_offset(-43_000, -10, 1, 4216 * 10000, 4218 * 10000) - .unwrap(); + let res = calculate_reservation_price_offset( + rev_price, + -43_000_000, + -10, + 1, + 4216 * 10000, + 4218 * 10000, + max_offset, + ) + .unwrap(); assert_eq!(res, 0); // none, wrong 24h_avg / base inventory sign - let res = calculate_reservation_price_offset(-43_000, -10, 1, 4216 * 10000, 4214 * 10000) - .unwrap(); - assert_eq!(res, -1032); // flipped - - let res = calculate_reservation_price_offset(10_000_000, 10, 1, 4216 * 10000, 4223 * 10000) - .unwrap(); + let res = calculate_reservation_price_offset( + rev_price, + -43_000_000, + -10, + 1, + 4216 * 10000, + 4214 * 10000, + max_offset, + ) + .unwrap(); + assert_eq!(res, -474); // flipped + + let res = calculate_reservation_price_offset( + rev_price, + 10_000_000, + 10, + 1, + 4216 * 10000, + 4223 * 10000, + max_offset, + ) + .unwrap(); + assert_eq!(res, 1660); // 7 penny divergence + + let res = calculate_reservation_price_offset( + rev_price, + 10_000_000, + 10, + 1, + 4216 * 10000, + 4233 * 10000, + max_offset, + ) + .unwrap(); assert_eq!(res, 2500); // upper bound - let res = - calculate_reservation_price_offset(-10_000_000, -10, 1, 4216 * 10000, 4123 * 10000) - .unwrap(); + let res = calculate_reservation_price_offset( + rev_price, + -10_000_000, + -10, + 1, + 4216 * 10000, + 4123 * 10000, + max_offset, + ) + .unwrap(); assert_eq!(res, -2500); // lower bound + + // max offset = 0 + let res = calculate_reservation_price_offset( + rev_price, + -10_000_000, + -10, + 1, + 4216 * 10000, + 4123 * 10000, + 0, + ) + .unwrap(); + assert_eq!(res, 0); // zero bound } #[test] From 9c6d86f22d49fea711522b8a7ba6121c876212b4 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:40:13 -0500 Subject: [PATCH 03/23] add reservation_price_offset to calculate_spread --- programs/drift/src/controller/amm.rs | 6 ++--- programs/drift/src/math/amm_spread.rs | 26 ++++++++++----------- programs/drift/src/math/amm_spread/tests.rs | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index d459862e1..b3afdb3af 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -168,9 +168,9 @@ pub fn update_spread_reserves(amm: &mut AMM) -> DriftResult { pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u32)> { let max_offset = - (amm.max_spread.cast::()? / 10).max(PERCENTAGE_PRECISION.cast::()? / 1000); + (amm.max_spread.cast::()? / 2).max(PERCENTAGE_PRECISION.cast::()? / 1000); - let _reservation_price_offset = if amm.curve_update_intensity > 0 { + let reservation_price_offset = if amm.curve_update_intensity > 0 { amm_spread::calculate_reservation_price_offset( reserve_price, amm.last_24h_avg_funding_rate, @@ -205,7 +205,7 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.long_intensity_volume, amm.short_intensity_volume, amm.volume_24h, - // reservation_price_offset, + reservation_price_offset, )? } else { let half_base_spread = amm.base_spread.safe_div(2)?; diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index a6e05e5f7..9f2237db4 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -316,7 +316,7 @@ pub fn calculate_spread( long_intensity_volume: u64, short_intensity_volume: u64, volume_24h: u64, - // reservation_price_offset: i32, + reservation_price_offset: i32, ) -> DriftResult<(u32, u32)> { let (long_vol_spread, short_vol_spread) = calculate_long_short_vol_spread( last_oracle_conf_pct, @@ -379,18 +379,18 @@ pub fn calculate_spread( .safe_div(BID_ASK_SPREAD_PRECISION)?; } - // if reservation_price_offset != 0 { - // let spread_shrinkage = reservation_price_offset.abs().cast::()?.safe_div(2)?; - // if reservation_price_offset > 0 { - // long_spread = long_spread - // .saturating_sub(spread_shrinkage) - // .max(half_base_spread_u64); - // } else { - // short_spread = short_spread - // .saturating_sub(spread_shrinkage) - // .max(half_base_spread_u64); - // } - // } + if reservation_price_offset != 0 { + let spread_shrinkage = reservation_price_offset.abs().cast::()?; + if reservation_price_offset > 0 { + long_spread = long_spread + .saturating_sub(spread_shrinkage) + .max(half_base_spread_u64); + } else { + short_spread = short_spread + .saturating_sub(spread_shrinkage) + .max(half_base_spread_u64); + } + } if total_fee_minus_distributions <= 0 { long_spread = long_spread diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index f3b80e71c..bc3d16b64 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -201,6 +201,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread1, (base_spread * 10 / 2)); @@ -230,6 +231,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread2, 16667); @@ -259,6 +261,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -294,6 +297,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert!(short_spread4 < long_spread4); @@ -323,6 +327,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -371,6 +376,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -397,6 +403,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -450,6 +457,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -498,6 +506,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread1, 500); @@ -525,6 +534,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread1, 345); @@ -551,6 +561,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread1, 110); @@ -600,6 +611,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread1, 199926); @@ -625,6 +637,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread1, 199951); @@ -650,6 +663,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread1, 199815); @@ -1006,6 +1020,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -1046,6 +1061,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -1072,6 +1088,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread, 195556); @@ -1097,6 +1114,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread, 1639); @@ -1196,6 +1214,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -1236,6 +1255,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); @@ -1262,6 +1282,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread, 197138); // big cause of oracel pct @@ -1287,6 +1308,7 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, + 0, ) .unwrap(); assert_eq!(long_spread, 1639); @@ -1315,6 +1337,7 @@ mod test { 12358265776, 72230366233, 432067603632, + 0, ) .unwrap(); assert_eq!(long_spread, 4262); @@ -1340,6 +1363,7 @@ mod test { 9520659647, 53979922148, 427588331503, + 0, ) .unwrap(); assert_eq!(long_spread, 4390); From bb21a8996a79100167d1758aaa29633006e9b28f Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Tue, 7 Nov 2023 17:10:28 +0100 Subject: [PATCH 04/23] add fields --- Cargo.lock | 11 +++++++++++ programs/drift/Cargo.toml | 2 ++ programs/drift/src/instructions/admin.rs | 5 ++++- programs/drift/src/state/perp_market.rs | 14 ++++++++++++-- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c97d1ba0..33ee5d50a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,6 +651,7 @@ dependencies = [ "borsh", "bytemuck", "bytes", + "drift-macros", "enumflags2", "num-derive", "num-integer", @@ -661,10 +662,20 @@ dependencies = [ "serum_dex", "solana-program", "solana-security-txt", + "static_assertions", "thiserror", "uint", ] +[[package]] +name = "drift-macros" +version = "0.1.0" +source = "git+https://github.com/drift-labs/drift-macros.git?rev=c57d87#c57d87e073d13d43f4d1fb09fe6822915a4ccc11" +dependencies = [ + "quote", + "syn 1.0.92", +] + [[package]] name = "ed25519" version = "1.5.3" diff --git a/programs/drift/Cargo.toml b/programs/drift/Cargo.toml index 8eb5ad1e7..fbe0407fc 100644 --- a/programs/drift/Cargo.toml +++ b/programs/drift/Cargo.toml @@ -33,6 +33,8 @@ serum_dex = { git = "https://github.com/project-serum/serum-dex", rev = "85b4f14 enumflags2 = "0.6.4" phoenix-v1 = { git = "https://github.com/drift-labs/phoenix-v1", rev = "4c65c9", version = "0.2.4", features = ["no-entrypoint"] } solana-security-txt = "1.1.0" +static_assertions = "1.1.0" +drift-macros = { git = "https://github.com/drift-labs/drift-macros.git", rev = "c57d87" } [dev-dependencies] bytes = "1.2.0" diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index eeca06ad5..f5a27287c 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -725,7 +725,10 @@ pub fn handle_initialize_perp_market( padding1: 0, padding2: 0, total_fee_earned_per_lp: 0, - padding: [0; 32], + net_unsettled_funding_pnl: 0, + quote_asset_amount_with_unsettled_lp: 0, + reservation_price_offset: 0, + padding: [0; 12], }, }; diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 6d49bb7b8..5a33196ba 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -30,6 +30,9 @@ use crate::state::traits::{MarketIndexOffset, Size}; use crate::{AMM_TO_QUOTE_PRECISION_RATIO, PRICE_PRECISION}; use borsh::{BorshDeserialize, BorshSerialize}; +use drift_macros::assert_no_slop; +use static_assertions::const_assert_eq; + #[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] pub enum MarketStatus { /// warm up period for initialization, fills are paused @@ -477,6 +480,7 @@ impl SpotBalance for PoolBalance { } } +#[assert_no_slop] #[zero_copy(unsafe)] #[derive(Debug, PartialEq, Eq)] #[repr(C)] @@ -654,7 +658,10 @@ pub struct AMM { pub padding1: u8, pub padding2: u16, pub total_fee_earned_per_lp: u64, - pub padding: [u8; 32], + pub net_unsettled_funding_pnl: i64, + pub quote_asset_amount_with_unsettled_lp: i64, + pub reservation_price_offset: i32, + pub padding: [u8; 12], } impl Default for AMM { @@ -740,7 +747,10 @@ impl Default for AMM { padding1: 0, padding2: 0, total_fee_earned_per_lp: 0, - padding: [0; 32], + net_unsettled_funding_pnl: 0, + quote_asset_amount_with_unsettled_lp: 0, + reservation_price_offset: 0, + padding: [0; 12], } } } From e936d931ca7520e05405a2de286466ee0859b9ad Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 7 Nov 2023 12:04:57 -0500 Subject: [PATCH 05/23] incorp into calculate_spread_reserves --- programs/drift/src/controller/amm.rs | 5 ++--- programs/drift/src/math/amm_spread.rs | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index b3afdb3af..d14b52a10 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -167,6 +167,7 @@ pub fn update_spread_reserves(amm: &mut AMM) -> DriftResult { } pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u32)> { + // always allow 10 bps of price offset, up to half the market's max_spread let max_offset = (amm.max_spread.cast::()? / 2).max(PERCENTAGE_PRECISION.cast::()? / 1000); @@ -214,9 +215,7 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.long_spread = long_spread; amm.short_spread = short_spread; - - // TODO - // amm.reservation_price_offset = reservation_price_offset; + amm.reservation_price_offset = reservation_price_offset; update_spread_reserves(amm)?; diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index 9f2237db4..c82c52a59 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -463,22 +463,25 @@ pub fn calculate_spread_reserves( PositionDirection::Short => amm.short_spread, }; - // spread = spread.safe_add(amm.reservation_price_offset)?; + let spread_with_offset: i32 = spread + .cast::()? + .safe_add(amm.reservation_price_offset)?; let quote_asset_reserve_delta = if spread > 0 { amm.quote_asset_reserve - .safe_div(BID_ASK_SPREAD_PRECISION_U128 / (spread.cast::()? / 2))? + .safe_div(BID_ASK_SPREAD_PRECISION_U128 / (spread_with_offset.cast::()? / 2))? } else { 0 }; - let quote_asset_reserve = match direction { - PositionDirection::Long => amm - .quote_asset_reserve - .safe_add(quote_asset_reserve_delta)?, - PositionDirection::Short => amm - .quote_asset_reserve - .safe_sub(quote_asset_reserve_delta)?, + let quote_asset_reserve = if spread_with_offset > 0 && direction == PositionDirection::Long + || spread_with_offset < 0 && direction == PositionDirection::Short + { + amm.quote_asset_reserve + .safe_add(quote_asset_reserve_delta)? + } else { + amm.quote_asset_reserve + .safe_sub(quote_asset_reserve_delta)? }; let invariant_sqrt_u192 = U192::from(amm.sqrt_k); From 9f48b7ee1bebd64243de73ec67123729cc8ac818 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:08:24 -0500 Subject: [PATCH 06/23] add typescript --- programs/drift/src/math/amm_spread.rs | 26 +++---- sdk/src/math/amm.ts | 107 ++++++++++++++++++++++++-- sdk/tests/amm/test.ts | 12 ++- 3 files changed, 121 insertions(+), 24 deletions(-) diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index c82c52a59..b2d395af4 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -379,19 +379,6 @@ pub fn calculate_spread( .safe_div(BID_ASK_SPREAD_PRECISION)?; } - if reservation_price_offset != 0 { - let spread_shrinkage = reservation_price_offset.abs().cast::()?; - if reservation_price_offset > 0 { - long_spread = long_spread - .saturating_sub(spread_shrinkage) - .max(half_base_spread_u64); - } else { - short_spread = short_spread - .saturating_sub(spread_shrinkage) - .max(half_base_spread_u64); - } - } - if total_fee_minus_distributions <= 0 { long_spread = long_spread .safe_mul(DEFAULT_LARGE_BID_ASK_FACTOR)? @@ -439,6 +426,19 @@ pub fn calculate_spread( } } + if reservation_price_offset != 0 { + let spread_shrinkage = reservation_price_offset.abs().cast::()?; + if reservation_price_offset > 0 { + long_spread = long_spread + .saturating_sub(spread_shrinkage) + .max(half_base_spread_u64); + } else { + short_spread = short_spread + .saturating_sub(spread_shrinkage) + .max(half_base_spread_u64); + } + } + let (long_spread, short_spread) = cap_to_max_spread(long_spread, short_spread, max_target_spread)?; diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 0706caafb..d55385dcf 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -12,6 +12,7 @@ import { PRICE_DIV_PEG, PERCENTAGE_PRECISION, DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT, + FUNDING_RATE_BUFFER_PRECISION, TWO, } from '../constants/numericConstants'; import { @@ -402,6 +403,50 @@ export function calculateInventoryScale( return inventoryScaleCapped; } +export function calculateReservationPriceOffset( + reservePrice: BN, + last24hAvgFundingRate: BN, + baseInventory: BN, + minOrderSize: BN, + oracleTwap: BN, + markTwap: BN, + maxOffset: BN +): BN { + let offset: BN = ZERO; + const baseInventoryThreshold = minOrderSize.mul(new BN(5)); + // Calculate quote denominated market premium + const markPremiumFast = markTwap.sub(oracleTwap); + + // Convert last_24h_avg_funding_rate to quote denominated premium + const markPremiumSlow = last24hAvgFundingRate + .div(FUNDING_RATE_BUFFER_PRECISION) + .mul(new BN(24)); + + // Only apply when inventory is consistent with recent and 24h market premium + if ( + markPremiumFast.gt(ZERO) && + markPremiumSlow.gt(ZERO) && + baseInventory.gt(baseInventoryThreshold) + ) { + offset = BN.min(markPremiumFast, markPremiumSlow); + } else if ( + markPremiumFast.lt(ZERO) && + markPremiumSlow.lt(ZERO) && + baseInventory.lt(baseInventoryThreshold.mul(new BN(-1))) + ) { + offset = BN.min(markPremiumFast, markPremiumSlow); + } + + const offsetPct: BN = offset.mul(PRICE_PRECISION).div(reservePrice); + const clampedOffsetPct = clampBN( + offsetPct, + maxOffset.mul(new BN(-1)), + maxOffset + ); + + return clampedOffsetPct; +} + export function calculateEffectiveLeverage( baseSpread: number, quoteAssetReserve: BN, @@ -510,6 +555,7 @@ export function calculateSpreadBN( longIntensity: BN, shortIntensity: BN, volume24H: BN, + reservationPriceOffset: BN, returnTerms = false ) { assert(Number.isInteger(baseSpread)); @@ -532,6 +578,8 @@ export function calculateSpreadBN( halfRevenueRetreatAmount: 0, longSpreadwRevRetreat: 0, shortSpreadwRevRetreat: 0, + longSpreadwRevOffsetShrink: 0, + shortSpreadwRevOffsetShrink: 0, totalSpread: 0, longSpread: 0, shortSpread: 0, @@ -664,6 +712,20 @@ export function calculateSpreadBN( spreadTerms.longSpreadwRevRetreat = longSpread; spreadTerms.shortSpreadwRevRetreat = shortSpread; + if (!reservationPriceOffset.eq(ZERO)) { + const spreadSkrinkage = reservationPriceOffset.abs().toNumber(); + if (reservationPriceOffset.gt(ZERO)) { + longSpread -= spreadSkrinkage; + longSpread = Math.max(longSpread, baseSpread / 2); + } else { + shortSpread -= spreadSkrinkage; + shortSpread = Math.max(shortSpread, baseSpread / 2); + } + } + + spreadTerms.longSpreadwOffsetShrink = longSpread; + spreadTerms.shortSpreadwOffsetShrink = shortSpread; + const totalSpread = longSpread + shortSpread; if (totalSpread > maxTargetSpread) { if (longSpread > shortSpread) { @@ -688,17 +750,21 @@ export function calculateSpreadBN( export function calculateSpread( amm: AMM, oraclePriceData: OraclePriceData, - now?: BN + now?: BN, + reservePrice?: BN, + reservationPriceOffset = ZERO ): [number, number] { if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) { return [amm.baseSpread / 2, amm.baseSpread / 2]; } - const reservePrice = calculatePrice( - amm.baseAssetReserve, - amm.quoteAssetReserve, - amm.pegMultiplier - ); + if (!reservePrice) { + reservePrice = calculatePrice( + amm.baseAssetReserve, + amm.quoteAssetReserve, + amm.pegMultiplier + ); + } const targetPrice = oraclePriceData?.price || reservePrice; const confInterval = oraclePriceData.confidence || ZERO; @@ -733,7 +799,8 @@ export function calculateSpread( liveOracleStd, amm.longIntensityVolume, amm.shortIntensityVolume, - amm.volume24H + amm.volume24H, + reservationPriceOffset ); const longSpread = spreads[0]; const shortSpread = spreads[1]; @@ -779,7 +846,31 @@ export function calculateSpreadReserves( }; } - const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData, now); + const reservePrice = calculatePrice( + amm.baseAssetReserve, + amm.quoteAssetReserve, + amm.pegMultiplier + ); + + const maxOffset = Math.max(amm.maxSpread / 2, 1000); + + const reservationPriceOffset = calculateReservationPriceOffset( + reservePrice, + amm.last24hAvgFundingRate, + amm.baseAssetAmountWithAmm, + amm.minOrderSize, + amm.historicalOracleData.lastOraclePriceTwap5Min, + amm.lastMarkPriceTwap5Min, + maxOffset + ); + + const [longSpread, shortSpread] = calculateSpread( + amm, + oraclePriceData, + now, + reservePrice, + reservationPriceOffset + ); const askReserves = calculateSpreadReserve( longSpread, PositionDirection.LONG, diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index 3f19f8fbd..54999716e 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -24,6 +24,7 @@ import { mockPerpMarkets } from '../dlob/helpers'; import { assert } from '../../src/assert/assert'; import * as _ from 'lodash'; +import { zeros } from '@project-serum/serum/lib/layout'; class AMMSpreadTerms { longVolSpread: number; @@ -264,7 +265,8 @@ describe('AMM Tests', () => { oracleStd, longIntensity, shortIntensity, - volume24H + volume24H, + ZERO ); const l1 = spreads[0]; const s1 = spreads[1]; @@ -291,6 +293,7 @@ describe('AMM Tests', () => { longIntensity, shortIntensity, volume24H, + ZERO, true ); console.log(terms1); @@ -323,6 +326,7 @@ describe('AMM Tests', () => { new BN(12358265776), new BN(72230366233), new BN(432067603632), + ZERO, true ); @@ -356,6 +360,7 @@ describe('AMM Tests', () => { new BN(768323534), new BN(243875031), new BN(130017761029), + ZERO, true ); @@ -967,8 +972,9 @@ describe('AMM Tests', () => { mockMarket1.amm.maxBaseAssetReserve = mockMarket1.amm.baseAssetReserve.add( new BN(9) ); - mockMarket1.amm.minBaseAssetReserve = - mockMarket1.amm.baseAssetReserve.sub(new BN(9)); + mockMarket1.amm.minBaseAssetReserve = mockMarket1.amm.baseAssetReserve.sub( + new BN(9) + ); mockMarket1.amm.quoteAssetReserve = new BN(cc).mul(BASE_PRECISION); mockMarket1.amm.pegMultiplier = new BN(18.32 * PEG_PRECISION.toNumber()); mockMarket1.amm.sqrtK = new BN(cc).mul(BASE_PRECISION); From c876b8707118905be44ed37cbefe20399017aba4 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:08:03 -0500 Subject: [PATCH 07/23] add rust / typescript test, make max 1/5th of max_spread --- programs/drift/src/controller/amm.rs | 4 +- programs/drift/src/math/amm_spread/tests.rs | 58 +++++++++++++++++ sdk/src/math/amm.ts | 16 +++-- sdk/tests/amm/test.ts | 72 ++++++++++++++++++++- 4 files changed, 140 insertions(+), 10 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index d14b52a10..01d89ff2f 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -167,9 +167,9 @@ pub fn update_spread_reserves(amm: &mut AMM) -> DriftResult { } pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u32)> { - // always allow 10 bps of price offset, up to half the market's max_spread + // always allow 10 bps of price offset, up to a fifth of the market's max_spread let max_offset = - (amm.max_spread.cast::()? / 2).max(PERCENTAGE_PRECISION.cast::()? / 1000); + (amm.max_spread.cast::()? / 5).max(PERCENTAGE_PRECISION.cast::()? / 1000); let reservation_price_offset = if amm.curve_update_intensity > 0 { amm_spread::calculate_reservation_price_offset( diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index bc3d16b64..8165ddd09 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -1317,6 +1317,8 @@ mod test { #[test] fn various_spread_tests() { + // should match typescript sdk tests in sdk/tests/amm/test.ts + let (long_spread, short_spread) = calculate_spread( 300, 0, @@ -1343,6 +1345,62 @@ mod test { assert_eq!(long_spread, 4262); assert_eq!(short_spread, 43238); + // terms 3 + let (long_spread, short_spread) = calculate_spread( + 300, + 0, + 484, + 47500, + 923807816209694, + 925117623772584, + 13731157, + -1314027016625, + 13667686, + 115876379475, + 91316628, + 928097825691666, + 907979542352912, + 945977491145601, + 161188, + 1459632439, + 12358265776, + 72230366233, + 432067603632, + 1_000_000 / 1000, + ) + .unwrap(); + assert_eq!(long_spread, 4257); + assert_eq!(short_spread, 43243); + + // terms 4 + let (long_spread, short_spread) = calculate_spread( + 300, + 0, + 484, + 47500, + 923807816209694, + 925117623772584, + 13731157, + -1314027016625, + 13667686, + 115876379475, + 91316628, + 928097825691666, + 907979542352912, + 945977491145601, + 161188, + 1459632439, + 12358265776, + 72230366233, + 432067603632, + -1_000_000 / 1000, + ) + .unwrap(); + assert_eq!(long_spread, 4263); + assert_eq!(short_spread, 43237); + + // extra one? + let (long_spread, short_spread) = calculate_spread( 300, 0, diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index d55385dcf..92cd2d88e 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -410,7 +410,7 @@ export function calculateReservationPriceOffset( minOrderSize: BN, oracleTwap: BN, markTwap: BN, - maxOffset: BN + maxOffset: number ): BN { let offset: BN = ZERO; const baseInventoryThreshold = minOrderSize.mul(new BN(5)); @@ -440,8 +440,8 @@ export function calculateReservationPriceOffset( const offsetPct: BN = offset.mul(PRICE_PRECISION).div(reservePrice); const clampedOffsetPct = clampBN( offsetPct, - maxOffset.mul(new BN(-1)), - maxOffset + new BN(-maxOffset), + new BN(maxOffset) ); return clampedOffsetPct; @@ -578,8 +578,8 @@ export function calculateSpreadBN( halfRevenueRetreatAmount: 0, longSpreadwRevRetreat: 0, shortSpreadwRevRetreat: 0, - longSpreadwRevOffsetShrink: 0, - shortSpreadwRevOffsetShrink: 0, + longSpreadwOffsetShrink: 0, + shortSpreadwOffsetShrink: 0, totalSpread: 0, longSpread: 0, shortSpread: 0, @@ -852,7 +852,11 @@ export function calculateSpreadReserves( amm.pegMultiplier ); - const maxOffset = Math.max(amm.maxSpread / 2, 1000); + // always allow 10 bps of price offset, up to a fifth of the market's max_spread + const maxOffset = Math.max( + amm.maxSpread / 5, + PERCENTAGE_PRECISION.toNumber() / 1000 + ); const reservationPriceOffset = calculateReservationPriceOffset( reservePrice, diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index 54999716e..354863bb1 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -296,7 +296,7 @@ describe('AMM Tests', () => { ZERO, true ); - console.log(terms1); + // console.log(terms1); console.log('long/short spread:', l1, s1); assert(l1 == 14864); @@ -330,11 +330,79 @@ describe('AMM Tests', () => { true ); - console.log(terms2); + // console.log(terms2); assert(terms2.effectiveLeverageCapped >= 1.0002); assert(terms2.inventorySpreadScale == 1.73492); assert(terms2.longSpread == 4262); assert(terms2.shortSpread == 43238); + + // add spread offset + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const terms3: AMMSpreadTerms = calculateSpreadBN( + 300, + new BN(0), + new BN(484), + 47500, + new BN(923807816209694), + new BN(925117623772584), + new BN(13731157), + new BN(-1314027016625), + new BN(13667686), + new BN(115876379475), + new BN(91316628), + new BN(928097825691666), + new BN(907979542352912), + new BN(945977491145601), + new BN(161188), + new BN(1459632439), + new BN(12358265776), + new BN(72230366233), + new BN(432067603632), + new BN(1_000_000 / 1000), // .1% offset + true + ); + + // console.log(terms3); + assert(terms3.effectiveLeverageCapped >= 1.0002); + assert(terms3.inventorySpreadScale == 1.73492); + assert(terms3.longSpread == 4257); + assert(terms3.shortSpread == 43243); + assert(terms3.longSpread + terms3.shortSpread == 47500); + + // add spread offset + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const terms4: AMMSpreadTerms = calculateSpreadBN( + 300, + new BN(0), + new BN(484), + 47500, + new BN(923807816209694), + new BN(925117623772584), + new BN(13731157), + new BN(-1314027016625), + new BN(13667686), + new BN(115876379475), + new BN(91316628), + new BN(928097825691666), + new BN(907979542352912), + new BN(945977491145601), + new BN(161188), + new BN(1459632439), + new BN(12358265776), + new BN(72230366233), + new BN(432067603632), + new BN(-1_000_000 / 1000), // .1% offset + true + ); + + console.log(terms4); + assert(terms4.effectiveLeverageCapped >= 1.0002); + assert(terms4.inventorySpreadScale == 1.73492); + assert(terms4.longSpread == 4263); + assert(terms4.shortSpread == 43237); + assert(terms4.longSpread + terms4.shortSpread == 47500); }); it('Corner Case Spreads', () => { From 78ceb8f25bad9230309142f2bbcd8013b022711f Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:40:36 -0500 Subject: [PATCH 08/23] update anchor types for 24h funding --- sdk/src/idl/drift.json | 14 +++++++++++++- sdk/src/math/amm.ts | 6 +++--- sdk/src/types.ts | 2 +- sdk/tests/amm/test.ts | 1 - test-scripts/single-anchor-test.sh | 2 +- tests/perpLpJit.ts | 6 +++--- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index 509c47f7a..44c9ad05c 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -7272,12 +7272,24 @@ "name": "totalFeeEarnedPerLp", "type": "u64" }, + { + "name": "netUnsettledFundingPnl", + "type": "i64" + }, + { + "name": "quoteAssetAmountWithUnsettledLp", + "type": "i64" + }, + { + "name": "reservationPriceOffset", + "type": "i32" + }, { "name": "padding", "type": { "array": [ "u8", - 32 + 12 ] } } diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 92cd2d88e..c8ae32325 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -812,7 +812,7 @@ export function calculateSpreadReserves( amm: AMM, oraclePriceData: OraclePriceData, now?: BN -) { +): [BN, BN] { function calculateSpreadReserve( spread: number, direction: PositionDirection, @@ -857,10 +857,10 @@ export function calculateSpreadReserves( amm.maxSpread / 5, PERCENTAGE_PRECISION.toNumber() / 1000 ); - + console.log(amm); const reservationPriceOffset = calculateReservationPriceOffset( reservePrice, - amm.last24hAvgFundingRate, + amm.last24HAvgFundingRate, amm.baseAssetAmountWithAmm, amm.minOrderSize, amm.historicalOracleData.lastOraclePriceTwap5Min, diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 70190b774..d7568d201 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -708,7 +708,7 @@ export type AMM = { pegMultiplier: BN; cumulativeFundingRateLong: BN; cumulativeFundingRateShort: BN; - last24hAvgFundingRate: BN; + last24HAvgFundingRate: BN; lastFundingRateShort: BN; lastFundingRateLong: BN; diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index 354863bb1..c3d2717a0 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -24,7 +24,6 @@ import { mockPerpMarkets } from '../dlob/helpers'; import { assert } from '../../src/assert/assert'; import * as _ from 'lodash'; -import { zeros } from '@project-serum/serum/lib/layout'; class AMMSpreadTerms { longVolSpread: number; diff --git a/test-scripts/single-anchor-test.sh b/test-scripts/single-anchor-test.sh index ac5711ff4..9a9bcef68 100644 --- a/test-scripts/single-anchor-test.sh +++ b/test-scripts/single-anchor-test.sh @@ -4,7 +4,7 @@ if [ "$1" != "--skip-build" ] cp target/idl/drift.json sdk/src/idl/ fi -test_files=(liquidityProvider.ts) +test_files=(perpLpJit.ts) for test_file in ${test_files[@]}; do ANCHOR_TEST_FILE=${test_file} anchor test --skip-build || exit 1; diff --git a/tests/perpLpJit.ts b/tests/perpLpJit.ts index d35a94c75..a7079a498 100644 --- a/tests/perpLpJit.ts +++ b/tests/perpLpJit.ts @@ -780,9 +780,9 @@ describe('lp jit', () => { }); await traderDriftClient.placePerpOrder(takerOrderParams); await traderDriftClient.fetchAccounts(); - console.log(takerOrderParams); + // console.log(takerOrderParams); const order = traderDriftClient.getUser().getOrderByUserOrderId(1); - console.log(order); + // console.log(order); assert(!order.postOnly); @@ -795,7 +795,7 @@ describe('lp jit', () => { postOnly: PostOnlyParams.MUST_POST_ONLY, immediateOrCancel: true, }); - console.log('maker:', makerOrderParams); + // console.log('maker:', makerOrderParams); const txSig = await poorDriftClient.placeAndMakePerpOrder( makerOrderParams, From 7067e673c02123ad09cc4b0687a407010aac3959 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Sun, 19 Nov 2023 19:14:19 -0500 Subject: [PATCH 09/23] fix calculateSpreadReserves return type --- sdk/src/math/amm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index c8ae32325..34c375578 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -812,7 +812,7 @@ export function calculateSpreadReserves( amm: AMM, oraclePriceData: OraclePriceData, now?: BN -): [BN, BN] { +) { function calculateSpreadReserve( spread: number, direction: PositionDirection, From 956775dc66dd53141e2f054cc370a6246025db47 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:28:56 -0500 Subject: [PATCH 10/23] incorp feedback --- programs/drift/src/controller/amm.rs | 19 +++-- programs/drift/src/instructions/admin.rs | 2 +- programs/drift/src/math/amm_spread.rs | 89 +++++++++++---------- programs/drift/src/math/amm_spread/tests.rs | 43 +++++++--- programs/drift/src/state/perp_market.rs | 4 +- 5 files changed, 96 insertions(+), 61 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index dde83e3c1..35c12324f 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -16,7 +16,7 @@ use crate::math::amm_spread::{calculate_spread_reserves, get_spread_reserves}; use crate::math::casting::Cast; use crate::math::constants::{ CONCENTRATION_PRECISION, FEE_POOL_TO_REVENUE_POOL_THRESHOLD, K_BPS_UPDATE_SCALE, - MAX_CONCENTRATION_COEFFICIENT, MAX_K_BPS_INCREASE, MAX_SQRT_K, PERCENTAGE_PRECISION, + MAX_CONCENTRATION_COEFFICIENT, MAX_K_BPS_INCREASE, MAX_SQRT_K, }; use crate::math::cp_curve::get_update_k_result; use crate::math::repeg::get_total_fee_lower_bound; @@ -169,14 +169,23 @@ pub fn update_spread_reserves(amm: &mut AMM) -> DriftResult { pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u32)> { let max_offset = amm.get_max_reference_price_offset()?; - let reservation_price_offset = if amm.curve_update_intensity > 0 { + let reference_price_offset = if amm.curve_update_intensity > 0 { + let liquidity_ratio = amm_spread::calculate_inventory_liquidity_ratio( + amm.base_asset_amount_with_amm, + amm.base_asset_reserve, + amm.max_base_asset_reserve, + amm.min_base_asset_reserve, + )?; + amm_spread::calculate_reference_price_offset( reserve_price, amm.last_24h_avg_funding_rate, - amm.base_asset_amount_with_amm, + liquidity_ratio, amm.min_order_size, amm.historical_oracle_data.last_oracle_price_twap_5min, amm.last_mark_price_twap_5min, + amm.historical_oracle_data.last_oracle_price_twap, + amm.last_mark_price_twap, max_offset, )? } else { @@ -204,7 +213,7 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.long_intensity_volume, amm.short_intensity_volume, amm.volume_24h, - reservation_price_offset, + reference_price_offset, )? } else { let half_base_spread = amm.base_spread.safe_div(2)?; @@ -213,7 +222,7 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.long_spread = long_spread; amm.short_spread = short_spread; - amm.reservation_price_offset = reservation_price_offset; + amm.reference_price_offset = reference_price_offset; update_spread_reserves(amm)?; diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 1931c1ba4..d4365524a 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -728,7 +728,7 @@ pub fn handle_initialize_perp_market( total_fee_earned_per_lp: 0, net_unsettled_funding_pnl: 0, quote_asset_amount_with_unsettled_lp: 0, - reservation_price_offset: 0, + reference_price_offset: 0, padding: [0; 12], }, }; diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index 7c3d56f0b..58bc8351b 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -316,7 +316,7 @@ pub fn calculate_spread( long_intensity_volume: u64, short_intensity_volume: u64, volume_24h: u64, - reservation_price_offset: i32, + reference_price_offset: i32, ) -> DriftResult<(u32, u32)> { let (long_vol_spread, short_vol_spread) = calculate_long_short_vol_spread( last_oracle_conf_pct, @@ -426,9 +426,9 @@ pub fn calculate_spread( } } - if reservation_price_offset != 0 { - let spread_shrinkage = reservation_price_offset.abs().cast::()?; - if reservation_price_offset > 0 { + if reference_price_offset != 0 { + let spread_shrinkage = reference_price_offset.abs().cast::()?; + if reference_price_offset > 0 { long_spread = long_spread .saturating_sub(spread_shrinkage) .max(half_base_spread_u64); @@ -463,9 +463,7 @@ pub fn calculate_spread_reserves( PositionDirection::Short => amm.short_spread, }; - let spread_with_offset: i32 = spread - .cast::()? - .safe_add(amm.reservation_price_offset)?; + let spread_with_offset: i32 = spread.cast::()?.safe_add(amm.reference_price_offset)?; let quote_asset_reserve_delta = if spread > 0 { amm.quote_asset_reserve @@ -498,52 +496,59 @@ pub fn calculate_spread_reserves( pub fn calculate_reference_price_offset( reserve_price: u64, last_24h_avg_funding_rate: i64, - base_inventory: i128, - min_order_size: u64, - oracle_twap: i64, - mark_twap: u64, - max_offset: i64, + liquidity_fraction: i128, + _min_order_size: u64, + oracle_twap_fast: i64, + mark_twap_fast: u64, + oracle_twap_slow: i64, + mark_twap_slow: u64, + max_offset_pct: i64, ) -> DriftResult { - let mut offset: i64 = 0; - let base_inventory_threshold: i128 = (min_order_size * 5).cast()?; - // calculate quote denominated market premium - let mark_premium_fast: i64 = mark_twap.cast::()?.safe_sub(oracle_twap)?; + if last_24h_avg_funding_rate == 0 { + return Ok(0) + } + let max_offset_in_price = max_offset_pct + .safe_mul(reserve_price.cast()?)? + .safe_div(PERCENTAGE_PRECISION.cast()?)?; + + // calculate quote denominated market premium + let mark_premium_minute: i64 = mark_twap_fast + .cast::()? + .safe_sub(oracle_twap_fast)? + .clamp(-max_offset_in_price, max_offset_in_price); + let mark_premium_hour: i64 = mark_twap_slow + .cast::()? + .safe_sub(oracle_twap_slow)? + .clamp(-max_offset_in_price, max_offset_in_price); // convert last_24h_avg_funding_rate to quote denominated premium - let mark_premium_slow: i64 = last_24h_avg_funding_rate + let mark_premium_day: i64 = last_24h_avg_funding_rate .safe_div(FUNDING_RATE_BUFFER.cast()?)? - .safe_mul(24)?; + .safe_mul(24)? + .clamp(-max_offset_in_price, max_offset_in_price); // todo: look at how 24h funding is calc w.r.t. the funding_period - // only apply when inventory is consistent with recent and 24h market premium - if mark_premium_fast > 0 && mark_premium_slow > 0 && base_inventory > base_inventory_threshold { - offset = mark_premium_fast.min(mark_premium_slow); - validate!( - offset > 0, - ErrorCode::InvalidAmmDetected, - "offset non-positive {}", - offset - )?; - } - if mark_premium_fast < 0 && mark_premium_slow < 0 && base_inventory < -base_inventory_threshold - { - offset = mark_premium_fast.max(mark_premium_slow); - validate!( - offset < 0, - ErrorCode::InvalidAmmDetected, - "offset non-negative {}", - offset - )?; - } + // take average clamped premium as the price-based offset + let mark_premium_avg = mark_premium_minute + .safe_add(mark_premium_hour)? + .safe_add(mark_premium_day)? + .safe_div(3_i64)?; - // TODO: convert quote offset to percent offset - let offset_pct: i64 = offset + let mark_premium_avg_pct: i64 = mark_premium_avg .safe_mul(PRICE_PRECISION_I64)? .safe_div(reserve_price.cast()?)?; - let clamped_offset_pct = offset_pct.clamp(-max_offset, max_offset); + let inventory_pct = liquidity_fraction.cast::()? + .safe_mul(max_offset_pct)? + .safe_div(PERCENTAGE_PRECISION.cast::()?)? + .clamp(-max_offset_pct, max_offset_pct); + + // only apply when inventory is consistent with recent and 24h market premium + let offset_pct = mark_premium_avg_pct.safe_add(inventory_pct)?; + + let clamped_offset_pct = offset_pct.clamp(-max_offset_pct, max_offset_pct); validate!( - clamped_offset_pct.abs() <= max_offset, + clamped_offset_pct.abs() <= max_offset_pct, ErrorCode::InvalidAmmDetected, "clamp offset pct failed {}", clamped_offset_pct diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index f2e755c18..92f256508 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -42,31 +42,38 @@ mod test { let rev_price = 4216 * 10000; let max_offset: i64 = 2500; // 25 bps - let res = calculate_reference_price_offset(rev_price, 0, 0, 0, 0, 0, max_offset).unwrap(); + let res = calculate_reference_price_offset(rev_price, + 0, 0, 0, 0, 0, + 0,0, + max_offset).unwrap(); assert_eq!(res, 0); let res = calculate_reference_price_offset( rev_price, - 430_000_000, + 1, 10, 1, 4216 * 10000, 4217 * 10000, + 4216 * 10000, + 4217 * 10000, max_offset, ) .unwrap(); - assert_eq!(res, 237); // 1 penny divergence + assert_eq!(res, 158); // 237*2/3); // 1 penny divergence let res = calculate_reference_price_offset( rev_price, - 430_000_000, + 1, 10, 1, 4216 * 10000, 4219 * 10000, + 4216 * 10000, + 4219 * 10000, max_offset, ) .unwrap(); - assert_eq!(res, 237 * 3); // 3 penny divergence + assert_eq!(res, 237 * 2); // 3 penny divergence let res = calculate_reference_price_offset( rev_price, @@ -75,22 +82,26 @@ mod test { 1, 4216 * 10000, 4218 * 10000, + 4216 * 10000, + 4218 * 10000, max_offset, ) .unwrap(); - assert_eq!(res, 0); // none, wrong 24h_avg sign + assert_eq!(res, -517); // counter acting 24h_avg sign let res = calculate_reference_price_offset( rev_price, -43_000_000, - -10, + -10000, 1, 4216 * 10000, 4218 * 10000, + 4216 * 10000, + 4218 * 10000, max_offset, ) .unwrap(); - assert_eq!(res, 0); // none, wrong 24h_avg / base inventory sign + assert_eq!(res, -542); // counteracting 24h_avg / base inventory sign let res = calculate_reference_price_offset( rev_price, @@ -99,22 +110,26 @@ mod test { 1, 4216 * 10000, 4214 * 10000, + 4216 * 10000, + 4214 * 10000, max_offset, ) .unwrap(); - assert_eq!(res, -474); // flipped + assert_eq!(res, -1149); // flipped let res = calculate_reference_price_offset( rev_price, - 10_000_000, + 1, 10, 1, 4216 * 10000, 4223 * 10000, + 4216 * 10000, + 4223 * 10000, max_offset, ) .unwrap(); - assert_eq!(res, 1660); // 7 penny divergence + assert_eq!(res, 1660 * 2/3); // 7 penny divergence let res = calculate_reference_price_offset( rev_price, @@ -123,6 +138,8 @@ mod test { 1, 4216 * 10000, 4233 * 10000, + 4216 * 10000, + 4233 * 10000, max_offset, ) .unwrap(); @@ -135,6 +152,8 @@ mod test { 1, 4216 * 10000, 4123 * 10000, + 4216 * 10000, + 4123 * 10000, max_offset, ) .unwrap(); @@ -148,6 +167,8 @@ mod test { 1, 4216 * 10000, 4123 * 10000, + 6 * 10000, + 4123 * 10000, 0, ) .unwrap(); diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index f81742bdf..67cc19b5d 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -661,7 +661,7 @@ pub struct AMM { pub total_fee_earned_per_lp: u64, pub net_unsettled_funding_pnl: i64, pub quote_asset_amount_with_unsettled_lp: i64, - pub reservation_price_offset: i32, + pub reference_price_offset: i32, pub padding: [u8; 12], } @@ -750,7 +750,7 @@ impl Default for AMM { total_fee_earned_per_lp: 0, net_unsettled_funding_pnl: 0, quote_asset_amount_with_unsettled_lp: 0, - reservation_price_offset: 0, + reference_price_offset: 0, padding: [0; 12], } } From 7df18ada8b2d9fe968a8b02c7a466931f21fce5e Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:31:39 -0500 Subject: [PATCH 11/23] support sdk and tests, remove shrinakge of spread --- programs/drift/src/math/amm_spread.rs | 24 ++-- programs/drift/src/math/amm_spread/tests.rs | 25 ++++- sdk/src/math/amm.ts | 115 +++++++++++--------- sdk/tests/amm/test.ts | 6 +- sdk/tests/dlob/helpers.ts | 2 +- 5 files changed, 101 insertions(+), 71 deletions(-) diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index 58bc8351b..b4b9acfd6 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -426,18 +426,18 @@ pub fn calculate_spread( } } - if reference_price_offset != 0 { - let spread_shrinkage = reference_price_offset.abs().cast::()?; - if reference_price_offset > 0 { - long_spread = long_spread - .saturating_sub(spread_shrinkage) - .max(half_base_spread_u64); - } else { - short_spread = short_spread - .saturating_sub(spread_shrinkage) - .max(half_base_spread_u64); - } - } + // if reference_price_offset != 0 { + // let spread_shrinkage = reference_price_offset.abs().cast::()?; + // if reference_price_offset > 0 { + // long_spread = long_spread + // .saturating_sub(spread_shrinkage) + // .max(half_base_spread_u64); + // } else { + // short_spread = short_spread + // .saturating_sub(spread_shrinkage) + // .max(half_base_spread_u64); + // } + // } let (long_spread, short_spread) = cap_to_max_spread(long_spread, short_spread, max_target_spread)?; diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index 92f256508..129068d10 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -38,7 +38,7 @@ mod test { } #[test] - fn calculate_reservation_offset_tests() { + fn calculate_reference_price_offset_tests() { let rev_price = 4216 * 10000; let max_offset: i64 = 2500; // 25 bps @@ -173,6 +173,21 @@ mod test { ) .unwrap(); assert_eq!(res, 0); // zero bound + + // counteracting fast/slow twaps to 0 + let res = calculate_reference_price_offset( + rev_price, + -1, + 1, + 1, + 4216 * 10000, + 4123 * 10000, + 4123 * 10000, + 4216 * 10000, + max_offset, + ) + .unwrap(); + assert_eq!(res, 0); } #[test] @@ -1390,8 +1405,8 @@ mod test { 1_000_000 / 1000, ) .unwrap(); - assert_eq!(long_spread, 4257); - assert_eq!(short_spread, 43243); + assert_eq!(long_spread, 4262); + assert_eq!(short_spread, 43238); // terms 4 let (long_spread, short_spread) = calculate_spread( @@ -1417,8 +1432,8 @@ mod test { -1_000_000 / 1000, ) .unwrap(); - assert_eq!(long_spread, 4263); - assert_eq!(short_spread, 43237); + assert_eq!(long_spread, 4262); + assert_eq!(short_spread, 43238); // extra one? diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index e4b70f720..6ee16cab7 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -403,48 +403,62 @@ export function calculateInventoryScale( return inventoryScaleCapped; } -export function calculateReservationPriceOffset( - reservePrice: BN, - last24hAvgFundingRate: BN, - baseInventory: BN, - minOrderSize: BN, - oracleTwap: BN, - markTwap: BN, - maxOffset: number +function calculateReferencePriceOffset( + reservePrice: BN, + last24hAvgFundingRate: BN, + liquidityFraction: BN, + minOrderSize: BN, + oracleTwapFast: BN, + markTwapFast: BN, + oracleTwapSlow: BN, + markTwapSlow: BN, + maxOffsetPct: number ): BN { - let offset: BN = ZERO; - const baseInventoryThreshold = minOrderSize.mul(new BN(5)); - // Calculate quote denominated market premium - const markPremiumFast = markTwap.sub(oracleTwap); - // Convert last_24h_avg_funding_rate to quote denominated premium - const markPremiumSlow = last24hAvgFundingRate - .div(FUNDING_RATE_BUFFER_PRECISION) - .mul(new BN(24)); + if (last24hAvgFundingRate.eq(ZERO)) { + return ZERO; + } - // Only apply when inventory is consistent with recent and 24h market premium - if ( - markPremiumFast.gt(ZERO) && - markPremiumSlow.gt(ZERO) && - baseInventory.gt(baseInventoryThreshold) - ) { - offset = BN.min(markPremiumFast, markPremiumSlow); - } else if ( - markPremiumFast.lt(ZERO) && - markPremiumSlow.lt(ZERO) && - baseInventory.lt(baseInventoryThreshold.mul(new BN(-1))) - ) { - offset = BN.min(markPremiumFast, markPremiumSlow); - } + const maxOffsetInPrice = new BN(maxOffsetPct) + .mul(reservePrice) + .div(PERCENTAGE_PRECISION); + + // Calculate quote denominated market premium + const markPremiumMinute = clampBN(markTwapFast + .sub(oracleTwapFast), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); + + const markPremiumHour = clampBN(markTwapSlow + .sub(oracleTwapSlow), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); + + // Convert last24hAvgFundingRate to quote denominated premium + const markPremiumDay = clampBN(last24hAvgFundingRate + .div(FUNDING_RATE_BUFFER_PRECISION) + .mul(new BN(24)), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); - const offsetPct: BN = offset.mul(PRICE_PRECISION).div(reservePrice); - const clampedOffsetPct = clampBN( + // Take average clamped premium as the price-based offset + const markPremiumAvg = markPremiumMinute + .add(markPremiumHour) + .add(markPremiumDay) + .div(new BN(3)); + + const markPremiumAvgPct = markPremiumAvg + .mul(PRICE_PRECISION) + .div(reservePrice); + + const inventoryPct = clampBN(liquidityFraction + .mul(new BN(maxOffsetPct)) + .div(PERCENTAGE_PRECISION), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); + + // Only apply when inventory is consistent with recent and 24h market premium + const offsetPct = markPremiumAvgPct.add(inventoryPct); + + const clampedOffsetPct = clampBN( offsetPct, - new BN(-maxOffset), - new BN(maxOffset) + new BN(-maxOffsetPct), + new BN(maxOffsetPct) ); - return clampedOffsetPct; + return clampedOffsetPct; } export function calculateEffectiveLeverage( @@ -555,7 +569,7 @@ export function calculateSpreadBN( longIntensity: BN, shortIntensity: BN, volume24H: BN, - reservationPriceOffset: BN, + referencePriceOffset: BN, returnTerms = false ) { assert(Number.isInteger(baseSpread)); @@ -712,19 +726,19 @@ export function calculateSpreadBN( spreadTerms.longSpreadwRevRetreat = longSpread; spreadTerms.shortSpreadwRevRetreat = shortSpread; - if (!reservationPriceOffset.eq(ZERO)) { - const spreadSkrinkage = reservationPriceOffset.abs().toNumber(); - if (reservationPriceOffset.gt(ZERO)) { - longSpread -= spreadSkrinkage; - longSpread = Math.max(longSpread, baseSpread / 2); - } else { - shortSpread -= spreadSkrinkage; - shortSpread = Math.max(shortSpread, baseSpread / 2); - } - } + // if (!referencePriceOffset.eq(ZERO)) { + // const spreadSkrinkage = referencePriceOffset.abs().toNumber(); + // if (referencePriceOffset.gt(ZERO)) { + // longSpread -= spreadSkrinkage; + // longSpread = Math.max(longSpread, baseSpread / 2); + // } else { + // shortSpread -= spreadSkrinkage; + // shortSpread = Math.max(shortSpread, baseSpread / 2); + // } + // } - spreadTerms.longSpreadwOffsetShrink = longSpread; - spreadTerms.shortSpreadwOffsetShrink = shortSpread; + // spreadTerms.longSpreadwOffsetShrink = longSpread; + // spreadTerms.shortSpreadwOffsetShrink = shortSpread; const totalSpread = longSpread + shortSpread; if (totalSpread > maxTargetSpread) { @@ -857,14 +871,15 @@ export function calculateSpreadReserves( amm.maxSpread / 5, PERCENTAGE_PRECISION.toNumber() / 1000 ); - console.log(amm); - const reservationPriceOffset = calculateReservationPriceOffset( + const reservationPriceOffset = calculateReferencePriceOffset( reservePrice, amm.last24HAvgFundingRate, amm.baseAssetAmountWithAmm, amm.minOrderSize, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, + amm.historicalOracleData.lastOraclePriceTwap, + amm.lastMarkPriceTwap, maxOffset ); diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index c3d2717a0..e7dc0af55 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -362,7 +362,7 @@ describe('AMM Tests', () => { true ); - // console.log(terms3); + console.log(terms3); assert(terms3.effectiveLeverageCapped >= 1.0002); assert(terms3.inventorySpreadScale == 1.73492); assert(terms3.longSpread == 4257); @@ -399,8 +399,8 @@ describe('AMM Tests', () => { console.log(terms4); assert(terms4.effectiveLeverageCapped >= 1.0002); assert(terms4.inventorySpreadScale == 1.73492); - assert(terms4.longSpread == 4263); - assert(terms4.shortSpread == 43237); + assert(terms4.longSpread == 4262); + assert(terms4.shortSpread == 43238); assert(terms4.longSpread + terms4.shortSpread == 47500); }); diff --git a/sdk/tests/dlob/helpers.ts b/sdk/tests/dlob/helpers.ts index 75bddf1c8..ae1c4d279 100644 --- a/sdk/tests/dlob/helpers.ts +++ b/sdk/tests/dlob/helpers.ts @@ -84,7 +84,7 @@ export const mockAMM: AMM = { baseAssetAmountWithUnsettledLp: new BN(0), orderStepSize: new BN(0), orderTickSize: new BN(1), - last24hAvgFundingRate: new BN(0), + last24HAvgFundingRate: new BN(0), lastFundingRateShort: new BN(0), lastFundingRateLong: new BN(0), concentrationCoef: new BN(0), From d9405cfc02dcdae36d46165042dc4f0f37cc3bce Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:40:49 -0500 Subject: [PATCH 12/23] cleanup commented out code --- programs/drift/src/controller/amm.rs | 1 - programs/drift/src/math/amm_spread.rs | 14 ----------- programs/drift/src/math/amm_spread/tests.rs | 26 --------------------- sdk/src/math/amm.ts | 16 ------------- sdk/tests/amm/test.ts | 5 ---- 5 files changed, 62 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index 35c12324f..809e75e47 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -213,7 +213,6 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.long_intensity_volume, amm.short_intensity_volume, amm.volume_24h, - reference_price_offset, )? } else { let half_base_spread = amm.base_spread.safe_div(2)?; diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index b4b9acfd6..d831d6ea6 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -316,7 +316,6 @@ pub fn calculate_spread( long_intensity_volume: u64, short_intensity_volume: u64, volume_24h: u64, - reference_price_offset: i32, ) -> DriftResult<(u32, u32)> { let (long_vol_spread, short_vol_spread) = calculate_long_short_vol_spread( last_oracle_conf_pct, @@ -426,19 +425,6 @@ pub fn calculate_spread( } } - // if reference_price_offset != 0 { - // let spread_shrinkage = reference_price_offset.abs().cast::()?; - // if reference_price_offset > 0 { - // long_spread = long_spread - // .saturating_sub(spread_shrinkage) - // .max(half_base_spread_u64); - // } else { - // short_spread = short_spread - // .saturating_sub(spread_shrinkage) - // .max(half_base_spread_u64); - // } - // } - let (long_spread, short_spread) = cap_to_max_spread(long_spread, short_spread, max_target_spread)?; diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index 129068d10..166c3bf1c 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -237,7 +237,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread1, (base_spread * 10 / 2)); @@ -267,7 +266,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread2, 16667); @@ -297,7 +295,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -333,7 +330,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert!(short_spread4 < long_spread4); @@ -363,7 +359,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -412,7 +407,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -439,7 +433,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -493,7 +486,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -542,7 +534,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread1, 500); @@ -570,7 +561,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread1, 345); @@ -597,7 +587,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread1, 110); @@ -647,7 +636,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread1, 199926); @@ -673,7 +661,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread1, 199951); @@ -699,7 +686,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread1, 199815); @@ -1056,7 +1042,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -1097,7 +1082,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -1124,7 +1108,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread, 195556); @@ -1150,7 +1133,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread, 1639); @@ -1250,7 +1232,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -1291,7 +1272,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); @@ -1318,7 +1298,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread, 197138); // big cause of oracel pct @@ -1344,7 +1323,6 @@ mod test { long_intensity_volume, short_intensity_volume, volume_24h, - 0, ) .unwrap(); assert_eq!(long_spread, 1639); @@ -1375,7 +1353,6 @@ mod test { 12358265776, 72230366233, 432067603632, - 0, ) .unwrap(); assert_eq!(long_spread, 4262); @@ -1402,7 +1379,6 @@ mod test { 12358265776, 72230366233, 432067603632, - 1_000_000 / 1000, ) .unwrap(); assert_eq!(long_spread, 4262); @@ -1429,7 +1405,6 @@ mod test { 12358265776, 72230366233, 432067603632, - -1_000_000 / 1000, ) .unwrap(); assert_eq!(long_spread, 4262); @@ -1457,7 +1432,6 @@ mod test { 9520659647, 53979922148, 427588331503, - 0, ) .unwrap(); assert_eq!(long_spread, 4390); diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 6ee16cab7..83c83c06b 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -569,7 +569,6 @@ export function calculateSpreadBN( longIntensity: BN, shortIntensity: BN, volume24H: BN, - referencePriceOffset: BN, returnTerms = false ) { assert(Number.isInteger(baseSpread)); @@ -726,20 +725,6 @@ export function calculateSpreadBN( spreadTerms.longSpreadwRevRetreat = longSpread; spreadTerms.shortSpreadwRevRetreat = shortSpread; - // if (!referencePriceOffset.eq(ZERO)) { - // const spreadSkrinkage = referencePriceOffset.abs().toNumber(); - // if (referencePriceOffset.gt(ZERO)) { - // longSpread -= spreadSkrinkage; - // longSpread = Math.max(longSpread, baseSpread / 2); - // } else { - // shortSpread -= spreadSkrinkage; - // shortSpread = Math.max(shortSpread, baseSpread / 2); - // } - // } - - // spreadTerms.longSpreadwOffsetShrink = longSpread; - // spreadTerms.shortSpreadwOffsetShrink = shortSpread; - const totalSpread = longSpread + shortSpread; if (totalSpread > maxTargetSpread) { if (longSpread > shortSpread) { @@ -814,7 +799,6 @@ export function calculateSpread( amm.longIntensityVolume, amm.shortIntensityVolume, amm.volume24H, - reservationPriceOffset ); const longSpread = spreads[0]; const shortSpread = spreads[1]; diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index e7dc0af55..c5531d419 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -292,7 +292,6 @@ describe('AMM Tests', () => { longIntensity, shortIntensity, volume24H, - ZERO, true ); // console.log(terms1); @@ -325,7 +324,6 @@ describe('AMM Tests', () => { new BN(12358265776), new BN(72230366233), new BN(432067603632), - ZERO, true ); @@ -358,7 +356,6 @@ describe('AMM Tests', () => { new BN(12358265776), new BN(72230366233), new BN(432067603632), - new BN(1_000_000 / 1000), // .1% offset true ); @@ -392,7 +389,6 @@ describe('AMM Tests', () => { new BN(12358265776), new BN(72230366233), new BN(432067603632), - new BN(-1_000_000 / 1000), // .1% offset true ); @@ -427,7 +423,6 @@ describe('AMM Tests', () => { new BN(768323534), new BN(243875031), new BN(130017761029), - ZERO, true ); From e8f00fdad9726e2cde7c2ae415386a1edc043b65 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 28 Nov 2023 12:17:16 -0500 Subject: [PATCH 13/23] amm.ts: add calculateInventoryLiquidityRatio --- sdk/src/math/amm.ts | 139 ++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 55 deletions(-) diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 83c83c06b..ad4d45f01 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -353,21 +353,12 @@ export function calculateMarketOpenBidAsk( return [openBids, openAsks]; } -export function calculateInventoryScale( +export function calculateInventoryLiquidityRatio( baseAssetAmountWithAmm: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, - maxBaseAssetReserve: BN, - directionalSpread: number, - maxSpread: number -): number { - if (baseAssetAmountWithAmm.eq(ZERO)) { - return 1; - } - - const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul( - new BN(10) - ); + maxBaseAssetReserve: BN +): BN { // inventory skew const [openBids, openAsks] = calculateMarketOpenBidAsk( baseAssetReserve, @@ -384,6 +375,31 @@ export function calculateInventoryScale( .abs(), PERCENTAGE_PRECISION ); + return inventoryScaleBN; +} + +export function calculateInventoryScale( + baseAssetAmountWithAmm: BN, + baseAssetReserve: BN, + minBaseAssetReserve: BN, + maxBaseAssetReserve: BN, + directionalSpread: number, + maxSpread: number +): number { + if (baseAssetAmountWithAmm.eq(ZERO)) { + return 1; + } + + const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul( + new BN(10) + ); + + const inventoryScaleBN = calculateInventoryLiquidityRatio( + baseAssetAmountWithAmm, + baseAssetReserve, + minBaseAssetReserve, + maxBaseAssetReserve + ); const inventoryScaleMaxBN = BN.max( MAX_BID_ASK_INVENTORY_SKEW_FACTOR, @@ -404,61 +420,69 @@ export function calculateInventoryScale( } function calculateReferencePriceOffset( - reservePrice: BN, - last24hAvgFundingRate: BN, - liquidityFraction: BN, - minOrderSize: BN, - oracleTwapFast: BN, - markTwapFast: BN, - oracleTwapSlow: BN, - markTwapSlow: BN, - maxOffsetPct: number + reservePrice: BN, + last24hAvgFundingRate: BN, + liquidityFraction: BN, + oracleTwapFast: BN, + markTwapFast: BN, + oracleTwapSlow: BN, + markTwapSlow: BN, + maxOffsetPct: number ): BN { + if (last24hAvgFundingRate.eq(ZERO)) { + return ZERO; + } - if (last24hAvgFundingRate.eq(ZERO)) { - return ZERO; - } - - const maxOffsetInPrice = new BN(maxOffsetPct) - .mul(reservePrice) - .div(PERCENTAGE_PRECISION); + const maxOffsetInPrice = new BN(maxOffsetPct) + .mul(reservePrice) + .div(PERCENTAGE_PRECISION); - // Calculate quote denominated market premium - const markPremiumMinute = clampBN(markTwapFast - .sub(oracleTwapFast), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); + // Calculate quote denominated market premium + const markPremiumMinute = clampBN( + markTwapFast.sub(oracleTwapFast), + maxOffsetInPrice.mul(new BN(-1)), + maxOffsetInPrice + ); - const markPremiumHour = clampBN(markTwapSlow - .sub(oracleTwapSlow), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); + const markPremiumHour = clampBN( + markTwapSlow.sub(oracleTwapSlow), + maxOffsetInPrice.mul(new BN(-1)), + maxOffsetInPrice + ); - // Convert last24hAvgFundingRate to quote denominated premium - const markPremiumDay = clampBN(last24hAvgFundingRate - .div(FUNDING_RATE_BUFFER_PRECISION) - .mul(new BN(24)), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); + // Convert last24hAvgFundingRate to quote denominated premium + const markPremiumDay = clampBN( + last24hAvgFundingRate.div(FUNDING_RATE_BUFFER_PRECISION).mul(new BN(24)), + maxOffsetInPrice.mul(new BN(-1)), + maxOffsetInPrice + ); - // Take average clamped premium as the price-based offset - const markPremiumAvg = markPremiumMinute - .add(markPremiumHour) - .add(markPremiumDay) - .div(new BN(3)); + // Take average clamped premium as the price-based offset + const markPremiumAvg = markPremiumMinute + .add(markPremiumHour) + .add(markPremiumDay) + .div(new BN(3)); - const markPremiumAvgPct = markPremiumAvg - .mul(PRICE_PRECISION) - .div(reservePrice); + const markPremiumAvgPct = markPremiumAvg + .mul(PRICE_PRECISION) + .div(reservePrice); - const inventoryPct = clampBN(liquidityFraction - .mul(new BN(maxOffsetPct)) - .div(PERCENTAGE_PRECISION), maxOffsetInPrice.mul(new BN(-1)), maxOffsetInPrice); + const inventoryPct = clampBN( + liquidityFraction.mul(new BN(maxOffsetPct)).div(PERCENTAGE_PRECISION), + maxOffsetInPrice.mul(new BN(-1)), + maxOffsetInPrice + ); - // Only apply when inventory is consistent with recent and 24h market premium - const offsetPct = markPremiumAvgPct.add(inventoryPct); + // Only apply when inventory is consistent with recent and 24h market premium + const offsetPct = markPremiumAvgPct.add(inventoryPct); - const clampedOffsetPct = clampBN( + const clampedOffsetPct = clampBN( offsetPct, new BN(-maxOffsetPct), new BN(maxOffsetPct) ); - return clampedOffsetPct; + return clampedOffsetPct; } export function calculateEffectiveLeverage( @@ -798,7 +822,7 @@ export function calculateSpread( liveOracleStd, amm.longIntensityVolume, amm.shortIntensityVolume, - amm.volume24H, + amm.volume24H ); const longSpread = spreads[0]; const shortSpread = spreads[1]; @@ -855,11 +879,16 @@ export function calculateSpreadReserves( amm.maxSpread / 5, PERCENTAGE_PRECISION.toNumber() / 1000 ); + const liquidityFraction = calculateInventoryLiquidityRatio( + amm.baseAssetAmountWithAmm, + amm.baseAssetReserve, + amm.minBaseAssetReserve, + amm.maxBaseAssetReserve + ); const reservationPriceOffset = calculateReferencePriceOffset( reservePrice, amm.last24HAvgFundingRate, - amm.baseAssetAmountWithAmm, - amm.minOrderSize, + liquidityFraction, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, From 6dd1b787e9d843ed5dc8d7b1554f6aa7687885c9 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:29:59 -0500 Subject: [PATCH 14/23] fix param in sdk/tests/amm/test.ts --- sdk/tests/amm/test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index c5531d419..ba20976d5 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -265,7 +265,6 @@ describe('AMM Tests', () => { longIntensity, shortIntensity, volume24H, - ZERO ); const l1 = spreads[0]; const s1 = spreads[1]; From 320d305c272081b288971c4ec88f7a88c9831434 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:10:23 -0500 Subject: [PATCH 15/23] fix some ts tests --- sdk/src/math/amm.ts | 2 -- sdk/src/user.ts | 4 ++-- sdk/tests/amm/test.ts | 16 ++++++++-------- sdk/tests/dlob/test.ts | 5 +++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index ad4d45f01..98cc720eb 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -775,7 +775,6 @@ export function calculateSpread( oraclePriceData: OraclePriceData, now?: BN, reservePrice?: BN, - reservationPriceOffset = ZERO ): [number, number] { if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) { return [amm.baseSpread / 2, amm.baseSpread / 2]; @@ -901,7 +900,6 @@ export function calculateSpreadReserves( oraclePriceData, now, reservePrice, - reservationPriceOffset ); const askReserves = calculateSpreadReserve( longSpread, diff --git a/sdk/src/user.ts b/sdk/src/user.ts index 0f89325b9..fc11551b6 100644 --- a/sdk/src/user.ts +++ b/sdk/src/user.ts @@ -113,8 +113,8 @@ export class User { this.accountSubscriber = new WebSocketUserAccountSubscriber( config.driftClient.program, config.userAccountPublicKey, - config.accountSubscription.resubTimeoutMs, - config.accountSubscription.commitment + config.accountSubscription?.resubTimeoutMs, + config.accountSubscription?.commitment ); } this.eventEmitter = this.accountSubscriber.eventEmitter; diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index ba20976d5..04ee8ceff 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -361,8 +361,8 @@ describe('AMM Tests', () => { console.log(terms3); assert(terms3.effectiveLeverageCapped >= 1.0002); assert(terms3.inventorySpreadScale == 1.73492); - assert(terms3.longSpread == 4257); - assert(terms3.shortSpread == 43243); + assert(terms3.longSpread == 4262); + assert(terms3.shortSpread == 43238); assert(terms3.longSpread + terms3.shortSpread == 47500); // add spread offset @@ -427,9 +427,9 @@ describe('AMM Tests', () => { console.log(terms2); assert(terms2.effectiveLeverageCapped <= 1.000001); - assert(terms2.inventorySpreadScale == 1.013527); - assert(terms2.longSpread == 1146); - assert(terms2.shortSpread == 6686); + assert(terms2.inventorySpreadScale == 1.0306); + assert(terms2.longSpread == 515); + assert(terms2.shortSpread == 5668); }); it('live update functions', () => { @@ -532,8 +532,8 @@ describe('AMM Tests', () => { assert(markTwapLive.eq(new BN('1949826'))); assert(oracleTwapLive.eq(new BN('1942510'))); - assert(est1.eq(new BN('15692'))); - assert(est2.eq(new BN('15692'))); + assert(est1.eq(new BN('16525'))); + assert(est2.eq(new BN('16525'))); }); it('predicted funding rate mock2', async () => { @@ -622,7 +622,7 @@ describe('AMM Tests', () => { assert(markTwapLive.eq(new BN('1222131'))); assert(oracleTwapLive.eq(new BN('1222586'))); assert(est1.eq(est2)); - assert(est2.eq(new BN('-1550'))); + assert(est2.eq(new BN('-719'))); }); it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders, low liquidity)', async () => { diff --git a/sdk/tests/dlob/test.ts b/sdk/tests/dlob/test.ts index 144b61432..2c71d1182 100644 --- a/sdk/tests/dlob/test.ts +++ b/sdk/tests/dlob/test.ts @@ -2550,8 +2550,9 @@ describe('DLOB Perp Tests', () => { ); expect(takingBids.length).to.equal(1); const triggerLimitBid = takingBids[0]; - expect(isAuctionComplete(triggerLimitBid.order, slot)).to.equal(true); - expect(isRestingLimitOrder(triggerLimitBid.order, slot)).to.equal(false); + expect(triggerLimitBid!==undefined); + expect(isAuctionComplete(triggerLimitBid.order as Order, slot)).to.equal(true); + expect(isRestingLimitOrder(triggerLimitBid.order as Order, slot)).to.equal(false); }); it('Test will return expired market orders to fill', () => { From c9e4499f2f9a11935190f1764d84d0604325a672 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:35:02 -0500 Subject: [PATCH 16/23] fix fmt/prettier --- programs/drift/src/math/amm_spread.rs | 5 +++-- programs/drift/src/math/amm_spread/tests.rs | 8 +++----- sdk/src/math/amm.ts | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index d831d6ea6..6d85c4571 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -491,7 +491,7 @@ pub fn calculate_reference_price_offset( max_offset_pct: i64, ) -> DriftResult { if last_24h_avg_funding_rate == 0 { - return Ok(0) + return Ok(0); } let max_offset_in_price = max_offset_pct @@ -523,7 +523,8 @@ pub fn calculate_reference_price_offset( .safe_mul(PRICE_PRECISION_I64)? .safe_div(reserve_price.cast()?)?; - let inventory_pct = liquidity_fraction.cast::()? + let inventory_pct = liquidity_fraction + .cast::()? .safe_mul(max_offset_pct)? .safe_div(PERCENTAGE_PRECISION.cast::()?)? .clamp(-max_offset_pct, max_offset_pct); diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index 166c3bf1c..d01151607 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -42,10 +42,8 @@ mod test { let rev_price = 4216 * 10000; let max_offset: i64 = 2500; // 25 bps - let res = calculate_reference_price_offset(rev_price, - 0, 0, 0, 0, 0, - 0,0, - max_offset).unwrap(); + let res = + calculate_reference_price_offset(rev_price, 0, 0, 0, 0, 0, 0, 0, max_offset).unwrap(); assert_eq!(res, 0); let res = calculate_reference_price_offset( @@ -129,7 +127,7 @@ mod test { max_offset, ) .unwrap(); - assert_eq!(res, 1660 * 2/3); // 7 penny divergence + assert_eq!(res, 1660 * 2 / 3); // 7 penny divergence let res = calculate_reference_price_offset( rev_price, diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 98cc720eb..6dc303a35 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -774,7 +774,7 @@ export function calculateSpread( amm: AMM, oraclePriceData: OraclePriceData, now?: BN, - reservePrice?: BN, + reservePrice?: BN ): [number, number] { if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) { return [amm.baseSpread / 2, amm.baseSpread / 2]; @@ -899,7 +899,7 @@ export function calculateSpreadReserves( amm, oraclePriceData, now, - reservePrice, + reservePrice ); const askReserves = calculateSpreadReserve( longSpread, From 76eedcbf97be1c00d7d164f5f7173f444ec248e3 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:42:22 -0500 Subject: [PATCH 17/23] add spread reserve logic to typescript --- sdk/src/math/amm.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 6dc303a35..a13d7c9ea 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -848,13 +848,19 @@ export function calculateSpreadReserves( quoteAssetReserve: amm.quoteAssetReserve, }; } - const spreadFraction = BN.max(new BN(spread / 2), ONE); + let spreadFraction = new BN(spread / 2); + + // make non-zero + if(spreadFraction.eq(ZERO)) { + spreadFraction = spread >= 0 ? new BN(1) : new BN(-1); + } + const quoteAssetReserveDelta = amm.quoteAssetReserve.div( BID_ASK_SPREAD_PRECISION.div(spreadFraction) ); let quoteAssetReserve; - if (isVariant(direction, 'long')) { + if (spread >=0 && isVariant(direction, 'long') || spread <=0 && isVariant(direction, 'short')) { quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta); } else { quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta); @@ -884,7 +890,7 @@ export function calculateSpreadReserves( amm.minBaseAssetReserve, amm.maxBaseAssetReserve ); - const reservationPriceOffset = calculateReferencePriceOffset( + const referencePriceOffset = calculateReferencePriceOffset( reservePrice, amm.last24HAvgFundingRate, liquidityFraction, @@ -902,12 +908,12 @@ export function calculateSpreadReserves( reservePrice ); const askReserves = calculateSpreadReserve( - longSpread, + longSpread + referencePriceOffset.toNumber(), PositionDirection.LONG, amm ); const bidReserves = calculateSpreadReserve( - shortSpread, + shortSpread + referencePriceOffset.toNumber(), PositionDirection.SHORT, amm ); From e973135feb8b550eb5a877ac52f9971ed363e1dc Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:47:37 -0500 Subject: [PATCH 18/23] fix idl --- sdk/src/idl/drift.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index ab788a11b..594a3aa70 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -7327,7 +7327,7 @@ "type": "i64" }, { - "name": "reservationPriceOffset", + "name": "referencePriceOffset", "type": "i32" }, { @@ -11108,4 +11108,4 @@ "msg": "InvalidMarginCalculation" } ] -} +} \ No newline at end of file From 4977f38af15508fe90120b2f17f26aba22d2c24c Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:22:14 -0500 Subject: [PATCH 19/23] wip test --- sdk/src/math/amm.ts | 12 ++++++++ sdk/tests/amm/test.ts | 69 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index a13d7c9ea..757f943a7 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -760,6 +760,8 @@ export function calculateSpreadBN( } } + console.log(maxTargetSpread, totalSpread); + spreadTerms.totalSpread = totalSpread; spreadTerms.longSpread = longSpread; spreadTerms.shortSpread = shortSpread; @@ -823,6 +825,7 @@ export function calculateSpread( amm.shortIntensityVolume, amm.volume24H ); + console.log('amm.maxSpread:', amm.maxSpread.toFixed(2)); const longSpread = spreads[0]; const shortSpread = spreads[1]; @@ -853,6 +856,10 @@ export function calculateSpreadReserves( // make non-zero if(spreadFraction.eq(ZERO)) { spreadFraction = spread >= 0 ? new BN(1) : new BN(-1); + console.log('spreadFractioN:', spreadFraction); + } + if(spreadFraction.gt(BID_ASK_SPREAD_PRECISION)) { + console.log('spreadFractioN ERRR:', spreadFraction.toNumber()); } const quoteAssetReserveDelta = amm.quoteAssetReserve.div( @@ -901,12 +908,17 @@ export function calculateSpreadReserves( maxOffset ); + console.log('referencePriceOffset:', referencePriceOffset.toNumber()); + const [longSpread, shortSpread] = calculateSpread( amm, oraclePriceData, now, reservePrice ); + console.log('longSpread:', longSpread); + console.log('shortSpread:', shortSpread); + const askReserves = calculateSpreadReserve( longSpread + referencePriceOffset.toNumber(), PositionDirection.LONG, diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index 04ee8ceff..cd8ce702b 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -4,6 +4,7 @@ import { PRICE_PRECISION, AMM_RESERVE_PRECISION, QUOTE_PRECISION, + calculateSpread, calculateSpreadBN, ZERO, ONE, @@ -19,6 +20,7 @@ import { L2Level, calculateUpdatedAMM, calculateMarketOpenBidAsk, + calculateSpreadReserves, } from '../../src'; import { mockPerpMarkets } from '../dlob/helpers'; @@ -432,8 +434,73 @@ describe('AMM Tests', () => { assert(terms2.shortSpread == 5668); }); + + it('Spread Reserves (with offset)', () => { + const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); + const mockMarket1 = myMockPerpMarkets[0]; + let mockAmm = mockMarket1.amm; + const now = new BN(new Date().getTime() / 1000); //todo + + const oraclePriceData = { + price: new BN(13.553 * PRICE_PRECISION.toNumber()), + slot: new BN(68 + 1), + confidence: new BN(1), + hasSufficientNumberOfDataPoints: true, + }; + + const reserves = calculateSpreadReserves( + mockAmm, + oraclePriceData, + now + ); + assert(reserves[0].baseAssetReserve.eq(new BN('1000000000'))); + assert(reserves[0].quoteAssetReserve.eq(new BN('12000000000'))); + assert(reserves[1].baseAssetReserve.eq(new BN('1000000000'))); + assert(reserves[1].quoteAssetReserve.eq(new BN('12000000000'))); + + mockAmm.ammJitIntensity = 100; + mockAmm.curveUpdateIntensity = 200; + mockAmm.baseSpread = 2500; + mockAmm.maxSpread = 25000; + + mockAmm.last24HAvgFundingRate = new BN(7590328523); + + mockAmm.lastMarkPriceTwap = new BN((oraclePriceData.price.toNumber()/1e6 - .01) * 1e6); + mockAmm.historicalOracleData.lastOraclePriceTwap = new BN((oraclePriceData.price.toNumber()/1e6 + .015) * 1e6); + + mockAmm.historicalOracleData.lastOraclePriceTwap5Min = new BN((oraclePriceData.price.toNumber()/1e6 + .005) * 1e6); + mockAmm.lastMarkPriceTwap5Min = new BN((oraclePriceData.price.toNumber()/1e6 - .005) * 1e6); + + + + const tt = calculateSpread( + mockAmm, + oraclePriceData, + now + ); + console.log(tt); + + const reserves2 = calculateSpreadReserves( + mockAmm, + oraclePriceData, + now + ); + console.log(reserves2); + + + assert(reserves2[0].baseAssetReserve.eq(new BN('1000000000'))); + assert(reserves2[0].quoteAssetReserve.eq(new BN('12000000000'))); + assert(reserves2[1].baseAssetReserve.eq(new BN('1000000000'))); + assert(reserves2[1].quoteAssetReserve.eq(new BN('12000000000'))); + + + }) + + it('live update functions', () => { - const mockAmm = mockPerpMarkets[0].amm; + const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); + const mockMarket1 = myMockPerpMarkets[0]; + const mockAmm = mockMarket1.amm; const now = new BN(new Date().getTime() / 1000); //todo const oraclePriceData = { From f49bd28a7438dd5095998ece1ce90982d5091700 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Wed, 6 Dec 2023 09:41:33 -0500 Subject: [PATCH 20/23] wip sdk test --- sdk/src/math/amm.ts | 3 ++- sdk/tests/amm/test.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 757f943a7..d8520df35 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -750,6 +750,7 @@ export function calculateSpreadBN( spreadTerms.shortSpreadwRevRetreat = shortSpread; const totalSpread = longSpread + shortSpread; + console.log(totalSpread, maxTargetSpread); if (totalSpread > maxTargetSpread) { if (longSpread > shortSpread) { longSpread = Math.ceil((longSpread * maxTargetSpread) / totalSpread); @@ -765,7 +766,7 @@ export function calculateSpreadBN( spreadTerms.totalSpread = totalSpread; spreadTerms.longSpread = longSpread; spreadTerms.shortSpread = shortSpread; - + console.log(spreadTerms); if (returnTerms) { return spreadTerms; } diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index cd8ce702b..2301b8b74 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -21,6 +21,8 @@ import { calculateUpdatedAMM, calculateMarketOpenBidAsk, calculateSpreadReserves, + calculatePrice, + BID_ASK_SPREAD_PRECISION } from '../../src'; import { mockPerpMarkets } from '../dlob/helpers'; @@ -458,6 +460,11 @@ describe('AMM Tests', () => { assert(reserves[1].baseAssetReserve.eq(new BN('1000000000'))); assert(reserves[1].quoteAssetReserve.eq(new BN('12000000000'))); + + mockAmm.baseAssetReserve = new BN(1000000000); + mockAmm.quoteAssetReserve = new BN(1000000000); + mockAmm.baseAssetAmountWithAmm = new BN(0); + mockAmm.pegMultiplier = new BN(13553); mockAmm.ammJitIntensity = 100; mockAmm.curveUpdateIntensity = 200; mockAmm.baseSpread = 2500; @@ -471,7 +478,34 @@ describe('AMM Tests', () => { mockAmm.historicalOracleData.lastOraclePriceTwap5Min = new BN((oraclePriceData.price.toNumber()/1e6 + .005) * 1e6); mockAmm.lastMarkPriceTwap5Min = new BN((oraclePriceData.price.toNumber()/1e6 - .005) * 1e6); + console.log('starting rr:'); + let reservePrice = undefined + if (!reservePrice) { + reservePrice = calculatePrice( + mockAmm.baseAssetReserve, + mockAmm.quoteAssetReserve, + mockAmm.pegMultiplier + ); + } + + const targetPrice = oraclePriceData?.price || reservePrice; + const confInterval = oraclePriceData.confidence || ZERO; + const targetMarkSpreadPct = reservePrice + .sub(targetPrice) + .mul(BID_ASK_SPREAD_PRECISION) + .div(reservePrice); + + const confIntervalPct = confInterval + .mul(BID_ASK_SPREAD_PRECISION) + .div(reservePrice); + + // now = now || new BN(new Date().getTime() / 1000); //todo + const liveOracleStd = calculateLiveOracleStd(mockAmm, oraclePriceData, now); + console.log('reservePrice:', reservePrice.toString()); + console.log('targetMarkSpreadPct:', targetMarkSpreadPct.toString()); + console.log('confIntervalPct:', confIntervalPct.toString()); + console.log('liveOracleStd:', liveOracleStd.toString()); const tt = calculateSpread( mockAmm, @@ -495,6 +529,7 @@ describe('AMM Tests', () => { }) + return 0; it('live update functions', () => { From c93f93afc1e90461ba143cb6283d02af183e8d25 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:59:51 -0500 Subject: [PATCH 21/23] working spread offset test --- sdk/src/driftClient.ts | 4 +-- sdk/src/math/amm.ts | 14 +++++++-- sdk/tests/amm/test.ts | 67 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index f7ccf30b2..a6957f2a4 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -1112,9 +1112,7 @@ export class DriftClient { ); } - public async getUserDeletionIx( - userAccountPublicKey: PublicKey - ) { + public async getUserDeletionIx(userAccountPublicKey: PublicKey) { const ix = await this.program.instruction.deleteUser({ accounts: { user: userAccountPublicKey, diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index d55b2ba92..aefd8b25a 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -419,7 +419,7 @@ export function calculateInventoryScale( return inventoryScaleCapped; } -function calculateReferencePriceOffset( +export function calculateReferencePriceOffset( reservePrice: BN, last24hAvgFundingRate: BN, liquidityFraction: BN, @@ -597,6 +597,11 @@ export function calculateSpreadBN( ) { assert(Number.isInteger(baseSpread)); assert(Number.isInteger(maxSpread)); + console.log('max spread;', maxSpread); + console.log( + 'lastOracleReservePriceSpreadPct:', + lastOracleReservePriceSpreadPct.toNumber() + ); const spreadTerms = { longVolSpread: 0, @@ -876,7 +881,12 @@ export function calculateSpreadReserves( } else { quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta); } - + console.log( + 'amm.sqrtK:', + amm.sqrtK.toString(), + amm.sqrtK.mul(amm.sqrtK).toString(), + quoteAssetReserve.toString() + ); const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve); return { baseAssetReserve, diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index b425a6303..9fb081cd6 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -4,6 +4,7 @@ import { PRICE_PRECISION, AMM_RESERVE_PRECISION, QUOTE_PRECISION, + PERCENTAGE_PRECISION, calculateSpread, calculateSpreadBN, ZERO, @@ -23,6 +24,9 @@ import { calculateSpreadReserves, calculatePrice, BID_ASK_SPREAD_PRECISION, + squareRootBN, + calculateReferencePriceOffset, + calculateInventoryLiquidityRatio, } from '../../src'; import { mockPerpMarkets } from '../dlob/helpers'; @@ -457,8 +461,10 @@ describe('AMM Tests', () => { mockAmm.baseAssetReserve = new BN(1000000000); mockAmm.quoteAssetReserve = new BN(1000000000); + mockAmm.sqrtK = new BN(1000000000); + mockAmm.baseAssetAmountWithAmm = new BN(0); - mockAmm.pegMultiplier = new BN(13553); + mockAmm.pegMultiplier = new BN(13.553 * PEG_PRECISION.toNumber()); mockAmm.ammJitIntensity = 100; mockAmm.curveUpdateIntensity = 200; mockAmm.baseSpread = 2500; @@ -512,15 +518,62 @@ describe('AMM Tests', () => { const tt = calculateSpread(mockAmm, oraclePriceData, now); console.log(tt); + console.log('amm.baseAssetReserve:', mockAmm.baseAssetReserve.toString()); + assert(mockAmm.baseAssetReserve.eq(new BN('1000000000'))); const reserves2 = calculateSpreadReserves(mockAmm, oraclePriceData, now); - console.log(reserves2); + console.log(reserves2[1].baseAssetReserve.toString()); + console.log(reserves2[1].quoteAssetReserve.toString()); + + assert(reserves2[0].baseAssetReserve.eq(new BN('1006711408'))); + assert(reserves2[0].quoteAssetReserve.eq(new BN('993333334'))); + assert(reserves2[1].baseAssetReserve.eq(new BN('993377484'))); + assert(reserves2[1].quoteAssetReserve.eq(new BN('1006666666'))); + + // create imbalance for reference price offset + mockAmm.baseAssetReserve = new BN(1000000000 * 1.1); + mockAmm.quoteAssetReserve = new BN(1000000000 / 1.1); + mockAmm.sqrtK = squareRootBN( + mockAmm.baseAssetReserve.mul(mockAmm.quoteAssetReserve) + ); + + mockAmm.baseAssetAmountWithAmm = new BN(-1000000000 * 0.1); - assert(reserves2[0].baseAssetReserve.eq(new BN('1000000000'))); - assert(reserves2[0].quoteAssetReserve.eq(new BN('12000000000'))); - assert(reserves2[1].baseAssetReserve.eq(new BN('1000000000'))); - assert(reserves2[1].quoteAssetReserve.eq(new BN('12000000000'))); + const maxOffset = Math.max( + mockAmm.maxSpread / 5, + PERCENTAGE_PRECISION.toNumber() / 1000 + ); + const liquidityFraction = calculateInventoryLiquidityRatio( + mockAmm.baseAssetAmountWithAmm, + mockAmm.baseAssetReserve, + mockAmm.minBaseAssetReserve, + mockAmm.maxBaseAssetReserve + ); + console.log('liquidityFraction:', liquidityFraction.toString()); + assert(liquidityFraction.eq(new BN(1000000))); // full + + const referencePriceOffset = calculateReferencePriceOffset( + reservePrice, + mockAmm.last24HAvgFundingRate, + liquidityFraction, + mockAmm.historicalOracleData.lastOraclePriceTwap5Min, + mockAmm.lastMarkPriceTwap5Min, + mockAmm.historicalOracleData.lastOraclePriceTwap, + mockAmm.lastMarkPriceTwap, + maxOffset + ); + console.log('referencePriceOffset:', referencePriceOffset.toString()); + assert(referencePriceOffset.eq(new BN(5000))); + assert(referencePriceOffset.eq(new BN(maxOffset))); + + const reserves3 = calculateSpreadReserves(mockAmm, oraclePriceData, now); + console.log(reserves3[1].baseAssetReserve.toString()); + console.log(reserves3[1].quoteAssetReserve.toString()); + + assert(reserves3[0].baseAssetReserve.eq(new BN('1164705879'))); + assert(reserves3[0].quoteAssetReserve.eq(new BN('858585859'))); + assert(reserves3[1].baseAssetReserve.eq(new BN('1042105261'))); + assert(reserves3[1].quoteAssetReserve.eq(new BN('959595959'))); }); - return 0; it('live update functions', () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); From f1da8eecc7b6806c7ba1a3ae0faf750fe7296d60 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:50:23 -0500 Subject: [PATCH 22/23] calculate_reference_price_offset: ensure sign is consistent with two terms --- programs/drift/src/math/amm_spread.rs | 8 +++++++- sdk/src/math/amm.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index 6d85c4571..3334fd5f7 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -530,7 +530,13 @@ pub fn calculate_reference_price_offset( .clamp(-max_offset_pct, max_offset_pct); // only apply when inventory is consistent with recent and 24h market premium - let offset_pct = mark_premium_avg_pct.safe_add(inventory_pct)?; + let offset_pct = if (mark_premium_avg_pct >= 0 && inventory_pct >= 0) + || (mark_premium_avg_pct <= 0 && inventory_pct <= 0) + { + mark_premium_avg_pct.safe_add(inventory_pct)? + } else { + 0 + }; let clamped_offset_pct = offset_pct.clamp(-max_offset_pct, max_offset_pct); diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index aefd8b25a..e0f622fa7 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -23,7 +23,7 @@ import { isVariant, } from '../types'; import { assert } from '../assert/assert'; -import { squareRootBN, clampBN, standardizeBaseAssetAmount } from '..'; +import { squareRootBN, sigNum, clampBN, standardizeBaseAssetAmount } from '..'; import { OraclePriceData } from '../oracles/types'; import { @@ -474,7 +474,11 @@ export function calculateReferencePriceOffset( ); // Only apply when inventory is consistent with recent and 24h market premium - const offsetPct = markPremiumAvgPct.add(inventoryPct); + let offsetPct = markPremiumAvgPct.add(inventoryPct); + + if (!sigNum(inventoryPct).eq(sigNum(markPremiumAvgPct))) { + offsetPct = ZERO; + } const clampedOffsetPct = clampBN( offsetPct, From eb25a25805062124db95925019a0402fad174870 Mon Sep 17 00:00:00 2001 From: 0xbigz <83473873+0xbigz@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:01:40 -0500 Subject: [PATCH 23/23] update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d5921b29..fa6f2afdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- program: amm can use reference price offset from oracle price based on clamped inventory and persist market premiums ([#681](https://github.com/drift-labs/protocol-v2/pull/681)) + ### Fixes ### Breaking @@ -55,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - program: account for step size when canceling reduce only orders ### Breaking + - sdk: UserStatsMap use bulkAccountLoader (`UserStatsMap.subscribe` and `UserStatsMap.sync` now requires list of authorities) ([#716](https://github.com/drift-labs/protocol-v2/pull/716)) ## [2.47.0] - 2023-11-26 @@ -134,6 +137,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - program: add positive perp funding rate offset ([#576](https://github.com/drift-labs/protocol-v2/pull/576/files)) ### Fixes + - program: add validation check in update max imbalances ([#667](https://github.com/drift-labs/protocol-v2/pull/667)) ### Breaking