538 lines
17 KiB
Rust
538 lines
17 KiB
Rust
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
use std::cmp::Ordering;
|
||
|
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||
|
|
|
||
|
|
/// Unique identifier for an order
|
||
|
|
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||
|
|
pub struct OrderId(pub String);
|
||
|
|
|
||
|
|
/// Public key representing a trader
|
||
|
|
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||
|
|
pub struct TraderId(pub String);
|
||
|
|
|
||
|
|
/// Side of the order (buy or sell)
|
||
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||
|
|
pub enum Side {
|
||
|
|
Buy,
|
||
|
|
Sell,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Status of an order
|
||
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||
|
|
pub enum OrderStatus {
|
||
|
|
Open,
|
||
|
|
PartiallyFilled,
|
||
|
|
Filled,
|
||
|
|
Cancelled,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// An order in the order book
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct Order {
|
||
|
|
pub id: OrderId,
|
||
|
|
pub trader: TraderId,
|
||
|
|
pub side: Side,
|
||
|
|
pub price: u128,
|
||
|
|
pub original_amount: u128,
|
||
|
|
pub remaining_amount: u128,
|
||
|
|
pub timestamp: u64,
|
||
|
|
pub status: OrderStatus,
|
||
|
|
pub signature: Vec<u8>, // Signature from the trader
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A trade execution
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct Execution {
|
||
|
|
pub id: String,
|
||
|
|
pub buy_order_id: OrderId,
|
||
|
|
pub sell_order_id: OrderId,
|
||
|
|
pub price: u128,
|
||
|
|
pub amount: u128,
|
||
|
|
pub timestamp: u64,
|
||
|
|
pub executor: TraderId, // Who executed the trade (could be either party)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A cancellation request
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct Cancellation {
|
||
|
|
pub order_id: OrderId,
|
||
|
|
pub timestamp: u64,
|
||
|
|
pub signature: Vec<u8>, // Must be signed by original trader
|
||
|
|
}
|
||
|
|
|
||
|
|
/// CRDT-based decentralized order book
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct OrderBookCRDT {
|
||
|
|
/// All orders ever placed
|
||
|
|
orders: HashMap<OrderId, Order>,
|
||
|
|
/// All executions
|
||
|
|
executions: HashMap<String, Execution>,
|
||
|
|
/// Cancellation tombstones
|
||
|
|
cancellations: HashMap<OrderId, Cancellation>,
|
||
|
|
/// Index for fast price-level lookups
|
||
|
|
buy_levels: BTreeMap<u128, HashSet<OrderId>>,
|
||
|
|
sell_levels: BTreeMap<u128, HashSet<OrderId>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl OrderBookCRDT {
|
||
|
|
pub fn new() -> Self {
|
||
|
|
Self {
|
||
|
|
orders: HashMap::new(),
|
||
|
|
executions: HashMap::new(),
|
||
|
|
cancellations: HashMap::new(),
|
||
|
|
buy_levels: BTreeMap::new(),
|
||
|
|
sell_levels: BTreeMap::new(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Add a new order to the book
|
||
|
|
pub fn add_order(&mut self, order: Order) -> Result<(), String> {
|
||
|
|
// Verify signature (simplified - real implementation would verify cryptographically)
|
||
|
|
if order.signature.is_empty() {
|
||
|
|
return Err("Order must be signed".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if order was previously cancelled
|
||
|
|
if self.cancellations.contains_key(&order.id) {
|
||
|
|
return Err("Order was previously cancelled".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add to orders map
|
||
|
|
let order_id = order.id.clone();
|
||
|
|
let price = order.price;
|
||
|
|
let side = order.side;
|
||
|
|
|
||
|
|
self.orders.insert(order_id.clone(), order);
|
||
|
|
|
||
|
|
// Update price level index
|
||
|
|
match side {
|
||
|
|
Side::Buy => {
|
||
|
|
self.buy_levels
|
||
|
|
.entry(price)
|
||
|
|
.or_insert_with(HashSet::new)
|
||
|
|
.insert(order_id);
|
||
|
|
}
|
||
|
|
Side::Sell => {
|
||
|
|
self.sell_levels
|
||
|
|
.entry(price)
|
||
|
|
.or_insert_with(HashSet::new)
|
||
|
|
.insert(order_id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Execute a trade between two orders
|
||
|
|
pub fn execute_trade(&mut self, execution: Execution) -> Result<(), String> {
|
||
|
|
// Verify both orders exist and are not cancelled
|
||
|
|
let buy_order = self
|
||
|
|
.orders
|
||
|
|
.get(&execution.buy_order_id)
|
||
|
|
.ok_or("Buy order not found")?;
|
||
|
|
let sell_order = self
|
||
|
|
.orders
|
||
|
|
.get(&execution.sell_order_id)
|
||
|
|
.ok_or("Sell order not found")?;
|
||
|
|
|
||
|
|
if self.cancellations.contains_key(&execution.buy_order_id) {
|
||
|
|
return Err("Buy order is cancelled".to_string());
|
||
|
|
}
|
||
|
|
if self.cancellations.contains_key(&execution.sell_order_id) {
|
||
|
|
return Err("Sell order is cancelled".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify price matching
|
||
|
|
if buy_order.price < sell_order.price {
|
||
|
|
return Err("Price mismatch - buy price less than sell price".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for duplicate execution
|
||
|
|
if self.executions.contains_key(&execution.id) {
|
||
|
|
return Ok(()); // Idempotent - already processed
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add execution
|
||
|
|
self.executions
|
||
|
|
.insert(execution.id.clone(), execution.clone());
|
||
|
|
|
||
|
|
// Update order states
|
||
|
|
self.update_order_after_execution(&execution.buy_order_id, execution.amount);
|
||
|
|
self.update_order_after_execution(&execution.sell_order_id, execution.amount);
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Cancel an order
|
||
|
|
pub fn cancel_order(&mut self, cancellation: Cancellation) -> Result<(), String> {
|
||
|
|
// Verify the order exists
|
||
|
|
let order = self
|
||
|
|
.orders
|
||
|
|
.get(&cancellation.order_id)
|
||
|
|
.ok_or("Order not found")?;
|
||
|
|
|
||
|
|
// Verify signature matches order owner (simplified)
|
||
|
|
if cancellation.signature.is_empty() {
|
||
|
|
return Err("Cancellation must be signed".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add cancellation tombstone
|
||
|
|
self.cancellations
|
||
|
|
.insert(cancellation.order_id.clone(), cancellation);
|
||
|
|
|
||
|
|
// Remove from price level indices
|
||
|
|
if let Some(order) = self.orders.get_mut(&cancellation.order_id) {
|
||
|
|
order.status = OrderStatus::Cancelled;
|
||
|
|
|
||
|
|
match order.side {
|
||
|
|
Side::Buy => {
|
||
|
|
if let Some(level) = self.buy_levels.get_mut(&order.price) {
|
||
|
|
level.remove(&order.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Side::Sell => {
|
||
|
|
if let Some(level) = self.sell_levels.get_mut(&order.price) {
|
||
|
|
level.remove(&order.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Merge another OrderBookCRDT into this one
|
||
|
|
pub fn merge(&mut self, other: &OrderBookCRDT) {
|
||
|
|
// Merge orders
|
||
|
|
for (id, order) in &other.orders {
|
||
|
|
if !self.orders.contains_key(id) {
|
||
|
|
let _ = self.add_order(order.clone());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Merge executions
|
||
|
|
for (id, execution) in &other.executions {
|
||
|
|
if !self.executions.contains_key(id) {
|
||
|
|
let _ = self.execute_trade(execution.clone());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Merge cancellations (cancellations always win)
|
||
|
|
for (id, cancellation) in &other.cancellations {
|
||
|
|
if !self.cancellations.contains_key(id) {
|
||
|
|
let _ = self.cancel_order(cancellation.clone());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the current order book view
|
||
|
|
pub fn get_book_view(&self) -> OrderBookView {
|
||
|
|
let mut bids = Vec::new();
|
||
|
|
let mut asks = Vec::new();
|
||
|
|
|
||
|
|
// Collect buy orders (descending price)
|
||
|
|
for (price, order_ids) in self.buy_levels.iter().rev() {
|
||
|
|
let mut level_amount = 0u128;
|
||
|
|
for order_id in order_ids {
|
||
|
|
if let Some(order) = self.orders.get(order_id) {
|
||
|
|
if order.status == OrderStatus::Open
|
||
|
|
|| order.status == OrderStatus::PartiallyFilled
|
||
|
|
{
|
||
|
|
level_amount += self.get_remaining_amount(order_id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if level_amount > 0 {
|
||
|
|
bids.push(PriceLevel {
|
||
|
|
price: *price,
|
||
|
|
amount: level_amount,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Collect sell orders (ascending price)
|
||
|
|
for (price, order_ids) in &self.sell_levels {
|
||
|
|
let mut level_amount = 0u128;
|
||
|
|
for order_id in order_ids {
|
||
|
|
if let Some(order) = self.orders.get(order_id) {
|
||
|
|
if order.status == OrderStatus::Open
|
||
|
|
|| order.status == OrderStatus::PartiallyFilled
|
||
|
|
{
|
||
|
|
level_amount += self.get_remaining_amount(order_id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if level_amount > 0 {
|
||
|
|
asks.push(PriceLevel {
|
||
|
|
price: *price,
|
||
|
|
amount: level_amount,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
OrderBookView { bids, asks }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get remaining amount for an order after all executions
|
||
|
|
fn get_remaining_amount(&self, order_id: &OrderId) -> u128 {
|
||
|
|
let order = match self.orders.get(order_id) {
|
||
|
|
Some(o) => o,
|
||
|
|
None => return 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut filled_amount = 0u128;
|
||
|
|
for execution in self.executions.values() {
|
||
|
|
if &execution.buy_order_id == order_id || &execution.sell_order_id == order_id {
|
||
|
|
filled_amount += execution.amount;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
order.original_amount.saturating_sub(filled_amount)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Update order status after execution
|
||
|
|
fn update_order_after_execution(&mut self, order_id: &OrderId, amount: u128) {
|
||
|
|
if let Some(order) = self.orders.get_mut(order_id) {
|
||
|
|
let remaining = self.get_remaining_amount(order_id);
|
||
|
|
order.remaining_amount = remaining;
|
||
|
|
|
||
|
|
if remaining == 0 {
|
||
|
|
order.status = OrderStatus::Filled;
|
||
|
|
// Remove from price level index
|
||
|
|
match order.side {
|
||
|
|
Side::Buy => {
|
||
|
|
if let Some(level) = self.buy_levels.get_mut(&order.price) {
|
||
|
|
level.remove(order_id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Side::Sell => {
|
||
|
|
if let Some(level) = self.sell_levels.get_mut(&order.price) {
|
||
|
|
level.remove(order_id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if remaining < order.original_amount {
|
||
|
|
order.status = OrderStatus::PartiallyFilled;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Find matching orders for a new order (for market making)
|
||
|
|
pub fn find_matches(&self, order: &Order) -> Vec<&Order> {
|
||
|
|
let mut matches = Vec::new();
|
||
|
|
|
||
|
|
match order.side {
|
||
|
|
Side::Buy => {
|
||
|
|
// Look for sell orders at or below buy price
|
||
|
|
for (price, order_ids) in &self.sell_levels {
|
||
|
|
if *price > order.price {
|
||
|
|
break; // Prices too high
|
||
|
|
}
|
||
|
|
for order_id in order_ids {
|
||
|
|
if let Some(sell_order) = self.orders.get(order_id) {
|
||
|
|
if !self.cancellations.contains_key(order_id)
|
||
|
|
&& self.get_remaining_amount(order_id) > 0
|
||
|
|
{
|
||
|
|
matches.push(sell_order);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Side::Sell => {
|
||
|
|
// Look for buy orders at or above sell price
|
||
|
|
for (price, order_ids) in self.buy_levels.iter().rev() {
|
||
|
|
if *price < order.price {
|
||
|
|
break; // Prices too low
|
||
|
|
}
|
||
|
|
for order_id in order_ids {
|
||
|
|
if let Some(buy_order) = self.orders.get(order_id) {
|
||
|
|
if !self.cancellations.contains_key(order_id)
|
||
|
|
&& self.get_remaining_amount(order_id) > 0
|
||
|
|
{
|
||
|
|
matches.push(buy_order);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
matches
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A view of the order book at a point in time
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct OrderBookView {
|
||
|
|
pub bids: Vec<PriceLevel>,
|
||
|
|
pub asks: Vec<PriceLevel>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct PriceLevel {
|
||
|
|
pub price: u128,
|
||
|
|
pub amount: u128,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_order_book_operations() {
|
||
|
|
let mut book1 = OrderBookCRDT::new();
|
||
|
|
let mut book2 = OrderBookCRDT::new();
|
||
|
|
|
||
|
|
// Add buy order to book1
|
||
|
|
let buy_order = Order {
|
||
|
|
id: OrderId("buy1".to_string()),
|
||
|
|
trader: TraderId("alice".to_string()),
|
||
|
|
side: Side::Buy,
|
||
|
|
price: 100,
|
||
|
|
original_amount: 50,
|
||
|
|
remaining_amount: 50,
|
||
|
|
timestamp: 1000,
|
||
|
|
status: OrderStatus::Open,
|
||
|
|
signature: vec![1, 2, 3],
|
||
|
|
};
|
||
|
|
book1.add_order(buy_order).unwrap();
|
||
|
|
|
||
|
|
// Add sell order to book2
|
||
|
|
let sell_order = Order {
|
||
|
|
id: OrderId("sell1".to_string()),
|
||
|
|
trader: TraderId("bob".to_string()),
|
||
|
|
side: Side::Sell,
|
||
|
|
price: 95,
|
||
|
|
original_amount: 30,
|
||
|
|
remaining_amount: 30,
|
||
|
|
timestamp: 1001,
|
||
|
|
status: OrderStatus::Open,
|
||
|
|
signature: vec![4, 5, 6],
|
||
|
|
};
|
||
|
|
book2.add_order(sell_order).unwrap();
|
||
|
|
|
||
|
|
// Merge books
|
||
|
|
book1.merge(&book2);
|
||
|
|
book2.merge(&book1);
|
||
|
|
|
||
|
|
// Both books should have both orders
|
||
|
|
assert_eq!(book1.orders.len(), 2);
|
||
|
|
assert_eq!(book2.orders.len(), 2);
|
||
|
|
|
||
|
|
// Execute trade on book1
|
||
|
|
let execution = Execution {
|
||
|
|
id: "exec1".to_string(),
|
||
|
|
buy_order_id: OrderId("buy1".to_string()),
|
||
|
|
sell_order_id: OrderId("sell1".to_string()),
|
||
|
|
price: 95,
|
||
|
|
amount: 30,
|
||
|
|
timestamp: 1002,
|
||
|
|
executor: TraderId("alice".to_string()),
|
||
|
|
};
|
||
|
|
book1.execute_trade(execution).unwrap();
|
||
|
|
|
||
|
|
// Merge again
|
||
|
|
book2.merge(&book1);
|
||
|
|
|
||
|
|
// Both should have the execution
|
||
|
|
assert_eq!(book1.executions.len(), 1);
|
||
|
|
assert_eq!(book2.executions.len(), 1);
|
||
|
|
|
||
|
|
// Check remaining amounts
|
||
|
|
assert_eq!(book1.get_remaining_amount(&OrderId("buy1".to_string())), 20);
|
||
|
|
assert_eq!(book1.get_remaining_amount(&OrderId("sell1".to_string())), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_order_cancellation() {
|
||
|
|
let mut book = OrderBookCRDT::new();
|
||
|
|
|
||
|
|
// Add order
|
||
|
|
let order = Order {
|
||
|
|
id: OrderId("order1".to_string()),
|
||
|
|
trader: TraderId("alice".to_string()),
|
||
|
|
side: Side::Buy,
|
||
|
|
price: 100,
|
||
|
|
original_amount: 50,
|
||
|
|
remaining_amount: 50,
|
||
|
|
timestamp: 1000,
|
||
|
|
status: OrderStatus::Open,
|
||
|
|
signature: vec![1, 2, 3],
|
||
|
|
};
|
||
|
|
book.add_order(order).unwrap();
|
||
|
|
|
||
|
|
// Cancel order
|
||
|
|
let cancellation = Cancellation {
|
||
|
|
order_id: OrderId("order1".to_string()),
|
||
|
|
timestamp: 1001,
|
||
|
|
signature: vec![1, 2, 3],
|
||
|
|
};
|
||
|
|
book.cancel_order(cancellation).unwrap();
|
||
|
|
|
||
|
|
// Verify order is cancelled
|
||
|
|
let order = book.orders.get(&OrderId("order1".to_string())).unwrap();
|
||
|
|
assert_eq!(order.status, OrderStatus::Cancelled);
|
||
|
|
|
||
|
|
// Try to execute against cancelled order
|
||
|
|
let execution = Execution {
|
||
|
|
id: "exec1".to_string(),
|
||
|
|
buy_order_id: OrderId("order1".to_string()),
|
||
|
|
sell_order_id: OrderId("sell1".to_string()),
|
||
|
|
price: 100,
|
||
|
|
amount: 10,
|
||
|
|
timestamp: 1002,
|
||
|
|
executor: TraderId("bob".to_string()),
|
||
|
|
};
|
||
|
|
assert!(book.execute_trade(execution).is_err());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_order_book_view() {
|
||
|
|
let mut book = OrderBookCRDT::new();
|
||
|
|
|
||
|
|
// Add multiple orders at different price levels
|
||
|
|
for i in 0..3 {
|
||
|
|
let buy_order = Order {
|
||
|
|
id: OrderId(format!("buy{}", i)),
|
||
|
|
trader: TraderId("alice".to_string()),
|
||
|
|
side: Side::Buy,
|
||
|
|
price: 100 - i as u128,
|
||
|
|
original_amount: 10 * (i + 1) as u128,
|
||
|
|
remaining_amount: 10 * (i + 1) as u128,
|
||
|
|
timestamp: 1000 + i,
|
||
|
|
status: OrderStatus::Open,
|
||
|
|
signature: vec![1, 2, 3],
|
||
|
|
};
|
||
|
|
book.add_order(buy_order).unwrap();
|
||
|
|
|
||
|
|
let sell_order = Order {
|
||
|
|
id: OrderId(format!("sell{}", i)),
|
||
|
|
trader: TraderId("bob".to_string()),
|
||
|
|
side: Side::Sell,
|
||
|
|
price: 105 + i as u128,
|
||
|
|
original_amount: 10 * (i + 1) as u128,
|
||
|
|
remaining_amount: 10 * (i + 1) as u128,
|
||
|
|
timestamp: 2000 + i,
|
||
|
|
status: OrderStatus::Open,
|
||
|
|
signature: vec![4, 5, 6],
|
||
|
|
};
|
||
|
|
book.add_order(sell_order).unwrap();
|
||
|
|
}
|
||
|
|
|
||
|
|
let view = book.get_book_view();
|
||
|
|
|
||
|
|
// Check bid side (should be sorted descending)
|
||
|
|
assert_eq!(view.bids.len(), 3);
|
||
|
|
assert_eq!(view.bids[0].price, 100);
|
||
|
|
assert_eq!(view.bids[1].price, 99);
|
||
|
|
assert_eq!(view.bids[2].price, 98);
|
||
|
|
|
||
|
|
// Check ask side (should be sorted ascending)
|
||
|
|
assert_eq!(view.asks.len(), 3);
|
||
|
|
assert_eq!(view.asks[0].price, 105);
|
||
|
|
assert_eq!(view.asks[1].price, 106);
|
||
|
|
assert_eq!(view.asks[2].price, 107);
|
||
|
|
}
|
||
|
|
}
|