rusty_oms/
types.rs

1//! Core type definitions for the Order Management System
2//!
3//! This module defines the fundamental types used throughout the OMS,
4//! including order representations, events, and lifecycle states.
5
6use rust_decimal::Decimal;
7use serde::{Deserialize, Serialize};
8use smartstring::{LazyCompact, SmartString};
9use std::fmt::{self, Display};
10use uuid::Uuid;
11
12// Import common enums from rusty-model and rusty-common
13use rusty_common::types::Exchange as Venue;
14use rusty_model::enums::{OrderSide, OrderStatus, OrderType, TimeInForce};
15
16// Type alias for our string type
17type OmsString = SmartString<LazyCompact>;
18
19/// OMS-specific order status values that extend the common order status enum
20///
21/// These statuses provide more granular tracking of order lifecycle within the OMS
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum OmsOrderStatus {
24    /// Order has been received but not yet validated
25    New,
26    /// Order has been validated and accepted by OMS
27    Accepted,
28    /// Order has been sent to exchange
29    Sent,
30    /// Order is active on the exchange (maps to common `OrderStatus::Open`)
31    Active,
32    /// Order was rejected by the exchange
33    ExchangeRejected,
34    /// Order status is unknown (e.g., due to connectivity issues)
35    Unknown,
36}
37
38impl From<OrderStatus> for OmsOrderStatus {
39    fn from(status: OrderStatus) -> Self {
40        match status {
41            OrderStatus::New => Self::New,
42            OrderStatus::Open => Self::Active,
43            OrderStatus::Rejected => Self::ExchangeRejected,
44            _ => Self::Unknown,
45        }
46    }
47}
48
49impl Display for OmsOrderStatus {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Self::New => write!(f, "New"),
53            Self::Accepted => write!(f, "Accepted"),
54            Self::Sent => write!(f, "Sent"),
55            Self::Active => write!(f, "Active"),
56            Self::ExchangeRejected => write!(f, "ExchangeRejected"),
57            Self::Unknown => write!(f, "Unknown"),
58        }
59    }
60}
61
62/// Rejection reason for orders
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum RejectionReason {
65    /// Invalid order parameters
66    InvalidParameters(String),
67    /// Risk check failed
68    RiskCheckFailed(String),
69    /// Insufficient balance
70    InsufficientBalance,
71    /// Exchange rejected the order
72    ExchangeRejected(String),
73    /// Rate limit exceeded
74    RateLimitExceeded,
75    /// Unknown failure
76    Unknown,
77}
78
79impl Display for RejectionReason {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            Self::InvalidParameters(s) => write!(f, "Invalid parameters: {s}"),
83            Self::RiskCheckFailed(s) => write!(f, "Risk check failed: {s}"),
84            Self::InsufficientBalance => write!(f, "Insufficient balance"),
85            Self::ExchangeRejected(s) => write!(f, "Exchange rejected: {s}"),
86            Self::RateLimitExceeded => write!(f, "Rate limit exceeded"),
87            Self::Unknown => write!(f, "Unknown failure"),
88        }
89    }
90}
91
92/// Order request representation
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct OrderRequest {
95    /// Client-assigned order ID
96    pub client_order_id: OmsString,
97    /// Exchange venue for the order
98    pub venue: Venue,
99    /// Trading symbol (e.g., "BTCUSDT")
100    pub symbol: OmsString,
101    /// Order side (buy or sell)
102    pub side: OrderSide,
103    /// Order type (market, limit, etc.)
104    pub order_type: OrderType,
105    /// Quantity to trade
106    pub quantity: Decimal,
107    /// Price (required for limit orders)
108    pub price: Option<Decimal>,
109    /// Stop price (required for stop orders)
110    pub stop_price: Option<Decimal>,
111    /// Time in force
112    pub time_in_force: Option<TimeInForce>,
113    /// Order is reducing only (close position)
114    pub reduce_only: bool,
115    /// Request timestamp in nanoseconds
116    pub timestamp_ns: u64,
117    /// Strategy ID that created this order
118    pub strategy_id: OmsString,
119    /// Additional order parameters
120    pub extra_params: simd_json::OwnedValue,
121    /// Take profit price
122    pub take_profit: Option<Decimal>,
123    /// Stop loss price
124    pub stop_loss: Option<Decimal>,
125}
126
127impl OrderRequest {
128    /// Create a new order request with current timestamp
129    #[must_use]
130    #[allow(clippy::too_many_arguments)]
131    pub fn new(
132        client_order_id: impl Into<OmsString>,
133        venue: Venue,
134        symbol: impl Into<OmsString>,
135        side: OrderSide,
136        order_type: OrderType,
137        quantity: Decimal,
138        price: Option<Decimal>,
139        strategy_id: impl Into<OmsString>,
140    ) -> Self {
141        let timestamp_ns = rusty_common::time::get_timestamp_ns_result().unwrap_or_else(|_| {
142            std::time::SystemTime::now()
143                .duration_since(std::time::UNIX_EPOCH)
144                .unwrap_or_default()
145                .as_nanos() as u64
146        });
147
148        Self {
149            client_order_id: client_order_id.into(),
150            venue,
151            symbol: symbol.into(),
152            side,
153            order_type,
154            quantity,
155            price,
156            stop_price: None,
157            time_in_force: None,
158            reduce_only: false,
159            timestamp_ns,
160            strategy_id: strategy_id.into(),
161            extra_params: simd_json::OwnedValue::from(()),
162            take_profit: None,
163            stop_loss: None,
164        }
165    }
166
167    /// Create a market buy order
168    pub fn market_buy(
169        venue: Venue,
170        symbol: impl Into<OmsString>,
171        quantity: Decimal,
172        strategy_id: impl Into<OmsString>,
173    ) -> Self {
174        Self::new(
175            OmsString::from(Uuid::new_v4().to_string()),
176            venue,
177            symbol,
178            OrderSide::Buy,
179            OrderType::Market,
180            quantity,
181            None,
182            strategy_id,
183        )
184    }
185
186    /// Create a market sell order
187    pub fn market_sell(
188        venue: Venue,
189        symbol: impl Into<OmsString>,
190        quantity: Decimal,
191        strategy_id: impl Into<OmsString>,
192    ) -> Self {
193        Self::new(
194            OmsString::from(Uuid::new_v4().to_string()),
195            venue,
196            symbol,
197            OrderSide::Sell,
198            OrderType::Market,
199            quantity,
200            None,
201            strategy_id,
202        )
203    }
204
205    /// Create a limit buy order
206    pub fn limit_buy(
207        venue: Venue,
208        symbol: impl Into<OmsString>,
209        quantity: Decimal,
210        price: Decimal,
211        strategy_id: impl Into<OmsString>,
212    ) -> Self {
213        Self::new(
214            OmsString::from(Uuid::new_v4().to_string()),
215            venue,
216            symbol,
217            OrderSide::Buy,
218            OrderType::Limit,
219            quantity,
220            Some(price),
221            strategy_id,
222        )
223    }
224
225    /// Create a limit sell order
226    pub fn limit_sell(
227        venue: Venue,
228        symbol: impl Into<OmsString>,
229        quantity: Decimal,
230        price: Decimal,
231        strategy_id: impl Into<OmsString>,
232    ) -> Self {
233        Self::new(
234            OmsString::from(Uuid::new_v4().to_string()),
235            venue,
236            symbol,
237            OrderSide::Sell,
238            OrderType::Limit,
239            quantity,
240            Some(price),
241            strategy_id,
242        )
243    }
244}
245
246/// Internal order representation
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct Order {
249    /// Unique order ID
250    pub id: Uuid,
251    /// Client-assigned order ID
252    pub client_order_id: OmsString,
253    /// Exchange-assigned order ID (once assigned)
254    pub exchange_order_id: Option<OmsString>,
255    /// Exchange venue
256    pub venue: Venue,
257    /// Trading symbol (e.g., "BTCUSDT")
258    pub symbol: OmsString,
259    /// Order side (buy or sell)
260    pub side: OrderSide,
261    /// Order type (market, limit, etc.)
262    pub order_type: OrderType,
263    /// Price (required for limit orders)
264    pub price: Option<Decimal>,
265    /// Stop price (required for stop orders)
266    pub stop_price: Option<Decimal>,
267    /// Quantity to trade
268    pub quantity: Decimal,
269    /// Filled quantity
270    pub filled_quantity: Decimal,
271    /// Average fill price
272    pub average_fill_price: Option<Decimal>,
273    /// Order status
274    pub status: OmsOrderStatus,
275    /// Time in force
276    pub time_in_force: Option<TimeInForce>,
277    /// Order is reducing only (close position)
278    pub reduce_only: bool,
279    /// Creation time in nanoseconds
280    pub creation_time_ns: u64,
281    /// Last update time in nanoseconds
282    pub update_time_ns: u64,
283    /// Strategy ID that created this order
284    pub strategy_id: OmsString,
285    /// Rejection reason if rejected
286    pub rejection_reason: Option<RejectionReason>,
287    /// Additional order metadata
288    pub metadata: simd_json::OwnedValue,
289    /// Take profit price
290    pub take_profit: Option<Decimal>,
291    /// Stop loss price
292    pub stop_loss: Option<Decimal>,
293}
294
295impl Order {
296    /// Create a new order from an order request
297    #[must_use]
298    pub fn from_request(request: &OrderRequest) -> Self {
299        let now = rusty_common::time::get_timestamp_ns_result().unwrap_or_else(|_| {
300            std::time::SystemTime::now()
301                .duration_since(std::time::UNIX_EPOCH)
302                .unwrap_or_default()
303                .as_nanos() as u64
304        });
305
306        Self {
307            id: Uuid::new_v4(),
308            client_order_id: request.client_order_id.clone(),
309            exchange_order_id: None,
310            venue: request.venue,
311            symbol: request.symbol.clone(),
312            side: request.side,
313            order_type: request.order_type,
314            price: request.price,
315            stop_price: request.stop_price,
316            quantity: request.quantity,
317            filled_quantity: Decimal::ZERO,
318            average_fill_price: None,
319            status: OmsOrderStatus::New,
320            time_in_force: request.time_in_force,
321            reduce_only: request.reduce_only,
322            creation_time_ns: now,
323            update_time_ns: now,
324            strategy_id: request.strategy_id.clone(),
325            rejection_reason: None,
326            metadata: request.extra_params.clone(),
327            take_profit: request.take_profit,
328            stop_loss: request.stop_loss,
329        }
330    }
331
332    /// Check if order is completely filled
333    #[must_use]
334    pub fn is_filled(&self) -> bool {
335        self.filled_quantity >= self.quantity
336    }
337
338    /// Calculate remaining quantity
339    #[must_use]
340    pub fn remaining_quantity(&self) -> Decimal {
341        self.quantity - self.filled_quantity
342    }
343
344    /// Update order status
345    pub fn update_status(&mut self, status: OmsOrderStatus) {
346        self.status = status;
347        self.update_time_ns = rusty_common::time::get_timestamp_ns_result().unwrap_or_else(|_| {
348            std::time::SystemTime::now()
349                .duration_since(std::time::UNIX_EPOCH)
350                .unwrap_or_default()
351                .as_nanos() as u64
352        });
353    }
354
355    /// Reject an order with reason
356    pub fn reject(&mut self, reason: RejectionReason) {
357        self.status = OmsOrderStatus::ExchangeRejected;
358        self.rejection_reason = Some(reason);
359        self.update_time_ns = rusty_common::time::get_timestamp_ns_result().unwrap_or_else(|_| {
360            std::time::SystemTime::now()
361                .duration_since(std::time::UNIX_EPOCH)
362                .unwrap_or_default()
363                .as_nanos() as u64
364        });
365    }
366
367    /// Add a fill to the order
368    pub fn add_fill(&mut self, fill_quantity: Decimal, fill_price: Decimal) {
369        let old_fill_qty = self.filled_quantity;
370        self.filled_quantity += fill_quantity;
371
372        // Calculate new average fill price
373        if let Some(avg_price) = self.average_fill_price {
374            // Weighted average calculation
375            let total_value = avg_price * old_fill_qty + fill_price * fill_quantity;
376            self.average_fill_price = Some(total_value / self.filled_quantity);
377        } else {
378            self.average_fill_price = Some(fill_price);
379        }
380
381        // Update status based on fill
382        if self.filled_quantity >= self.quantity {
383            // Status updates based on fills should be handled by the OMS
384            // which has full context of the order lifecycle
385        }
386
387        self.update_time_ns = rusty_common::time::get_timestamp_ns_result().unwrap_or_else(|_| {
388            std::time::SystemTime::now()
389                .duration_since(std::time::UNIX_EPOCH)
390                .unwrap_or_default()
391                .as_nanos() as u64
392        });
393    }
394}
395
396/// Order response returned after processing an order request
397#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct OrderResponse {
399    /// Unique order ID
400    pub order_id: Uuid,
401    /// Client-assigned order ID
402    pub client_order_id: OmsString,
403    /// Order status
404    pub status: OmsOrderStatus,
405    /// Result message
406    pub message: OmsString,
407    /// Rejection reason if rejected
408    pub rejection_reason: Option<RejectionReason>,
409    /// Response timestamp in nanoseconds
410    pub timestamp_ns: u64,
411}
412
413impl OrderResponse {
414    /// Create a successful order response
415    #[must_use]
416    pub fn success(order: &Order) -> Self {
417        let now = rusty_common::time::get_timestamp_ns_result().unwrap_or_else(|_| {
418            std::time::SystemTime::now()
419                .duration_since(std::time::UNIX_EPOCH)
420                .unwrap_or_default()
421                .as_nanos() as u64
422        });
423
424        Self {
425            order_id: order.id,
426            client_order_id: order.client_order_id.clone(),
427            status: order.status,
428            message: format!("Order {id} accepted", id = order.id).into(),
429            rejection_reason: None,
430            timestamp_ns: now,
431        }
432    }
433
434    /// Create a rejected order response
435    pub fn rejected(
436        order_id: Uuid,
437        client_order_id: impl Into<OmsString>,
438        reason: RejectionReason,
439    ) -> Self {
440        let now = rusty_common::time::get_timestamp_ns_result().unwrap_or_else(|_| {
441            std::time::SystemTime::now()
442                .duration_since(std::time::UNIX_EPOCH)
443                .unwrap_or_default()
444                .as_nanos() as u64
445        });
446
447        Self {
448            order_id,
449            client_order_id: client_order_id.into(),
450            status: OmsOrderStatus::ExchangeRejected,
451            message: format!("Order rejected: {reason}").into(),
452            rejection_reason: Some(reason),
453            timestamp_ns: now,
454        }
455    }
456}
457
458/// Order update event
459#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct OrderUpdate {
461    /// Unique order ID
462    pub order_id: Uuid,
463    /// Client-assigned order ID
464    pub client_order_id: OmsString,
465    /// Exchange-assigned order ID (if available)
466    pub exchange_order_id: Option<OmsString>,
467    /// New order status
468    pub status: OmsOrderStatus,
469    /// Filled quantity
470    pub filled_quantity: Decimal,
471    /// Average fill price
472    pub average_fill_price: Option<Decimal>,
473    /// Last update time in nanoseconds
474    pub update_time_ns: u64,
475    /// Rejection reason if rejected
476    pub rejection_reason: Option<RejectionReason>,
477}
478
479/// Order book representation with bid and ask levels
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct OrderBook {
482    /// Bid levels as (price, quantity) pairs, sorted by price descending
483    pub bids: Vec<(Decimal, Decimal)>,
484    /// Ask levels as (price, quantity) pairs, sorted by price ascending
485    pub asks: Vec<(Decimal, Decimal)>,
486}
487
488impl OrderUpdate {
489    /// Create an order update from an order
490    #[must_use]
491    pub fn from_order(order: &Order) -> Self {
492        Self {
493            order_id: order.id,
494            client_order_id: order.client_order_id.clone(),
495            exchange_order_id: order.exchange_order_id.clone(),
496            status: order.status,
497            filled_quantity: order.filled_quantity,
498            average_fill_price: order.average_fill_price,
499            update_time_ns: order.update_time_ns,
500            rejection_reason: order.rejection_reason.clone(),
501        }
502    }
503}
504
505/// Order event types for lifecycle tracking
506#[derive(Debug, Clone, Serialize, Deserialize)]
507pub enum OrderEvent {
508    /// New order request received
509    New(OrderRequest),
510    /// Order validated and accepted
511    Accepted(Uuid),
512    /// Order sent to exchange
513    Sent {
514        /// Order ID
515        order_id: Uuid,
516        /// Exchange order ID
517        exchange_order_id: Option<String>,
518    },
519    /// Order active on exchange
520    Active(Uuid),
521    /// Order partially filled
522    PartialFill {
523        /// Order ID
524        order_id: Uuid,
525        /// Fill quantity
526        fill_quantity: Decimal,
527        /// Fill price
528        fill_price: Decimal,
529        /// Fill timestamp in nanoseconds
530        timestamp_ns: u64,
531    },
532    /// Order completely filled
533    Filled {
534        /// Order ID
535        order_id: Uuid,
536        /// Final fill quantity
537        fill_quantity: Decimal,
538        /// Final fill price
539        fill_price: Decimal,
540        /// Fill timestamp in nanoseconds
541        timestamp_ns: u64,
542    },
543    /// Order canceled
544    Canceled {
545        /// Order ID
546        order_id: Uuid,
547        /// Remaining quantity
548        remaining_quantity: Decimal,
549        /// Cancel timestamp in nanoseconds
550        timestamp_ns: u64,
551    },
552    /// Order rejected
553    Rejected {
554        /// Order ID
555        order_id: Uuid,
556        /// Rejection reason
557        reason: RejectionReason,
558        /// Rejection timestamp in nanoseconds
559        timestamp_ns: u64,
560    },
561}