diff --git a/src/state/markets/test_market.rs b/src/state/markets/test_market.rs index b43c78e..75fee76 100644 --- a/src/state/markets/test_market.rs +++ b/src/state/markets/test_market.rs @@ -155,7 +155,7 @@ fn test_market_simple() { ) = market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Bid, price.as_u64(), size.as_u64(), @@ -194,7 +194,7 @@ fn test_market_simple() { ) = market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Ask, price.as_u64(), size.as_u64(), @@ -222,7 +222,7 @@ fn test_market_simple() { market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Ask, ladder.asks[0].price_in_ticks.as_u64(), ladder.asks[0].size_in_base_lots.as_u64(), @@ -264,7 +264,7 @@ fn test_market_simple() { ) = market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Ask, price.as_u64(), size.as_u64(), @@ -903,7 +903,7 @@ fn test_orders_with_only_free_funds() { assert!(market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Ask, 100, 5, @@ -919,7 +919,7 @@ fn test_orders_with_only_free_funds() { assert!(market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Ask, 100, 5, @@ -985,7 +985,7 @@ fn test_orders_with_only_free_funds() { assert!(market .place_order( &trader, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Bid, 100, 6, @@ -1614,6 +1614,118 @@ fn test_fok_with_slippage_3() { assert!(ladder.bids[0].size_in_base_lots == prev_ladder.bids[0].size_in_base_lots); } +#[test] +fn test_exact_out_order() { + let mut rng = StdRng::seed_from_u64(2); + let mut market = setup_market_with_params(10000, 100, 2); + let mut event_recorder = VecDeque::new(); + let mut record_event_fn = |e: MarketEvent| event_recorder.push_back(e); + + let maker = rng.gen::(); + let bid_start = rng.gen_range(9996, 10_000); + let bid_end = rng.gen_range(9900, 9978); + let ask_start = rng.gen_range(10_001, 10_005); + let ask_end = rng.gen_range(10_033, 10_100); + let start_size = rng.gen_range(10, 20); + let size_step = rng.gen_range(1, 5); + layer_orders( + &mut market, + maker, + bid_start, + bid_end, + 1, + start_size, + size_step, + Side::Bid, + &mut record_event_fn, + ); + layer_orders( + &mut market, + maker, + ask_start, + ask_end, + 1, + start_size, + size_step, + Side::Ask, + &mut record_event_fn, + ); + + let ladder = market.get_typed_ladder(5); + let taker = rng.gen::(); + + // Send IOC buy order specifying exact number of base lots out + let price = ladder.asks[0].price_in_ticks; + let base_lots_to_fill = BaseLots::new(ladder.asks[0].size_in_base_lots.as_u64() / 2); // don't take the whole level + + let ( + _, + MatchingEngineResponse { + num_quote_lots_in: num_quote_lots, + num_base_lots_out: num_base_lots, + .. + }, + ) = market + .place_order( + &taker, + OrderPacket::new_ioc_by_base_lots( + Side::Bid, + price.as_u64() + 10, // supply limit price higher than the best ask + base_lots_to_fill.as_u64(), + SelfTradeBehavior::DecrementTake, + None, + rng.gen::(), + false, + ), + &mut record_event_fn, + ) + .unwrap(); + + // check that the exact number of base lots was filled, and that they were filled at the expected price + assert_eq!(base_lots_to_fill, num_base_lots); + // assert_eq!( + // num_quote_lots * market.base_lots_per_base_unit, + // market.tick_size_in_quote_lots_per_base_unit * price * base_lots_to_fill + // ); + + // Send IOC sell order specifying exact number of quote lots out + let price = ladder.bids[0].price_in_ticks; + let mut quote_lots_to_fill = + market.tick_size_in_quote_lots_per_base_unit * price * ladder.bids[0].size_in_base_lots + / market.base_lots_per_base_unit; + quote_lots_to_fill = QuoteLots::new(quote_lots_to_fill.as_u64() / 2); // don't take the whole level + + let ( + _, + MatchingEngineResponse { + num_quote_lots_out: num_quote_lots, + num_base_lots_in: num_base_lots, + .. + }, + ) = market + .place_order( + &taker, + OrderPacket::new_ioc_by_quote_lots( + Side::Ask, + price.as_u64() - 10, // supply limit price lower than the best bid + quote_lots_to_fill.as_u64(), + SelfTradeBehavior::DecrementTake, + None, + rng.gen::(), + false, + ), + &mut record_event_fn, + ) + .unwrap(); + + // check that the exact number of quote lots was filled, and that they were filled at the expected price + assert_eq!(quote_lots_to_fill, num_quote_lots); + assert_eq!( + num_quote_lots * market.base_lots_per_base_unit, + market.tick_size_in_quote_lots_per_base_unit * price * num_base_lots + ); +} + #[test] fn test_fees_basic() { let mut rng = StdRng::seed_from_u64(2); @@ -1649,7 +1761,7 @@ fn test_fees_basic() { let (o_id, release_quantities) = market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Bid, 10100, 10, @@ -1675,7 +1787,7 @@ fn test_fees_basic() { let (o_id, release_quantities) = market .place_order( &taker, - OrderPacket::new_ioc_by_lots( + OrderPacket::new_ioc_by_base_lots( Side::Ask, 9900, 10, diff --git a/src/state/order_schema/order_packet.rs b/src/state/order_schema/order_packet.rs index 2145db8..a0db5b4 100644 --- a/src/state/order_schema/order_packet.rs +++ b/src/state/order_schema/order_packet.rs @@ -359,7 +359,7 @@ impl OrderPacket { ) } - pub fn new_ioc_by_lots( + pub fn new_ioc_by_base_lots( side: Side, price_in_ticks: u64, base_lot_budget: u64, @@ -382,6 +382,29 @@ impl OrderPacket { ) } + pub fn new_ioc_by_quote_lots( + side: Side, + price_in_ticks: u64, + quote_lot_budget: u64, + self_trade_behavior: SelfTradeBehavior, + match_limit: Option, + client_order_id: u128, + use_only_deposited_funds: bool, + ) -> Self { + Self::new_ioc( + side, + Some(price_in_ticks), + 0, + quote_lot_budget, + 0, + 0, + self_trade_behavior, + match_limit, + client_order_id, + use_only_deposited_funds, + ) + } + pub fn new_ioc_buy_with_slippage(quote_lots_in: u64, min_base_lots_out: u64) -> Self { Self::new_ioc( Side::Bid, diff --git a/tests/test_phoenix.rs b/tests/test_phoenix.rs index 73bc818..1a9eda7 100644 --- a/tests/test_phoenix.rs +++ b/tests/test_phoenix.rs @@ -973,7 +973,7 @@ async fn test_phoenix_admin() { .is_ok(), "Should be able to claim authority if you are the successor" ); - let params = OrderPacket::new_ioc_by_lots( + let params = OrderPacket::new_ioc_by_base_lots( Side::Bid, sdk.float_price_to_ticks(102.0), 1, @@ -1312,7 +1312,7 @@ async fn test_phoenix_basic() { .await; sdk.set_payer(clone_keypair(&default_taker.user)); - let params = OrderPacket::new_ioc_by_lots( + let params = OrderPacket::new_ioc_by_base_lots( Side::Ask, sdk.float_price_to_ticks(39.7), sdk.raw_base_units_to_base_lots(10.0), @@ -1820,7 +1820,7 @@ async fn test_phoenix_orders_with_free_funds() { sdk.set_payer(clone_keypair(&default_taker.user)); //Attempt to use free funds to trade, will reject because the taker has no seat - let sell_params = OrderPacket::new_ioc_by_lots( + let sell_params = OrderPacket::new_ioc_by_base_lots( Side::Ask, sdk.float_price_to_ticks(31.0), sdk.raw_base_units_to_base_lots(55.0), @@ -1843,7 +1843,7 @@ async fn test_phoenix_orders_with_free_funds() { .is_err()); //Trade through the first 10 levels of the book and self trade the last level on each side - let sell_params = OrderPacket::new_ioc_by_lots( + let sell_params = OrderPacket::new_ioc_by_base_lots( Side::Ask, sdk.float_price_to_ticks(31.0), sdk.raw_base_units_to_base_lots(55.0), @@ -1853,7 +1853,7 @@ async fn test_phoenix_orders_with_free_funds() { false, ); - let buy_params = OrderPacket::new_ioc_by_lots( + let buy_params = OrderPacket::new_ioc_by_base_lots( Side::Bid, sdk.float_price_to_ticks(59.0), sdk.raw_base_units_to_base_lots(55.0), @@ -1863,7 +1863,7 @@ async fn test_phoenix_orders_with_free_funds() { false, ); - let self_trade_bid_params = OrderPacket::new_ioc_by_lots( + let self_trade_bid_params = OrderPacket::new_ioc_by_base_lots( Side::Ask, sdk.float_price_to_ticks(30.0), sdk.raw_base_units_to_base_lots(11.0), @@ -1873,7 +1873,7 @@ async fn test_phoenix_orders_with_free_funds() { false, ); - let self_trade_offer_params = OrderPacket::new_ioc_by_lots( + let self_trade_offer_params = OrderPacket::new_ioc_by_base_lots( Side::Bid, sdk.float_price_to_ticks(60.0), sdk.raw_base_units_to_base_lots(11.0), @@ -1953,7 +1953,7 @@ async fn test_phoenix_orders_with_free_funds() { sdk.raw_base_units_to_base_lots(10.0), ); - let ioc_buy_params = OrderPacket::new_ioc_by_lots( + let ioc_buy_params = OrderPacket::new_ioc_by_base_lots( Side::Bid, sdk.float_price_to_ticks(34.1), sdk.raw_base_units_to_base_lots(10.0), @@ -1963,7 +1963,7 @@ async fn test_phoenix_orders_with_free_funds() { true, ); - let ioc_sell_params = OrderPacket::new_ioc_by_lots( + let ioc_sell_params = OrderPacket::new_ioc_by_base_lots( Side::Ask, sdk.float_price_to_ticks(30.0), sdk.raw_base_units_to_base_lots(10.0), @@ -2051,7 +2051,7 @@ async fn test_phoenix_orders_with_free_funds() { let new_order_ix = create_new_order_with_free_funds_instruction( &sdk.core.active_market_key, &second_maker.user.pubkey(), - &OrderPacket::new_ioc_by_lots( + &OrderPacket::new_ioc_by_base_lots( Side::Bid, sdk.float_price_to_ticks(250.0), sdk.raw_base_units_to_base_lots(10.0), @@ -3060,7 +3060,7 @@ async fn test_phoenix_place_multiple_memory_management() { &default_taker.user.pubkey(), &sdk.base_mint, &sdk.quote_mint, - &OrderPacket::new_ioc_by_lots( + &OrderPacket::new_ioc_by_base_lots( Side::Ask, 0, u64::MAX, @@ -3133,7 +3133,7 @@ async fn test_phoenix_place_multiple_limit_orders_adversarial() { // Normally this would crash due to compute usage, but we now coalesce the orders // at the same price in place multiple orders sdk.set_payer(clone_keypair(&default_taker.user)); - let order_packet = OrderPacket::new_ioc_by_lots( + let order_packet = OrderPacket::new_ioc_by_base_lots( Side::Bid, sdk.float_price_to_ticks(101.0), 700, @@ -3265,7 +3265,7 @@ async fn test_phoenix_basic_with_raw_base_unit_adjustment() { let second_cross_price = sdk.float_price_to_ticks(*bid_price_range.first().unwrap()); // Takes the last price in the bid price_range (40.0) let second_cross_size = sdk.raw_base_units_to_base_lots(1000_f64); - let params = OrderPacket::new_ioc_by_lots( + let params = OrderPacket::new_ioc_by_base_lots( Side::Ask, second_cross_price, first_cross_size + second_cross_size,