diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b30fc66..9c775de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - dev-* pull_request: branches: - main @@ -21,8 +22,7 @@ jobs: uses: actions-rs/toolchain@v1 with: toolchain: 1.86.0 - # components: clippy, rustfmt - components: rustfmt + components: clippy, rustfmt - name: Cache Cargo registry uses: actions/cache@v4 @@ -48,5 +48,5 @@ jobs: - name: Run cargo test run: cargo test --workspace --all-targets --no-fail-fast - # - name: Run cargo clippy - # run: cargo clippy --workspace --all-targets -- -D warnings \ No newline at end of file + - name: Run cargo clippy + run: cargo clippy --workspace --all-targets -- -D warnings \ No newline at end of file diff --git a/apex-core/benches/common.rs b/apex-core/benches/common.rs index c96b39a..2277472 100644 --- a/apex-core/benches/common.rs +++ b/apex-core/benches/common.rs @@ -1,16 +1,18 @@ use apex_core::prelude::*; +use std::cell::UnsafeCell; /// Quickly generate a simple limit order for testing pub fn make_limit_order(id: u64, side: Side, price: u64, qty: u64, ts: u64) -> Order { - let mut value = Order::default(); - value.id = id; - value.user_id = 1; - value.side = side; - value.price = Price::from(price); - *value.quantity.get_mut() = Quantity::from(qty); - value.created_at = ts; - value.updated_at = ts; - value + Order { + id, + user_id: 1, + side, + price: Price::from(price), + quantity: UnsafeCell::new(Quantity::from(qty)), + created_at: ts, + updated_at: ts, + ..Order::default() + } } /// Quickly generate a market order for testing diff --git a/apex-core/src/engine.rs b/apex-core/src/engine.rs index 7e7eed3..f88203b 100644 --- a/apex-core/src/engine.rs +++ b/apex-core/src/engine.rs @@ -1,13 +1,13 @@ pub mod book; -pub mod engine; pub mod error; +pub mod matching; pub mod syncer; pub mod types; pub mod prelude { pub use super::book::*; - pub use super::engine::*; pub use super::error::*; + pub use super::matching::*; pub use super::syncer::*; pub use super::types::*; } diff --git a/apex-core/src/engine/book.rs b/apex-core/src/engine/book.rs index b75b020..a6e73b2 100644 --- a/apex-core/src/engine/book.rs +++ b/apex-core/src/engine/book.rs @@ -24,7 +24,7 @@ pub trait OrderBook { /// Get the book fn get_book(&self, side: Side) -> &SkipList; /// Sync orders that's matched and trades - fn sync_matched(&self, updated: &Vec, trades: &Vec); + fn sync_matched(&self, updated: &[Order], trades: &[Trade]); } /// WalkingResult is used for match engine walking results @@ -239,7 +239,7 @@ impl OrderBook for DefaultOrderBook { order_entry.remove(); order_index.remove(&order_id); let id = self.id.fetch_add(1, Ordering::Acquire); - self.syncer.cancel_order(id, &book_order); + self.syncer.cancel_order(id, book_order); Ok(()) } @@ -251,10 +251,7 @@ impl OrderBook for DefaultOrderBook { Side::Buy => self.buy_orders.front(guard), Side::Sell => self.sell_orders.front(guard), }; - match entry { - Some(entry) => Some(entry.key().price), - None => None, - } + entry.map(|entry| entry.key().price) } fn get_book(&self, side: Side) -> &SkipList { @@ -265,7 +262,7 @@ impl OrderBook for DefaultOrderBook { } /// Sync orders that are matched and trades - fn sync_matched(&self, updated: &Vec, trades: &Vec) { + fn sync_matched(&self, updated: &[Order], trades: &[Trade]) { let id = self.id.fetch_add(1, Ordering::Acquire); self.syncer.matched(id, updated, trades); } @@ -363,14 +360,16 @@ impl MatchingEngineWalker for DefaultOrderBook { continue; } - let taker = if buy_maker_only && !sell_maker_only { - sell_order - } else if sell_maker_only && !buy_maker_only { - buy_order - } else if buy_key.priority < sell_key.priority { - buy_order - } else { - sell_order + let taker = match (buy_maker_only, sell_maker_only) { + (true, false) => sell_order, + (false, true) => buy_order, + _ => { + if buy_key.priority < sell_key.priority { + buy_order + } else { + sell_order + } + } }; let taker_is_buy = taker.side == Side::Buy; diff --git a/apex-core/src/engine/engine.rs b/apex-core/src/engine/matching.rs similarity index 99% rename from apex-core/src/engine/engine.rs rename to apex-core/src/engine/matching.rs index 312f191..948a8b3 100644 --- a/apex-core/src/engine/engine.rs +++ b/apex-core/src/engine/matching.rs @@ -93,7 +93,7 @@ impl DefaultMatchingEngine { self.order_book .walking_by_order_id_list(order_id_list.as_slice(), &mut |o| { o.exit_matched(); - return WalkingResult::next(); + WalkingResult::next() }); None } diff --git a/apex-core/src/engine/syncer.rs b/apex-core/src/engine/syncer.rs index c68056f..5a5d335 100644 --- a/apex-core/src/engine/syncer.rs +++ b/apex-core/src/engine/syncer.rs @@ -9,7 +9,7 @@ pub trait OrderBookSyncer: Send + Sync { /// This function is called when the order book cancels an order fn cancel_order(&self, id: u64, order: &Order); /// This function is called when the order engine matches an order - fn matched(&self, id: u64, updated: &Vec, trades: &Vec); + fn matched(&self, id: u64, updated: &[Order], trades: &[Trade]); } /// EmptyOrderBookSyncer is a no-op implementation of OrderBookSyncer @@ -22,5 +22,5 @@ impl OrderBookSyncer for EmptyOrderBookSyncer { fn cancel_order(&self, _id: u64, _order: &Order) {} - fn matched(&self, _id: u64, _updated: &Vec, _trades: &Vec) {} + fn matched(&self, _id: u64, _updated: &[Order], _trades: &[Trade]) {} } diff --git a/apex-core/src/engine/types.rs b/apex-core/src/engine/types.rs index 57d0ed3..1c18f8b 100644 --- a/apex-core/src/engine/types.rs +++ b/apex-core/src/engine/types.rs @@ -75,7 +75,7 @@ pub enum OrderStatus { /// - `Active` → `Finished` (cancellation thread removes order) /// - `Matched` → `Active` (matching thread partially fills order) /// - `Matched` → `Finished` (matching thread completes order) -/// So finally state is `Finished`. +/// So finally state is `Finished`. #[derive(PartialEq, Eq, Default, Clone, Copy, Debug)] pub enum OrderLifecycle { /// The order is live and can be matched or canceled. @@ -331,7 +331,7 @@ impl Clone for Order { id: self.id, user_id: self.user_id, side: self.side, - lifecycle: AtomicU8::new(self.lifecycle.load(Ordering::Acquire).into()), + lifecycle: AtomicU8::new(self.lifecycle.load(Ordering::Acquire)), order_type: self.order_type, status: UnsafeCell::new(unsafe { *self.status.get() }), match_strategy: self.match_strategy, @@ -341,8 +341,8 @@ impl Clone for Order { slippage_tolerance: self.slippage_tolerance, quantity: UnsafeCell::new(unsafe { *self.quantity.get() }), filled_quantity: UnsafeCell::new(unsafe { *self.filled_quantity.get() }), - cancel_reason: UnsafeCell::new(unsafe { (*self.cancel_reason.get()).clone() }), - reject_reason: UnsafeCell::new(unsafe { (*self.reject_reason.get()).clone() }), + cancel_reason: UnsafeCell::new(unsafe { *self.cancel_reason.get() }), + reject_reason: UnsafeCell::new(unsafe { *self.reject_reason.get() }), created_at: self.created_at, updated_at: self.updated_at, } @@ -387,6 +387,7 @@ impl Order { } /// Get the current lifecycle state is `Finished`. + #[allow(dead_code)] #[inline(always)] pub(crate) fn is_finished(&self) -> bool { self.lifecycle.load(Ordering::Acquire) == OrderLifecycle::Finished.into() @@ -402,57 +403,53 @@ impl Order { /// Enter matched lifecycle state. #[inline(always)] pub(crate) fn enter_matched(&self) -> bool { - match self.lifecycle.compare_exchange_weak( - OrderLifecycle::Active.into(), - OrderLifecycle::Matched.into(), - Ordering::AcqRel, - Ordering::Relaxed, - ) { - Ok(_) => true, - Err(_) => false, - } + self.lifecycle + .compare_exchange_weak( + OrderLifecycle::Active.into(), + OrderLifecycle::Matched.into(), + Ordering::AcqRel, + Ordering::Relaxed, + ) + .is_ok() } /// Exit from matched to active lifecycle state. #[inline(always)] pub(crate) fn exit_matched(&self) -> bool { - match self.lifecycle.compare_exchange_weak( - OrderLifecycle::Matched.into(), - OrderLifecycle::Active.into(), - Ordering::AcqRel, - Ordering::Relaxed, - ) { - Ok(_) => true, - Err(_) => false, - } + self.lifecycle + .compare_exchange_weak( + OrderLifecycle::Matched.into(), + OrderLifecycle::Active.into(), + Ordering::AcqRel, + Ordering::Relaxed, + ) + .is_ok() } /// Enter the finished lifecycle state from active. #[inline(always)] pub(crate) fn enter_finished_from_active(&self) -> bool { - match self.lifecycle.compare_exchange_weak( - OrderLifecycle::Active.into(), - OrderLifecycle::Finished.into(), - Ordering::AcqRel, - Ordering::Relaxed, - ) { - Ok(_) => true, - Err(_) => false, - } + self.lifecycle + .compare_exchange_weak( + OrderLifecycle::Active.into(), + OrderLifecycle::Finished.into(), + Ordering::AcqRel, + Ordering::Relaxed, + ) + .is_ok() } /// Enter the finished lifecycle state from matched. #[inline(always)] pub(crate) fn enter_finished_from_matched(&self) -> bool { - match self.lifecycle.compare_exchange_weak( - OrderLifecycle::Matched.into(), - OrderLifecycle::Finished.into(), - Ordering::AcqRel, - Ordering::Relaxed, - ) { - Ok(_) => true, - Err(_) => false, - } + self.lifecycle + .compare_exchange_weak( + OrderLifecycle::Matched.into(), + OrderLifecycle::Finished.into(), + Ordering::AcqRel, + Ordering::Relaxed, + ) + .is_ok() } /// Get the order priority of the order book. @@ -488,6 +485,7 @@ impl Order { /// SAFETY: /// Only the matching engine thread modifies cancel_reason, /// ensuring safe access under shared reference. + #[allow(dead_code)] #[inline(always)] pub(crate) fn update_cancel_reason(&self, reason: CancelReason) { unsafe { @@ -512,11 +510,7 @@ impl Order { /// For 'Sell' orders, the lowest price. /// Returns `None` if no slippage tolerance is set. pub fn slippage_bound_price(&self, price: Price) -> Option { - if self.slippage_tolerance.is_none() { - return None; - } - - let slippage = self.slippage_tolerance.unwrap(); + let slippage = self.slippage_tolerance?; let mut factor = U512::from(slippage.0); factor = factor.mul(price); let (quotient, _) = factor.div_rem_limb_with_reciprocal(&RECIPROCAL_10000); diff --git a/apex-core/tests/common.rs b/apex-core/tests/common.rs index 2b46477..f19185a 100644 --- a/apex-core/tests/common.rs +++ b/apex-core/tests/common.rs @@ -2,21 +2,24 @@ use apex_core::prelude::*; use crossbeam::epoch; use crossbeam::epoch::default_collector; use crossbeam_skiplist::SkipList; +use std::cell::UnsafeCell; /// Quickly generate a simple limit order for testing pub fn make_limit_order(id: u64, side: Side, price: u64, qty: u64, ts: u64) -> Order { - let mut value = Order::default(); - value.id = id; - value.user_id = 1; - value.side = side; - value.price = Price::from(price); - *value.quantity.get_mut() = Quantity::from(qty); - value.created_at = ts; - value.updated_at = ts; - value + Order { + id, + user_id: 1, + side, + price: Price::from(price), + quantity: UnsafeCell::new(Quantity::from(qty)), + created_at: ts, + updated_at: ts, + ..Order::default() + } } /// Quickly generate a market order for testing +#[allow(dead_code)] pub fn make_market_order(id: u64, side: Side, qty: u64, ts: u64) -> Order { let mut value = make_limit_order(id, side, 0, qty, ts); value.order_type = OrderType::Market; @@ -24,6 +27,7 @@ pub fn make_market_order(id: u64, side: Side, qty: u64, ts: u64) -> Order { } /// Get the current state of a side of the book +#[allow(dead_code)] pub fn get_book_state(book: &dyn OrderBookWalker, side: Side) -> Vec<(OrderID, Quantity)> { let guard = &epoch::pin(); book.get_book(side) diff --git a/apex-core/tests/stress.rs b/apex-core/tests/stress.rs index 3cd7d54..be2040f 100644 --- a/apex-core/tests/stress.rs +++ b/apex-core/tests/stress.rs @@ -36,7 +36,7 @@ fn test_massive_order_cancellation() { // Randomly cancel half of them let mut rng = rand::rng(); - for i in 0..25_000 { + for _i in 0..25_000 { let id_to_cancel = rng.random_range(0..50_000); let _ = engine.cancel_order(id_to_cancel); }