Skip to content

Commit

Permalink
jit: min order size, fallback price, logging for missed fill
Browse files Browse the repository at this point in the history
jit: account for min order size
  • Loading branch information
crispheaney authored Jan 23, 2024
2 parents 69832d6 + f63a671 commit a719bb2
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 25 deletions.
2 changes: 2 additions & 0 deletions programs/jit-proxy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ pub enum ErrorCode {
UnprofitableArb,
#[msg("PositionLimitBreached")]
PositionLimitBreached,
#[msg("NoFill")]
NoFill,
}
99 changes: 74 additions & 25 deletions programs/jit-proxy/src/instructions/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,44 @@ pub fn jit<'info>(ctx: Context<'_, '_, '_, 'info, Jit<'info>>, params: JitParams
None,
)?;

let (oracle_price, tick_size) = if market_type == DriftMarketType::Perp {
let (oracle_price, tick_size, min_order_size) = if market_type == DriftMarketType::Perp {
let perp_market = perp_market_map.get_ref(&market_index)?;
let oracle_price = oracle_map.get_price_data(&perp_market.amm.oracle)?.price;

(oracle_price, perp_market.amm.order_tick_size)
(
oracle_price,
perp_market.amm.order_tick_size,
perp_market.amm.min_order_size,
)
} else {
let spot_market = spot_market_map.get_ref(&market_index)?;
let oracle_price = oracle_map.get_price_data(&spot_market.oracle)?.price;

(oracle_price, spot_market.order_tick_size)
(
oracle_price,
spot_market.order_tick_size,
spot_market.min_order_size,
)
};

let taker_price =
taker_order.force_get_limit_price(Some(oracle_price), None, slot, tick_size)?;
match taker_order.get_limit_price(Some(oracle_price), None, slot, tick_size)? {
Some(price) => price,
None if market_type == DriftMarketType::Perp => {
// if the order doesn't have a price, drift users amm price for taker price
let perp_market = perp_market_map.get_ref(&market_index)?;
let reserve_price = perp_market.amm.reserve_price()?;
match taker_direction {
PositionDirection::Long => perp_market.amm.ask_price(reserve_price)?,
PositionDirection::Short => perp_market.amm.bid_price(reserve_price)?,
}
}
None => {
// Shouldnt be possible for spot
msg!("taker order didnt have price");
return Err(ErrorCode::TakerOrderNotFound.into());
}
};

let maker_direction = taker_direction.opposite();
let maker_worst_price = params.get_worst_price(oracle_price, taker_direction)?;
Expand Down Expand Up @@ -83,7 +107,9 @@ pub fn jit<'info>(ctx: Context<'_, '_, '_, 'info, Jit<'info>>, params: JitParams
}
let maker_price = taker_price;

