use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use crate::Decimal;
use crate::types::error::{MMError, MMResult};
use super::connector::{Fill, OrderId, OrderRequest, OrderResponse, OrderStatus, OrderType, Side};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ManagedOrder {
pub order_id: OrderId,
pub client_order_id: String,
pub symbol: String,
pub side: Side,
pub order_type: OrderType,
pub original_price: Decimal,
pub original_quantity: Decimal,
pub filled_quantity: Decimal,
pub remaining_quantity: Decimal,
pub average_fill_price: Decimal,
pub status: OrderStatus,
pub created_at: u64,
pub updated_at: u64,
pub fills: Vec<Fill>,
}
impl ManagedOrder {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
order_id: OrderId,
client_order_id: String,
symbol: String,
side: Side,
order_type: OrderType,
price: Decimal,
quantity: Decimal,
timestamp: u64,
) -> Self {
Self {
order_id,
client_order_id,
symbol,
side,
order_type,
original_price: price,
original_quantity: quantity,
filled_quantity: Decimal::ZERO,
remaining_quantity: quantity,
average_fill_price: Decimal::ZERO,
status: OrderStatus::Pending,
created_at: timestamp,
updated_at: timestamp,
fills: Vec::new(),
}
}
#[must_use]
pub fn is_pending(&self) -> bool {
matches!(self.status, OrderStatus::Pending)
}
#[must_use]
pub fn is_open(&self) -> bool {
self.status.is_open()
}
#[must_use]
pub fn is_terminal(&self) -> bool {
self.status.is_terminal()
}
#[must_use]
pub fn fill_ratio(&self) -> Decimal {
if self.original_quantity > Decimal::ZERO {
self.filled_quantity / self.original_quantity
} else {
Decimal::ZERO
}
}
#[must_use]
pub fn original_notional(&self) -> Decimal {
self.original_price * self.original_quantity
}
#[must_use]
pub fn filled_notional(&self) -> Decimal {
self.average_fill_price * self.filled_quantity
}
#[must_use]
pub fn age_ms(&self, current_time: u64) -> u64 {
current_time.saturating_sub(self.created_at)
}
pub fn record_fill(&mut self, fill: &Fill, timestamp: u64) {
let new_filled = self.filled_quantity + fill.quantity;
if new_filled > Decimal::ZERO {
let old_value = self.average_fill_price * self.filled_quantity;
let new_value = fill.price * fill.quantity;
self.average_fill_price = (old_value + new_value) / new_filled;
}
self.filled_quantity = new_filled;
self.remaining_quantity = self.original_quantity - new_filled;
self.updated_at = timestamp;
self.fills.push(fill.clone());
if self.remaining_quantity <= Decimal::ZERO {
self.status = OrderStatus::Filled {
filled_qty: self.filled_quantity,
avg_price: self.average_fill_price,
};
} else {
self.status = OrderStatus::PartiallyFilled {
filled_qty: self.filled_quantity,
remaining_qty: self.remaining_quantity,
};
}
}
pub fn update_status(&mut self, status: OrderStatus, timestamp: u64) {
self.status = status;
self.updated_at = timestamp;
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderManagerConfig {
pub order_timeout_ms: u64,
pub max_open_orders: usize,
pub detect_duplicates: bool,
}
impl Default for OrderManagerConfig {
fn default() -> Self {
Self {
order_timeout_ms: 0,
max_open_orders: 0,
detect_duplicates: true,
}
}
}
impl OrderManagerConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_order_timeout_ms(mut self, timeout_ms: u64) -> Self {
self.order_timeout_ms = timeout_ms;
self
}
#[must_use]
pub fn with_max_open_orders(mut self, max: usize) -> Self {
self.max_open_orders = max;
self
}
#[must_use]
pub fn with_detect_duplicates(mut self, detect: bool) -> Self {
self.detect_duplicates = detect;
self
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderManagerStats {
pub total_orders: usize,
pub open_orders: usize,
pub filled_orders: usize,
pub cancelled_orders: usize,
pub total_fills: usize,
pub pending_orders: usize,
pub rejected_orders: usize,
}
#[derive(Debug)]
pub struct OrderManager {
config: OrderManagerConfig,
orders: HashMap<String, ManagedOrder>,
orders_by_exchange_id: HashMap<String, String>,
open_orders_by_symbol: HashMap<String, Vec<String>>,
}
impl OrderManager {
#[must_use]
pub fn new(config: OrderManagerConfig) -> Self {
Self {
config,
orders: HashMap::new(),
orders_by_exchange_id: HashMap::new(),
open_orders_by_symbol: HashMap::new(),
}
}
#[must_use]
pub fn with_defaults() -> Self {
Self::new(OrderManagerConfig::default())
}
#[must_use]
pub fn config(&self) -> &OrderManagerConfig {
&self.config
}
pub fn register_order(
&mut self,
request: &OrderRequest,
client_order_id: String,
timestamp: u64,
) -> MMResult<()> {
if self.config.detect_duplicates && self.orders.contains_key(&client_order_id) {
return Err(MMError::InvalidMarketState(format!(
"duplicate client_order_id: {}",
client_order_id
)));
}
if self.config.max_open_orders > 0 {
let symbol_orders = self
.open_orders_by_symbol
.get(&request.symbol)
.map(|v| v.len())
.unwrap_or(0);
if symbol_orders >= self.config.max_open_orders {
return Err(MMError::InvalidMarketState(format!(
"max open orders ({}) exceeded for symbol {}",
self.config.max_open_orders, request.symbol
)));
}
}
let order = ManagedOrder::new(
OrderId::new(&client_order_id), client_order_id.clone(),
request.symbol.clone(),
request.side,
request.order_type,
request.price.unwrap_or(Decimal::ZERO),
request.quantity,
timestamp,
);
self.open_orders_by_symbol
.entry(request.symbol.clone())
.or_default()
.push(client_order_id.clone());
self.orders.insert(client_order_id, order);
Ok(())
}
pub fn update_order(
&mut self,
client_order_id: &str,
response: &OrderResponse,
timestamp: u64,
) -> MMResult<()> {
let (symbol, is_terminal) = {
let order = self.orders.get_mut(client_order_id).ok_or_else(|| {
MMError::InvalidMarketState(format!("order not found: {}", client_order_id))
})?;
let exchange_id = response.order_id.as_str().to_string();
if order.order_id.as_str() != exchange_id {
order.order_id = response.order_id.clone();
self.orders_by_exchange_id
.insert(exchange_id, client_order_id.to_string());
}
order.update_status(response.status.clone(), timestamp);
(order.symbol.clone(), order.is_terminal())
};
if is_terminal {
self.remove_from_open_orders(&symbol, client_order_id);
}
Ok(())
}
pub fn record_fill(&mut self, fill: &Fill, timestamp: u64) -> MMResult<()> {
let client_id = self
.orders_by_exchange_id
.get(fill.order_id.as_str())
.cloned()
.ok_or_else(|| {
MMError::InvalidMarketState(format!("order not found for fill: {}", fill.order_id))
})?;
let (symbol, is_terminal) = {
let order = self.orders.get_mut(&client_id).ok_or_else(|| {
MMError::InvalidMarketState(format!("order not found: {}", client_id))
})?;
order.record_fill(fill, timestamp);
(order.symbol.clone(), order.is_terminal())
};
if is_terminal {
self.remove_from_open_orders(&symbol, &client_id);
}
Ok(())
}
#[must_use]
pub fn get_order(&self, order_id: &OrderId) -> Option<&ManagedOrder> {
self.orders_by_exchange_id
.get(order_id.as_str())
.and_then(|client_id| self.orders.get(client_id))
}
#[must_use]
pub fn get_order_by_client_id(&self, client_order_id: &str) -> Option<&ManagedOrder> {
self.orders.get(client_order_id)
}
pub fn get_order_by_client_id_mut(
&mut self,
client_order_id: &str,
) -> Option<&mut ManagedOrder> {
self.orders.get_mut(client_order_id)
}
#[must_use]
pub fn get_open_orders(&self) -> Vec<&ManagedOrder> {
self.orders
.values()
.filter(|o| o.is_open() || o.is_pending())
.collect()
}
#[must_use]
pub fn get_open_orders_for_symbol(&self, symbol: &str) -> Vec<&ManagedOrder> {
self.open_orders_by_symbol
.get(symbol)
.map(|ids| {
ids.iter()
.filter_map(|id| self.orders.get(id))
.filter(|o| o.is_open() || o.is_pending())
.collect()
})
.unwrap_or_default()
}
#[must_use]
pub fn has_order(&self, order_id: &OrderId) -> bool {
self.orders_by_exchange_id.contains_key(order_id.as_str())
}
#[must_use]
pub fn has_order_by_client_id(&self, client_order_id: &str) -> bool {
self.orders.contains_key(client_order_id)
}
#[must_use]
pub fn get_open_quantity(&self, symbol: &str, side: Side) -> Decimal {
self.get_open_orders_for_symbol(symbol)
.iter()
.filter(|o| o.side == side)
.map(|o| o.remaining_quantity)
.sum()
}
pub fn mark_cancelled(&mut self, client_order_id: &str, timestamp: u64) -> MMResult<()> {
let symbol = {
let order = self.orders.get_mut(client_order_id).ok_or_else(|| {
MMError::InvalidMarketState(format!("order not found: {}", client_order_id))
})?;
let filled_qty = order.filled_quantity;
order.update_status(OrderStatus::Cancelled { filled_qty }, timestamp);
order.symbol.clone()
};
self.remove_from_open_orders(&symbol, client_order_id);
Ok(())
}
#[must_use]
pub fn check_timeouts(&self, current_time: u64) -> Vec<String> {
if self.config.order_timeout_ms == 0 {
return Vec::new();
}
self.orders
.iter()
.filter(|(_, order)| {
(order.is_open() || order.is_pending())
&& order.age_ms(current_time) > self.config.order_timeout_ms
})
.map(|(id, _)| id.clone())
.collect()
}
pub fn cleanup(&mut self, retention_ms: u64, current_time: u64) {
let to_remove: Vec<String> = self
.orders
.iter()
.filter(|(_, order)| {
order.is_terminal() && current_time.saturating_sub(order.updated_at) > retention_ms
})
.map(|(id, _)| id.clone())
.collect();
for client_id in to_remove {
if let Some(order) = self.orders.remove(&client_id) {
self.orders_by_exchange_id.remove(order.order_id.as_str());
}
}
}
#[must_use]
pub fn get_stats(&self) -> OrderManagerStats {
let mut stats = OrderManagerStats::default();
for order in self.orders.values() {
stats.total_orders += 1;
stats.total_fills += order.fills.len();
match &order.status {
OrderStatus::Pending => stats.pending_orders += 1,
OrderStatus::Open { .. } | OrderStatus::PartiallyFilled { .. } => {
stats.open_orders += 1
}
OrderStatus::Filled { .. } => stats.filled_orders += 1,
OrderStatus::Cancelled { .. } => stats.cancelled_orders += 1,
OrderStatus::Rejected { .. } => stats.rejected_orders += 1,
}
}
stats
}
#[must_use]
pub fn order_count(&self) -> usize {
self.orders.len()
}
#[must_use]
pub fn open_order_count(&self) -> usize {
self.orders
.values()
.filter(|o| o.is_open() || o.is_pending())
.count()
}
fn remove_from_open_orders(&mut self, symbol: &str, client_order_id: &str) {
if let Some(ids) = self.open_orders_by_symbol.get_mut(symbol) {
ids.retain(|id| id != client_order_id);
}
}
}
#[derive(Debug, Clone)]
pub struct ThreadSafeOrderManager {
inner: Arc<RwLock<OrderManager>>,
}
impl ThreadSafeOrderManager {
#[must_use]
pub fn new(config: OrderManagerConfig) -> Self {
Self {
inner: Arc::new(RwLock::new(OrderManager::new(config))),
}
}
#[must_use]
pub fn with_defaults() -> Self {
Self::new(OrderManagerConfig::default())
}
pub fn read<F, R>(&self, f: F) -> R
where
F: FnOnce(&OrderManager) -> R,
{
let guard = self.inner.read().unwrap();
f(&guard)
}
pub fn write<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut OrderManager) -> R,
{
let mut guard = self.inner.write().unwrap();
f(&mut guard)
}
pub fn register_order(
&self,
request: &OrderRequest,
client_order_id: String,
timestamp: u64,
) -> MMResult<()> {
self.write(|m| m.register_order(request, client_order_id, timestamp))
}
pub fn update_order(
&self,
client_order_id: &str,
response: &OrderResponse,
timestamp: u64,
) -> MMResult<()> {
self.write(|m| m.update_order(client_order_id, response, timestamp))
}
pub fn record_fill(&self, fill: &Fill, timestamp: u64) -> MMResult<()> {
self.write(|m| m.record_fill(fill, timestamp))
}
#[must_use]
pub fn get_stats(&self) -> OrderManagerStats {
self.read(|m| m.get_stats())
}
#[must_use]
pub fn open_order_count(&self) -> usize {
self.read(|m| m.open_order_count())
}
#[must_use]
pub fn get_open_quantity(&self, symbol: &str, side: Side) -> Decimal {
self.read(|m| m.get_open_quantity(symbol, side))
}
#[must_use]
pub fn check_timeouts(&self, current_time: u64) -> Vec<String> {
self.read(|m| m.check_timeouts(current_time))
}
pub fn mark_cancelled(&self, client_order_id: &str, timestamp: u64) -> MMResult<()> {
self.write(|m| m.mark_cancelled(client_order_id, timestamp))
}
pub fn cleanup(&self, retention_ms: u64, current_time: u64) {
self.write(|m| m.cleanup(retention_ms, current_time));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dec;
fn create_test_request() -> OrderRequest {
OrderRequest::limit_buy("BTC-USD", dec!(50000.0), dec!(0.1))
}
fn create_test_fill(order_id: &OrderId, price: Decimal, quantity: Decimal) -> Fill {
Fill {
order_id: order_id.clone(),
trade_id: "trade-1".to_string(),
price,
quantity,
side: Side::Buy,
timestamp: 1000,
fee: dec!(0.01),
fee_currency: "USD".to_string(),
}
}
#[test]
fn test_managed_order_new() {
let order = ManagedOrder::new(
OrderId::new("12345"),
"client-1".to_string(),
"BTC-USD".to_string(),
Side::Buy,
OrderType::Limit,
dec!(50000.0),
dec!(0.1),
1000,
);
assert!(order.is_pending());
assert!(!order.is_open());
assert!(!order.is_terminal());
assert_eq!(order.filled_quantity, Decimal::ZERO);
assert_eq!(order.remaining_quantity, dec!(0.1));
assert_eq!(order.fill_ratio(), Decimal::ZERO);
}
#[test]
fn test_managed_order_record_fill() {
let mut order = ManagedOrder::new(
OrderId::new("12345"),
"client-1".to_string(),
"BTC-USD".to_string(),
Side::Buy,
OrderType::Limit,
dec!(50000.0),
dec!(1.0),
1000,
);
let fill1 = create_test_fill(&order.order_id, dec!(50000.0), dec!(0.3));
order.record_fill(&fill1, 1001);
assert_eq!(order.filled_quantity, dec!(0.3));
assert_eq!(order.remaining_quantity, dec!(0.7));
assert_eq!(order.average_fill_price, dec!(50000.0));
assert!(order.is_open());
let fill2 = create_test_fill(&order.order_id, dec!(49900.0), dec!(0.2));
order.record_fill(&fill2, 1002);
assert_eq!(order.filled_quantity, dec!(0.5));
assert_eq!(order.remaining_quantity, dec!(0.5));
assert_eq!(order.average_fill_price, dec!(49960.0));
let fill3 = create_test_fill(&order.order_id, dec!(50100.0), dec!(0.5));
order.record_fill(&fill3, 1003);
assert_eq!(order.filled_quantity, dec!(1.0));
assert_eq!(order.remaining_quantity, Decimal::ZERO);
assert!(order.is_terminal());
assert_eq!(order.fills.len(), 3);
}
#[test]
fn test_order_manager_register() {
let mut manager = OrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
assert_eq!(manager.order_count(), 1);
assert_eq!(manager.open_order_count(), 1);
let order = manager.get_order_by_client_id("client-1").unwrap();
assert!(order.is_pending());
assert_eq!(order.symbol, "BTC-USD");
}
#[test]
fn test_order_manager_duplicate_detection() {
let mut manager = OrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
let result = manager.register_order(&request, "client-1".to_string(), 1001);
assert!(result.is_err());
}
#[test]
fn test_order_manager_max_open_orders() {
let config = OrderManagerConfig::default().with_max_open_orders(2);
let mut manager = OrderManager::new(config);
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
manager
.register_order(&request, "client-2".to_string(), 1001)
.unwrap();
let result = manager.register_order(&request, "client-3".to_string(), 1002);
assert!(result.is_err());
}
#[test]
fn test_order_manager_update_order() {
let mut manager = OrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
let response = OrderResponse::new(
OrderId::new("exchange-123"),
OrderStatus::Open {
filled_qty: Decimal::ZERO,
},
1001,
);
manager.update_order("client-1", &response, 1001).unwrap();
let order = manager.get_order_by_client_id("client-1").unwrap();
assert!(order.is_open());
assert_eq!(order.order_id.as_str(), "exchange-123");
let order2 = manager.get_order(&OrderId::new("exchange-123")).unwrap();
assert_eq!(order2.client_order_id, "client-1");
}
#[test]
fn test_order_manager_record_fill() {
let mut manager = OrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
let exchange_id = OrderId::new("exchange-123");
let response = OrderResponse::new(
exchange_id.clone(),
OrderStatus::Open {
filled_qty: Decimal::ZERO,
},
1001,
);
manager.update_order("client-1", &response, 1001).unwrap();
let fill = create_test_fill(&exchange_id, dec!(50000.0), dec!(0.1));
manager.record_fill(&fill, 1002).unwrap();
let order = manager.get_order_by_client_id("client-1").unwrap();
assert!(order.is_terminal());
assert_eq!(order.filled_quantity, dec!(0.1));
}
#[test]
fn test_order_manager_get_open_orders() {
let mut manager = OrderManager::with_defaults();
let btc_request = OrderRequest::limit_buy("BTC-USD", dec!(50000.0), dec!(0.1));
let eth_request = OrderRequest::limit_buy("ETH-USD", dec!(3000.0), dec!(1.0));
manager
.register_order(&btc_request, "btc-1".to_string(), 1000)
.unwrap();
manager
.register_order(&btc_request, "btc-2".to_string(), 1001)
.unwrap();
manager
.register_order(ð_request, "eth-1".to_string(), 1002)
.unwrap();
let all_open = manager.get_open_orders();
assert_eq!(all_open.len(), 3);
let btc_open = manager.get_open_orders_for_symbol("BTC-USD");
assert_eq!(btc_open.len(), 2);
let eth_open = manager.get_open_orders_for_symbol("ETH-USD");
assert_eq!(eth_open.len(), 1);
}
#[test]
fn test_order_manager_get_open_quantity() {
let mut manager = OrderManager::with_defaults();
let buy_request = OrderRequest::limit_buy("BTC-USD", dec!(50000.0), dec!(0.5));
let sell_request = OrderRequest::limit_sell("BTC-USD", dec!(51000.0), dec!(0.3));
manager
.register_order(&buy_request, "buy-1".to_string(), 1000)
.unwrap();
manager
.register_order(&buy_request, "buy-2".to_string(), 1001)
.unwrap();
manager
.register_order(&sell_request, "sell-1".to_string(), 1002)
.unwrap();
let buy_qty = manager.get_open_quantity("BTC-USD", Side::Buy);
assert_eq!(buy_qty, dec!(1.0));
let sell_qty = manager.get_open_quantity("BTC-USD", Side::Sell);
assert_eq!(sell_qty, dec!(0.3));
}
#[test]
fn test_order_manager_mark_cancelled() {
let mut manager = OrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
manager.mark_cancelled("client-1", 1001).unwrap();
let order = manager.get_order_by_client_id("client-1").unwrap();
assert!(order.is_terminal());
assert!(matches!(order.status, OrderStatus::Cancelled { .. }));
assert_eq!(manager.open_order_count(), 0);
}
#[test]
fn test_order_manager_check_timeouts() {
let config = OrderManagerConfig::default().with_order_timeout_ms(1000);
let mut manager = OrderManager::new(config);
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
manager
.register_order(&request, "client-2".to_string(), 2000)
.unwrap();
let timeouts = manager.check_timeouts(2500);
assert_eq!(timeouts.len(), 1);
assert!(timeouts.contains(&"client-1".to_string()));
let timeouts = manager.check_timeouts(3500);
assert_eq!(timeouts.len(), 2);
}
#[test]
fn test_order_manager_cleanup() {
let mut manager = OrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
manager.mark_cancelled("client-1", 1001).unwrap();
manager.cleanup(1000, 1500);
assert_eq!(manager.order_count(), 1);
manager.cleanup(1000, 3000);
assert_eq!(manager.order_count(), 0);
}
#[test]
fn test_order_manager_stats() {
let mut manager = OrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
manager
.register_order(&request, "client-2".to_string(), 1001)
.unwrap();
let response = OrderResponse::new(
OrderId::new("exchange-1"),
OrderStatus::Open {
filled_qty: Decimal::ZERO,
},
1002,
);
manager.update_order("client-1", &response, 1002).unwrap();
manager.mark_cancelled("client-2", 1003).unwrap();
let stats = manager.get_stats();
assert_eq!(stats.total_orders, 2);
assert_eq!(stats.open_orders, 1);
assert_eq!(stats.cancelled_orders, 1);
}
#[test]
fn test_thread_safe_order_manager() {
let manager = ThreadSafeOrderManager::with_defaults();
let request = create_test_request();
manager
.register_order(&request, "client-1".to_string(), 1000)
.unwrap();
assert_eq!(manager.open_order_count(), 1);
let stats = manager.get_stats();
assert_eq!(stats.total_orders, 1);
assert_eq!(stats.pending_orders, 1);
}
#[test]
fn test_order_age() {
let order = ManagedOrder::new(
OrderId::new("12345"),
"client-1".to_string(),
"BTC-USD".to_string(),
Side::Buy,
OrderType::Limit,
dec!(50000.0),
dec!(0.1),
1000,
);
assert_eq!(order.age_ms(1500), 500);
assert_eq!(order.age_ms(2000), 1000);
assert_eq!(order.age_ms(500), 0); }
#[test]
fn test_order_notional() {
let order = ManagedOrder::new(
OrderId::new("12345"),
"client-1".to_string(),
"BTC-USD".to_string(),
Side::Buy,
OrderType::Limit,
dec!(50000.0),
dec!(0.1),
1000,
);
assert_eq!(order.original_notional(), dec!(5000.0));
assert_eq!(order.filled_notional(), Decimal::ZERO);
}
#[cfg(feature = "serde")]
#[test]
fn test_serialization() {
let config = OrderManagerConfig::default();
let json = serde_json::to_string(&config).unwrap();
let deserialized: OrderManagerConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.order_timeout_ms, deserialized.order_timeout_ms);
}
}