Skip to content

Commit

Permalink
program: improve get_fallback_price (#797)
Browse files Browse the repository at this point in the history
* bigz/improve-perp-match-get_fallback_price

* add tests

* rm excess code
  • Loading branch information
0xbigz authored Dec 29, 2023
1 parent e9ac408 commit 71d7931
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 26 deletions.
9 changes: 3 additions & 6 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2098,22 +2098,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),
)?
};

Expand Down
20 changes: 0 additions & 20 deletions programs/drift/src/math/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,26 +693,6 @@ pub fn calculate_fill_price(
.cast::<u64>()
}

pub fn get_fallback_price(
direction: &PositionDirection,
bid_price: u64,
ask_price: u64,
amm_available_liquidity: u64,
oracle_price: i64,
) -> DriftResult<u64> {
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,
Expand Down
143 changes: 143 additions & 0 deletions programs/drift/src/math/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
52 changes: 52 additions & 0 deletions programs/drift/src/state/perp_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,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<u64> {
// 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::<u64>()
} else {
oracle_price
.safe_add(
self.last_ask_price_twap
.cast::<i64>()?
.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::<u64>()
}
} 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::<u64>()
} else {
oracle_price
.safe_add(
self.last_bid_price_twap
.cast::<i64>()?
.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::<u64>()
}
}
}

pub fn get_protocol_owned_position(self) -> DriftResult<i64> {
self.base_asset_amount_with_amm
.safe_add(self.base_asset_amount_with_unsettled_lp)?
Expand Down
4 changes: 4 additions & 0 deletions programs/drift/src/state/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 71d7931

Please sign in to comment.