-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
program: try to match against multiple of makers orders #316
Changes from 5 commits
7032f7a
80a45b0
85cadb4
d819515
0c6ba37
c721c21
7b1942a
24085c4
b8f8ab3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -613,7 +613,7 @@ pub fn fill_perp_order( | |
filler_stats: &AccountLoader<UserStats>, | ||
maker: Option<&AccountLoader<User>>, | ||
maker_stats: Option<&AccountLoader<UserStats>>, | ||
maker_order_id: Option<u32>, | ||
_maker_order_id: Option<u32>, | ||
referrer: Option<&AccountLoader<User>>, | ||
referrer_stats: Option<&AccountLoader<UserStats>>, | ||
clock: &Clock, | ||
|
@@ -743,13 +743,12 @@ pub fn fill_perp_order( | |
(None, None) | ||
}; | ||
|
||
let (mut maker, mut maker_stats, maker_key, maker_order_index) = sanitize_maker_order( | ||
let (mut maker, mut maker_stats, maker_key, maker_order_indexes) = sanitize_maker_order( | ||
perp_market_map, | ||
spot_market_map, | ||
oracle_map, | ||
maker, | ||
maker_stats, | ||
maker_order_id, | ||
&user_key, | ||
&user.authority, | ||
&user.orders[order_index], | ||
|
@@ -814,7 +813,7 @@ pub fn fill_perp_order( | |
user_stats, | ||
&mut maker.as_deref_mut(), | ||
&mut maker_stats.as_deref_mut(), | ||
maker_order_index, | ||
maker_order_indexes, | ||
maker_key.as_ref(), | ||
&mut filler.as_deref_mut(), | ||
&filler_key, | ||
|
@@ -986,7 +985,6 @@ fn sanitize_maker_order<'a>( | |
oracle_map: &mut OracleMap, | ||
maker: Option<&'a AccountLoader<User>>, | ||
maker_stats: Option<&'a AccountLoader<UserStats>>, | ||
maker_order_id: Option<u32>, | ||
taker_key: &Pubkey, | ||
taker_authority: &Pubkey, | ||
taker_order: &Order, | ||
|
@@ -1000,7 +998,7 @@ fn sanitize_maker_order<'a>( | |
Option<RefMut<'a, User>>, | ||
Option<RefMut<'a, UserStats>>, | ||
Option<Pubkey>, | ||
Option<usize>, | ||
Option<Vec<(usize, u64)>>, | ||
)> { | ||
if maker.is_none() || maker_stats.is_none() { | ||
return Ok((None, None, None, None)); | ||
|
@@ -1027,129 +1025,113 @@ fn sanitize_maker_order<'a>( | |
Some(maker_stats) | ||
}; | ||
|
||
if maker.is_being_liquidated() || maker.is_bankrupt() { | ||
return Ok((None, None, None, None)); | ||
} | ||
|
||
let market = perp_market_map.get_ref(&taker_order.market_index)?; | ||
|
||
let maker_order_id = maker_order_id.ok_or(ErrorCode::MakerOrderNotFound)?; | ||
let maker_order_index = match maker.get_order_index(maker_order_id) { | ||
Ok(order_index) => order_index, | ||
Err(_) => { | ||
msg!("Maker has no order id {}", maker_order_id); | ||
let fallback_order_index = find_fallback_maker_order( | ||
&maker, | ||
match taker_order.direction { | ||
PositionDirection::Long => &PositionDirection::Short, | ||
PositionDirection::Short => &PositionDirection::Long, | ||
}, | ||
&MarketType::Perp, | ||
taker_order.market_index, | ||
Some(oracle_price), | ||
slot, | ||
market.amm.order_tick_size, | ||
)?; | ||
let mut maker_order_indexes = find_maker_orders( | ||
&maker, | ||
match taker_order.direction { | ||
PositionDirection::Long => &PositionDirection::Short, | ||
PositionDirection::Short => &PositionDirection::Long, | ||
}, | ||
&MarketType::Perp, | ||
taker_order.market_index, | ||
Some(oracle_price), | ||
slot, | ||
market.amm.order_tick_size, | ||
)?; | ||
|
||
if let Some(order_index) = fallback_order_index { | ||
msg!( | ||
"Using fallback maker order id {}", | ||
maker.orders[order_index].order_id | ||
); | ||
order_index | ||
} else { | ||
return Ok((None, None, None, None)); | ||
} | ||
} | ||
}; | ||
drop(market); | ||
|
||
sort_maker_orders(&mut maker_order_indexes, taker_order.direction); | ||
|
||
let mut filtered_maker_order_indexes = vec![]; | ||
for (maker_order_index, maker_order_price) in maker_order_indexes.iter() { | ||
let maker_order_index = *maker_order_index; | ||
let maker_order_price = *maker_order_price; | ||
|
||
{ | ||
let maker_order = &maker.orders[maker_order_index]; | ||
if !is_maker_for_taker(maker_order, taker_order)? { | ||
msg!("maker cant make for taker"); | ||
return Ok((None, None, None, None)); | ||
continue; | ||
} | ||
|
||
if !are_orders_same_market_but_different_sides(maker_order, taker_order) { | ||
msg!("maker is not same market but different side for taker"); | ||
return Ok((None, None, None, None)); | ||
continue; | ||
} | ||
|
||
if maker.is_being_liquidated() || maker.is_bankrupt() { | ||
return Ok((None, None, None, None)); | ||
} | ||
let market = perp_market_map.get_ref(&taker_order.market_index)?; | ||
|
||
validate!( | ||
!maker_order.must_be_triggered() || maker_order.triggered(), | ||
ErrorCode::OrderMustBeTriggeredFirst, | ||
"Maker order not triggered" | ||
)?; | ||
let breaches_oracle_price_limits = { | ||
limit_price_breaches_oracle_price_bands( | ||
maker_order_price, | ||
maker_order.direction, | ||
oracle_price, | ||
market.margin_ratio_initial, | ||
market.margin_ratio_maintenance, | ||
)? | ||
}; | ||
|
||
validate!( | ||
maker_order.market_type == MarketType::Perp, | ||
ErrorCode::InvalidOrderMarketType, | ||
"Maker order not a perp order" | ||
)? | ||
} | ||
drop(market); | ||
|
||
let breaches_oracle_price_limits = { | ||
order_breaches_oracle_price_limits( | ||
&maker.orders[maker_order_index], | ||
oracle_price, | ||
slot, | ||
market.amm.order_tick_size, | ||
market.margin_ratio_initial, | ||
market.margin_ratio_maintenance, | ||
)? | ||
}; | ||
let should_expire_order = should_expire_order(&maker, maker_order_index, now)?; | ||
|
||
drop(market); | ||
let existing_base_asset_amount = maker | ||
.get_perp_position(maker.orders[maker_order_index].market_index)? | ||
.base_asset_amount; | ||
let should_cancel_reduce_only_order = should_cancel_reduce_only_order( | ||
&maker.orders[maker_order_index], | ||
existing_base_asset_amount, | ||
)?; | ||
|
||
let should_expire_order = should_expire_order(&maker, maker_order_index, now)?; | ||
if breaches_oracle_price_limits || should_expire_order || should_cancel_reduce_only_order { | ||
let filler_reward = { | ||
let mut market = | ||
perp_market_map.get_ref_mut(&maker.orders[maker_order_index].market_index)?; | ||
pay_keeper_flat_reward_for_perps( | ||
&mut maker, | ||
filler.as_deref_mut(), | ||
market.deref_mut(), | ||
filler_reward, | ||
)? | ||
}; | ||
|
||
let existing_base_asset_amount = maker | ||
.get_perp_position(maker.orders[maker_order_index].market_index)? | ||
.base_asset_amount; | ||
let should_cancel_reduce_only_order = should_cancel_reduce_only_order( | ||
&maker.orders[maker_order_index], | ||
existing_base_asset_amount, | ||
)?; | ||
let explanation = if breaches_oracle_price_limits { | ||
OrderActionExplanation::OraclePriceBreachedLimitPrice | ||
} else if should_expire_order { | ||
OrderActionExplanation::OrderExpired | ||
} else { | ||
OrderActionExplanation::ReduceOnlyOrderIncreasedPosition | ||
}; | ||
|
||
// Dont fulfill with a maker order if oracle has diverged significantly, it should be expired or it is position increasing reduce only | ||
if breaches_oracle_price_limits || should_expire_order || should_cancel_reduce_only_order { | ||
let filler_reward = { | ||
let mut market = | ||
perp_market_map.get_ref_mut(&maker.orders[maker_order_index].market_index)?; | ||
pay_keeper_flat_reward_for_perps( | ||
&mut maker, | ||
filler.as_deref_mut(), | ||
market.deref_mut(), | ||
cancel_order( | ||
maker_order_index, | ||
maker.deref_mut(), | ||
&maker_key, | ||
perp_market_map, | ||
spot_market_map, | ||
oracle_map, | ||
now, | ||
slot, | ||
explanation, | ||
Some(filler_key), | ||
filler_reward, | ||
)? | ||
}; | ||
false, | ||
)?; | ||
|
||
let explanation = if breaches_oracle_price_limits { | ||
OrderActionExplanation::OraclePriceBreachedLimitPrice | ||
} else if should_expire_order { | ||
OrderActionExplanation::OrderExpired | ||
} else { | ||
OrderActionExplanation::ReduceOnlyOrderIncreasedPosition | ||
}; | ||
continue; | ||
} | ||
|
||
cancel_order( | ||
maker_order_index, | ||
maker.deref_mut(), | ||
&maker_key, | ||
perp_market_map, | ||
spot_market_map, | ||
oracle_map, | ||
now, | ||
slot, | ||
explanation, | ||
Some(filler_key), | ||
filler_reward, | ||
false, | ||
)?; | ||
filtered_maker_order_indexes.push((maker_order_index, maker_order_price)); | ||
} | ||
|
||
if filtered_maker_order_indexes.is_empty() { | ||
return Ok((None, None, None, None)); | ||
} | ||
|
||
let market_index = maker.orders[maker_order_index].market_index; | ||
let market_index = taker_order.market_index; | ||
settle_funding_payment( | ||
&mut maker, | ||
&maker_key, | ||
|
@@ -1161,10 +1143,21 @@ fn sanitize_maker_order<'a>( | |
Some(maker), | ||
maker_stats, | ||
Some(maker_key), | ||
Some(maker_order_index), | ||
Some(filtered_maker_order_indexes), | ||
)) | ||
} | ||
|
||
#[inline(always)] | ||
fn sort_maker_orders( | ||
maker_order_indexes: &mut [(usize, u64)], | ||
taker_order_direction: PositionDirection, | ||
) { | ||
maker_order_indexes.sort_by(|a, b| match taker_order_direction { | ||
PositionDirection::Long => a.1.cmp(&b.1), | ||
PositionDirection::Short => b.1.cmp(&a.1), | ||
}); | ||
} | ||
|
||
#[allow(clippy::type_complexity)] | ||
fn sanitize_referrer<'a>( | ||
referrer: Option<&'a AccountLoader<User>>, | ||
|
@@ -1211,7 +1204,7 @@ fn fulfill_perp_order( | |
user_stats: &mut UserStats, | ||
maker: &mut Option<&mut User>, | ||
maker_stats: &mut Option<&mut UserStats>, | ||
maker_order_index: Option<usize>, | ||
maker_order_indexes: Option<Vec<(usize, u64)>>, | ||
maker_key: Option<&Pubkey>, | ||
filler: &mut Option<&mut User>, | ||
filler_key: &Pubkey, | ||
|
@@ -1236,23 +1229,19 @@ fn fulfill_perp_order( | |
let user_order_risk_decreasing = | ||
determine_if_user_order_is_risk_decreasing(user, market_index, user_order_index)?; | ||
|
||
let maker_order_risk_decreasing = | ||
if let (Some(maker), Some(maker_order_index)) = (maker.as_ref(), maker_order_index) { | ||
determine_if_user_order_is_risk_decreasing(maker, market_index, maker_order_index)? | ||
} else { | ||
false | ||
}; | ||
let maker_base_asset_amount_before = if let Some(maker) = maker { | ||
let maker_position_index = get_position_index(&maker.perp_positions, market_index)?; | ||
maker.perp_positions[maker_position_index].base_asset_amount | ||
} else { | ||
0 | ||
}; | ||
|
||
let fulfillment_methods = { | ||
let market = perp_market_map.get_ref(&market_index)?; | ||
|
||
determine_perp_fulfillment_methods( | ||
&user.orders[user_order_index], | ||
if let Some(maker) = maker { | ||
Some(&maker.orders[maker_order_index.safe_unwrap()?]) | ||
} else { | ||
None | ||
}, | ||
&maker_order_indexes.as_ref(), | ||
&market.amm, | ||
reserve_price_before, | ||
valid_oracle_price, | ||
|
@@ -1299,15 +1288,15 @@ fn fulfill_perp_order( | |
*maker_price, | ||
true, | ||
)?, | ||
PerpFulfillmentMethod::Match => fulfill_perp_order_with_match( | ||
PerpFulfillmentMethod::Match(maker_order_index) => fulfill_perp_order_with_match( | ||
market.deref_mut(), | ||
user, | ||
user_stats, | ||
user_order_index, | ||
user_key, | ||
maker.as_deref_mut().safe_unwrap()?, | ||
maker_stats, | ||
maker_order_index.safe_unwrap()?, | ||
*maker_order_index, | ||
maker_key.safe_unwrap()?, | ||
filler, | ||
filler_stats, | ||
|
@@ -1324,8 +1313,8 @@ fn fulfill_perp_order( | |
)?, | ||
}; | ||
|
||
if fulfillment_method == &PerpFulfillmentMethod::Match && fill_base_asset_amount != 0 { | ||
maker_filled = true; | ||
if let PerpFulfillmentMethod::Match(_) = fulfillment_method { | ||
maker_filled = fill_base_asset_amount != 0; | ||
} | ||
|
||
base_asset_amount = base_asset_amount.safe_add(fill_base_asset_amount)?; | ||
|
@@ -1339,6 +1328,20 @@ fn fulfill_perp_order( | |
emit!(order_record) | ||
} | ||
|
||
let maker_risk_reducing = if let Some(maker) = maker { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wonder if checking before/after reduce allows for some intermediate risk increasing orders to go through.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't matter as long as the final state is lower risk, it just affects the margin requirement check we enforce anyway |
||
match get_position_index(&maker.perp_positions, market_index) { | ||
Ok(maker_position_index) => { | ||
maker.perp_positions[maker_position_index] | ||
.base_asset_amount | ||
.signum() | ||
== maker_base_asset_amount_before.signum() | ||
} | ||
Err(_) => true, | ||
} | ||
} else { | ||
false | ||
}; | ||
|
||
let perp_market = perp_market_map.get_ref(&market_index)?; | ||
let taker_maintenance_margin_buffer = calculate_maintenance_buffer_ratio( | ||
perp_market.margin_ratio_initial, | ||
|
@@ -1348,7 +1351,7 @@ fn fulfill_perp_order( | |
let maker_maintenance_margin_buffer = calculate_maintenance_buffer_ratio( | ||
perp_market.margin_ratio_initial, | ||
perp_market.margin_ratio_maintenance, | ||
maker_order_risk_decreasing, | ||
maker_risk_reducing, | ||
)?; | ||
drop(perp_market); | ||
|
||
|
@@ -3119,7 +3122,7 @@ fn sanitize_spot_maker_order<'a>( | |
let initial_margin_ratio = spot_market.get_margin_ratio(&MarginRequirementType::Initial)?; | ||
let maintenance_margin_ratio = | ||
spot_market.get_margin_ratio(&MarginRequirementType::Maintenance)?; | ||
order_breaches_oracle_price_limits( | ||
order_breaches_oracle_price_bands( | ||
&maker.orders[maker_order_index], | ||
oracle_price.price, | ||
slot, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maker_order_indexes
do we wanna rename this since its the price and index?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah can do