diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index 9aa33e087..a2d90b586 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -209,6 +209,14 @@ pub fn update_spreads(market: &mut PerpMarket, reserve_price: u64) -> DriftResul 0 }; + let reference_price_decay = + if reference_price_offset == 0 && market.amm.reference_price_offset != 0 { + // smooth decay toward 0 + market.amm.reference_price_offset.safe_div(10)? + } else { + 0 + }; + let (mut long_spread, mut short_spread) = if market.amm.curve_update_intensity > 0 { amm_spread::calculate_spread( market.amm.base_spread, @@ -257,7 +265,27 @@ pub fn update_spreads(market: &mut PerpMarket, reserve_price: u64) -> DriftResul market.amm.long_spread = long_spread; market.amm.short_spread = short_spread; - market.amm.reference_price_offset = reference_price_offset; + + if reference_price_decay != 0 { + market.amm.reference_price_offset = market + .amm + .reference_price_offset + .safe_sub(reference_price_decay)?; + + if reference_price_decay > 0 { + market.amm.long_spread = market + .amm + .long_spread + .safe_add(reference_price_decay.unsigned_abs())?; + } else { + market.amm.short_spread = market + .amm + .short_spread + .safe_add(reference_price_decay.unsigned_abs())?; + } + } else { + market.amm.reference_price_offset = reference_price_offset; + } update_spread_reserves(market)?; diff --git a/programs/drift/src/controller/position/tests.rs b/programs/drift/src/controller/position/tests.rs index 7145c7dab..9d66f0c7d 100644 --- a/programs/drift/src/controller/position/tests.rs +++ b/programs/drift/src/controller/position/tests.rs @@ -591,6 +591,144 @@ fn amm_pred_market_example() { assert_eq!(cost, 6333935); } +#[test] +fn amm_ref_price_offset_decay_logic() { + // sample btc market + let perp_market_str = String::from("Ct8MLGv1N/cV6vWLwJY+18dY2GsrmrNldgnISB7pmbcf7cn9S4FZ4B7U/fA1on6uX4cAPWh+6q5kflQbDzfTC/LJrf1AdS22jhnK8BsAAAAAAAAAAAAAAAEAAAAAAAAA46fs5xsAAADJQ2HmGwAAANhndWgAAAAA0MlT6v///////////////yF75IAAAAAAAAAAAAAAAADHCg8Gw/4GAAAAAAAAAAAAAAAAAAAAAADpl1aFUVEAAAAAAAAAAAAAd5bGp2BRAAAAAAAAAAAAAHxFDwAAAAAAAAAAAAAAAADYi6VkR1EAAAAAAAAAAAAAjzRN3WlRAAAAAAAAAAAAAMF8NBZZUQAAAAAAAAAAAACx1JfrGwAAAAAAAAAAAAAA27hDjVlRAAAAAAAAAAAAAAAvMJpRAAAAAAAAAAAAAACAeFmAtf///////////////VbPGQcAAAAAAAAAAAAAAINQugAAAAAAAAAAAAAAAAAAuEHoLgMAAAAAAAAAAAAA0BBxPAX+/////////////x+EvMLH3v////////////9dJGEqRB4AAAAAAAAAAAAAvT+NfU3e/////////////yp6wB2KHgAAAAAAAAAAAAAAqKvhEAAAAAAAAAAAAAAAPsFFqQAAAAA+wUWpAAAAAD7BRakAAAAAbrDDcQAAAAAaJWKGrwMAAAAAAAAAAAAAS7R+idYBAAAAAAAAAAAAAF2WRnPdAQAAAAAAAAAAAADRdFqB6AIAAAAAAAAAAAAAiHk9siQBAAAAAAAAAAAAANqRAEIxAQAAAAAAAAAAAABxdu2rEhsAAAAAAAAAAAAAluV7kBIbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/RQAMTFEAAAAAAAAAAAAAcSSAImZRAAAAAAAAAAAAADqAU5VRUQAAAAAAAAAAAACLt8aXYFEAAAAAAAAAAAAAjhnK8BsAAAAAAAAAAAAAALcQR+kbAAAAFBqR6xsAAABlFWzqGwAAALmLxOgbAAAAqDIPFQAAAABvAAAAAAAAANPXJTQAAAAAxmF1aAAAAAAQDgAAAAAAAKCGAQAAAAAAoIYBAAAAAACghgEAAAAAAAAAAAAAAAAAPM5NNkwmAQAtW2Wj6QQAAAfQ/dycBgAA12d1aAAAAAAj//YHAAAAAF+/cQoAAAAA12d1aAAAAAAUAAAA3AUAAA4CAAAHAAAAAAAAAHgAAADcBTIAZGQMAYCLLeUABf8FcpekBQAAAADLSnrF+v///32cLdP/////AAAAAM4AAAAAAAAAAAAAAP+OXyMvTQcAAAAAAAAAAAAAAAAAAAAAAEJUQy1QRVJQICAgICAgICAgICAgICAgICAgICAgICAgAB8K+v////8A4fUFAAAAAP8PpdToAAAA7YdGAwQAAABBY3VoAAAAAADh9QUAAAAAAAAAAAAAAAAAAAAAAAAAAIdLVAAAAAAAmlgAAAAAAABvBwAAAAAAAGwHAAAAAAAAiBMAAEwdAAD0AQAALAEAAAAAAAAQJwAAwQQAANMDAAABAAEAAAAAAJz/AAAAAGMAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="); + let mut decoded_bytes = base64::decode(perp_market_str).unwrap(); + let perp_market_bytes = decoded_bytes.as_mut_slice(); + + let key = Pubkey::default(); + let owner = Pubkey::from_str("dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH").unwrap(); + let mut lamports = 0; + let perp_market_account_info = + create_account_info(&key, true, &mut lamports, perp_market_bytes, &owner); + + let perp_market_loader: AccountLoader = + AccountLoader::try_from(&perp_market_account_info).unwrap(); + let mut perp_market = perp_market_loader.load_mut().unwrap(); + + let reserve_price = perp_market.amm.reserve_price().unwrap(); + let (b1, a1) = perp_market.amm.bid_ask_price(reserve_price).unwrap(); + assert_eq!(reserve_price, 120003893645); + assert_eq!(b1, 120003053617); + assert_eq!(a1, 120067015693); + assert_eq!( + perp_market.amm.historical_oracle_data.last_oracle_price, + 120003893646 + ); + assert_eq!(perp_market.amm.reference_price_offset, 0); + assert_eq!(perp_market.amm.last_update_slot, 353317544); + + perp_market.amm.curve_update_intensity = 200; + + let max_ref_offset = perp_market.amm.get_max_reference_price_offset().unwrap(); + + let liquidity_ratio = crate::math::amm_spread::calculate_inventory_liquidity_ratio( + perp_market.amm.base_asset_amount_with_amm, + perp_market.amm.base_asset_reserve, + perp_market.amm.max_base_asset_reserve, + perp_market.amm.min_base_asset_reserve, + ) + .unwrap(); + + let signed_liquidity_ratio = liquidity_ratio + .checked_mul( + (perp_market + .amm + .get_protocol_owned_position() + .unwrap() + .signum() as i128), + ) + .unwrap(); + + let res = crate::math::amm_spread::calculate_reference_price_offset( + reserve_price, + perp_market.amm.last_24h_avg_funding_rate, + signed_liquidity_ratio, + perp_market.amm.min_order_size, + perp_market + .amm + .historical_oracle_data + .last_oracle_price_twap_5min, + perp_market.amm.last_mark_price_twap_5min, + perp_market + .amm + .historical_oracle_data + .last_oracle_price_twap, + perp_market.amm.last_mark_price_twap, + max_ref_offset, + ) + .unwrap(); + assert_eq!(res, 10000); + + let mut now = perp_market.amm.last_mark_price_twap_ts + 10; + let mut clock_slot = 353317544 + 20; // todo + let state = State::default(); + let oracle_price_data = OraclePriceData { + price: 120003893646, + confidence: PRICE_PRECISION_U64 / 1000, + delay: 1, + has_sufficient_number_of_data_points: true, + }; + let cost = _update_amm( + &mut perp_market, + &oracle_price_data, + &state, + now, + clock_slot, + ) + .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); + + perp_market.amm.curve_update_intensity = 0; + + // Run decay steps + let mut offsets = Vec::new(); + let mut lspreads = Vec::new(); + let mut sspreads = Vec::new(); + + for i in 0..15 { + // advance time for next iteration + now += i % 2; + clock_slot += 1; + + let cost = _update_amm( + &mut perp_market, + &oracle_price_data, + &state, + now, + clock_slot, + ) + .unwrap(); + assert_eq!(perp_market.amm.last_update_slot, clock_slot); + assert_eq!(perp_market.amm.last_oracle_valid, true); + + // capture the new offset + offsets.push(perp_market.amm.reference_price_offset); + lspreads.push(perp_market.amm.long_spread); + sspreads.push(perp_market.amm.short_spread); + } + + assert_eq!( + offsets, + [ + 6615, 5954, 5359, 4824, 4342, 3908, 3518, 3167, 2851, 2566, 2310, 2079, 1872, 1685, + 1517 + ] + ); + assert_eq!( + lspreads, + [745, 671, 605, 545, 492, 444, 400, 361, 326, 295, 266, 241, 217, 197, 178] + ); + assert_eq!( + sspreads, + [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] + ); +} + #[test] fn amm_perp_ref_offset() { let perp_market_str = String::from("Ct8MLGv1N/frxfcToe675SrQivb0F67YUSLVM3KDMaqsrnwc8fwczsz5oyRPeWWnXBDAXzWarbuAhSPT0bfoyy4yyWBLxtoIoFxsAAAAAAAAAAAAAAAAAAEAAAAAAAAAwt1rAAAAAAAiZmwAAAAAAES4yGcAAAAAtlzFXyUAAAAAAAAAAAAAALSB+4IAAAAAAAAAAAAAAAD2TULXx84AAAAAAAAAAAAAAAAAAAAAAABslCM7QZsQAAAAAAAAAAAAk4WjVa59CAAAAAAAAAAAADxrEgAAAAAAAAAAAAAAAAAFC7zM58ENAAAAAAAAAAAAemIeFLwLFAAAAAAAAAAAAFJYZFbh3wsAAAAAAAAAAAC57tMAAAAAAAAAAAAAAAAAHopkdKl9CAAAAAAAAAAAAACyqjNmBAAAAAAAAAAAAAAA3oQco/v/////////////IX9HiwkAAAAAAAAAAAAAAN8Q6MT///////////////8AgMakfo0DAAAAAAAAAAAANEmHdQAAAAAAAAAAAAAAAE/4Lvzz//////////////8XEpOoCwAAAAAAAAAAAAAABKUfVPP//////////////3ckDIgNAAAAAAAAAAAAAAAAGJUuKwMAAAAAAAAAAAAA/E0BAAAAAAD8TQEAAAAAAPxNAQAAAAAAFlABAAAAAADJ8AhjKAAAAAAAAAAAAAAADJaguhwAAAAAAAAAAAAAAKZ++bELAAAAAAAAAAAAAABae48GKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMpu/m4UAAAAAAAAAAAAAACjl79nAQAAAAAAAAAAAAAAyZm9ZwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqle+hQHQQAAAAAAAAAAAAs/93w86RCAAAAAAAAAAAAGyUIztBmxAAAAAAAAAAAACThaNVrn0IAAAAAAAAAAAAoFxsAAAAAAAAAAAAAAAAAAHSawAAAAAAv+1rAAAAAADg32sAAAAAAAJnbAAAAAAAYVNcEwAAAAChAwAAAAAAADfd8f//////Q63IZwAAAAAQDgAAAAAAAADKmjsAAAAAZAAAAAAAAAAA8gUqAQAAAAAAAAAAAAAAzxOSPQAAAADE4TUGAAAAAAAAAAAAAAAAzHHIZwAAAACAfQAAAAAAAN5+AAAAAAAARLjIZwAAAADoAwAAkF8BAPgBAAD0AQAAqwEAABYBAADoAzIAyGQOAQAAAAAEAAAAYE+5CAAAAADJKrR8AQAAAFf04Pb/////UEYAAAAAAAAAAAAAAAAAAD7kkISSGgAAAAAAAAAAAAAAAAAAAAAAADFNUEVQRS1QRVJQICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJflAAAAAAAAFj0AAAAAAADYGwAAAAAAAO4CAADuAgAAqGEAAFDDAADECQAA4gQAAAAAAAAQJwAAbQAAAKgAAAAKAAEAAwAAAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="); @@ -666,7 +804,7 @@ fn amm_perp_ref_offset() { max_ref_offset, ) .unwrap(); - assert_eq!(res, 45000); + assert_eq!(res, (perp_market.amm.max_spread / 2) as i32); assert_eq!(perp_market.amm.reference_price_offset, 18000); // not updated vs market account let now = 1741207620 + 1; diff --git a/programs/drift/src/controller/repeg/tests.rs b/programs/drift/src/controller/repeg/tests.rs index 85f93dceb..0a2906f6e 100644 --- a/programs/drift/src/controller/repeg/tests.rs +++ b/programs/drift/src/controller/repeg/tests.rs @@ -248,7 +248,7 @@ pub fn update_amm_test_bad_oracle() { #[test] pub fn update_amm_larg_conf_test() { let now = 1662800000 + 60; - let slot = 81680085; + let mut slot = 81680085; let mut market = PerpMarket::default_btc_test(); assert_eq!(market.amm.base_asset_amount_with_amm, -1000000000); @@ -299,6 +299,7 @@ pub fn update_amm_larg_conf_test() { has_sufficient_number_of_data_points: true, }; + slot += 1; let cost_of_update = _update_amm(&mut market, &oracle_price_data, &state, now, slot).unwrap(); assert_eq!(cost_of_update, 0); @@ -377,7 +378,7 @@ pub fn update_amm_larg_conf_test() { #[test] pub fn update_amm_larg_conf_w_neg_tfmd_test() { let now = 1662800000 + 60; - let slot = 81680085; + let mut slot = 81680085; let mut market = PerpMarket::default_btc_test(); market.amm.concentration_coef = 1414213; @@ -460,6 +461,7 @@ pub fn update_amm_larg_conf_w_neg_tfmd_test() { has_sufficient_number_of_data_points: true, }; + slot += 1; let cost_of_update = _update_amm(&mut market, &oracle_price_data, &state, now, slot).unwrap(); assert_eq!(cost_of_update, 0); diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 8983162b7..2135183f2 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -1230,7 +1230,7 @@ impl AMM { let lower_bound_multiplier: i64 = self.curve_update_intensity.safe_sub(100)?.cast::()?; - // always allow 1-100 bps of price offset, up to half of the market's max_spread + // always higher of 1-100 bps of price offset and half of the market's max_spread let lb_bps = (PERCENTAGE_PRECISION.cast::()? / 10000).safe_mul(lower_bound_multiplier)?; let max_offset = (self.max_spread.cast::()? / 2).max(lb_bps);