let taker_base_asset_amount_unfilled = taker_order.get_base_asset_amount_unfilled(None)?;
let taker_base_asset_amount_unfilled = taker_order
.get_base_asset_amount_unfilled(None)?
.max(min_order_size);
let maker_existing_position = if market_type == DriftMarketType::Perp {
let perp_market = perp_market_map.get_ref(&market_index)?;
let perp_position = maker.get_perp_position(market_index);
Expand All @@ -108,6 +134,7 @@ pub fn jit<'info>(ctx: Context<'_, '_, '_, 'info, Jit<'info>>, params: JitParams
maker_direction,
taker_base_asset_amount_unfilled,
maker_existing_position,
min_order_size,
) {
Ok(size) => size,
Err(e) => {
Expand Down Expand Up @@ -141,7 +168,26 @@ pub fn jit<'info>(ctx: Context<'_, '_, '_, 'info, Jit<'info>>, params: JitParams
drop(taker);
drop(maker);

place_and_make(ctx, params.taker_order_id, order_params)?;
place_and_make(&ctx, params.taker_order_id, order_params)?;

let taker = ctx.accounts.taker.load()?;

let taker_base_asset_amount_unfilled_after = match taker.get_order(params.taker_order_id) {
Some(order) => order.get_base_asset_amount_unfilled(None)?,
None => 0,
};

if taker_base_asset_amount_unfilled_after == taker_base_asset_amount_unfilled {
// taker order failed to fill
msg!("taker order failed to fill");
msg!(
"taker price = {} maker price = {} oracle price {}",
taker_price,
maker_price,
oracle_price
);
return Err(ErrorCode::NoFill.into());
}

Ok(())
}
Expand Down Expand Up @@ -210,15 +256,17 @@ fn check_position_limits(
maker_direction: PositionDirection,
taker_base_asset_amount_unfilled: u64,
maker_existing_position: i64,
min_order_size: u64,
) -> Result<u64> {
if maker_direction == PositionDirection::Long {
let size = params.max_position.safe_sub(maker_existing_position)?;

if size <= 0 {
if size <= min_order_size.cast()? {
msg!(
"maker existing position {} >= max position {}",
"maker existing position {} >= max position {} + min order size {}",
maker_existing_position,
params.max_position
params.max_position,
min_order_size
);
return Err(ErrorCode::PositionLimitBreached.into());
}
Expand All @@ -227,11 +275,12 @@ fn check_position_limits(
} else {
let size = maker_existing_position.safe_sub(params.min_position)?;

if size <= 0 {
if size <= min_order_size.cast()? {
msg!(
"maker existing position {} <= min position {}",
"maker existing position {} <= min position {} + min order size {}",
maker_existing_position,
params.min_position
params.min_position,
min_order_size
);
return Err(ErrorCode::PositionLimitBreached.into());
}
Expand All @@ -253,55 +302,55 @@ mod tests {
};

// same direction, doesn't breach
let result = check_position_limits(params, PositionDirection::Long, 10, 40);
let result = check_position_limits(params, PositionDirection::Long, 10, 40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 10);
let result = check_position_limits(params, PositionDirection::Short, 10, -40);
let result = check_position_limits(params, PositionDirection::Short, 10, -40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 10);

// same direction, whole order breaches, only takes enough to hit limit
let result = check_position_limits(params, PositionDirection::Long, 100, 40);
let result = check_position_limits(params, PositionDirection::Long, 100, 40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 60);
let result = check_position_limits(params, PositionDirection::Short, 100, -40);
let result = check_position_limits(params, PositionDirection::Short, 100, -40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 60);

// opposite direction, doesn't breach
let result = check_position_limits(params, PositionDirection::Long, 10, -40);
let result = check_position_limits(params, PositionDirection::Long, 10, -40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 10);
let result = check_position_limits(params, PositionDirection::Short, 10, 40);
let result = check_position_limits(params, PositionDirection::Short, 10, 40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 10);

// opposite direction, whole order breaches, only takes enough to take flipped limit
let result = check_position_limits(params, PositionDirection::Long, 200, -40);
let result = check_position_limits(params, PositionDirection::Long, 200, -40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 140);
let result = check_position_limits(params, PositionDirection::Short, 200, 40);
let result = check_position_limits(params, PositionDirection::Short, 200, 40, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 140);

// opposite direction, maker already breached, allows reducing
let result = check_position_limits(params, PositionDirection::Long, 200, -150);
let result = check_position_limits(params, PositionDirection::Long, 200, -150, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 200);
let result = check_position_limits(params, PositionDirection::Short, 200, 150);
let result = check_position_limits(params, PositionDirection::Short, 200, 150, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 200);

// same direction, maker already breached, errors
let result = check_position_limits(params, PositionDirection::Long, 200, 150);
let result = check_position_limits(params, PositionDirection::Long, 200, 150, 0);
assert!(result.is_err());
let result = check_position_limits(params, PositionDirection::Short, 200, -150);
let result = check_position_limits(params, PositionDirection::Short, 200, -150, 0);
assert!(result.is_err());
}
}

fn place_and_make<'info>(
ctx: Context<'_, '_, '_, 'info, Jit<'info>>,
ctx: &Context<'_, '_, '_, 'info, Jit<'info>>,
taker_order_id: u32,
order_params: OrderParams,
) -> Result<()> {
Expand Down
26 changes: 26 additions & 0 deletions ts/sdk/src/types/jit_proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ export type JitProxy = {
},
{
name: 'TryPostOnly';
},
{
name: 'Slide';
}
];
};
Expand Down Expand Up @@ -262,6 +265,16 @@ export type JitProxy = {
code: 6007;
name: 'UnprofitableArb';
msg: 'UnprofitableArb';
},
{
code: 6008;
name: 'PositionLimitBreached';
msg: 'PositionLimitBreached';
},
{
code: 6009;
name: 'NoFill';
msg: 'NoFill';
}
];
};
Expand Down Expand Up @@ -458,6 +471,9 @@ export const IDL: JitProxy = {
{
name: 'TryPostOnly',
},
{
name: 'Slide',
},
],
},
},
Expand Down Expand Up @@ -531,5 +547,15 @@ export const IDL: JitProxy = {
name: 'UnprofitableArb',
msg: 'UnprofitableArb',
},
{
code: 6008,
name: 'PositionLimitBreached',
msg: 'PositionLimitBreached',
},
{
code: 6009,
name: 'NoFill',
msg: 'NoFill',
},
],
};

0 comments on commit a719bb2

Please sign in to comment.