Added some crazy AI generated ideas, as a brainstorming exercise.
This commit is contained in:
537
examples/orderbook_crdt.rs
Normal file
537
examples/orderbook_crdt.rs
Normal file
@@ -0,0 +1,537 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user