diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 85d2f0595..2c5ceaa22 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -2089,22 +2089,19 @@ pub fn fulfill_perp_order_with_match( return Ok((0_u64, 0_u64, 0_u64)); } - let (bid_price, ask_price) = market.amm.bid_ask_price(market.amm.reserve_price()?)?; - let oracle_price = oracle_map.get_price_data(&market.amm.oracle)?.price; - let taker_direction = taker.orders[taker_order_index].direction; + let taker_direction: PositionDirection = taker.orders[taker_order_index].direction; let taker_price = if let Some(taker_limit_price) = taker_limit_price { taker_limit_price } else { let amm_available_liquidity = calculate_amm_available_liquidity(&market.amm, &taker_direction)?; - get_fallback_price( + market.amm.get_fallback_price( &taker_direction, - bid_price, - ask_price, amm_available_liquidity, oracle_price, + taker.orders[taker_order_index].seconds_til_expiry(now), )? }; diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 8f92a0ee2..944512620 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -693,26 +693,6 @@ pub fn calculate_fill_price( .cast::() } -pub fn get_fallback_price( - direction: &PositionDirection, - bid_price: u64, - ask_price: u64, - amm_available_liquidity: u64, - oracle_price: i64, -) -> DriftResult { - let oracle_price = oracle_price.unsigned_abs(); - match direction { - PositionDirection::Long if amm_available_liquidity > 0 => { - ask_price.safe_add(ask_price / 200) - } - PositionDirection::Long => oracle_price.safe_add(oracle_price / 20), - PositionDirection::Short if amm_available_liquidity > 0 => { - bid_price.safe_sub(bid_price / 200) - } - PositionDirection::Short => oracle_price.safe_sub(oracle_price / 20), - } -} - pub fn get_max_fill_amounts( user: &User, user_order_index: usize, diff --git a/programs/drift/src/math/orders/tests.rs b/programs/drift/src/math/orders/tests.rs index 954548349..e760bc75e 100644 --- a/programs/drift/src/math/orders/tests.rs +++ b/programs/drift/src/math/orders/tests.rs @@ -3718,3 +3718,146 @@ mod select_margin_type_for_perp_maker { assert_eq!(margin_type, MarginRequirementType::Maintenance); } } + +mod fallback_price_logic { + use crate::math::constants::{ + AMM_RESERVE_PRECISION, PEG_PRECISION, PRICE_PRECISION, PRICE_PRECISION_I64, + }; + use crate::state::oracle::HistoricalOracleData; + use crate::state::perp_market::{PerpMarket, AMM}; + use crate::{MarketStatus, PositionDirection}; + + #[test] + fn test() { + let mut market = PerpMarket { + amm: AMM { + base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + bid_base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + bid_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + ask_base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + ask_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + sqrt_k: 100 * AMM_RESERVE_PRECISION, + peg_multiplier: 100 * PEG_PRECISION, + max_slippage_ratio: 50, + max_fill_reserve_fraction: 100, + order_step_size: 1000, + order_tick_size: 1, + min_order_size: 1000, + // oracle: oracle_price_key, + base_spread: 0, + historical_oracle_data: HistoricalOracleData { + last_oracle_price: (100 * PRICE_PRECISION) as i64, + last_oracle_price_twap: (100 * PRICE_PRECISION) as i64, + last_oracle_price_twap_5min: (100 * PRICE_PRECISION) as i64, + + ..HistoricalOracleData::default() + }, + ..AMM::default() + }, + margin_ratio_initial: 2000, + margin_ratio_maintenance: 1000, + status: MarketStatus::Initialized, + ..PerpMarket::default_test() + }; + market.amm.max_base_asset_reserve = u128::MAX; + market.amm.min_base_asset_reserve = 0; + + // fallback are wide from oracle cause twaps arent set on amm + let result = market + .amm + .get_fallback_price( + &PositionDirection::Long, + 0, + 2012 * PRICE_PRECISION_I64 / 100, + 0, + ) + .unwrap(); + assert_eq!(result, 22132000); + + let result = market + .amm + .get_fallback_price( + &PositionDirection::Short, + 0, + 2012 * PRICE_PRECISION_I64 / 100, + 0, + ) + .unwrap(); + assert_eq!(result, 0); + + // make non-zero bid/ask twaps + market.amm.last_ask_price_twap = (101 * PRICE_PRECISION) as u64; + market.amm.last_bid_price_twap = (99 * PRICE_PRECISION) as u64; + + // fallback is offset from oracle + let result = market + .amm + .get_fallback_price( + &PositionDirection::Long, + 0, + 2012 * PRICE_PRECISION_I64 / 100, + 0, + ) + .unwrap(); + assert_eq!(result, 23132000); + + let result = market + .amm + .get_fallback_price( + &PositionDirection::Short, + 0, + 2012 * PRICE_PRECISION_I64 / 100, + 0, + ) + .unwrap(); + assert_eq!(result, 17108000); + + // ignores current oracle price and just prices fallback based on amm liquidity + let result = market + .amm + .get_fallback_price( + &PositionDirection::Long, + 1000000000, + 2012 * PRICE_PRECISION_I64 / 100, + 0, + ) + .unwrap(); + assert_eq!(result, 101000000); + + let result = market + .amm + .get_fallback_price( + &PositionDirection::Short, + 1000000000, + 2012 * PRICE_PRECISION_I64 / 100, + 0, + ) + .unwrap(); + assert_eq!(result, 99000000); + + // ignores current oracle price and just prices fallback based on amm liquidity + // tighter when seconds til expiry is long + let result = market + .amm + .get_fallback_price( + &PositionDirection::Long, + 1000000000, + 2012 * PRICE_PRECISION_I64 / 100, + 100, + ) + .unwrap(); + assert_eq!(result, 100500000); + + let result = market + .amm + .get_fallback_price( + &PositionDirection::Short, + 1000000000, + 2012 * PRICE_PRECISION_I64 / 100, + 100, + ) + .unwrap(); + assert_eq!(result, 99500000); + } +} diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index eef2b9a02..da9a56014 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -771,6 +771,58 @@ impl Default for AMM { } impl AMM { + pub fn get_fallback_price( + self, + direction: &PositionDirection, + amm_available_liquidity: u64, + oracle_price: i64, + seconds_til_order_expiry: i64, + ) -> DriftResult { + // PRICE_PRECISION + if direction.eq(&PositionDirection::Long) { + // pick amm ask + buffer if theres liquidity + // otherwise be aggressive vs oracle + 1hr premium + if amm_available_liquidity >= self.min_order_size { + let reserve_price = self.reserve_price()?; + let amm_ask_price: i64 = self.ask_price(reserve_price)?.cast()?; + amm_ask_price + .safe_add(amm_ask_price / (seconds_til_order_expiry * 20).clamp(100, 200))? + .cast::() + } else { + oracle_price + .safe_add( + self.last_ask_price_twap + .cast::()? + .safe_sub(self.historical_oracle_data.last_oracle_price_twap)? + .max(0), + )? + .safe_add(oracle_price / (seconds_til_order_expiry * 2).clamp(10, 50))? + .cast::() + } + } else { + // pick amm bid - buffer if theres liquidity + // otherwise be aggressive vs oracle + 1hr bid premium + if amm_available_liquidity >= self.min_order_size { + let reserve_price = self.reserve_price()?; + let amm_bid_price: i64 = self.bid_price(reserve_price)?.cast()?; + amm_bid_price + .safe_sub(amm_bid_price / (seconds_til_order_expiry * 20).clamp(100, 200))? + .cast::() + } else { + oracle_price + .safe_add( + self.last_bid_price_twap + .cast::()? + .safe_sub(self.historical_oracle_data.last_oracle_price_twap)? + .min(0), + )? + .safe_sub(oracle_price / (seconds_til_order_expiry * 2).clamp(10, 50))? + .max(0) + .cast::() + } + } + } + pub fn get_protocol_owned_position(self) -> DriftResult { self.base_asset_amount_with_amm .safe_add(self.base_asset_amount_with_unsettled_lp)? diff --git a/programs/drift/src/state/user.rs b/programs/drift/src/state/user.rs index 21ec1ba33..7c8f9c0af 100644 --- a/programs/drift/src/state/user.rs +++ b/programs/drift/src/state/user.rs @@ -999,6 +999,10 @@ pub enum AssetType { } impl Order { + pub fn seconds_til_expiry(self, now: i64) -> i64 { + (self.max_ts - now).max(0) + } + pub fn has_oracle_price_offset(self) -> bool { self.oracle_price_offset != 0 } diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 6fa030eb1..74a645470 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -3274,8 +3274,9 @@ export class DriftClient { subAccountId?: number ): Promise { orderParams = getOrderParams(orderParams, { marketType: MarketType.SPOT }); - const userAccountPublicKey = - await this.getUserAccountPublicKey(subAccountId); + const userAccountPublicKey = await this.getUserAccountPublicKey( + subAccountId + ); const remainingAccounts = this.getRemainingAccounts({ userAccounts: [this.getUserAccount(subAccountId)], @@ -5785,8 +5786,9 @@ export class DriftClient { } if (initializeStakeAccount) { - const initializeIx = - await this.getInitializeInsuranceFundStakeIx(marketIndex); + const initializeIx = await this.getInitializeInsuranceFundStakeIx( + marketIndex + ); addIfStakeIxs.push(initializeIx); }