diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index 0012b4804f..7162e9d2dd 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -185,20 +185,33 @@ pub fn update_spreads( let max_ref_offset = market.amm.get_max_reference_price_offset()?; let reference_price_offset = if max_ref_offset > 0 { - let liquidity_ratio = amm_spread::calculate_inventory_liquidity_ratio( - market.amm.base_asset_amount_with_amm, - market.amm.base_asset_reserve, - market.amm.min_base_asset_reserve, - market.amm.max_base_asset_reserve, - )?; + let liquidity_ratio = + amm_spread::calculate_inventory_liquidity_ratio_for_reference_price_offset( + market.amm.base_asset_amount_with_amm, + market.amm.base_asset_reserve, + market.amm.min_base_asset_reserve, + market.amm.max_base_asset_reserve, + )?; let signed_liquidity_ratio = liquidity_ratio.safe_mul(market.amm.get_protocol_owned_position()?.signum().cast()?)?; + let deadband_pct = market.amm.get_reference_price_offset_deadband_pct()?; + let liquidity_fraction_after_deadband = + if signed_liquidity_ratio.unsigned_abs() <= deadband_pct { + 0 + } else { + signed_liquidity_ratio.safe_sub( + deadband_pct + .cast::()? + .safe_mul(signed_liquidity_ratio.signum())?, + )? + }; + amm_spread::calculate_reference_price_offset( reserve_price, market.amm.last_24h_avg_funding_rate, - signed_liquidity_ratio, + liquidity_fraction_after_deadband, market.amm.min_order_size, market .amm diff --git a/programs/drift/src/controller/position/tests.rs b/programs/drift/src/controller/position/tests.rs index ab6cf1034c..89f8305340 100644 --- a/programs/drift/src/controller/position/tests.rs +++ b/programs/drift/src/controller/position/tests.rs @@ -843,7 +843,7 @@ fn amm_ref_price_offset_decay_logic() { .unwrap(); assert_eq!(perp_market.amm.last_update_slot, clock_slot); assert_eq!(perp_market.amm.last_oracle_valid, true); - assert_eq!(perp_market.amm.reference_price_offset, 7350); + assert_eq!(perp_market.amm.reference_price_offset, 4458); perp_market.amm.last_mark_price_twap_5min = (perp_market .amm @@ -893,28 +893,28 @@ fn amm_ref_price_offset_decay_logic() { assert_eq!( offsets, [ - 7140, 6930, 6720, 6510, 6300, 6090, 6070, 6050, 6030, 6010, 5800, 5590, 5380, 5170, - 4960, 4750, 4540, 4330, 4120, 3910, 3700, 3490, 3280, 3070, 2860, 2650, 2440, 2230, - 2020, 1810, 1620, 1449, 1296, 1158, 1034, 922, 821, 730, 648, 575, 509, 450, 396, 348, - 305, 266, 231, 199, 171, 145, 122, 101, 81, 61, 41, 21, 1, 0, 0, 0 + 4248, 4038, 3828, 3618, 3408, 3198, 3178, 3158, 3138, 3118, 2908, 2698, 2488, 2278, + 2068, 1858, 1664, 1489, 1332, 1190, 1062, 947, 844, 751, 667, 592, 524, 463, 408, 359, + 315, 275, 239, 207, 178, 152, 128, 107, 87, 67, 47, 27, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( lspreads, [ - 726, 726, 726, 726, 726, 726, 536, 536, 536, 536, 726, 726, 726, 726, 726, 726, 726, - 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 706, 687, 669, 654, - 640, 628, 617, 607, 598, 589, 582, 575, 570, 564, 559, 555, 551, 548, 544, 542, 539, - 537, 536, 536, 536, 536, 536, 526, 526, 526 + 726, 726, 726, 726, 726, 726, 536, 536, 536, 536, 726, 726, 726, 726, 726, 726, 710, + 691, 673, 658, 644, 631, 619, 609, 600, 591, 584, 577, 571, 565, 560, 556, 552, 548, + 545, 542, 540, 537, 536, 536, 536, 536, 536, 526, 526, 526, 526, 526, 526, 526, 526, + 526, 526, 526, 526, 526, 526, 526, 526, 526 ] ); assert_eq!( sspreads, [ - 7150, 6940, 6730, 6520, 6310, 6100, 6080, 6060, 6040, 6020, 5810, 5600, 5390, 5180, - 4970, 4760, 4550, 4340, 4130, 3920, 3710, 3500, 3290, 3080, 2870, 2660, 2450, 2240, - 2030, 1820, 1630, 1459, 1306, 1168, 1044, 932, 831, 740, 658, 585, 519, 460, 406, 358, - 315, 276, 241, 209, 181, 155, 132, 111, 91, 71, 51, 31, 11, 10, 10, 10 + 4258, 4048, 3838, 3628, 3418, 3208, 3188, 3168, 3148, 3128, 2918, 2708, 2498, 2288, + 2078, 1868, 1674, 1499, 1342, 1200, 1072, 957, 854, 761, 677, 602, 534, 473, 418, 369, + 325, 285, 249, 217, 188, 162, 138, 117, 97, 77, 57, 37, 17, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 ] ); } @@ -1017,7 +1017,7 @@ fn amm_negative_ref_price_offset_decay_logic() { .unwrap(); assert_eq!(perp_market.amm.last_update_slot, clock_slot); assert_eq!(perp_market.amm.last_oracle_valid, true); - assert_eq!(perp_market.amm.reference_price_offset, 7350); + assert_eq!(perp_market.amm.reference_price_offset, 4458); perp_market.amm.last_mark_price_twap_5min = (perp_market .amm @@ -1068,34 +1068,31 @@ fn amm_negative_ref_price_offset_decay_logic() { assert_eq!( offsets, [ - -7140, -6930, -6720, -6510, -6300, -6090, -6070, -6050, -6030, -6010, -5800, -5590, - -5380, -5170, -4960, -4750, -4540, -4330, -4120, -3910, -3700, -3490, -3280, -3070, - -2860, -2650, -2440, -2230, -2020, -1810, -1600, -1390, -1180, -970, -760, -550, -340, - -130, 0, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, - 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, - 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, - 10000, 10000, 10000, 10000, 10000, 10000 + -4248, -4038, -3828, -3618, -3408, -3198, -3178, -3158, -3138, -3118, -2908, -2698, + -2488, -2278, -2068, -1858, -1648, -1438, -1228, -1018, -808, -598, -388, -178, 0, + 7654, 7652, 7651, 7649, 7648, 7646, 7645, 7643, 7641, 7640, 7638, 7637, 7635, 7634, + 7632, 7631, 7629, 7628, 7626, 7625, 7623, 7622, 7620, 7619, 7618, 7616, 7615, 7613, + 7612, 7610, 7609, 7607, 7606, 7605, 7603, 7602, 7600, 7599, 7597, 7596, 7595, 7593, + 7592, 7591, 7589, 7588, 7586, 7585, 7584, 7582, 7581, 7580, 7578, 7577, 7576 ] ); assert_eq!( sspreads, [ 210, 210, 210, 210, 210, 210, 20, 20, 20, 20, 210, 210, 210, 210, 210, 210, 210, 210, - 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, - 210, 210, 210, 130, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 210, 210, 210, 210, 210, 210, 178, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 10 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 ] ); assert_eq!( lspreads, [ - 7666, 7456, 7246, 7036, 6826, 6616, 6596, 6576, 6556, 6536, 6326, 6116, 5906, 5696, - 5486, 5276, 5066, 4856, 4646, 4436, 4226, 4016, 3806, 3596, 3386, 3176, 2966, 2756, - 2546, 2336, 2126, 1916, 1706, 1496, 1286, 1076, 866, 656, 526, 526, 526, 526, 526, 526, + 4774, 4564, 4354, 4144, 3934, 3724, 3704, 3684, 3664, 3644, 3434, 3224, 3014, 2804, + 2594, 2384, 2174, 1964, 1754, 1544, 1334, 1124, 914, 704, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, - 526, 526 + 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526 ] ); } diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 94fe90f15f..dd4a0d72fa 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -1079,7 +1079,8 @@ pub fn handle_initialize_perp_market( quote_asset_amount_with_unsettled_lp: 0, reference_price_offset: 0, amm_inventory_spread_adjustment: 0, - padding: [0; 3], + reference_price_offset_deadband_pct: 0, + padding: [0; 2], last_funding_oracle_twap: 0, }, }; @@ -3439,6 +3440,49 @@ pub fn handle_update_perp_market_curve_update_intensity( Ok(()) } +#[access_control( + perp_market_valid(&ctx.accounts.perp_market) +)] +pub fn handle_update_perp_market_reference_price_offset_deadband_pct( + ctx: Context, + reference_price_offset_deadband_pct: u8, +) -> Result<()> { + validate!( + reference_price_offset_deadband_pct <= 100, + ErrorCode::DefaultError, + "invalid reference_price_offset_deadband_pct", + )?; + let perp_market = &mut load_mut!(ctx.accounts.perp_market)?; + msg!("perp market {}", perp_market.market_index); + + msg!( + "perp_market.amm.reference_price_offset_deadband_pct: {} -> {}", + perp_market.amm.reference_price_offset_deadband_pct, + reference_price_offset_deadband_pct + ); + + let liquidity_ratio = + crate::math::amm_spread::calculate_inventory_liquidity_ratio_for_reference_price_offset( + perp_market.amm.base_asset_amount_with_amm, + perp_market.amm.base_asset_reserve, + perp_market.amm.min_base_asset_reserve, + perp_market.amm.max_base_asset_reserve, + )?; + + let signed_liquidity_ratio = liquidity_ratio.safe_mul( + perp_market + .amm + .get_protocol_owned_position()? + .signum() + .cast()?, + )?; + + msg!("current signed liquidity ratio: {}", signed_liquidity_ratio); + + perp_market.amm.reference_price_offset_deadband_pct = reference_price_offset_deadband_pct; + Ok(()) +} + pub fn handle_update_lp_cooldown_time( ctx: Context, lp_cooldown_time: u64, diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index e71b6de2a1..d91c7e80bc 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -1387,6 +1387,16 @@ pub mod drift { handle_update_perp_market_curve_update_intensity(ctx, curve_update_intensity) } + pub fn update_perp_market_reference_price_offset_deadband_pct( + ctx: Context, + reference_price_offset_deadband_pct: u8, + ) -> Result<()> { + handle_update_perp_market_reference_price_offset_deadband_pct( + ctx, + reference_price_offset_deadband_pct, + ) + } + pub fn update_lp_cooldown_time( ctx: Context, lp_cooldown_time: u64, diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index 80915b83b9..cb90937315 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -12,8 +12,8 @@ use crate::math::constants::{ BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_I128, 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_I128, PERCENTAGE_PRECISION_U64, PRICE_PRECISION, PRICE_PRECISION_I128, - PRICE_PRECISION_I64, + PERCENTAGE_PRECISION_I128, PERCENTAGE_PRECISION_I64, PERCENTAGE_PRECISION_U64, PRICE_PRECISION, + PRICE_PRECISION_I128, PRICE_PRECISION_I64, }; use crate::math::safe_math::SafeMath; use crate::state::perp_market::{ContractType, PerpMarket, AMM}; @@ -193,6 +193,35 @@ pub fn calculate_inventory_liquidity_ratio( Ok(amm_inventory_pct) } +pub fn calculate_inventory_liquidity_ratio_for_reference_price_offset( + base_asset_amount_with_amm: i128, + base_asset_reserve: u128, + min_base_asset_reserve: u128, + max_base_asset_reserve: u128, +) -> DriftResult { + // inventory scale + let (max_bids, max_asks) = _calculate_market_open_bids_asks( + base_asset_reserve, + min_base_asset_reserve, + max_base_asset_reserve, + )?; + + let avg_liquidity = (max_bids.safe_add(max_asks.abs())?).safe_div(2)?; + + let amm_inventory_pct = if base_asset_amount_with_amm.abs() < avg_liquidity { + base_asset_amount_with_amm + .abs() + .safe_mul(PERCENTAGE_PRECISION_I128) + .unwrap_or(i128::MAX) + .safe_div(avg_liquidity.max(1))? + .min(PERCENTAGE_PRECISION_I128) + } else { + PERCENTAGE_PRECISION_I128 // 100% + }; + + Ok(amm_inventory_pct) +} + pub fn calculate_spread_inventory_scale( base_asset_amount_with_amm: i128, base_asset_reserve: u128, @@ -570,7 +599,7 @@ pub fn calculate_reference_price_offset( mark_twap_slow: u64, max_offset_pct: i64, ) -> DriftResult { - if last_24h_avg_funding_rate == 0 { + if last_24h_avg_funding_rate == 0 || liquidity_fraction == 0 { return Ok(0); } diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index 5953db1e04..ac57fb45df 100644 --- a/programs/drift/src/math/amm_spread/tests.rs +++ b/programs/drift/src/math/amm_spread/tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod test { + use crate::controller::amm::update_spreads; use crate::math::amm::calculate_price; use crate::math::amm_spread::*; use crate::math::constants::{ @@ -189,6 +190,73 @@ mod test { assert_eq!(res, 0); } + #[test] + fn calculate_reference_price_offset_deadband_tests() { + let mut market = PerpMarket { + amm: AMM { + base_asset_reserve: AMM_RESERVE_PRECISION * 11, + quote_asset_reserve: AMM_RESERVE_PRECISION * 10, + sqrt_k: AMM_RESERVE_PRECISION * 10, + peg_multiplier: 34_000_000, + min_base_asset_reserve: AMM_RESERVE_PRECISION * 7, + max_base_asset_reserve: AMM_RESERVE_PRECISION * 14, + base_spread: 1000, + max_spread: 20_000, + curve_update_intensity: 110, + last_24h_avg_funding_rate: 1, + last_mark_price_twap_5min: 4216 * 10000 + 2 * 10000, + last_mark_price_twap: 4216 * 10000 + 2 * 10000, + historical_oracle_data: { + let mut hod: crate::state::oracle::HistoricalOracleData = Default::default(); + hod.last_oracle_price_twap_5min = 4216 * 10000; + hod.last_oracle_price_twap = 4216 * 10000; + hod + }, + ..AMM::default() + }, + ..PerpMarket::default() + }; + + let reserve_price = 4216 * 10000; + + market.amm.base_asset_amount_with_amm = (AMM_RESERVE_PRECISION * 7 / 20) as i128; + let inventory_ratio = calculate_inventory_liquidity_ratio_for_reference_price_offset( + market.amm.base_asset_amount_with_amm, + market.amm.base_asset_reserve, + market.amm.min_base_asset_reserve, + market.amm.max_base_asset_reserve, + ) + .unwrap(); + assert_eq!(inventory_ratio, 100000); // 10% + + market.amm.reference_price_offset_deadband_pct = 10; // 10% + + // If inventory exceeds threshold positive ref price offset + market.amm.base_asset_amount_with_amm = (AMM_RESERVE_PRECISION * 8 / 20) as i128; + let (_l, _s) = update_spreads(&mut market, reserve_price as u64, None).unwrap(); + assert!(market.amm.reference_price_offset > 0); + + // If inventory is small, goes to 0 + market.amm.base_asset_amount_with_amm = (AMM_RESERVE_PRECISION * 6 / 20) as i128; + let (_l, _s) = update_spreads(&mut market, reserve_price as u64, None).unwrap(); + assert_eq!(market.amm.reference_price_offset, 0); + + // Same for short pos + // Make sure that the premium is also short + market.amm.last_24h_avg_funding_rate = -1; + market.amm.last_mark_price_twap_5min = 4216 * 10000 - 2 * 10000; + market.amm.last_mark_price_twap = 4216 * 10000 - 2 * 10000; + market.amm.base_asset_amount_with_amm = (AMM_RESERVE_PRECISION * 8 / 20) as i128 * -1; + let (_l, _s) = update_spreads(&mut market, reserve_price as u64, None).unwrap(); + println!("ref offset: {}", market.amm.reference_price_offset); + assert!(market.amm.reference_price_offset < 0); + + // Same for short pos + market.amm.base_asset_amount_with_amm = (AMM_RESERVE_PRECISION * 6 / 20) as i128 * -1; + let (_l, _s) = update_spreads(&mut market, reserve_price as u64, None).unwrap(); + assert_eq!(market.amm.reference_price_offset, 0); + } + #[test] fn calculate_spread_tests() { let base_spread = 1000; // .1% diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 815fa83fc2..69d2e80f38 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -1167,7 +1167,8 @@ pub struct AMM { pub reference_price_offset: i32, /// signed scale amm_spread similar to fee_adjustment logic (-100 = 0, 100 = double) pub amm_inventory_spread_adjustment: i8, - pub padding: [u8; 3], + pub reference_price_offset_deadband_pct: u8, + pub padding: [u8; 2], pub last_funding_oracle_twap: i64, } @@ -1258,13 +1259,18 @@ impl Default for AMM { quote_asset_amount_with_unsettled_lp: 0, reference_price_offset: 0, amm_inventory_spread_adjustment: 0, - padding: [0; 3], + reference_price_offset_deadband_pct: 0, + padding: [0; 2], last_funding_oracle_twap: 0, } } } impl AMM { + pub fn get_reference_price_offset_deadband_pct(&self) -> DriftResult { + let pct = self.reference_price_offset_deadband_pct as u128; + Ok(PERCENTAGE_PRECISION.safe_mul(pct)?.safe_div(100_u128)?) + } pub fn get_fallback_price( self, direction: &PositionDirection, diff --git a/sdk/src/adminClient.ts b/sdk/src/adminClient.ts index b5b6244866..ad5307ad70 100644 --- a/sdk/src/adminClient.ts +++ b/sdk/src/adminClient.ts @@ -1232,6 +1232,46 @@ export class AdminClient extends DriftClient { ); } + public async updatePerpMarketReferencePriceOffsetDeadbandPct( + perpMarketIndex: number, + referencePriceOffsetDeadbandPct: number + ): Promise { + const updatePerpMarketReferencePriceOffsetDeadbandPctIx = + await this.getUpdatePerpMarketReferencePriceOffsetDeadbandPctIx( + perpMarketIndex, + referencePriceOffsetDeadbandPct + ); + + const tx = await this.buildTransaction( + updatePerpMarketReferencePriceOffsetDeadbandPctIx + ); + + const { txSig } = await this.sendTransaction(tx, [], this.opts); + + return txSig; + } + + public async getUpdatePerpMarketReferencePriceOffsetDeadbandPctIx( + perpMarketIndex: number, + referencePriceOffsetDeadbandPct: number + ): Promise { + return await this.program.instruction.updatePerpMarketReferencePriceOffsetDeadbandPct( + referencePriceOffsetDeadbandPct, + { + accounts: { + admin: this.useHotWalletAdmin + ? this.wallet.publicKey + : this.getStateAccount().admin, + state: await this.getStatePublicKey(), + perpMarket: await getPerpMarketPublicKey( + this.program.programId, + perpMarketIndex + ), + }, + } + ); + } + public async updatePerpMarketTargetBaseAssetAmountPerLp( perpMarketIndex: number, targetBaseAssetAmountPerLP: number diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index 10f90b9c8d..ed162a931f 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -5874,6 +5874,32 @@ } ] }, + { + "name": "updatePerpMarketReferencePriceOffsetDeadbandPct", + "accounts": [ + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "state", + "isMut": false, + "isSigner": false + }, + { + "name": "perpMarket", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "referencePriceOffsetDeadbandPct", + "type": "u8" + } + ] + }, { "name": "updateLpCooldownTime", "accounts": [ @@ -11209,12 +11235,16 @@ ], "type": "i8" }, + { + "name": "referencePriceOffsetDeadbandPct", + "type": "u8" + }, { "name": "padding", "type": { "array": [ "u8", - 3 + 2 ] } }, @@ -16782,8 +16812,5 @@ "name": "UnableToLoadRevenueShareAccount", "msg": "Unable to load builder account" } - ], - "metadata": { - "address": "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH" - } + ] } \ No newline at end of file diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index 7b41b4a783..88a9ec9f5b 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -389,6 +389,31 @@ export function calculateInventoryLiquidityRatio( return inventoryScaleBN; } +export function calculateInventoryLiquidityRatioForReferencePriceOffset( + baseAssetAmountWithAmm: BN, + baseAssetReserve: BN, + minBaseAssetReserve: BN, + maxBaseAssetReserve: BN +): BN { + // inventory skew + const [openBids, openAsks] = calculateMarketOpenBidAsk( + baseAssetReserve, + minBaseAssetReserve, + maxBaseAssetReserve + ); + + const avgSideLiquidity = openBids.abs().add(openAsks.abs()).div(TWO); + + const inventoryScaleBN = BN.min( + baseAssetAmountWithAmm + .mul(PERCENTAGE_PRECISION) + .div(BN.max(avgSideLiquidity, ONE)) + .abs(), + PERCENTAGE_PRECISION + ); + return inventoryScaleBN; +} + export function calculateInventoryScale( baseAssetAmountWithAmm: BN, baseAssetReserve: BN, @@ -440,7 +465,7 @@ export function calculateReferencePriceOffset( markTwapSlow: BN, maxOffsetPct: number ): BN { - if (last24hAvgFundingRate.eq(ZERO)) { + if (last24hAvgFundingRate.eq(ZERO) || liquidityFraction.eq(ZERO)) { return ZERO; } @@ -1013,19 +1038,38 @@ export function calculateSpreadReserves( (amm.curveUpdateIntensity - 100) ); - const liquidityFraction = calculateInventoryLiquidityRatio( - amm.baseAssetAmountWithAmm, - amm.baseAssetReserve, - amm.minBaseAssetReserve, - amm.maxBaseAssetReserve - ); + const liquidityFraction = + calculateInventoryLiquidityRatioForReferencePriceOffset( + amm.baseAssetAmountWithAmm, + amm.baseAssetReserve, + amm.minBaseAssetReserve, + amm.maxBaseAssetReserve + ); const liquidityFractionSigned = liquidityFraction.mul( sigNum(amm.baseAssetAmountWithAmm.add(amm.baseAssetAmountWithUnsettledLp)) ); + + let liquidityFractionAfterDeadband = liquidityFractionSigned; + const deadbandPct = amm.referencePriceOffsetDeadbandPct + ? PERCENTAGE_PRECISION.mul( + new BN(amm.referencePriceOffsetDeadbandPct as number) + ).divn(100) + : ZERO; + if (!liquidityFractionAfterDeadband.eq(ZERO) && deadbandPct.gt(ZERO)) { + const abs = liquidityFractionAfterDeadband.abs(); + if (abs.lte(deadbandPct)) { + liquidityFractionAfterDeadband = ZERO; + } else { + liquidityFractionAfterDeadband = liquidityFractionAfterDeadband.sub( + deadbandPct.mul(sigNum(liquidityFractionAfterDeadband)) + ); + } + } + referencePriceOffset = calculateReferencePriceOffset( reservePrice, amm.last24HAvgFundingRate, - liquidityFractionSigned, + liquidityFractionAfterDeadband, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, diff --git a/sdk/src/types.ts b/sdk/src/types.ts index ae9fc45533..fd0cb165cd 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -1112,6 +1112,7 @@ export type AMM = { ammInventorySpreadAdjustment: number; lastFundingOracleTwap: BN; + referencePriceOffsetDeadbandPct: number; }; // # User Account Types diff --git a/tests/admin.ts b/tests/admin.ts index 7eb87c396a..c397b3f350 100644 --- a/tests/admin.ts +++ b/tests/admin.ts @@ -472,6 +472,19 @@ describe('admin', () => { assert(perpMarket.amm.ammSpreadAdjustment == ammSpreadAdjustment); }); + it('update perp market reference offset deadband pct', async () => { + const referenceOffsetDeadbandPct = 5; + await driftClient.updatePerpMarketReferencePriceOffsetDeadbandPct( + 0, + referenceOffsetDeadbandPct + ); + const perpMarket = driftClient.getPerpMarketAccount(0); + assert( + perpMarket.amm.referencePriceOffsetDeadbandPct == + referenceOffsetDeadbandPct + ); + }); + it('update pnl pool', async () => { const quoteVault = driftClient.getSpotMarketAccount(0).vault;