Crates.io | silhouette-mvp-orderbook |
lib.rs | silhouette-mvp-orderbook |
version | |
source | src |
created_at | 2025-04-10 12:14:57.767706+00 |
updated_at | 2025-04-23 07:25:43.769813+00 |
description | A price-time priority order book matching engine |
homepage | |
repository | |
max_upload_size | |
id | 1628219 |
Cargo.toml error: | TOML parse error at line 17, column 1 | 17 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
A price-time priority order book matching engine.
order_received(order: Order)
Adds an order to the order book. Takes in an Order
struct (in src/types.rs
):
pub struct Order {
/// Unique identifier for the order
pub id: alloy_primitives::U64,
/// Address that receives tokens after order settlement
pub receiver: alloy_primitives::Address,
/// Token pair being traded (e.g., "ETH/USDC")
pub token_pair: String,
/// Whether this is a buy or sell order
pub side: OrderSide,
/// Amount of base token to trade
pub amount: alloy_primitives::U64,
/// Limit price for the order
pub price: alloy_primitives::U64,
/// Timestamp after which the order expires
pub expiry: alloy_primitives::U64,
/// Timestamp when the order was received
pub timestamp: alloy_primitives::U64,
}
Match
: Represents a successful trade with:cancel_order(order_id: U64) -> CancellationResult
Cancels an order by its ID. Returns a CancellationResult
with details about the cancelled order, or an error if the order doesn't exist.
pub struct CancellationResult {
/// ID of the cancelled order
pub order_id: U64,
/// Address that would have received tokens after order settlement
pub receiver: Address,
/// Remaining amount of base token that was cancelled
pub amount: U64,
/// Token pair being traded (e.g., "ETH/USDC")
pub token_pair: String,
/// Limit price for the order
pub limit_price: U64,
/// Whether this was a buy or sell order
pub side: OrderSide,
}
settle_batch(current_time: U64, exchange_price: U64) -> BatchResult
Matches all orders in the order book. Can be called every time a new order is received. Orders are matched based on price-time priority, with order ID as a tiebreaker for orders with the same price and time. For two compatible orders (each within the other's limit price), the trade executes at the sell order's price. For example, these two orders will be matched at price 1000:
If an order has not been completely filled and settle_batch()
is called after that order's expiry, the remaining amount will be settled on the L1. exchange_price
is provided but no longer used for compatibility checks - all expired orders are included in the remainders
vector in BatchResult
.
settle_batch()
returns a BatchResult
struct (in src/types.rs
):
pub struct BatchResult {
/// Matches that occurred during this batch
pub matches: Vec<Match>,
/// Orders that expired with unfilled amounts
pub remainders: Vec<Remainder>,
/// Total remainder amount
pub total_remainder: U64,
}
The system supports both limit and market orders:
Standard orders with a specified price limit:
Orders that execute immediately at the best available price:
src/orderbook.rs
)Main matching engine implementation:
BTreeMap
for price-time priority ordering with order ID as a tiebreakerorder_received()
: Processes new orderscancel_order()
: Cancels an existing ordersettle_batch()
: Returns and clears matched orderstry_match()
: Internal matching logicsrc/builder.rs
)A more intuitive API for creating orders:
// Create a buy order
let buy_order = OrderBuilder::buy("ETH")
.using("USDC")
.id(U64::from(1))
.receiver(Address::zero())
.amount(dec!(1.0)) // Use dec! macro for Decimal input (e.g., 1 ETH)
.price(dec!(2000)) // Use dec! macro for Decimal input (e.g., $2000)
.expiry(U64::from(u64::MAX))
.timestamp(U64::from(1))
.build()
.unwrap();
// Create a sell order
let sell_order = OrderBuilder::sell("ETH")
.using("USDC")
.id(U64::from(2))
.receiver(Address::repeat_byte(1))
.amount(dec!(1.0)) // Use dec! macro
.price(dec!(1900)) // Use dec! macro
.expiry(U64::from(u64::MAX))
.timestamp(U64::from(2))
.build()
.unwrap();
// Create a market buy order
let market_buy_order = OrderBuilder::market_buy("ETH")
.using("USDC")
.id(U64::from(3))
.receiver(Address::zero())
.amount(dec!(1.0)) // Use dec! macro (price is not needed for market orders)
.expiry(U64::from(u64::MAX))
.timestamp(U64::from(3))
.build()
.unwrap();
// Create a market sell order
let market_sell_order = OrderBuilder::market_sell("ETH")
.using("USDC")
.id(U64::from(4))
.receiver(Address::zero())
.amount(dec!(1.5)) // Use dec! macro
.expiry(U64::from(u64::MAX))
.timestamp(U64::from(4))
.build()
.unwrap();
This maintains compatibility with the standard base/quote convention used internally.
For any trading pair "BASE/QUOTE" (e.g., "ETH/USDC"):
The OrderBuilder
handles this convention internally, so users can specify which token they're buying or selling without knowing which token is the base/quote
Both Price (px) and Size (sz) have a maximum number of decimals that are accepted.