From 46f52ce6974e21c9e51b6767cb25ed7f2639a53d Mon Sep 17 00:00:00 2001 From: bigz_Pubkey <83473873+0xbigz@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:15:29 -0500 Subject: [PATCH] program: reference price feature gate (#782) * bigz/reference-price-offset-feature-gate-fixes * fix liquidity ratio sign bug * fix amm sdk tests * fix cargo fmt * fix format --- programs/drift/src/controller/amm.rs | 11 +- programs/drift/src/instructions/admin.rs | 4 +- programs/drift/src/math/amm_spread.rs | 25 +- programs/drift/src/math/amm_spread/tests.rs | 84 ++++- programs/drift/src/state/perp_market.rs | 20 +- sdk/src/math/amm.ts | 64 ++-- sdk/tests/amm/test.ts | 331 ++++++++++++++++++-- 7 files changed, 470 insertions(+), 69 deletions(-) diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index 809e75e47..9b253ac2b 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)> { - let max_offset = amm.get_max_reference_price_offset()?; + let max_ref_offset = amm.get_max_reference_price_offset()?; - let reference_price_offset = if amm.curve_update_intensity > 0 { + let reference_price_offset = if max_ref_offset > 0 { let liquidity_ratio = amm_spread::calculate_inventory_liquidity_ratio( amm.base_asset_amount_with_amm, amm.base_asset_reserve, @@ -177,16 +177,19 @@ pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u3 amm.min_base_asset_reserve, )?; + let signed_liquidity_ratio = + liquidity_ratio.safe_mul(amm.get_protocol_owned_position()?.signum().cast()?)?; + amm_spread::calculate_reference_price_offset( reserve_price, amm.last_24h_avg_funding_rate, - liquidity_ratio, + signed_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, + max_ref_offset, )? } else { 0 diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index a70e4091e..0e13d6645 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -1845,8 +1845,10 @@ pub fn handle_update_perp_market_curve_update_intensity( ctx: Context, curve_update_intensity: u8, ) -> Result<()> { + // (0, 100] is for repeg / formulaic k intensity + // (100, 200] is for reference price offset intensity validate!( - curve_update_intensity <= 100, + curve_update_intensity <= 200, ErrorCode::DefaultError, "invalid curve_update_intensity", )?; diff --git a/programs/drift/src/math/amm_spread.rs b/programs/drift/src/math/amm_spread.rs index 3334fd5f7..ac66c2b6f 100644 --- a/programs/drift/src/math/amm_spread.rs +++ b/programs/drift/src/math/amm_spread.rs @@ -9,9 +9,9 @@ use crate::math::bn::U192; use crate::math::casting::Cast; 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, - FUNDING_RATE_BUFFER, MAX_BID_ASK_INVENTORY_SKEW_FACTOR, PEG_PRECISION, PERCENTAGE_PRECISION, + 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, }; @@ -449,23 +449,26 @@ pub fn calculate_spread_reserves( PositionDirection::Short => amm.short_spread, }; - let spread_with_offset: i32 = spread.cast::()?.safe_add(amm.reference_price_offset)?; + let spread_with_offset: i32 = if direction == PositionDirection::Short { + (-spread.cast::()?).safe_add(amm.reference_price_offset)? + } else { + spread.cast::()?.safe_add(amm.reference_price_offset)? + }; - let quote_asset_reserve_delta = if spread > 0 { + let quote_asset_reserve_delta = if spread_with_offset.abs() > 1 { amm.quote_asset_reserve - .safe_div(BID_ASK_SPREAD_PRECISION_U128 / (spread_with_offset.cast::()? / 2))? + .cast::()? + .safe_div(BID_ASK_SPREAD_PRECISION_I128 / (spread_with_offset.cast::()? / 2))? } else { 0 }; - let quote_asset_reserve = if spread_with_offset >= 0 && direction == PositionDirection::Long - || spread_with_offset <= 0 && direction == PositionDirection::Short - { + let quote_asset_reserve = if quote_asset_reserve_delta > 0 { amm.quote_asset_reserve - .safe_add(quote_asset_reserve_delta)? + .safe_add(quote_asset_reserve_delta.unsigned_abs())? } else { amm.quote_asset_reserve - .safe_sub(quote_asset_reserve_delta)? + .safe_sub(quote_asset_reserve_delta.unsigned_abs())? }; let invariant_sqrt_u192 = U192::from(amm.sqrt_k); diff --git a/programs/drift/src/math/amm_spread/tests.rs b/programs/drift/src/math/amm_spread/tests.rs index d01151607..8b333e340 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::math::amm::calculate_price; use crate::math::amm_spread::*; use crate::math::constants::{ AMM_RESERVE_PRECISION, BASE_PRECISION_I128, BID_ASK_SPREAD_PRECISION, @@ -362,29 +363,102 @@ mod test { assert!(long_spread5 < long_spread4); assert_eq!(short_spread5, short_spread4); + assert_eq!(long_spread5, 27270); + assert_eq!(short_spread5, 500); - let amm = AMM { + let mut amm = AMM { base_asset_reserve: 2 * AMM_RESERVE_PRECISION, quote_asset_reserve: 2 * AMM_RESERVE_PRECISION, sqrt_k: 2 * AMM_RESERVE_PRECISION, peg_multiplier: PEG_PRECISION, long_spread: long_spread5, short_spread: short_spread5, + max_spread: 1000, + curve_update_intensity: 100, ..AMM::default() }; + let max_ref_offset = amm.get_max_reference_price_offset().unwrap(); + assert_eq!(max_ref_offset, 0); + + amm.curve_update_intensity = 110; + let max_ref_offset = amm.get_max_reference_price_offset().unwrap(); + assert_eq!(max_ref_offset, 1000); // 10 bps + + amm.curve_update_intensity = 200; + let max_ref_offset = amm.get_max_reference_price_offset().unwrap(); + assert_eq!(max_ref_offset, 10000); // 100 bps + + amm.max_spread = 10000 * 10; // 10% + let max_ref_offset = amm.get_max_reference_price_offset().unwrap(); + assert_eq!(max_ref_offset, 20000); // 200 bps (5% of max spread) + + let orig_price = calculate_price( + amm.quote_asset_reserve, + amm.base_asset_reserve, + amm.peg_multiplier, + ) + .unwrap(); + assert_eq!(orig_price, 1000000); + let (bar_l, qar_l) = calculate_spread_reserves(&amm, PositionDirection::Long).unwrap(); let (bar_s, qar_s) = calculate_spread_reserves(&amm, PositionDirection::Short).unwrap(); - assert!(qar_l > amm.quote_asset_reserve); - assert!(bar_l < amm.base_asset_reserve); - assert!(qar_s < amm.quote_asset_reserve); - assert!(bar_s > amm.base_asset_reserve); assert_eq!(bar_s, 2000500125); assert_eq!(bar_l, 1972972973); assert_eq!(qar_l, 2027397260); assert_eq!(qar_s, 1999500000); + assert!(qar_l > amm.quote_asset_reserve); + assert!(bar_l < amm.base_asset_reserve); + assert!(qar_s < amm.quote_asset_reserve); + assert!(bar_s > amm.base_asset_reserve); + + let l_price = calculate_price(qar_l, bar_l, amm.peg_multiplier).unwrap(); + let s_price = calculate_price(qar_s, bar_s, amm.peg_multiplier).unwrap(); + assert_eq!(l_price, 1027584); + assert_eq!(s_price, 999500); + assert!(l_price > s_price); + + amm.reference_price_offset = 1000; // 10 bps + + let (bar_l, qar_l) = calculate_spread_reserves(&amm, PositionDirection::Long).unwrap(); + let (bar_s, qar_s) = calculate_spread_reserves(&amm, PositionDirection::Short).unwrap(); + + assert!(qar_l > amm.quote_asset_reserve); + assert!(bar_l < amm.base_asset_reserve); + assert!(qar_s > amm.quote_asset_reserve); + assert!(bar_s < amm.base_asset_reserve); + assert_eq!(bar_s, 1999500124); // up + assert_eq!(bar_l, 1971830986); // down + assert_eq!(qar_l, 2028571428); // up + assert_eq!(qar_s, 2000500000); // down + + let l_price = calculate_price(qar_l, bar_l, amm.peg_multiplier).unwrap(); + let s_price = calculate_price(qar_s, bar_s, amm.peg_multiplier).unwrap(); + assert_eq!(l_price, 1028775); + assert_eq!(s_price, 1000500); + assert!(l_price > s_price); + + amm.reference_price_offset = -1000; // -10 bps + let (bar_l, qar_l) = calculate_spread_reserves(&amm, PositionDirection::Long).unwrap(); + let (bar_s, qar_s) = calculate_spread_reserves(&amm, PositionDirection::Short).unwrap(); + + assert!(qar_l > amm.quote_asset_reserve); + assert!(bar_l < amm.base_asset_reserve); + assert!(qar_s < amm.quote_asset_reserve); + assert!(bar_s > amm.base_asset_reserve); + assert_eq!(bar_s, 2001501501); // up + assert_eq!(bar_l, 1974025974); // up + assert_eq!(qar_l, 2026315789); // down + assert_eq!(qar_s, 1998499625); // down + + let l_price = calculate_price(qar_l, bar_l, amm.peg_multiplier).unwrap(); + let s_price = calculate_price(qar_s, bar_s, amm.peg_multiplier).unwrap(); + assert_eq!(l_price, 1026488); + assert_eq!(s_price, 998500); + assert!(l_price > s_price); + let (long_spread_btc, short_spread_btc) = calculate_spread( 500, 62099, diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 6c30d49d5..eef2b9a02 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -771,10 +771,24 @@ impl Default for AMM { } impl AMM { + pub fn get_protocol_owned_position(self) -> DriftResult { + self.base_asset_amount_with_amm + .safe_add(self.base_asset_amount_with_unsettled_lp)? + .cast::() + } + pub fn get_max_reference_price_offset(self) -> DriftResult { - // always allow 10 bps of price offset, up to a fifth of the market's max_spread - let ten_bps = PERCENTAGE_PRECISION.cast::()? / 1000; - let max_offset = (self.max_spread.cast::()? / 5).max(ten_bps); + if self.curve_update_intensity <= 100 { + return Ok(0); + } + + let lower_bound_multiplier: i64 = + self.curve_update_intensity.safe_sub(100)?.cast::()?; + + // always allow 1-100 bps of price offset, up to a fifth 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::()? / 5).max(lb_bps); Ok(max_offset) } diff --git a/sdk/src/math/amm.ts b/sdk/src/math/amm.ts index e510930aa..b77fbea0d 100644 --- a/sdk/src/math/amm.ts +++ b/sdk/src/math/amm.ts @@ -863,13 +863,14 @@ export function calculateSpreadReserves( ); let quoteAssetReserve; - if ( - (spread >= 0 && isVariant(direction, 'long')) || - (spread <= 0 && isVariant(direction, 'short')) - ) { - quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta); + if (quoteAssetReserveDelta.gte(ZERO)) { + quoteAssetReserve = amm.quoteAssetReserve.add( + quoteAssetReserveDelta.abs() + ); } else { - quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta); + quoteAssetReserve = amm.quoteAssetReserve.sub( + quoteAssetReserveDelta.abs() + ); } const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve); @@ -886,26 +887,35 @@ export function calculateSpreadReserves( ); // 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 liquidityFraction = calculateInventoryLiquidityRatio( - amm.baseAssetAmountWithAmm, - amm.baseAssetReserve, - amm.minBaseAssetReserve, - amm.maxBaseAssetReserve - ); - const referencePriceOffset = calculateReferencePriceOffset( - reservePrice, - amm.last24HAvgFundingRate, - liquidityFraction, - amm.historicalOracleData.lastOraclePriceTwap5Min, - amm.lastMarkPriceTwap5Min, - amm.historicalOracleData.lastOraclePriceTwap, - amm.lastMarkPriceTwap, - maxOffset - ); + let maxOffset = 0; + let referencePriceOffset = ZERO; + if (amm.curveUpdateIntensity > 100) { + maxOffset = Math.max( + amm.maxSpread / 5, + (PERCENTAGE_PRECISION.toNumber() / 10000) * + (amm.curveUpdateIntensity - 100) + ); + + const liquidityFraction = calculateInventoryLiquidityRatio( + amm.baseAssetAmountWithAmm, + amm.baseAssetReserve, + amm.minBaseAssetReserve, + amm.maxBaseAssetReserve + ); + const liquidityFractionSigned = liquidityFraction.mul( + sigNum(amm.baseAssetAmountWithAmm.add(amm.baseAssetAmountWithUnsettledLp)) + ); + referencePriceOffset = calculateReferencePriceOffset( + reservePrice, + amm.last24HAvgFundingRate, + liquidityFractionSigned, + amm.historicalOracleData.lastOraclePriceTwap5Min, + amm.lastMarkPriceTwap5Min, + amm.historicalOracleData.lastOraclePriceTwap, + amm.lastMarkPriceTwap, + maxOffset + ); + } const [longSpread, shortSpread] = calculateSpread( amm, @@ -920,7 +930,7 @@ export function calculateSpreadReserves( amm ); const bidReserves = calculateSpreadReserve( - shortSpread + referencePriceOffset.toNumber(), + -shortSpread + referencePriceOffset.toNumber(), PositionDirection.SHORT, amm ); diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index f7233e605..484d075bc 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -8,6 +8,7 @@ import { calculateSpread, calculateSpreadBN, ZERO, + sigNum, ONE, calculateLiveOracleStd, calculateLiveOracleTwap, @@ -465,7 +466,7 @@ describe('AMM Tests', () => { mockAmm.baseAssetAmountWithAmm = new BN(0); mockAmm.pegMultiplier = new BN(13.553 * PEG_PRECISION.toNumber()); - mockAmm.ammJitIntensity = 100; + mockAmm.ammJitIntensity = 200; mockAmm.curveUpdateIntensity = 200; mockAmm.baseSpread = 2500; mockAmm.maxSpread = 25000; @@ -521,13 +522,13 @@ describe('AMM Tests', () => { console.log('amm.baseAssetReserve:', mockAmm.baseAssetReserve.toString()); assert(mockAmm.baseAssetReserve.eq(new BN('1000000000'))); const reserves2 = calculateSpreadReserves(mockAmm, oraclePriceData, now); - console.log(reserves2[1].baseAssetReserve.toString()); - console.log(reserves2[1].quoteAssetReserve.toString()); + console.log(reserves2[0].baseAssetReserve.toString()); + console.log(reserves2[0].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'))); + assert(reserves2[0].baseAssetReserve.eq(new BN('1005050504'))); + assert(reserves2[0].quoteAssetReserve.eq(new BN('994974875'))); + assert(reserves2[1].baseAssetReserve.eq(new BN('992537314'))); + assert(reserves2[1].quoteAssetReserve.eq(new BN('1007518796'))); // create imbalance for reference price offset mockAmm.baseAssetReserve = new BN(1000000000 * 1.1); @@ -536,11 +537,12 @@ describe('AMM Tests', () => { mockAmm.baseAssetReserve.mul(mockAmm.quoteAssetReserve) ); - mockAmm.baseAssetAmountWithAmm = new BN(-1000000000 * 0.1); + mockAmm.baseAssetAmountWithAmm = new BN(1000000000 * 0.1); const maxOffset = Math.max( mockAmm.maxSpread / 5, - PERCENTAGE_PRECISION.toNumber() / 1000 + (PERCENTAGE_PRECISION.toNumber() / 10000) * + (mockAmm.curveUpdateIntensity - 100) ); const liquidityFraction = calculateInventoryLiquidityRatio( mockAmm.baseAssetAmountWithAmm, @@ -550,11 +552,17 @@ describe('AMM Tests', () => { ); console.log('liquidityFraction:', liquidityFraction.toString()); assert(liquidityFraction.eq(new BN(1000000))); // full - + const liquidityFractionSigned = liquidityFraction.mul( + sigNum( + mockAmm.baseAssetAmountWithAmm.add( + mockAmm.baseAssetAmountWithUnsettledLp + ) + ) + ); const referencePriceOffset = calculateReferencePriceOffset( reservePrice, mockAmm.last24HAvgFundingRate, - liquidityFraction, + liquidityFractionSigned, mockAmm.historicalOracleData.lastOraclePriceTwap5Min, mockAmm.lastMarkPriceTwap5Min, mockAmm.historicalOracleData.lastOraclePriceTwap, @@ -562,17 +570,304 @@ describe('AMM Tests', () => { maxOffset ); console.log('referencePriceOffset:', referencePriceOffset.toString()); - assert(referencePriceOffset.eq(new BN(5000))); + assert(referencePriceOffset.eq(new BN(10000))); assert(referencePriceOffset.eq(new BN(maxOffset))); + // mockAmm.curveUpdateIntensity = 100; + const reserves3 = calculateSpreadReserves(mockAmm, oraclePriceData, now); + console.log(reserves3[0].baseAssetReserve.toString()); + console.log(reserves3[0].quoteAssetReserve.toString()); + + assert(reserves3[0].baseAssetReserve.eq(new BN('1094581278'))); + assert(reserves3[0].quoteAssetReserve.eq(new BN('913591359'))); + assert(reserves3[1].baseAssetReserve.eq(new BN('989999998'))); + assert(reserves3[1].quoteAssetReserve.eq(new BN('1010101010'))); + + const p1 = calculatePrice( + reserves3[0].baseAssetReserve, + reserves3[0].quoteAssetReserve, + mockAmm.pegMultiplier + ); + + const p2 = calculatePrice( + reserves3[1].baseAssetReserve, + reserves3[1].quoteAssetReserve, + mockAmm.pegMultiplier + ); + console.log(p1.toString(), p2.toString()); + + assert(p1.eq(new BN(11312000))); + assert(p2.eq(new BN(13828180))); + + mockAmm.curveUpdateIntensity = 110; + const reserves4 = calculateSpreadReserves(mockAmm, oraclePriceData, now); + console.log(reserves4[1].baseAssetReserve.toString()); + console.log(reserves4[1].quoteAssetReserve.toString()); + + assert(reserves4[0].baseAssetReserve.eq(new BN('1097323599'))); + assert(reserves4[0].quoteAssetReserve.eq(new BN('911308203'))); + assert(reserves4[1].baseAssetReserve.eq(new BN('989999998'))); + assert(reserves4[1].quoteAssetReserve.eq(new BN('1010101010'))); + + const p1RF = calculatePrice( + reserves4[0].baseAssetReserve, + reserves4[0].quoteAssetReserve, + mockAmm.pegMultiplier + ); + + const p2RF = calculatePrice( + reserves4[1].baseAssetReserve, + reserves4[1].quoteAssetReserve, + mockAmm.pegMultiplier + ); + console.log(p1RF.toString(), p2RF.toString()); + + assert(p1RF.eq(new BN(11255531))); + assert(p2RF.eq(new BN(13828180))); + // no ref price offset at 100 + mockAmm.curveUpdateIntensity = 100; + const reserves5 = calculateSpreadReserves(mockAmm, oraclePriceData, now); + console.log(reserves5[0].baseAssetReserve.toString()); + console.log(reserves5[0].quoteAssetReserve.toString()); + + assert(reserves5[0].baseAssetReserve.eq(new BN('1100068201'))); + assert(reserves5[0].quoteAssetReserve.eq(new BN('909034546'))); + assert(reserves5[1].baseAssetReserve.eq(new BN('989999998'))); + assert(reserves5[1].quoteAssetReserve.eq(new BN('1010101010'))); + + const p1RFNone = calculatePrice( + reserves5[0].baseAssetReserve, + reserves5[0].quoteAssetReserve, + mockAmm.pegMultiplier + ); + + const p2RFNone = calculatePrice( + reserves5[1].baseAssetReserve, + reserves5[1].quoteAssetReserve, + mockAmm.pegMultiplier + ); + console.log(p1RFNone.toString(), p2RFNone.toString()); + + assert(p1RFNone.eq(new BN(11199437))); + assert(p2RFNone.eq(new BN(13828180))); + assert(p1RF.sub(p1RFNone).eq(new BN(56094))); + assert(p2RF.sub(p2RFNone).eq(new BN(0))); // todo? + }); + it('Spread Reserves (with negative offset)', () => { + const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); + const mockMarket1 = myMockPerpMarkets[0]; + const 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.baseAssetReserve = new BN(1000000000); + mockAmm.quoteAssetReserve = new BN(1000000000); + mockAmm.sqrtK = new BN(1000000000); + + mockAmm.baseAssetAmountWithAmm = new BN(0); + mockAmm.pegMultiplier = new BN(13.553 * PEG_PRECISION.toNumber()); + mockAmm.ammJitIntensity = 200; + mockAmm.curveUpdateIntensity = 200; + mockAmm.baseSpread = 2500; + mockAmm.maxSpread = 25000; + + mockAmm.last24HAvgFundingRate = new BN(-7590328523); + + mockAmm.lastMarkPriceTwap = new BN( + (oraclePriceData.price.toNumber() / 1e6 + 0.01) * 1e6 + ); + mockAmm.historicalOracleData.lastOraclePriceTwap = new BN( + (oraclePriceData.price.toNumber() / 1e6 - 0.015) * 1e6 + ); + + mockAmm.historicalOracleData.lastOraclePriceTwap5Min = new BN( + (oraclePriceData.price.toNumber() / 1e6 + 0.005) * 1e6 + ); + mockAmm.lastMarkPriceTwap5Min = new BN( + (oraclePriceData.price.toNumber() / 1e6 - 0.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, 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[1].baseAssetReserve.toString()); + console.log(reserves2[1].quoteAssetReserve.toString()); + + assert(reserves2[0].baseAssetReserve.eq(new BN('1006289308'))); + assert(reserves2[0].quoteAssetReserve.eq(new BN('993750000'))); + assert(reserves2[1].baseAssetReserve.eq(new BN('993788819'))); + assert(reserves2[1].quoteAssetReserve.eq(new BN('1006250000'))); + + // 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); + + const maxOffset = Math.max( + mockAmm.maxSpread / 5, + (PERCENTAGE_PRECISION.toNumber() / 10000) * + (mockAmm.curveUpdateIntensity - 100) + ); + const liquidityFraction = calculateInventoryLiquidityRatio( + mockAmm.baseAssetAmountWithAmm, + mockAmm.baseAssetReserve, + mockAmm.minBaseAssetReserve, + mockAmm.maxBaseAssetReserve + ); + console.log('liquidityFraction:', liquidityFraction.toString()); + assert(liquidityFraction.eq(new BN(1000000))); // full + const liquidityFractionSigned = liquidityFraction.mul( + sigNum( + mockAmm.baseAssetAmountWithAmm.add( + mockAmm.baseAssetAmountWithUnsettledLp + ) + ) + ); + const referencePriceOffset = calculateReferencePriceOffset( + reservePrice, + mockAmm.last24HAvgFundingRate, + liquidityFractionSigned, + mockAmm.historicalOracleData.lastOraclePriceTwap5Min, + mockAmm.lastMarkPriceTwap5Min, + mockAmm.historicalOracleData.lastOraclePriceTwap, + mockAmm.lastMarkPriceTwap, + maxOffset + ); + console.log('referencePriceOffset:', referencePriceOffset.toString()); + assert(referencePriceOffset.eq(new BN(-10000))); // neg + + // assert(referencePriceOffset.eq(new BN(maxOffset))); + + // mockAmm.curveUpdateIntensity = 100; const reserves3 = calculateSpreadReserves(mockAmm, oraclePriceData, now); - console.log(reserves3[1].baseAssetReserve.toString()); - console.log(reserves3[1].quoteAssetReserve.toString()); + console.log(reserves3[0].baseAssetReserve.toString()); + console.log(reserves3[0].quoteAssetReserve.toString()); + + assert(reserves3[0].baseAssetReserve.eq(new BN('1010101008'))); + assert(reserves3[0].quoteAssetReserve.eq(new BN('990000000'))); + assert(reserves3[1].baseAssetReserve.eq(new BN('913613747'))); + assert(reserves3[1].quoteAssetReserve.eq(new BN('1094554456'))); + + const p1 = calculatePrice( + reserves3[0].baseAssetReserve, + reserves3[0].quoteAssetReserve, + mockAmm.pegMultiplier + ); + + const p2 = calculatePrice( + reserves3[1].baseAssetReserve, + reserves3[1].quoteAssetReserve, + mockAmm.pegMultiplier + ); + console.log(p1.toString(), p2.toString()); + + assert(p1.eq(new BN(13283295))); + assert(p2.eq(new BN(16237164))); + + mockAmm.curveUpdateIntensity = 110; + const reserves4 = calculateSpreadReserves(mockAmm, oraclePriceData, now); + console.log(reserves4[1].baseAssetReserve.toString()); + console.log(reserves4[1].quoteAssetReserve.toString()); + + assert(reserves4[0].baseAssetReserve.eq(new BN('999999998'))); + assert(reserves4[0].quoteAssetReserve.eq(new BN('1000000000'))); + assert(reserves4[1].baseAssetReserve.eq(new BN('911313622'))); + assert(reserves4[1].quoteAssetReserve.eq(new BN('1097317074'))); + + const p1RF = calculatePrice( + reserves4[0].baseAssetReserve, + reserves4[0].quoteAssetReserve, + mockAmm.pegMultiplier + ); + + const p2RF = calculatePrice( + reserves4[1].baseAssetReserve, + reserves4[1].quoteAssetReserve, + mockAmm.pegMultiplier + ); + console.log(p1RF.toString(), p2RF.toString()); + + assert(p1RF.eq(new BN(13553000))); + assert(p2RF.eq(new BN(16319231))); + + // no ref price offset at 100 + mockAmm.curveUpdateIntensity = 100; + const reserves5 = calculateSpreadReserves(mockAmm, oraclePriceData, now); + console.log(reserves5[0].baseAssetReserve.toString()); + console.log(reserves5[0].quoteAssetReserve.toString()); + + assert(reserves5[0].baseAssetReserve.eq(new BN('999999998'))); + assert(reserves5[0].quoteAssetReserve.eq(new BN('1000000000'))); + assert(reserves5[1].baseAssetReserve.eq(new BN('909034547'))); + assert(reserves5[1].quoteAssetReserve.eq(new BN('1100068200'))); + + const p1RFNone = calculatePrice( + reserves5[0].baseAssetReserve, + reserves5[0].quoteAssetReserve, + mockAmm.pegMultiplier + ); + + const p2RFNone = calculatePrice( + reserves5[1].baseAssetReserve, + reserves5[1].quoteAssetReserve, + mockAmm.pegMultiplier + ); + console.log(p1RFNone.toString(), p2RFNone.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'))); + const rr = p2RF.sub(p2RFNone).mul(PERCENTAGE_PRECISION).div(p2RF); + console.log(rr.toNumber()); + assert(p1RFNone.eq(new BN(13553000))); + assert(p2RFNone.eq(new BN(16401163))); + assert(p1RF.sub(p1RFNone).eq(new BN(0))); // todo? + assert(rr.eq(new BN(-5020))); }); it('live update functions', () => {