/home/jgrusewski/Work/foxhunt/common/src/types.rs
Line | Count | Source |
1 | | //! Common data types used across services |
2 | | //! |
3 | | //! This module provides shared data types that are used throughout |
4 | | //! the Foxhunt HFT trading system. This includes both infrastructure types |
5 | | //! and core trading types migrated from foxhunt-common-types. |
6 | | |
7 | | use crate::error::ErrorCategory; |
8 | | use chrono::{DateTime, Utc}; |
9 | | // ELIMINATED: Re-exports removed to force explicit imports |
10 | | // NO RE-EXPORTS: Import rust_decimal::Decimal directly in each crate that needs it |
11 | | use rust_decimal::Decimal; // Internal use only - other crates must import directly |
12 | | use serde::{Deserialize, Serialize}; |
13 | | use serde_json::Value; |
14 | | use std::collections::HashMap; |
15 | | use std::sync::{Arc, Mutex, RwLock}; |
16 | | |
17 | | use crate::error::{CommonError, ErrorCategory as CommonErrorCategory}; |
18 | | use num_traits::FromPrimitive; |
19 | | use std::convert::TryFrom; |
20 | | use std::fmt; |
21 | | use std::iter::Sum; |
22 | | use std::num::ParseIntError; |
23 | | use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; |
24 | | use std::str::FromStr; |
25 | | use uuid::Uuid; |
26 | | |
27 | | // ============================================================================= |
28 | | // Type Aliases for Complex Types |
29 | | // ============================================================================= |
30 | | |
31 | | /// Common error type for async operations |
32 | | pub type AsyncResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>; |
33 | | |
34 | | /// Thread-safe hash map for shared state |
35 | | pub type SharedHashMap<K, V> = Arc<RwLock<HashMap<K, V>>>; |
36 | | |
37 | | /// Thread-safe hash map with Mutex for shared state |
38 | | pub type MutexHashMap<K, V> = Arc<Mutex<HashMap<K, V>>>; |
39 | | |
40 | | /// Thread-safe container for any value |
41 | | pub type SharedValue<T> = Arc<RwLock<T>>; |
42 | | |
43 | | /// Thread-safe container with Mutex for any value |
44 | | pub type MutexValue<T> = Arc<Mutex<T>>; |
45 | | |
46 | | // Trading-specific type aliases |
47 | | /// Map of positions by symbol |
48 | | pub type PositionMap<T> = SharedHashMap<String, T>; |
49 | | |
50 | | /// Map of orders by order ID |
51 | | pub type OrderMap<T> = SharedHashMap<String, T>; |
52 | | |
53 | | /// Map of accounts by account ID |
54 | | pub type AccountMap<T> = SharedHashMap<String, T>; |
55 | | |
56 | | /// Map of instruments by instrument ID |
57 | | pub type InstrumentMap<T> = SharedHashMap<String, T>; |
58 | | |
59 | | /// Map of market data by symbol |
60 | | pub type MarketDataMap<T> = SharedHashMap<String, T>; |
61 | | |
62 | | /// Cache entry with timestamp |
63 | | pub type CacheEntry<T> = (T, DateTime<Utc>); |
64 | | |
65 | | /// Cache map with timestamped entries |
66 | | pub type CacheMap<K, V> = SharedHashMap<K, CacheEntry<V>>; |
67 | | |
68 | | /// Risk factor loadings by instrument |
69 | | pub type RiskFactorMap = SharedHashMap<String, HashMap<String, Decimal>>; |
70 | | |
71 | | /// Performance metrics history |
72 | | pub type PerformanceHistory<T> = SharedHashMap<String, std::collections::VecDeque<T>>; |
73 | | |
74 | | /// Model registry for ML models |
75 | | pub type ModelRegistry<T> = SharedHashMap<String, T>; |
76 | | |
77 | | /// Generic configuration cache |
78 | | pub type ConfigCache<K, V> = SharedHashMap<K, V>; |
79 | | |
80 | | // ============================================================================= |
81 | | // Event Types - Moved from trading_engine to enforce pure client architecture |
82 | | // ============================================================================= |
83 | | |
84 | | /// Order events for the complete order lifecycle |
85 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
86 | | pub struct OrderEvent { |
87 | | /// Unique identifier for the order |
88 | | pub order_id: OrderId, |
89 | | /// Trading symbol for the order |
90 | | pub symbol: Symbol, |
91 | | /// Type of order (market, limit, stop, etc.) |
92 | | pub order_type: OrderType, |
93 | | /// Order side (buy or sell) |
94 | | pub side: OrderSide, |
95 | | /// Order quantity |
96 | | pub quantity: Quantity, |
97 | | /// Order price (None for market orders) |
98 | | pub price: Option<Price>, |
99 | | /// Timestamp when the event occurred |
100 | | pub timestamp: DateTime<Utc>, |
101 | | /// Strategy or client identifier |
102 | | pub strategy_id: String, |
103 | | /// Order event type (placed, modified, cancelled) |
104 | | pub event_type: OrderEventType, |
105 | | /// Previous quantity for modifications |
106 | | pub previous_quantity: Option<Quantity>, |
107 | | /// Previous price for modifications |
108 | | pub previous_price: Option<Price>, |
109 | | /// Reason for cancellation or modification |
110 | | pub reason: Option<String>, |
111 | | } |
112 | | |
113 | | /// Types of order events |
114 | | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
115 | | pub enum OrderEventType { |
116 | | /// Order was placed |
117 | | Placed, |
118 | | /// Order was modified |
119 | | Modified, |
120 | | /// Order was cancelled |
121 | | Cancelled, |
122 | | /// Order was rejected |
123 | | Rejected, |
124 | | } |
125 | | |
126 | | // ============================================================================= |
127 | | // Core Data Types |
128 | | // ============================================================================= |
129 | | |
130 | | /// Unique identifier for services |
131 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
132 | | pub struct ServiceId(pub String); |
133 | | |
134 | | impl ServiceId { |
135 | | /// Create a new service ID |
136 | 2 | pub fn new<S: Into<String>>(id: S) -> Self { |
137 | 2 | Self(id.into()) |
138 | 2 | } |
139 | | |
140 | | /// Get the inner string value |
141 | | /// Get the execution ID as a string slice |
142 | | /// Get execution ID as string slice |
143 | 2 | pub fn as_str(&self) -> &str { |
144 | 2 | &self.0 |
145 | 2 | } |
146 | | } |
147 | | |
148 | | impl fmt::Display for ServiceId { |
149 | 1 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
150 | 1 | write!(f, "{}", self.0) |
151 | 1 | } |
152 | | } |
153 | | |
154 | | impl From<&str> for ServiceId { |
155 | 0 | fn from(s: &str) -> Self { |
156 | 0 | Self(s.to_owned()) |
157 | 0 | } |
158 | | } |
159 | | |
160 | | impl From<String> for ServiceId { |
161 | 1 | fn from(s: String) -> Self { |
162 | 1 | Self(s) |
163 | 1 | } |
164 | | } |
165 | | |
166 | | /// Service status enumeration |
167 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
168 | | pub enum ServiceStatus { |
169 | | /// Service is starting up |
170 | | Starting, |
171 | | /// Service is running normally |
172 | | Running, |
173 | | /// Service is degraded but functional |
174 | | Degraded, |
175 | | /// Service is stopping |
176 | | Stopping, |
177 | | /// Service is stopped |
178 | | Stopped, |
179 | | /// Service has encountered an error |
180 | | Error, |
181 | | /// Service is in maintenance mode |
182 | | Maintenance, |
183 | | } |
184 | | |
185 | | impl fmt::Display for ServiceStatus { |
186 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
187 | 0 | match self { |
188 | 0 | Self::Starting => write!(f, "STARTING"), |
189 | 0 | Self::Running => write!(f, "RUNNING"), |
190 | 0 | Self::Degraded => write!(f, "DEGRADED"), |
191 | 0 | Self::Stopping => write!(f, "STOPPING"), |
192 | 0 | Self::Stopped => write!(f, "STOPPED"), |
193 | 0 | Self::Error => write!(f, "ERROR"), |
194 | 0 | Self::Maintenance => write!(f, "MAINTENANCE"), |
195 | | } |
196 | 0 | } |
197 | | } |
198 | | |
199 | | impl ServiceStatus { |
200 | | /// Check if the service is healthy |
201 | 4 | pub fn is_healthy(&self) -> bool { |
202 | 4 | matches!2 (self, Self::Running | Self::Starting) |
203 | 4 | } |
204 | | |
205 | | /// Check if the service is available for requests |
206 | 4 | pub fn is_available(&self) -> bool { |
207 | 4 | matches!2 (self, Self::Running | Self::Degraded) |
208 | 4 | } |
209 | | } |
210 | | |
211 | | /// Configuration version for tracking changes |
212 | | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
213 | | pub struct ConfigVersion { |
214 | | /// Version number |
215 | | pub version: u64, |
216 | | /// Timestamp when version was created |
217 | | pub timestamp: DateTime<Utc>, |
218 | | /// Optional description of changes |
219 | | pub description: Option<String>, |
220 | | } |
221 | | |
222 | | impl ConfigVersion { |
223 | | /// Create a new config version |
224 | 1 | pub fn new(version: u64) -> Self { |
225 | 1 | Self { |
226 | 1 | version, |
227 | 1 | timestamp: Utc::now(), |
228 | 1 | description: None, |
229 | 1 | } |
230 | 1 | } |
231 | | |
232 | | /// Create a new config version with description |
233 | 1 | pub fn with_description<S: Into<String>>(version: u64, description: S) -> Self { |
234 | 1 | Self { |
235 | 1 | version, |
236 | 1 | timestamp: Utc::now(), |
237 | 1 | description: Some(description.into()), |
238 | 1 | } |
239 | 1 | } |
240 | | } |
241 | | |
242 | | // TECHNICAL DEBT ELIMINATED - Use DateTime<Utc> directly instead of Timestamp alias |
243 | | |
244 | | /// Timestamp type alias for consistency across the system |
245 | | pub type Timestamp = DateTime<Utc>; |
246 | | |
247 | | /// Request ID for tracing and correlation |
248 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
249 | | pub struct RequestId(pub Uuid); |
250 | | |
251 | | impl Default for RequestId { |
252 | | /// Create a default request ID with a new UUID |
253 | 0 | fn default() -> Self { |
254 | 0 | Self::new() |
255 | 0 | } |
256 | | } |
257 | | |
258 | | impl RequestId { |
259 | | /// Generate a new random request ID |
260 | 2 | pub fn new() -> Self { |
261 | 2 | Self(Uuid::new_v4()) |
262 | 2 | } |
263 | | |
264 | | /// Create from UUID |
265 | 0 | pub fn from_uuid(uuid: Uuid) -> Self { |
266 | 0 | Self(uuid) |
267 | 0 | } |
268 | | |
269 | | /// Get the inner UUID |
270 | 0 | pub fn as_uuid(&self) -> Uuid { |
271 | 0 | self.0 |
272 | 0 | } |
273 | | } |
274 | | |
275 | | // Default implementation is now in the derive macro above |
276 | | |
277 | | // ============================================================================= |
278 | | // MARKET DATA EVENT TYPES (Consolidated from data and trading_engine crates) |
279 | | // ============================================================================= |
280 | | |
281 | | /// Market data event types - CANONICAL DEFINITION |
282 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
283 | | pub enum MarketDataEvent { |
284 | | /// Quote update (bid/ask) |
285 | | Quote(QuoteEvent), |
286 | | /// Trade execution |
287 | | Trade(TradeEvent), |
288 | | /// Aggregate trade data |
289 | | Aggregate(Aggregate), |
290 | | /// Bar/candle data |
291 | | Bar(BarEvent), |
292 | | /// Level 2 market data update |
293 | | Level2(Level2Update), |
294 | | /// Market status update |
295 | | Status(MarketStatus), |
296 | | /// Connection status updates |
297 | | ConnectionStatus(ConnectionEvent), |
298 | | /// Error events with details |
299 | | Error(ErrorEvent), |
300 | | /// Order book update |
301 | | OrderBook(OrderBookEvent), |
302 | | /// Level 2 order book snapshot |
303 | | OrderBookL2Snapshot(OrderBookSnapshot), |
304 | | /// Level 2 order book incremental update |
305 | | OrderBookL2Update(OrderBookUpdate), |
306 | | } |
307 | | |
308 | | /// Quote event structure - CANONICAL DEFINITION |
309 | | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] |
310 | | pub struct QuoteEvent { |
311 | | /// Symbol |
312 | | pub symbol: String, |
313 | | /// Bid price |
314 | | pub bid: Option<Decimal>, |
315 | | /// Ask price |
316 | | pub ask: Option<Decimal>, |
317 | | /// Bid size |
318 | | pub bid_size: Option<Decimal>, |
319 | | /// Ask size |
320 | | pub ask_size: Option<Decimal>, |
321 | | /// Exchange |
322 | | pub exchange: Option<String>, |
323 | | /// Bid exchange |
324 | | pub bid_exchange: Option<String>, |
325 | | /// Ask exchange |
326 | | pub ask_exchange: Option<String>, |
327 | | /// Quote conditions |
328 | | pub conditions: Vec<String>, |
329 | | /// Timestamp |
330 | | pub timestamp: DateTime<Utc>, |
331 | | /// Sequence number |
332 | | pub sequence: u64, |
333 | | } |
334 | | |
335 | | impl QuoteEvent { |
336 | | /// Create a new quote event |
337 | | #[must_use] |
338 | 6 | pub fn new(symbol: String, timestamp: DateTime<Utc>) -> Self { |
339 | 6 | Self { |
340 | 6 | symbol, |
341 | 6 | bid: None, |
342 | 6 | ask: None, |
343 | 6 | bid_size: None, |
344 | 6 | ask_size: None, |
345 | 6 | exchange: None, |
346 | 6 | bid_exchange: None, |
347 | 6 | ask_exchange: None, |
348 | 6 | conditions: Vec::new(), |
349 | 6 | timestamp, |
350 | 6 | sequence: 0, |
351 | 6 | } |
352 | 6 | } |
353 | | |
354 | | /// Set bid price and size |
355 | 4 | pub fn with_bid(mut self, price: Decimal, size: Decimal) -> Self { |
356 | 4 | self.bid = Some(price); |
357 | 4 | self.bid_size = Some(size); |
358 | 4 | self |
359 | 4 | } |
360 | | |
361 | | /// Set ask price and size |
362 | 4 | pub fn with_ask(mut self, price: Decimal, size: Decimal) -> Self { |
363 | 4 | self.ask = Some(price); |
364 | 4 | self.ask_size = Some(size); |
365 | 4 | self |
366 | 4 | } |
367 | | |
368 | | /// Set exchange |
369 | 1 | pub fn with_exchange<S: Into<String>>(mut self, exchange: S) -> Self { |
370 | 1 | self.exchange = Some(exchange.into()); |
371 | 1 | self |
372 | 1 | } |
373 | | |
374 | | /// Set bid exchange |
375 | 0 | pub fn with_bid_exchange<S: Into<String>>(mut self, exchange: S) -> Self { |
376 | 0 | self.bid_exchange = Some(exchange.into()); |
377 | 0 | self |
378 | 0 | } |
379 | | |
380 | | /// Set ask exchange |
381 | 0 | pub fn with_ask_exchange<S: Into<String>>(mut self, exchange: S) -> Self { |
382 | 0 | self.ask_exchange = Some(exchange.into()); |
383 | 0 | self |
384 | 0 | } |
385 | | |
386 | | /// Add quote condition |
387 | 0 | pub fn with_condition<S: Into<String>>(mut self, condition: S) -> Self { |
388 | 0 | self.conditions.push(condition.into()); |
389 | 0 | self |
390 | 0 | } |
391 | | |
392 | | /// Set sequence number |
393 | 1 | pub fn with_sequence(mut self, sequence: u64) -> Self { |
394 | 1 | self.sequence = sequence; |
395 | 1 | self |
396 | 1 | } |
397 | | |
398 | | /// Get mid price |
399 | 1 | pub fn mid_price(&self) -> Option<Decimal> { |
400 | 1 | match (self.bid, self.ask) { |
401 | 1 | (Some(bid), Some(ask)) => Some((bid + ask) / Decimal::from(2)), |
402 | 0 | _ => None, |
403 | | } |
404 | 1 | } |
405 | | |
406 | | /// Get spread |
407 | 1 | pub fn spread(&self) -> Option<Decimal> { |
408 | 1 | match (self.bid, self.ask) { |
409 | 1 | (Some(bid), Some(ask)) => Some(ask - bid), |
410 | 0 | _ => None, |
411 | | } |
412 | 1 | } |
413 | | } |
414 | | |
415 | | /// Trade event structure - CANONICAL DEFINITION |
416 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
417 | | pub struct TradeEvent { |
418 | | /// Symbol |
419 | | pub symbol: String, |
420 | | /// Trade price |
421 | | pub price: Decimal, |
422 | | /// Trade size |
423 | | pub size: Decimal, |
424 | | /// Trade ID |
425 | | pub trade_id: Option<String>, |
426 | | /// Exchange |
427 | | pub exchange: Option<String>, |
428 | | /// Trade conditions |
429 | | pub conditions: Vec<String>, |
430 | | /// Timestamp |
431 | | pub timestamp: DateTime<Utc>, |
432 | | /// Sequence number |
433 | | pub sequence: u64, |
434 | | } |
435 | | |
436 | | impl TradeEvent { |
437 | | /// Create a new trade event |
438 | | #[must_use] |
439 | 4 | pub fn new(symbol: String, price: Decimal, size: Decimal, timestamp: DateTime<Utc>) -> Self { |
440 | 4 | Self { |
441 | 4 | symbol, |
442 | 4 | price, |
443 | 4 | size, |
444 | 4 | trade_id: None, |
445 | 4 | exchange: None, |
446 | 4 | conditions: Vec::new(), |
447 | 4 | timestamp, |
448 | 4 | sequence: 0, |
449 | 4 | } |
450 | 4 | } |
451 | | |
452 | | /// Set trade ID |
453 | 0 | pub fn with_trade_id<S: Into<String>>(mut self, trade_id: S) -> Self { |
454 | 0 | self.trade_id = Some(trade_id.into()); |
455 | 0 | self |
456 | 0 | } |
457 | | |
458 | | /// Set exchange |
459 | 0 | pub fn with_exchange<S: Into<String>>(mut self, exchange: S) -> Self { |
460 | 0 | self.exchange = Some(exchange.into()); |
461 | 0 | self |
462 | 0 | } |
463 | | |
464 | | /// Add trade condition |
465 | 0 | pub fn with_condition<S: Into<String>>(mut self, condition: S) -> Self { |
466 | 0 | self.conditions.push(condition.into()); |
467 | 0 | self |
468 | 0 | } |
469 | | |
470 | | /// Set sequence number |
471 | 0 | pub fn with_sequence(mut self, sequence: u64) -> Self { |
472 | 0 | self.sequence = sequence; |
473 | 0 | self |
474 | 0 | } |
475 | | |
476 | | /// Get notional value |
477 | 1 | pub fn notional_value(&self) -> Decimal { |
478 | 1 | self.price * self.size |
479 | 1 | } |
480 | | } |
481 | | |
482 | | /// Aggregate trade data |
483 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
484 | | pub struct Aggregate { |
485 | | /// Symbol |
486 | | pub symbol: String, |
487 | | /// Open price |
488 | | pub open: Decimal, |
489 | | /// High price |
490 | | pub high: Decimal, |
491 | | /// Low price |
492 | | pub low: Decimal, |
493 | | /// Close price |
494 | | pub close: Decimal, |
495 | | /// Volume |
496 | | pub volume: Decimal, |
497 | | /// Volume weighted average price |
498 | | pub vwap: Option<Decimal>, |
499 | | /// Start timestamp |
500 | | pub start_timestamp: DateTime<Utc>, |
501 | | /// End timestamp |
502 | | pub end_timestamp: DateTime<Utc>, |
503 | | } |
504 | | |
505 | | /// Bar/candle event structure |
506 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
507 | | pub struct BarEvent { |
508 | | /// Symbol |
509 | | pub symbol: String, |
510 | | /// Open price |
511 | | pub open: Decimal, |
512 | | /// High price |
513 | | pub high: Decimal, |
514 | | /// Low price |
515 | | pub low: Decimal, |
516 | | /// Close price |
517 | | pub close: Decimal, |
518 | | /// Volume |
519 | | pub volume: Decimal, |
520 | | /// Volume weighted average price |
521 | | pub vwap: Option<Decimal>, |
522 | | /// Start timestamp |
523 | | pub start_timestamp: DateTime<Utc>, |
524 | | /// End timestamp |
525 | | pub end_timestamp: DateTime<Utc>, |
526 | | /// Timeframe (e.g., "1m", "5m", "1h") |
527 | | pub timeframe: String, |
528 | | } |
529 | | |
530 | | /// Level 2 market data update |
531 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
532 | | pub struct Level2Update { |
533 | | /// Symbol |
534 | | pub symbol: String, |
535 | | /// Bid levels |
536 | | pub bids: Vec<PriceLevel>, |
537 | | /// Ask levels |
538 | | pub asks: Vec<PriceLevel>, |
539 | | /// Timestamp |
540 | | pub timestamp: DateTime<Utc>, |
541 | | } |
542 | | |
543 | | /// Price level for order book |
544 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
545 | | pub struct PriceLevel { |
546 | | /// Price |
547 | | pub price: Decimal, |
548 | | /// Size at this price level |
549 | | pub size: Decimal, |
550 | | } |
551 | | |
552 | | /// Order book snapshot from providers |
553 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
554 | | pub struct OrderBookSnapshot { |
555 | | /// Symbol |
556 | | pub symbol: String, |
557 | | /// Bid levels (price, size) sorted by price descending |
558 | | pub bids: Vec<PriceLevel>, |
559 | | /// Ask levels (price, size) sorted by price ascending |
560 | | pub asks: Vec<PriceLevel>, |
561 | | /// Exchange |
562 | | pub exchange: String, |
563 | | /// Timestamp of snapshot |
564 | | pub timestamp: DateTime<Utc>, |
565 | | /// Sequence number |
566 | | pub sequence: u64, |
567 | | } |
568 | | |
569 | | /// Incremental order book update from providers |
570 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
571 | | pub struct OrderBookUpdate { |
572 | | /// Symbol |
573 | | pub symbol: String, |
574 | | /// Changes to bid levels |
575 | | pub bid_changes: Vec<PriceLevelChange>, |
576 | | /// Changes to ask levels |
577 | | pub ask_changes: Vec<PriceLevelChange>, |
578 | | /// Exchange |
579 | | pub exchange: String, |
580 | | /// Timestamp of update |
581 | | pub timestamp: DateTime<Utc>, |
582 | | /// Sequence number |
583 | | pub sequence: u64, |
584 | | } |
585 | | |
586 | | /// Change to a price level |
587 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
588 | | pub struct PriceLevelChange { |
589 | | /// Price level being modified |
590 | | pub price: Decimal, |
591 | | /// New size (0 = remove level) |
592 | | pub size: Decimal, |
593 | | /// Type of change |
594 | | pub change_type: PriceLevelChangeType, |
595 | | /// Side (bid or ask) |
596 | | pub side: OrderBookSide, |
597 | | } |
598 | | |
599 | | /// Type of price level change |
600 | | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] |
601 | | pub enum PriceLevelChangeType { |
602 | | /// Add new price level |
603 | | Add, |
604 | | /// Update existing price level |
605 | | Update, |
606 | | /// Remove price level |
607 | | Delete, |
608 | | } |
609 | | |
610 | | /// Order book side |
611 | | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] |
612 | | pub enum OrderBookSide { |
613 | | /// Bid side |
614 | | Bid, |
615 | | /// Ask side |
616 | | Ask, |
617 | | } |
618 | | |
619 | | /// Market status information |
620 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
621 | | pub struct MarketStatus { |
622 | | /// Market |
623 | | pub market: String, |
624 | | /// Status (open, closed, early_hours, etc.) |
625 | | pub status: String, |
626 | | /// Timestamp |
627 | | pub timestamp: DateTime<Utc>, |
628 | | } |
629 | | |
630 | | /// Connection event for status updates |
631 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
632 | | pub struct ConnectionEvent { |
633 | | /// Provider name |
634 | | pub provider: String, |
635 | | /// Connection status |
636 | | pub status: ConnectionStatus, |
637 | | /// Optional message |
638 | | pub message: Option<String>, |
639 | | /// Timestamp |
640 | | pub timestamp: DateTime<Utc>, |
641 | | } |
642 | | |
643 | | /// Connection status enumeration |
644 | | /// Connection status for data providers and brokers |
645 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
646 | | #[cfg_attr(feature = "database", derive(sqlx::Type))] |
647 | | #[cfg_attr( |
648 | | feature = "database", |
649 | | sqlx(type_name = "connection_status", rename_all = "snake_case") |
650 | | )] |
651 | | pub enum ConnectionStatus { |
652 | | /// Successfully connected and operational |
653 | | Connected, |
654 | | /// Disconnected from the service |
655 | | Disconnected, |
656 | | /// Currently attempting to reconnect |
657 | | Reconnecting, |
658 | | } |
659 | | |
660 | | /// Error event structure |
661 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
662 | | pub struct ErrorEvent { |
663 | | /// Provider name |
664 | | pub provider: String, |
665 | | /// Error message |
666 | | pub message: String, |
667 | | /// Error category |
668 | | pub category: ErrorCategory, |
669 | | /// Timestamp |
670 | | pub timestamp: DateTime<Utc>, |
671 | | } |
672 | | |
673 | | // ErrorCategory is imported from crate::error as CommonErrorCategory |
674 | | |
675 | | /// Order book event |
676 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
677 | | pub struct OrderBookEvent { |
678 | | /// Symbol |
679 | | pub symbol: String, |
680 | | /// Timestamp |
681 | | pub timestamp: DateTime<Utc>, |
682 | | /// Bid levels |
683 | | pub bids: Vec<(Price, Quantity)>, |
684 | | /// Ask levels |
685 | | pub asks: Vec<(Price, Quantity)>, |
686 | | } |
687 | | |
688 | | /// Data types for subscription |
689 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
690 | | pub enum DataType { |
691 | | /// Real-time quotes |
692 | | Quotes, |
693 | | /// Real-time trades |
694 | | Trades, |
695 | | /// Aggregate/minute bars |
696 | | Aggregates, |
697 | | /// Level 2 order book |
698 | | Level2, |
699 | | /// Market status |
700 | | Status, |
701 | | /// Historical bars/aggregates |
702 | | Bars, |
703 | | /// Order book data |
704 | | OrderBook, |
705 | | /// Volume data |
706 | | Volume, |
707 | | } |
708 | | |
709 | | /// Market data subscription request |
710 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
711 | | pub struct Subscription { |
712 | | /// Symbols to subscribe to |
713 | | pub symbols: Vec<String>, |
714 | | /// Data types to subscribe to |
715 | | pub data_types: Vec<DataType>, |
716 | | /// Exchange filter (optional) |
717 | | pub exchanges: Vec<String>, |
718 | | } |
719 | | |
720 | | impl MarketDataEvent { |
721 | | /// Get the symbol for any market data event |
722 | 1 | pub fn symbol(&self) -> &str { |
723 | 1 | match self { |
724 | 1 | MarketDataEvent::Quote(q) => &q.symbol, |
725 | 0 | MarketDataEvent::Trade(t) => &t.symbol, |
726 | 0 | MarketDataEvent::Aggregate(a) => &a.symbol, |
727 | 0 | MarketDataEvent::Bar(b) => &b.symbol, |
728 | 0 | MarketDataEvent::Level2(l) => &l.symbol, |
729 | 0 | MarketDataEvent::Status(s) => &s.market, |
730 | 0 | MarketDataEvent::ConnectionStatus(_) => "", |
731 | 0 | MarketDataEvent::Error(_) => "", |
732 | 0 | MarketDataEvent::OrderBook(o) => &o.symbol, |
733 | 0 | MarketDataEvent::OrderBookL2Snapshot(s) => &s.symbol, |
734 | 0 | MarketDataEvent::OrderBookL2Update(u) => &u.symbol, |
735 | | } |
736 | 1 | } |
737 | | |
738 | | /// Get the timestamp for any market data event |
739 | 1 | pub fn timestamp(&self) -> Option<DateTime<Utc>> { |
740 | 1 | match self { |
741 | 0 | MarketDataEvent::Quote(q) => Some(q.timestamp), |
742 | 1 | MarketDataEvent::Trade(t) => Some(t.timestamp), |
743 | 0 | MarketDataEvent::Aggregate(a) => Some(a.end_timestamp), |
744 | 0 | MarketDataEvent::Bar(b) => Some(b.end_timestamp), |
745 | 0 | MarketDataEvent::Level2(l) => Some(l.timestamp), |
746 | 0 | MarketDataEvent::Status(s) => Some(s.timestamp), |
747 | 0 | MarketDataEvent::ConnectionStatus(c) => Some(c.timestamp), |
748 | 0 | MarketDataEvent::Error(e) => Some(e.timestamp), |
749 | 0 | MarketDataEvent::OrderBook(o) => Some(o.timestamp), |
750 | 0 | MarketDataEvent::OrderBookL2Snapshot(s) => Some(s.timestamp), |
751 | 0 | MarketDataEvent::OrderBookL2Update(u) => Some(u.timestamp), |
752 | | } |
753 | 1 | } |
754 | | } |
755 | | impl fmt::Display for RequestId { |
756 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
757 | 0 | write!(f, "{}", self.0) |
758 | 0 | } |
759 | | } |
760 | | |
761 | | /// Connection information for services |
762 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
763 | | pub struct ConnectionInfo { |
764 | | /// Host address |
765 | | pub host: String, |
766 | | /// Port number |
767 | | pub port: u16, |
768 | | /// Whether TLS is enabled |
769 | | pub tls: bool, |
770 | | /// Connection timeout in milliseconds |
771 | | pub timeout_ms: u64, |
772 | | } |
773 | | |
774 | | impl ConnectionInfo { |
775 | | /// Create new connection info |
776 | 1 | pub fn new<S: Into<String>>(host: S, port: u16) -> Self { |
777 | 1 | Self { |
778 | 1 | host: host.into(), |
779 | 1 | port, |
780 | 1 | tls: false, |
781 | 1 | timeout_ms: 5000, |
782 | 1 | } |
783 | 1 | } |
784 | | |
785 | | /// Enable TLS |
786 | 1 | pub fn with_tls(mut self) -> Self { |
787 | 1 | self.tls = true; |
788 | 1 | self |
789 | 1 | } |
790 | | |
791 | | /// Set timeout |
792 | 0 | pub fn with_timeout(mut self, timeout_ms: u64) -> Self { |
793 | 0 | self.timeout_ms = timeout_ms; |
794 | 0 | self |
795 | 0 | } |
796 | | |
797 | | /// Get connection URL |
798 | 2 | pub fn url(&self) -> String { |
799 | 2 | let scheme = if self.tls { "https"1 } else { "http"1 }; |
800 | 2 | format!("{}://{}:{}", scheme, self.host, self.port) |
801 | 2 | } |
802 | | } |
803 | | |
804 | | /// Resource limits for services |
805 | | #[derive(Debug, Clone, Serialize, Deserialize, Default)] |
806 | | pub struct ResourceLimits { |
807 | | /// Maximum memory usage in bytes |
808 | | pub max_memory_bytes: Option<u64>, |
809 | | /// Maximum CPU usage as percentage (0-100) |
810 | | pub max_cpu_percent: Option<f64>, |
811 | | /// Maximum number of open file descriptors |
812 | | pub max_file_descriptors: Option<u32>, |
813 | | /// Maximum number of network connections |
814 | | pub max_connections: Option<u32>, |
815 | | } |
816 | | |
817 | | // ============================================================================= |
818 | | // TRADING TYPES (Migrated from foxhunt-common-types) |
819 | | // ============================================================================= |
820 | | |
821 | | /// Common error types for trading operations |
822 | | /// |
823 | | /// This error type implements Send + Sync for use in async contexts |
824 | | #[derive(thiserror::Error, Debug)] |
825 | | pub enum CommonTypeError { |
826 | | /// Invalid price value |
827 | | #[error("Invalid price: {value} - {reason}")] |
828 | | InvalidPrice { |
829 | | /// The invalid price value as string |
830 | | value: String, |
831 | | /// Reason why the price is invalid |
832 | | reason: String, |
833 | | }, |
834 | | |
835 | | /// Invalid quantity value |
836 | | #[error("Invalid quantity: {value} - {reason}")] |
837 | | InvalidQuantity { |
838 | | /// The invalid quantity value as string |
839 | | value: String, |
840 | | /// Reason why the quantity is invalid |
841 | | reason: String, |
842 | | }, |
843 | | |
844 | | /// Invalid identifier |
845 | | #[error("Invalid {field}: {reason}")] |
846 | | InvalidIdentifier { |
847 | | /// The field name that contains the invalid identifier |
848 | | field: String, |
849 | | /// Reason why the identifier is invalid |
850 | | reason: String, |
851 | | }, |
852 | | |
853 | | /// Validation error |
854 | | #[error("Validation error for {field}: {reason}")] |
855 | | ValidationError { |
856 | | /// The field name that failed validation |
857 | | field: String, |
858 | | /// Reason why the validation failed |
859 | | reason: String, |
860 | | }, |
861 | | |
862 | | /// Conversion error |
863 | | #[error("Conversion error: {message}")] |
864 | | ConversionError { |
865 | | /// Detailed error message describing the conversion failure |
866 | | message: String, |
867 | | }, |
868 | | |
869 | | /// I/O error |
870 | | #[error("I/O error: {0}")] |
871 | | IoError(#[from] std::io::Error), |
872 | | |
873 | | /// JSON serialization/deserialization error |
874 | | #[error("JSON error: {0}")] |
875 | | JsonError(#[from] serde_json::Error), |
876 | | |
877 | | /// Float parsing error |
878 | | #[error("Float parsing error: {0}")] |
879 | | ParseFloatError(#[from] std::num::ParseFloatError), |
880 | | |
881 | | /// Integer parsing error |
882 | | #[error("Integer parsing error: {0}")] |
883 | | ParseIntError(#[from] std::num::ParseIntError), |
884 | | } |
885 | | |
886 | | // Manual trait implementations for CommonTypeError |
887 | | // (Cannot derive Clone, PartialEq, Eq, Serialize due to std::io::Error and serde_json::Error) |
888 | | |
889 | | impl Clone for CommonTypeError { |
890 | | /// Clone the error, converting IO and JSON errors to conversion errors |
891 | 2 | fn clone(&self) -> Self { |
892 | 2 | match self { |
893 | 1 | Self::InvalidPrice { value, reason } => Self::InvalidPrice { |
894 | 1 | value: value.clone(), |
895 | 1 | reason: reason.clone(), |
896 | 1 | }, |
897 | 0 | Self::InvalidQuantity { value, reason } => Self::InvalidQuantity { |
898 | 0 | value: value.clone(), |
899 | 0 | reason: reason.clone(), |
900 | 0 | }, |
901 | 0 | Self::InvalidIdentifier { field, reason } => Self::InvalidIdentifier { |
902 | 0 | field: field.clone(), |
903 | 0 | reason: reason.clone(), |
904 | 0 | }, |
905 | 0 | Self::ValidationError { field, reason } => Self::ValidationError { |
906 | 0 | field: field.clone(), |
907 | 0 | reason: reason.clone(), |
908 | 0 | }, |
909 | 0 | Self::ConversionError { message } => Self::ConversionError { |
910 | 0 | message: message.clone(), |
911 | 0 | }, |
912 | | // Cannot clone std::io::Error or serde_json::Error, so create new instances |
913 | 1 | Self::IoError(e) => Self::ConversionError { |
914 | 1 | message: format!("I/O error: {}", e), |
915 | 1 | }, |
916 | 0 | Self::JsonError(e) => Self::ConversionError { |
917 | 0 | message: format!("JSON error: {}", e), |
918 | 0 | }, |
919 | 0 | Self::ParseFloatError(e) => Self::ParseFloatError(e.clone()), |
920 | 0 | Self::ParseIntError(e) => Self::ParseIntError(e.clone()), |
921 | | } |
922 | 2 | } |
923 | | } |
924 | | impl PartialEq for CommonTypeError { |
925 | | /// Compare two errors for equality |
926 | 3 | fn eq(&self, other: &Self) -> bool { |
927 | 3 | match (self, other) { |
928 | | ( |
929 | | Self::InvalidPrice { |
930 | 3 | value: v1, |
931 | 3 | reason: r1, |
932 | | }, |
933 | | Self::InvalidPrice { |
934 | 3 | value: v2, |
935 | 3 | reason: r2, |
936 | | }, |
937 | 3 | ) => v1 == v2 && r1 == r22 , |
938 | | ( |
939 | | Self::InvalidQuantity { |
940 | 0 | value: v1, |
941 | 0 | reason: r1, |
942 | | }, |
943 | | Self::InvalidQuantity { |
944 | 0 | value: v2, |
945 | 0 | reason: r2, |
946 | | }, |
947 | 0 | ) => v1 == v2 && r1 == r2, |
948 | | ( |
949 | | Self::InvalidIdentifier { |
950 | 0 | field: f1, |
951 | 0 | reason: r1, |
952 | | }, |
953 | | Self::InvalidIdentifier { |
954 | 0 | field: f2, |
955 | 0 | reason: r2, |
956 | | }, |
957 | 0 | ) => f1 == f2 && r1 == r2, |
958 | | ( |
959 | | Self::ValidationError { |
960 | 0 | field: f1, |
961 | 0 | reason: r1, |
962 | | }, |
963 | | Self::ValidationError { |
964 | 0 | field: f2, |
965 | 0 | reason: r2, |
966 | | }, |
967 | 0 | ) => f1 == f2 && r1 == r2, |
968 | 0 | (Self::ConversionError { message: m1 }, Self::ConversionError { message: m2 }) => { |
969 | 0 | m1 == m2 |
970 | | }, |
971 | 0 | (Self::ParseFloatError(e1), Self::ParseFloatError(e2)) => e1 == e2, |
972 | 0 | (Self::ParseIntError(e1), Self::ParseIntError(e2)) => e1 == e2, |
973 | | // std::io::Error and serde_json::Error don't implement PartialEq, so they're never equal |
974 | 0 | (Self::IoError(_), Self::IoError(_)) => false, |
975 | 0 | (Self::JsonError(_), Self::JsonError(_)) => false, |
976 | 0 | _ => false, |
977 | | } |
978 | 3 | } |
979 | | } |
980 | | |
981 | | impl Eq for CommonTypeError {} |
982 | | |
983 | | // Note: Display is automatically implemented by thiserror::Error derive |
984 | | // based on the #[error("...")] attributes on each variant |
985 | | impl Serialize for CommonTypeError { |
986 | 1 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
987 | 1 | where |
988 | 1 | S: serde::Serializer, |
989 | | { |
990 | | use serde::ser::SerializeStruct; |
991 | 1 | match self { |
992 | 0 | Self::InvalidPrice { value, reason } => { |
993 | 0 | let mut state = serializer.serialize_struct("InvalidPrice", 2)?; |
994 | 0 | state.serialize_field("value", value)?; |
995 | 0 | state.serialize_field("reason", reason)?; |
996 | 0 | state.end() |
997 | | }, |
998 | 0 | Self::InvalidQuantity { value, reason } => { |
999 | 0 | let mut state = serializer.serialize_struct("InvalidQuantity", 2)?; |
1000 | 0 | state.serialize_field("value", value)?; |
1001 | 0 | state.serialize_field("reason", reason)?; |
1002 | 0 | state.end() |
1003 | | }, |
1004 | 0 | Self::InvalidIdentifier { field, reason } => { |
1005 | 0 | let mut state = serializer.serialize_struct("InvalidIdentifier", 2)?; |
1006 | 0 | state.serialize_field("field", field)?; |
1007 | 0 | state.serialize_field("reason", reason)?; |
1008 | 0 | state.end() |
1009 | | }, |
1010 | 1 | Self::ValidationError { field, reason } => { |
1011 | 1 | let mut state = serializer.serialize_struct("ValidationError", 2)?0 ; |
1012 | 1 | state.serialize_field("field", field)?0 ; |
1013 | 1 | state.serialize_field("reason", reason)?0 ; |
1014 | 1 | state.end() |
1015 | | }, |
1016 | 0 | Self::ConversionError { message } => { |
1017 | 0 | let mut state = serializer.serialize_struct("ConversionError", 1)?; |
1018 | 0 | state.serialize_field("message", message)?; |
1019 | 0 | state.end() |
1020 | | }, |
1021 | 0 | Self::IoError(e) => { |
1022 | 0 | let mut state = serializer.serialize_struct("IoError", 1)?; |
1023 | 0 | state.serialize_field("message", &format!("I/O error: {}", e))?; |
1024 | 0 | state.end() |
1025 | | }, |
1026 | 0 | Self::JsonError(e) => { |
1027 | 0 | let mut state = serializer.serialize_struct("JsonError", 1)?; |
1028 | 0 | state.serialize_field("message", &format!("JSON error: {}", e))?; |
1029 | 0 | state.end() |
1030 | | }, |
1031 | 0 | Self::ParseFloatError(e) => { |
1032 | 0 | let mut state = serializer.serialize_struct("ParseFloatError", 1)?; |
1033 | 0 | state.serialize_field("message", &format!("Float parsing error: {}", e))?; |
1034 | 0 | state.end() |
1035 | | }, |
1036 | 0 | Self::ParseIntError(e) => { |
1037 | 0 | let mut state = serializer.serialize_struct("ParseIntError", 1)?; |
1038 | 0 | state.serialize_field("message", &format!("Integer parsing error: {}", e))?; |
1039 | 0 | state.end() |
1040 | | }, |
1041 | | } |
1042 | 1 | } |
1043 | | } |
1044 | | |
1045 | | impl<'de> Deserialize<'de> for CommonTypeError { |
1046 | 1 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
1047 | 1 | where |
1048 | 1 | D: serde::Deserializer<'de>, |
1049 | | { |
1050 | | // For deserialization, we'll convert everything to ConversionError since |
1051 | | // we can't reconstruct std::io::Error or serde_json::Error from serialized form |
1052 | | use serde::de::{MapAccess, Visitor}; |
1053 | | use std::fmt; |
1054 | | |
1055 | | struct CommonTypeErrorVisitor; |
1056 | | |
1057 | | impl<'de> Visitor<'de> for CommonTypeErrorVisitor { |
1058 | | type Value = CommonTypeError; |
1059 | | |
1060 | 0 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
1061 | 0 | formatter.write_str("a CommonTypeError") |
1062 | 0 | } |
1063 | | |
1064 | 1 | fn visit_map<V>(self, mut map: V) -> Result<CommonTypeError, V::Error> |
1065 | 1 | where |
1066 | 1 | V: MapAccess<'de>, |
1067 | | { |
1068 | | // For simplicity, deserialize everything as ConversionError |
1069 | 1 | let mut message = String::new(); |
1070 | 3 | while let Some(key2 ) = map.next_key::<String>()?0 { |
1071 | 2 | let value: serde_json::Value = map.next_value()?0 ; |
1072 | 2 | if key == "message" { |
1073 | 0 | if let Some(msg) = value.as_str() { |
1074 | 0 | message = msg.to_string(); |
1075 | 0 | } |
1076 | 2 | } else { |
1077 | 2 | message = format!("Deserialized error: {}: {}", key, value); |
1078 | 2 | } |
1079 | | } |
1080 | 1 | if message.is_empty() { |
1081 | 0 | message = "Unknown deserialized error".to_string(); |
1082 | 1 | } |
1083 | 1 | Ok(CommonTypeError::ConversionError { message }) |
1084 | 1 | } |
1085 | | } |
1086 | | |
1087 | 1 | deserializer.deserialize_struct( |
1088 | | "CommonTypeError", |
1089 | 1 | &["value", "reason", "field", "message"], |
1090 | 1 | CommonTypeErrorVisitor, |
1091 | | ) |
1092 | 1 | } |
1093 | | } |
1094 | | |
1095 | | // ============================================================================= |
1096 | | // ORDER TYPES (Moved from trading_engine) |
1097 | | // ============================================================================= |
1098 | | |
1099 | | /// Order type specifying execution behavior - CANONICAL DEFINITION |
1100 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1101 | | #[non_exhaustive] |
1102 | | pub enum OrderType { |
1103 | | /// Market order - executes immediately at current market price |
1104 | | Market, |
1105 | | /// Limit order - executes only at specified price or better |
1106 | | Limit, |
1107 | | /// Stop order - becomes market order when stop price is reached |
1108 | | Stop, |
1109 | | /// Stop-limit order - becomes limit order when stop price is reached |
1110 | | StopLimit, |
1111 | | /// Iceberg order - large order split into smaller visible portions |
1112 | | Iceberg, |
1113 | | /// Trailing stop order - stop price adjusts with favorable price movement |
1114 | | TrailingStop, |
1115 | | /// Hidden order - not displayed in order book |
1116 | | Hidden, |
1117 | | } |
1118 | | |
1119 | | impl fmt::Display for OrderType { |
1120 | 11 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1121 | 11 | match self { |
1122 | 2 | Self::Market => write!(f, "MARKET"), |
1123 | 2 | Self::Limit => write!(f, "LIMIT"), |
1124 | 2 | Self::Stop => write!(f, "STOP"), |
1125 | 2 | Self::StopLimit => write!(f, "STOP_LIMIT"), |
1126 | 1 | Self::Iceberg => write!(f, "ICEBERG"), |
1127 | 1 | Self::TrailingStop => write!(f, "TRAILING_STOP"), |
1128 | 1 | Self::Hidden => write!(f, "HIDDEN"), |
1129 | | } |
1130 | 11 | } |
1131 | | } |
1132 | | |
1133 | | impl Default for OrderType { |
1134 | | /// Returns the default order type (Market) |
1135 | 2 | fn default() -> Self { |
1136 | 2 | Self::Market |
1137 | 2 | } |
1138 | | } |
1139 | | |
1140 | | impl TryFrom<i32> for OrderType { |
1141 | | type Error = String; |
1142 | | |
1143 | 9 | fn try_from(value: i32) -> Result<Self, Self::Error> { |
1144 | 9 | match value { |
1145 | 2 | 0 => Ok(OrderType::Market), |
1146 | 2 | 1 => Ok(OrderType::Limit), |
1147 | 2 | 2 => Ok(OrderType::Stop), |
1148 | 1 | 3 => Ok(OrderType::StopLimit), |
1149 | 0 | 4 => Ok(OrderType::Iceberg), |
1150 | 0 | 5 => Ok(OrderType::TrailingStop), |
1151 | 0 | 6 => Ok(OrderType::Hidden), |
1152 | 2 | _ => Err(format!("Invalid OrderType: {}", value)), |
1153 | | } |
1154 | 9 | } |
1155 | | } |
1156 | | |
1157 | | /// Supported broker types - CANONICAL DEFINITION |
1158 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1159 | | pub enum BrokerType { |
1160 | | /// Interactive Brokers TWS/API |
1161 | | InteractiveBrokers, |
1162 | | /// IC Markets FIX API |
1163 | | ICMarkets, |
1164 | | /// Paper trading simulation |
1165 | | PaperTrading, |
1166 | | /// Demo/Test broker |
1167 | | Demo, |
1168 | | } |
1169 | | |
1170 | | impl Default for BrokerType { |
1171 | | /// Returns the default broker type (InteractiveBrokers) |
1172 | 1 | fn default() -> Self { |
1173 | 1 | Self::InteractiveBrokers |
1174 | 1 | } |
1175 | | } |
1176 | | |
1177 | | /// Order status throughout its lifecycle - CANONICAL DEFINITION |
1178 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1179 | | #[non_exhaustive] |
1180 | | pub enum OrderStatus { |
1181 | | /// Order has been created but not yet submitted to broker |
1182 | | Created, |
1183 | | /// Order has been submitted to broker for execution |
1184 | | Submitted, |
1185 | | /// Order has been partially executed with remaining quantity |
1186 | | PartiallyFilled, |
1187 | | /// Order has been completely executed |
1188 | | Filled, |
1189 | | /// Order was rejected by broker or exchange |
1190 | | Rejected, |
1191 | | /// Order was cancelled by user or system |
1192 | | Cancelled, |
1193 | | /// New order accepted by broker |
1194 | | New, |
1195 | | /// Order expired due to time restrictions |
1196 | | Expired, |
1197 | | /// Order is pending broker acceptance |
1198 | | Pending, |
1199 | | /// Order is actively working in the market |
1200 | | Working, |
1201 | | /// Order status is unknown or not yet determined |
1202 | | Unknown, |
1203 | | /// Order is temporarily suspended |
1204 | | Suspended, |
1205 | | /// Order cancellation is pending |
1206 | | PendingCancel, |
1207 | | /// Order modification is pending |
1208 | | PendingReplace, |
1209 | | } |
1210 | | impl fmt::Display for OrderStatus { |
1211 | 9 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1212 | 9 | match self { |
1213 | 2 | Self::Created => write!(f, "CREATED"), |
1214 | 1 | Self::Submitted => write!(f, "SUBMITTED"), |
1215 | 1 | Self::PartiallyFilled => write!(f, "PARTIALLY_FILLED"), |
1216 | 2 | Self::Filled => write!(f, "FILLED"), |
1217 | 1 | Self::Rejected => write!(f, "REJECTED"), |
1218 | 2 | Self::Cancelled => write!(f, "CANCELLED"), |
1219 | 0 | Self::New => write!(f, "NEW"), |
1220 | 0 | Self::Expired => write!(f, "EXPIRED"), |
1221 | 0 | Self::Pending => write!(f, "PENDING"), |
1222 | 0 | Self::Working => write!(f, "WORKING"), |
1223 | 0 | Self::Unknown => write!(f, "UNKNOWN"), |
1224 | 0 | Self::Suspended => write!(f, "SUSPENDED"), |
1225 | 0 | Self::PendingCancel => write!(f, "PENDING_CANCEL"), |
1226 | 0 | Self::PendingReplace => write!(f, "PENDING_REPLACE"), |
1227 | | } |
1228 | 9 | } |
1229 | | } |
1230 | | |
1231 | | impl Default for OrderStatus { |
1232 | | /// Returns the default order status (Created) |
1233 | 0 | fn default() -> Self { |
1234 | 0 | Self::Created |
1235 | 0 | } |
1236 | | } |
1237 | | |
1238 | | impl TryFrom<i32> for OrderStatus { |
1239 | | type Error = String; |
1240 | | |
1241 | 8 | fn try_from(value: i32) -> Result<Self, Self::Error> { |
1242 | 8 | match value { |
1243 | 2 | 0 => Ok(OrderStatus::Created), |
1244 | 0 | 1 => Ok(OrderStatus::Submitted), |
1245 | 0 | 2 => Ok(OrderStatus::PartiallyFilled), |
1246 | 2 | 3 => Ok(OrderStatus::Filled), |
1247 | 0 | 4 => Ok(OrderStatus::Rejected), |
1248 | 2 | 5 => Ok(OrderStatus::Cancelled), |
1249 | 0 | 6 => Ok(OrderStatus::New), |
1250 | 0 | 7 => Ok(OrderStatus::Expired), |
1251 | 0 | 8 => Ok(OrderStatus::Pending), |
1252 | 0 | 9 => Ok(OrderStatus::Working), |
1253 | 0 | 10 => Ok(OrderStatus::Unknown), |
1254 | 0 | 11 => Ok(OrderStatus::Suspended), |
1255 | 0 | 12 => Ok(OrderStatus::PendingCancel), |
1256 | 0 | 13 => Ok(OrderStatus::PendingReplace), |
1257 | 2 | _ => Err(format!("Invalid OrderStatus: {}", value)), |
1258 | | } |
1259 | 8 | } |
1260 | | } |
1261 | | |
1262 | | /// Order side - whether the order is a buy or sell - CANONICAL DEFINITION |
1263 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1264 | | pub enum OrderSide { |
1265 | | /// Buy order - purchasing securities |
1266 | | Buy, |
1267 | | /// Sell order - selling securities |
1268 | | Sell, |
1269 | | } |
1270 | | |
1271 | | impl fmt::Display for OrderSide { |
1272 | 4 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1273 | 4 | match self { |
1274 | 2 | Self::Buy => write!(f, "BUY"), |
1275 | 2 | Self::Sell => write!(f, "SELL"), |
1276 | | } |
1277 | 4 | } |
1278 | | } |
1279 | | |
1280 | | impl Default for OrderSide { |
1281 | | /// Returns the default order side (Buy) |
1282 | 1 | fn default() -> Self { |
1283 | 1 | Self::Buy |
1284 | 1 | } |
1285 | | } |
1286 | | |
1287 | | impl TryFrom<i32> for OrderSide { |
1288 | | type Error = String; |
1289 | | |
1290 | 6 | fn try_from(value: i32) -> Result<Self, Self::Error> { |
1291 | 6 | match value { |
1292 | 2 | 0 => Ok(OrderSide::Buy), |
1293 | 2 | 1 => Ok(OrderSide::Sell), |
1294 | 2 | _ => Err(format!("Invalid OrderSide: {}", value)), |
1295 | | } |
1296 | 6 | } |
1297 | | } |
1298 | | |
1299 | | // REMOVED: Side alias - use OrderSide directly |
1300 | | |
1301 | | /// Currency enumeration - CANONICAL DEFINITION |
1302 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] |
1303 | | #[cfg_attr(feature = "database", derive(sqlx::Type))] |
1304 | | pub enum Currency { |
1305 | | /// US Dollar |
1306 | | USD, |
1307 | | /// Euro |
1308 | | EUR, |
1309 | | /// British Pound Sterling |
1310 | | GBP, |
1311 | | /// Japanese Yen |
1312 | | JPY, |
1313 | | /// Swiss Franc |
1314 | | CHF, |
1315 | | /// Canadian Dollar |
1316 | | CAD, |
1317 | | /// Australian Dollar |
1318 | | AUD, |
1319 | | /// New Zealand Dollar |
1320 | | NZD, |
1321 | | /// Bitcoin |
1322 | | BTC, |
1323 | | /// Ethereum |
1324 | | ETH, |
1325 | | } |
1326 | | |
1327 | | impl fmt::Display for Currency { |
1328 | 11 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1329 | 11 | match self { |
1330 | 4 | Self::USD => write!(f, "USD"), |
1331 | 2 | Self::EUR => write!(f, "EUR"), |
1332 | 1 | Self::GBP => write!(f, "GBP"), |
1333 | 1 | Self::JPY => write!(f, "JPY"), |
1334 | 0 | Self::CHF => write!(f, "CHF"), |
1335 | 0 | Self::CAD => write!(f, "CAD"), |
1336 | 0 | Self::AUD => write!(f, "AUD"), |
1337 | 0 | Self::NZD => write!(f, "NZD"), |
1338 | 2 | Self::BTC => write!(f, "BTC"), |
1339 | 1 | Self::ETH => write!(f, "ETH"), |
1340 | | } |
1341 | 11 | } |
1342 | | } |
1343 | | |
1344 | | impl Default for Currency { |
1345 | | /// Returns the default currency (USD) |
1346 | 2 | fn default() -> Self { |
1347 | 2 | Self::USD |
1348 | 2 | } |
1349 | | } |
1350 | | |
1351 | | /// Time in force enumeration - CANONICAL DEFINITION |
1352 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1353 | | pub enum TimeInForce { |
1354 | | /// Order is valid for the current trading day only |
1355 | | Day, |
1356 | | /// Order remains active until explicitly cancelled |
1357 | | GoodTillCancel, |
1358 | | /// Order must be executed immediately or cancelled |
1359 | | ImmediateOrCancel, |
1360 | | /// Order must be executed completely or cancelled |
1361 | | FillOrKill, |
1362 | | } |
1363 | | |
1364 | | impl fmt::Display for TimeInForce { |
1365 | 8 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1366 | 8 | match self { |
1367 | 2 | Self::Day => write!(f, "DAY"), |
1368 | 2 | Self::GoodTillCancel => write!(f, "GTC"), |
1369 | 2 | Self::ImmediateOrCancel => write!(f, "IOC"), |
1370 | 2 | Self::FillOrKill => write!(f, "FOK"), |
1371 | | } |
1372 | 8 | } |
1373 | | } |
1374 | | |
1375 | | impl Default for TimeInForce { |
1376 | | /// Returns the default time in force (Day) |
1377 | 15 | fn default() -> Self { |
1378 | 15 | Self::Day |
1379 | 15 | } |
1380 | | } |
1381 | | |
1382 | | // ============================================================================= |
1383 | | // CORE ID TYPES (MIGRATED FROM TRADING_ENGINE) |
1384 | | // ============================================================================= |
1385 | | |
1386 | | // Duplicate TradeId removed - using definition from line 1008 |
1387 | | |
1388 | | /// Event identifier for tracking system events |
1389 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1390 | | pub struct EventId(String); |
1391 | | |
1392 | | impl EventId { |
1393 | | /// Create a new random event ID |
1394 | 1 | pub fn new() -> Self { |
1395 | | use uuid::Uuid; |
1396 | 1 | Self(Uuid::new_v4().to_string()) |
1397 | 1 | } |
1398 | | |
1399 | | /// Create an event ID from a string, generating new if empty |
1400 | 1 | pub fn from_string<S: Into<String>>(id: S) -> Self { |
1401 | 1 | let id = id.into(); |
1402 | 1 | if id.is_empty() { |
1403 | 1 | Self::new() // Generate new ID if empty |
1404 | | } else { |
1405 | 0 | Self(id) |
1406 | | } |
1407 | 1 | } |
1408 | | |
1409 | | /// Get the string value of the event ID |
1410 | 1 | pub fn value(&self) -> &str { |
1411 | 1 | &self.0 |
1412 | 1 | } |
1413 | | } |
1414 | | |
1415 | | impl fmt::Display for EventId { |
1416 | | /// Format the event ID for display |
1417 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1418 | 0 | write!(f, "{}", self.0) |
1419 | 0 | } |
1420 | | } |
1421 | | |
1422 | | impl From<String> for EventId { |
1423 | | /// Create an EventId from a String |
1424 | 0 | fn from(s: String) -> Self { |
1425 | 0 | Self(s) |
1426 | 0 | } |
1427 | | } |
1428 | | |
1429 | | impl Default for EventId { |
1430 | | /// Create a default EventId with a new UUID |
1431 | 0 | fn default() -> Self { |
1432 | 0 | Self::new() |
1433 | 0 | } |
1434 | | } |
1435 | | |
1436 | | /// Fill identifier with validation |
1437 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1438 | | pub struct FillId(String); |
1439 | | |
1440 | | impl FillId { |
1441 | | /// Create a new fill ID with validation |
1442 | 0 | pub fn new<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> { |
1443 | 0 | let id = id.into(); |
1444 | 0 | if id.is_empty() { |
1445 | 0 | return Err(CommonTypeError::ValidationError { |
1446 | 0 | field: "fill_id".to_owned(), |
1447 | 0 | reason: "Fill ID cannot be empty".to_owned(), |
1448 | 0 | }); |
1449 | 0 | } |
1450 | 0 | Ok(Self(id)) |
1451 | 0 | } |
1452 | | |
1453 | | /// Get the fill ID as a string slice |
1454 | 0 | pub fn as_str(&self) -> &str { |
1455 | 0 | &self.0 |
1456 | 0 | } |
1457 | | /// Convert the fill ID into an owned string |
1458 | | /// Convert the execution ID into an owned string |
1459 | | /// Convert execution ID into owned string |
1460 | 0 | pub fn into_string(self) -> String { |
1461 | 0 | self.0 |
1462 | 0 | } |
1463 | | } |
1464 | | |
1465 | | impl fmt::Display for FillId { |
1466 | | /// Format the fill ID for display |
1467 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1468 | 0 | write!(f, "{}", self.0) |
1469 | 0 | } |
1470 | | } |
1471 | | |
1472 | | /// Aggregate identifier with validation |
1473 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1474 | | pub struct AggregateId(String); |
1475 | | |
1476 | | impl AggregateId { |
1477 | | /// Create a new aggregate ID with validation |
1478 | 0 | pub fn new<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> { |
1479 | 0 | let id = id.into(); |
1480 | 0 | if id.is_empty() { |
1481 | 0 | return Err(CommonTypeError::ValidationError { |
1482 | 0 | field: "aggregate_id".to_owned(), |
1483 | 0 | reason: "Aggregate ID cannot be empty".to_owned(), |
1484 | 0 | }); |
1485 | 0 | } |
1486 | 0 | Ok(Self(id)) |
1487 | 0 | } |
1488 | | |
1489 | | /// Get the aggregate ID as a string slice |
1490 | 0 | pub fn as_str(&self) -> &str { |
1491 | 0 | &self.0 |
1492 | 0 | } |
1493 | | /// Convert the aggregate ID into an owned string |
1494 | 0 | pub fn into_string(self) -> String { |
1495 | 0 | self.0 |
1496 | 0 | } |
1497 | | } |
1498 | | |
1499 | | impl fmt::Display for AggregateId { |
1500 | | /// Format the aggregate ID for display |
1501 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1502 | 0 | write!(f, "{}", self.0) |
1503 | 0 | } |
1504 | | } |
1505 | | |
1506 | | /// Asset identifier with validation |
1507 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1508 | | pub struct AssetId(String); |
1509 | | |
1510 | | impl AssetId { |
1511 | | /// Create a new asset ID with validation |
1512 | 0 | pub fn new<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> { |
1513 | 0 | let id = id.into(); |
1514 | 0 | if id.is_empty() { |
1515 | 0 | return Err(CommonTypeError::ValidationError { |
1516 | 0 | field: "asset_id".to_owned(), |
1517 | 0 | reason: "Asset ID cannot be empty".to_owned(), |
1518 | 0 | }); |
1519 | 0 | } |
1520 | 0 | Ok(Self(id)) |
1521 | 0 | } |
1522 | | |
1523 | | /// Get the asset ID as a string slice |
1524 | 0 | pub fn as_str(&self) -> &str { |
1525 | 0 | &self.0 |
1526 | 0 | } |
1527 | | /// Convert the asset ID into an owned string |
1528 | 0 | pub fn into_string(self) -> String { |
1529 | 0 | self.0 |
1530 | 0 | } |
1531 | | } |
1532 | | |
1533 | | impl fmt::Display for AssetId { |
1534 | | /// Format the asset ID for display |
1535 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1536 | 0 | write!(f, "{}", self.0) |
1537 | 0 | } |
1538 | | } |
1539 | | |
1540 | | /// Client identifier with validation |
1541 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
1542 | | pub struct ClientId(String); |
1543 | | |
1544 | | impl ClientId { |
1545 | | /// Create a new client ID with validation |
1546 | 0 | pub fn new<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> { |
1547 | 0 | let id = id.into(); |
1548 | 0 | if id.is_empty() { |
1549 | 0 | return Err(CommonTypeError::ValidationError { |
1550 | 0 | field: "client_id".to_owned(), |
1551 | 0 | reason: "Client ID cannot be empty".to_owned(), |
1552 | 0 | }); |
1553 | 0 | } |
1554 | 0 | Ok(Self(id)) |
1555 | 0 | } |
1556 | | |
1557 | | /// Get the client ID as a string slice |
1558 | 0 | pub fn as_str(&self) -> &str { |
1559 | 0 | &self.0 |
1560 | 0 | } |
1561 | | /// Convert the client ID into an owned string |
1562 | 0 | pub fn into_string(self) -> String { |
1563 | 0 | self.0 |
1564 | 0 | } |
1565 | | } |
1566 | | |
1567 | | impl fmt::Display for ClientId { |
1568 | | /// Format the client ID for display |
1569 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1570 | 0 | write!(f, "{}", self.0) |
1571 | 0 | } |
1572 | | } |
1573 | | |
1574 | | // ============================================================================= |
1575 | | // CORE TRADING TYPES - MIGRATED FROM TRADING_ENGINE |
1576 | | // ============================================================================= |
1577 | | |
1578 | | /// Canonical Order struct - UNIFIED DEFINITION based on Agent 1's comprehensive analysis |
1579 | | /// This represents the single source of truth for Order across all services |
1580 | | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] |
1581 | | #[cfg_attr(feature = "database", derive(sqlx::FromRow))] |
1582 | | pub struct Order { |
1583 | | // Core Identity |
1584 | | /// Unique order identifier |
1585 | | pub id: OrderId, |
1586 | | /// Client-provided order identifier |
1587 | | pub client_order_id: Option<String>, |
1588 | | /// Broker-assigned order identifier |
1589 | | pub broker_order_id: Option<String>, |
1590 | | /// Account identifier for the order |
1591 | | pub account_id: Option<String>, |
1592 | | |
1593 | | // Trading Details |
1594 | | /// Trading symbol for the order |
1595 | | pub symbol: Symbol, |
1596 | | /// Order side (buy or sell) |
1597 | | pub side: OrderSide, |
1598 | | /// Type of order (market, limit, etc.) |
1599 | | pub order_type: OrderType, |
1600 | | /// Current status of the order |
1601 | | pub status: OrderStatus, |
1602 | | /// Time in force policy |
1603 | | pub time_in_force: TimeInForce, |
1604 | | |
1605 | | // Quantities & Pricing |
1606 | | /// Total order quantity |
1607 | | pub quantity: Quantity, |
1608 | | /// Limit price for the order |
1609 | | pub price: Option<Price>, |
1610 | | /// Stop price for stop orders |
1611 | | pub stop_price: Option<Price>, |
1612 | | /// Quantity that has been filled |
1613 | | pub filled_quantity: Quantity, |
1614 | | /// Remaining quantity to be filled |
1615 | | pub remaining_quantity: Quantity, |
1616 | | /// Average execution price |
1617 | | pub average_price: Option<Price>, |
1618 | | /// Alias for average_price for database compatibility |
1619 | | pub avg_fill_price: Option<Price>, |
1620 | | |
1621 | | // Strategy Fields (from Agent 1) |
1622 | | /// Parent order ID for iceberg/algo orders |
1623 | | pub parent_id: Option<String>, |
1624 | | /// Execution algorithm name |
1625 | | pub execution_algorithm: Option<String>, |
1626 | | /// Execution algorithm parameters stored as JSON |
1627 | | pub execution_params: Value, |
1628 | | |
1629 | | // Risk Management (from Agent 1) |
1630 | | /// Stop loss price for risk management |
1631 | | pub stop_loss: Option<Price>, |
1632 | | /// Take profit price for profit taking |
1633 | | pub take_profit: Option<Price>, |
1634 | | |
1635 | | // Timestamps |
1636 | | /// Order creation timestamp |
1637 | | pub created_at: HftTimestamp, |
1638 | | /// Last update timestamp |
1639 | | pub updated_at: Option<HftTimestamp>, |
1640 | | /// Order expiration timestamp |
1641 | | pub expires_at: Option<HftTimestamp>, |
1642 | | |
1643 | | // Extensibility |
1644 | | /// Additional order metadata stored as JSON |
1645 | | pub metadata: Value, |
1646 | | } |
1647 | | |
1648 | | impl Order { |
1649 | | /// Create a new order with canonical fields |
1650 | 14 | pub fn new( |
1651 | 14 | symbol: Symbol, |
1652 | 14 | side: OrderSide, |
1653 | 14 | quantity: Quantity, |
1654 | 14 | price: Option<Price>, |
1655 | 14 | order_type: OrderType, |
1656 | 14 | ) -> Self { |
1657 | 14 | let now = HftTimestamp::now_or_zero(); |
1658 | 14 | Self { |
1659 | 14 | // Core Identity |
1660 | 14 | id: OrderId::new(), |
1661 | 14 | client_order_id: None, |
1662 | 14 | broker_order_id: None, |
1663 | 14 | account_id: None, |
1664 | 14 | |
1665 | 14 | // Trading Details |
1666 | 14 | symbol, |
1667 | 14 | side, |
1668 | 14 | order_type, |
1669 | 14 | status: OrderStatus::Created, |
1670 | 14 | time_in_force: TimeInForce::default(), |
1671 | 14 | |
1672 | 14 | // Quantities & Pricing |
1673 | 14 | quantity, |
1674 | 14 | price, |
1675 | 14 | stop_price: None, |
1676 | 14 | filled_quantity: Quantity::ZERO, |
1677 | 14 | remaining_quantity: quantity, |
1678 | 14 | average_price: None, |
1679 | 14 | avg_fill_price: None, // Database compatibility alias |
1680 | 14 | |
1681 | 14 | // Strategy Fields |
1682 | 14 | parent_id: None, |
1683 | 14 | execution_algorithm: None, |
1684 | 14 | execution_params: serde_json::json!({}), |
1685 | 14 | |
1686 | 14 | // Risk Management |
1687 | 14 | stop_loss: None, |
1688 | 14 | take_profit: None, |
1689 | 14 | |
1690 | 14 | // Timestamps |
1691 | 14 | created_at: now, |
1692 | 14 | updated_at: None, |
1693 | 14 | expires_at: None, |
1694 | 14 | |
1695 | 14 | // Extensibility |
1696 | 14 | metadata: serde_json::json!({}), |
1697 | 14 | } |
1698 | 14 | } |
1699 | | |
1700 | | /// Check if the order is fully filled |
1701 | 12 | pub fn is_filled(&self) -> bool { |
1702 | 12 | self.filled_quantity == self.quantity |
1703 | 12 | } |
1704 | | |
1705 | | /// Check if the order is partially filled |
1706 | 3 | pub fn is_partially_filled(&self) -> bool { |
1707 | 3 | self.filled_quantity > Quantity::ZERO && self.filled_quantity < self.quantity2 |
1708 | 3 | } |
1709 | | |
1710 | | /// Calculate fill percentage |
1711 | 5 | pub fn fill_percentage(&self) -> f64 { |
1712 | 5 | if self.quantity.is_zero() { |
1713 | 1 | 0.0 |
1714 | | } else { |
1715 | 4 | (self.filled_quantity.to_f64() / self.quantity.to_f64()) * 100.0 |
1716 | | } |
1717 | 5 | } |
1718 | | |
1719 | | /// Set client order ID for tracking |
1720 | 1 | pub fn with_client_order_id(mut self, client_order_id: String) -> Self { |
1721 | 1 | self.client_order_id = Some(client_order_id); |
1722 | 1 | self |
1723 | 1 | } |
1724 | | |
1725 | | /// Set account ID |
1726 | 1 | pub fn with_account_id(mut self, account_id: String) -> Self { |
1727 | 1 | self.account_id = Some(account_id); |
1728 | 1 | self |
1729 | 1 | } |
1730 | | |
1731 | | /// Set time in force |
1732 | 1 | pub fn with_time_in_force(mut self, time_in_force: TimeInForce) -> Self { |
1733 | 1 | self.time_in_force = time_in_force; |
1734 | 1 | self |
1735 | 1 | } |
1736 | | |
1737 | | /// Set stop price |
1738 | 0 | pub fn with_stop_price(mut self, stop_price: Price) -> Self { |
1739 | 0 | self.stop_price = Some(stop_price); |
1740 | 0 | self |
1741 | 0 | } |
1742 | | |
1743 | | /// Set execution algorithm |
1744 | 0 | pub fn with_execution_algorithm(mut self, algorithm: String) -> Self { |
1745 | 0 | self.execution_algorithm = Some(algorithm); |
1746 | 0 | self |
1747 | 0 | } |
1748 | | |
1749 | | /// Add execution parameter |
1750 | 0 | pub fn with_execution_param(mut self, key: String, value: f64) -> Self { |
1751 | 0 | if let Some(obj) = self.execution_params.as_object_mut() { |
1752 | 0 | obj.insert(key, serde_json::to_value(value).unwrap_or(Value::Null)); |
1753 | 0 | } else { |
1754 | 0 | let mut map = serde_json::Map::new(); |
1755 | 0 | map.insert(key, serde_json::to_value(value).unwrap_or(Value::Null)); |
1756 | 0 | self.execution_params = Value::Object(map); |
1757 | 0 | } |
1758 | 0 | self |
1759 | 0 | } |
1760 | | |
1761 | | /// Set stop loss |
1762 | 0 | pub fn with_stop_loss(mut self, stop_loss: Price) -> Self { |
1763 | 0 | self.stop_loss = Some(stop_loss); |
1764 | 0 | self |
1765 | 0 | } |
1766 | | |
1767 | | /// Set take profit |
1768 | 0 | pub fn with_take_profit(mut self, take_profit: Price) -> Self { |
1769 | 0 | self.take_profit = Some(take_profit); |
1770 | 0 | self |
1771 | 0 | } |
1772 | | |
1773 | | /// Add metadata |
1774 | 0 | pub fn with_metadata(mut self, key: String, value: String) -> Self { |
1775 | 0 | if let Some(obj) = self.metadata.as_object_mut() { |
1776 | 0 | obj.insert(key, Value::String(value)); |
1777 | 0 | } else { |
1778 | 0 | let mut map = serde_json::Map::new(); |
1779 | 0 | map.insert(key, Value::String(value)); |
1780 | 0 | self.metadata = Value::Object(map); |
1781 | 0 | } |
1782 | 0 | self |
1783 | 0 | } |
1784 | | |
1785 | | /// Update order status and timestamp |
1786 | 10 | pub fn update_status(&mut self, status: OrderStatus) { |
1787 | 10 | self.status = status; |
1788 | 10 | self.updated_at = Some(HftTimestamp::now_or_zero()); |
1789 | 10 | } |
1790 | | |
1791 | | /// Fill order with given quantity and price |
1792 | 11 | pub fn fill( |
1793 | 11 | &mut self, |
1794 | 11 | fill_quantity: Quantity, |
1795 | 11 | fill_price: Price, |
1796 | 11 | ) -> Result<(), CommonTypeError> { |
1797 | 11 | if self.filled_quantity + fill_quantity > self.quantity { |
1798 | 1 | return Err(CommonTypeError::ValidationError { |
1799 | 1 | field: "fill_quantity".to_string(), |
1800 | 1 | reason: "Fill quantity exceeds remaining quantity".to_string(), |
1801 | 1 | }); |
1802 | 10 | } |
1803 | | |
1804 | | // Update filled quantity |
1805 | 10 | let previous_filled = self.filled_quantity; |
1806 | 10 | self.filled_quantity = self.filled_quantity + fill_quantity; |
1807 | 10 | self.remaining_quantity = self.quantity - self.filled_quantity; |
1808 | | |
1809 | | // Update average price |
1810 | 10 | if let Some(avg_price5 ) = self.average_price { |
1811 | 5 | let total_value = avg_price.to_f64() * previous_filled.to_f64() |
1812 | 5 | + fill_price.to_f64() * fill_quantity.to_f64(); |
1813 | 5 | let new_avg = Some( |
1814 | 5 | Price::from_f64(total_value / self.filled_quantity.to_f64()).unwrap_or(fill_price), |
1815 | 5 | ); |
1816 | 5 | self.average_price = new_avg; |
1817 | 5 | self.avg_fill_price = new_avg; // Keep in sync |
1818 | 5 | } else { |
1819 | 5 | self.average_price = Some(fill_price); |
1820 | 5 | self.avg_fill_price = Some(fill_price); // Keep in sync |
1821 | 5 | } |
1822 | | |
1823 | | // Update status |
1824 | 10 | if self.is_filled() { |
1825 | 4 | self.update_status(OrderStatus::Filled); |
1826 | 6 | } else { |
1827 | 6 | self.update_status(OrderStatus::PartiallyFilled); |
1828 | 6 | } |
1829 | | |
1830 | 10 | Ok(()) |
1831 | 11 | } |
1832 | | |
1833 | | /// Create a limit order - convenience constructor |
1834 | 12 | pub fn limit(symbol: Symbol, side: OrderSide, quantity: Quantity, price: Price) -> Self { |
1835 | 12 | Self::new(symbol, side, quantity, Some(price), OrderType::Limit) |
1836 | 12 | } |
1837 | | |
1838 | | /// Create a market order - convenience constructor |
1839 | 1 | pub fn market(symbol: Symbol, side: OrderSide, quantity: Quantity) -> Self { |
1840 | 1 | Self::new(symbol, side, quantity, None, OrderType::Market) |
1841 | 1 | } |
1842 | | |
1843 | | /// Get symbol hash for performance-critical operations |
1844 | 1 | pub fn symbol_hash(&self) -> i64 { |
1845 | | use std::collections::hash_map::DefaultHasher; |
1846 | | use std::hash::{Hash, Hasher}; |
1847 | | |
1848 | 1 | let mut hasher = DefaultHasher::new(); |
1849 | 1 | self.symbol.as_str().hash(&mut hasher); |
1850 | 1 | hasher.finish() as i64 |
1851 | 1 | } |
1852 | | |
1853 | | /// Get order timestamp |
1854 | 0 | pub fn timestamp(&self) -> HftTimestamp { |
1855 | 0 | self.created_at |
1856 | 0 | } |
1857 | | } |
1858 | | |
1859 | | impl Default for Order { |
1860 | 0 | fn default() -> Self { |
1861 | 0 | Self { |
1862 | 0 | id: OrderId::new(), |
1863 | 0 | client_order_id: None, |
1864 | 0 | broker_order_id: None, |
1865 | 0 | account_id: None, |
1866 | 0 |
|
1867 | 0 | symbol: Symbol::from("DEFAULT"), |
1868 | 0 | side: OrderSide::Buy, |
1869 | 0 | order_type: OrderType::Market, |
1870 | 0 | status: OrderStatus::Created, |
1871 | 0 | time_in_force: TimeInForce::Day, |
1872 | 0 |
|
1873 | 0 | quantity: Quantity::ONE, |
1874 | 0 | price: None, |
1875 | 0 | stop_price: None, |
1876 | 0 | filled_quantity: Quantity::ZERO, |
1877 | 0 | remaining_quantity: Quantity::ONE, |
1878 | 0 | average_price: None, |
1879 | 0 | avg_fill_price: None, |
1880 | 0 |
|
1881 | 0 | parent_id: None, |
1882 | 0 | execution_algorithm: None, |
1883 | 0 | execution_params: serde_json::json!({}), |
1884 | 0 |
|
1885 | 0 | stop_loss: None, |
1886 | 0 | take_profit: None, |
1887 | 0 |
|
1888 | 0 | created_at: HftTimestamp::now().unwrap_or(HftTimestamp { nanos: 0 }), |
1889 | 0 | updated_at: None, |
1890 | 0 | expires_at: None, |
1891 | 0 |
|
1892 | 0 | metadata: serde_json::json!({}), |
1893 | 0 | } |
1894 | 0 | } |
1895 | | } |
1896 | | |
1897 | | /// Represents a trading position - CANONICAL DEFINITION |
1898 | | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
1899 | | #[cfg_attr(feature = "database", derive(sqlx::FromRow))] |
1900 | | pub struct Position { |
1901 | | /// Unique position identifier |
1902 | | pub id: Uuid, |
1903 | | |
1904 | | /// Trading symbol |
1905 | | pub symbol: String, |
1906 | | |
1907 | | /// Position quantity (positive for long, negative for short) |
1908 | | pub quantity: Decimal, |
1909 | | |
1910 | | /// Average entry price |
1911 | | pub avg_price: Decimal, |
1912 | | |
1913 | | /// Average cost per share |
1914 | | pub avg_cost: Decimal, |
1915 | | |
1916 | | /// Cost basis for tax calculations |
1917 | | pub basis: Decimal, |
1918 | | |
1919 | | /// Average entry price |
1920 | | pub average_price: Decimal, |
1921 | | |
1922 | | /// Market value of position |
1923 | | pub market_value: Decimal, |
1924 | | |
1925 | | /// Unrealized P&L |
1926 | | pub unrealized_pnl: Decimal, |
1927 | | |
1928 | | /// Realized P&L |
1929 | | pub realized_pnl: Decimal, |
1930 | | |
1931 | | /// Position creation timestamp |
1932 | | pub created_at: DateTime<Utc>, |
1933 | | |
1934 | | /// Last update timestamp |
1935 | | pub updated_at: DateTime<Utc>, |
1936 | | |
1937 | | /// Last updated timestamp |
1938 | | pub last_updated: DateTime<Utc>, |
1939 | | |
1940 | | /// Current market price (for P&L calculation) |
1941 | | pub current_price: Option<Decimal>, |
1942 | | |
1943 | | /// Position size in base currency |
1944 | | pub notional_value: Decimal, |
1945 | | |
1946 | | /// Margin requirement |
1947 | | pub margin_requirement: Decimal, |
1948 | | } |
1949 | | |
1950 | | impl Position { |
1951 | | /// Create a new position |
1952 | 7 | pub fn new(symbol: String, quantity: Decimal, avg_price: Decimal) -> Self { |
1953 | 7 | let now = Utc::now(); |
1954 | 7 | let notional_value = quantity.abs() * avg_price; |
1955 | | |
1956 | 7 | Self { |
1957 | 7 | id: Uuid::new_v4(), |
1958 | 7 | symbol, |
1959 | 7 | quantity, |
1960 | 7 | avg_price, |
1961 | 7 | avg_cost: avg_price, // Keep avg_cost synchronized with avg_price |
1962 | 7 | basis: quantity * avg_price, // Cost basis calculation |
1963 | 7 | average_price: avg_price, // Same as avg_price for compatibility |
1964 | 7 | market_value: notional_value, // Initialize market value to notional value |
1965 | 7 | unrealized_pnl: Decimal::ZERO, |
1966 | 7 | realized_pnl: Decimal::ZERO, |
1967 | 7 | created_at: now, |
1968 | 7 | updated_at: now, |
1969 | 7 | last_updated: now, // Same as updated_at for compatibility |
1970 | 7 | current_price: None, |
1971 | 7 | notional_value, |
1972 | 7 | margin_requirement: notional_value |
1973 | 7 | * Decimal::from_str_exact("0.02").unwrap_or(Decimal::ZERO), // 2% margin |
1974 | 7 | } |
1975 | 7 | } |
1976 | | |
1977 | | /// Check if position is long |
1978 | 2 | pub fn is_long(&self) -> bool { |
1979 | 2 | self.quantity > Decimal::ZERO |
1980 | 2 | } |
1981 | | |
1982 | | /// Check if position is short |
1983 | 2 | pub fn is_short(&self) -> bool { |
1984 | 2 | self.quantity < Decimal::ZERO |
1985 | 2 | } |
1986 | | |
1987 | | /// Calculate unrealized P&L based on current price |
1988 | 3 | pub fn calculate_unrealized_pnl(&mut self, current_price: Decimal) { |
1989 | 3 | self.current_price = Some(current_price); |
1990 | 3 | self.market_value = self.quantity.abs() * current_price; |
1991 | | // For both long and short: quantity * (current_price - avg_price) |
1992 | 3 | self.unrealized_pnl = self.quantity * (current_price - self.avg_price); |
1993 | 3 | let now = Utc::now(); |
1994 | 3 | self.updated_at = now; |
1995 | 3 | self.last_updated = now; // Keep alias synchronized |
1996 | 3 | } |
1997 | | |
1998 | | /// Get total P&L (realized + unrealized) |
1999 | 1 | pub fn total_pnl(&self) -> Decimal { |
2000 | 1 | self.realized_pnl + self.unrealized_pnl |
2001 | 1 | } |
2002 | | |
2003 | | /// Calculate return on investment percentage |
2004 | 2 | pub fn roi_percentage(&self) -> Decimal { |
2005 | 2 | if self.notional_value.is_zero() { |
2006 | 1 | Decimal::ZERO |
2007 | | } else { |
2008 | 1 | self.total_pnl() / self.notional_value * Decimal::from(100) |
2009 | | } |
2010 | 2 | } |
2011 | | } |
2012 | | |
2013 | | /// Represents a trade execution - CANONICAL DEFINITION |
2014 | | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
2015 | | #[cfg_attr(feature = "database", derive(sqlx::FromRow))] |
2016 | | pub struct Execution { |
2017 | | /// Unique execution identifier |
2018 | | pub id: Uuid, |
2019 | | |
2020 | | /// Related order ID |
2021 | | pub order_id: Uuid, |
2022 | | |
2023 | | /// Trading symbol |
2024 | | pub symbol: String, |
2025 | | |
2026 | | /// Executed quantity |
2027 | | pub quantity: Decimal, |
2028 | | |
2029 | | /// Execution price |
2030 | | pub price: Decimal, |
2031 | | |
2032 | | /// Execution side |
2033 | | pub side: OrderSide, |
2034 | | |
2035 | | /// Trading fees |
2036 | | pub fees: Decimal, |
2037 | | |
2038 | | /// Fee currency |
2039 | | pub fee_currency: String, |
2040 | | |
2041 | | /// Execution timestamp |
2042 | | pub executed_at: DateTime<Utc>, |
2043 | | |
2044 | | /// Execution timestamp |
2045 | | pub timestamp: DateTime<Utc>, |
2046 | | |
2047 | | /// Symbol hash for performance |
2048 | | pub symbol_hash: i64, |
2049 | | |
2050 | | /// Broker execution ID |
2051 | | pub broker_execution_id: Option<String>, |
2052 | | |
2053 | | /// Counterparty information |
2054 | | pub counterparty: Option<String>, |
2055 | | |
2056 | | /// Trade venue |
2057 | | pub venue: Option<String>, |
2058 | | |
2059 | | /// Gross trade value |
2060 | | pub gross_value: Decimal, |
2061 | | |
2062 | | /// Net trade value (after fees) |
2063 | | pub net_value: Decimal, |
2064 | | } |
2065 | | |
2066 | | impl Execution { |
2067 | | /// Create a new execution |
2068 | 5 | pub fn new( |
2069 | 5 | order_id: Uuid, |
2070 | 5 | symbol: String, |
2071 | 5 | quantity: Decimal, |
2072 | 5 | price: Decimal, |
2073 | 5 | side: OrderSide, |
2074 | 5 | fees: Decimal, |
2075 | 5 | ) -> Self { |
2076 | 5 | let gross_value = quantity * price; |
2077 | 5 | let net_value = if side == OrderSide::Buy { |
2078 | 4 | gross_value + fees |
2079 | | } else { |
2080 | 1 | gross_value - fees |
2081 | | }; |
2082 | 5 | let now = Utc::now(); |
2083 | 5 | let symbol_hash = Self::hash_symbol(&symbol); |
2084 | | |
2085 | 5 | Self { |
2086 | 5 | id: Uuid::new_v4(), |
2087 | 5 | order_id, |
2088 | 5 | symbol, |
2089 | 5 | quantity, |
2090 | 5 | price, |
2091 | 5 | side, |
2092 | 5 | fees, |
2093 | 5 | fee_currency: "USD".to_string(), // Default to USD |
2094 | 5 | executed_at: now, |
2095 | 5 | timestamp: now, // Same as executed_at for compatibility |
2096 | 5 | symbol_hash, |
2097 | 5 | broker_execution_id: None, |
2098 | 5 | counterparty: None, |
2099 | 5 | venue: None, |
2100 | 5 | gross_value, |
2101 | 5 | net_value, |
2102 | 5 | } |
2103 | 5 | } |
2104 | | |
2105 | | /// Calculate effective price including fees |
2106 | 2 | pub fn effective_price(&self) -> Decimal { |
2107 | 2 | if self.quantity.is_zero() { |
2108 | 1 | self.price |
2109 | | } else { |
2110 | 1 | self.net_value / self.quantity |
2111 | | } |
2112 | 2 | } |
2113 | | |
2114 | | /// Hash symbol for performance |
2115 | 5 | fn hash_symbol(symbol: &str) -> i64 { |
2116 | | use std::collections::hash_map::DefaultHasher; |
2117 | | use std::hash::{Hash, Hasher}; |
2118 | | |
2119 | 5 | let mut hasher = DefaultHasher::new(); |
2120 | 5 | symbol.hash(&mut hasher); |
2121 | 5 | hasher.finish() as i64 |
2122 | 5 | } |
2123 | | } |
2124 | | |
2125 | | /// Core Price type using fixed-point arithmetic for precision |
2126 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] |
2127 | | pub struct Price { |
2128 | | value: u64, |
2129 | | } |
2130 | | |
2131 | | impl Price { |
2132 | | /// Zero price constant |
2133 | | pub const ZERO: Self = Self { value: 0 }; |
2134 | | /// One unit price constant (1.0) |
2135 | | pub const ONE: Self = Self { value: 100_000_000 }; |
2136 | | /// One cent constant (0.01) |
2137 | | pub const CENT: Self = Self { value: 1_000_000 }; |
2138 | | /// Maximum price value |
2139 | | pub const MAX: Self = Self { value: u64::MAX }; |
2140 | | |
2141 | | /// Create a Price from a floating-point value |
2142 | 70 | pub fn from_f64(value: f64) -> Result<Self, CommonTypeError> { |
2143 | 70 | if value < 0.0 || !value.is_finite()66 { |
2144 | 8 | return Err(CommonTypeError::InvalidPrice { |
2145 | 8 | value: value.to_string(), |
2146 | 8 | reason: "Price validation failed".to_owned(), |
2147 | 8 | }); |
2148 | 62 | } |
2149 | 62 | Ok(Self { |
2150 | 62 | value: (value * 100_000_000.0).round() as u64, |
2151 | 62 | }) |
2152 | 70 | } |
2153 | | |
2154 | | /// Convert to floating-point representation |
2155 | | #[must_use] |
2156 | | /// Convert the quantity to a floating point value |
2157 | 51 | pub fn to_f64(&self) -> f64 { |
2158 | 51 | self.value as f64 / 100_000_000.0 |
2159 | 51 | } |
2160 | | |
2161 | | /// Get floating-point representation (alias for to_f64) |
2162 | | /// Convert quantity to f64 representation |
2163 | | /// Convert quantity to f64 representation |
2164 | | /// Convert quantity to f64 representation |
2165 | | #[must_use] |
2166 | 0 | pub fn as_f64(&self) -> f64 { |
2167 | 0 | self.to_f64() |
2168 | 0 | } |
2169 | | |
2170 | | /// Create a zero price |
2171 | | /// Create a zero quantity |
2172 | | /// Create zero quantity |
2173 | | #[must_use] |
2174 | 0 | pub const fn zero() -> Self { |
2175 | 0 | Self::ZERO |
2176 | 0 | } |
2177 | | |
2178 | | /// Convert to Decimal type for precise calculations |
2179 | 1 | pub fn to_decimal(&self) -> Result<Decimal, CommonTypeError> { |
2180 | 1 | Decimal::from_f64(self.to_f64()).ok_or_else(|| CommonTypeError::InvalidPrice { |
2181 | 0 | value: "0.0".to_owned(), |
2182 | 0 | reason: "Price to Decimal conversion failed".to_owned(), |
2183 | 0 | }) |
2184 | 1 | } |
2185 | | |
2186 | | /// Create a Price from a Decimal value |
2187 | | #[must_use] |
2188 | 1 | pub fn from_decimal(decimal: Decimal) -> Self { |
2189 | 1 | Self::from(decimal) |
2190 | 1 | } |
2191 | | |
2192 | | /// Create a new Price (alias for from_f64) |
2193 | | /// Create a new quantity from a floating point value |
2194 | | /// Create new quantity from f64 value |
2195 | 0 | pub fn new(value: f64) -> Result<Self, CommonTypeError> { |
2196 | 0 | Self::from_f64(value) |
2197 | 0 | } |
2198 | | |
2199 | | /// Get the raw internal value representation |
2200 | | /// Get the raw internal value |
2201 | | /// Get the raw internal value representation |
2202 | | #[must_use] |
2203 | 1 | pub const fn raw_value(&self) -> u64 { |
2204 | 1 | self.value |
2205 | 1 | } |
2206 | | |
2207 | | /// Get the price as a u64 value (same as raw_value) |
2208 | | /// Convert to u64 representation |
2209 | | /// Convert quantity to u64 representation |
2210 | | #[must_use] |
2211 | 0 | pub const fn as_u64(&self) -> u64 { |
2212 | 0 | self.value |
2213 | 0 | } |
2214 | | |
2215 | | /// Create a Price from a raw u64 value |
2216 | | /// Create a quantity from raw internal value |
2217 | | /// Create quantity from raw u64 value |
2218 | | #[must_use] |
2219 | 0 | pub const fn from_raw(value: u64) -> Self { |
2220 | 0 | Self { value } |
2221 | 0 | } |
2222 | | |
2223 | | /// Convert price to cents (divides by 1M for 8 decimal places) |
2224 | | #[must_use] |
2225 | 2 | pub const fn to_cents(&self) -> u64 { |
2226 | 2 | self.value / 1_000_000 |
2227 | 2 | } |
2228 | | |
2229 | | /// Create a Price from cents value |
2230 | | #[must_use] |
2231 | 2 | pub const fn from_cents(cents: u64) -> Self { |
2232 | 2 | Self { |
2233 | 2 | value: cents * 1_000_000, |
2234 | 2 | } |
2235 | 2 | } |
2236 | | |
2237 | | /// Check if the price is zero |
2238 | | /// Check if the quantity is zero |
2239 | | /// Check if quantity is zero |
2240 | | #[must_use] |
2241 | 2 | pub const fn is_zero(&self) -> bool { |
2242 | 2 | self.value == 0 |
2243 | 2 | } |
2244 | | |
2245 | | /// Check if the price is non-zero (has some value) |
2246 | | /// Check if the quantity is non-zero (has some value) |
2247 | | /// Check if quantity has a non-zero value |
2248 | | #[must_use] |
2249 | 0 | pub const fn is_some(&self) -> bool { |
2250 | 0 | !self.is_zero() |
2251 | 0 | } |
2252 | | |
2253 | | /// Check if the price is zero (has no value) |
2254 | | /// Check if the quantity is zero (has no value) |
2255 | | /// Check if quantity is zero (none) |
2256 | | #[must_use] |
2257 | 0 | pub const fn is_none(&self) -> bool { |
2258 | 0 | self.is_zero() |
2259 | 0 | } |
2260 | | |
2261 | | /// Get a reference to this price |
2262 | | /// Get a reference to self |
2263 | | /// Get a reference to self |
2264 | | #[must_use] |
2265 | 0 | pub const fn as_ref(&self) -> &Self { |
2266 | 0 | self |
2267 | 0 | } |
2268 | | |
2269 | | /// Get the absolute value of the price (prices are always positive) |
2270 | | /// Get the absolute value (quantities are always positive) |
2271 | | /// Get absolute value (always positive for Quantity) |
2272 | | #[must_use] |
2273 | 0 | pub const fn abs(&self) -> Self { |
2274 | 0 | *self |
2275 | 0 | } |
2276 | | |
2277 | | /// Multiply this price by another price |
2278 | 1 | pub fn multiply(&self, other: Self) -> Result<Self, CommonTypeError> { |
2279 | 1 | *self * other |
2280 | 1 | } |
2281 | | |
2282 | | /// Subtract another price from this price |
2283 | | /// Subtract another quantity from this quantity |
2284 | | /// Subtract another quantity from this quantity |
2285 | | /// Subtract another quantity from this quantity |
2286 | | /// Subtract another quantity from this quantity |
2287 | | #[must_use] |
2288 | 0 | pub fn subtract(&self, other: Self) -> Self { |
2289 | 0 | *self - other |
2290 | 0 | } |
2291 | | |
2292 | | /// Divide this price by a floating point divisor |
2293 | 0 | pub fn divide(&self, divisor: f64) -> Result<Self, CommonTypeError> { |
2294 | 0 | *self / divisor |
2295 | 0 | } |
2296 | | } |
2297 | | |
2298 | | impl fmt::Display for Price { |
2299 | | /// Format the price for display with 8 decimal places |
2300 | 2 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
2301 | 2 | write!(f, "{:.8}", self.to_f64()) |
2302 | 2 | } |
2303 | | } |
2304 | | |
2305 | | impl Default for Price { |
2306 | | /// Returns the default price (zero) |
2307 | 0 | fn default() -> Self { |
2308 | 0 | Self::ZERO |
2309 | 0 | } |
2310 | | } |
2311 | | |
2312 | | impl FromStr for Price { |
2313 | | type Err = CommonTypeError; |
2314 | | |
2315 | 5 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
2316 | 5 | let parsed_value3 = s |
2317 | 5 | .parse::<f64>() |
2318 | 5 | .map_err(|_| CommonTypeError::InvalidPrice { |
2319 | 2 | value: s.to_owned(), |
2320 | 2 | reason: format!("Cannot parse '{}' as price", s), |
2321 | 2 | })?; |
2322 | 3 | Self::from_f64(parsed_value) |
2323 | 5 | } |
2324 | | } |
2325 | | |
2326 | | impl Add for Price { |
2327 | | type Output = Self; |
2328 | 4 | fn add(self, rhs: Self) -> Self::Output { |
2329 | 4 | Self { |
2330 | 4 | value: self.value.saturating_add(rhs.value), |
2331 | 4 | } |
2332 | 4 | } |
2333 | | } |
2334 | | |
2335 | | impl Sub for Price { |
2336 | | type Output = Self; |
2337 | 3 | fn sub(self, rhs: Self) -> Self::Output { |
2338 | 3 | Self { |
2339 | 3 | value: self.value.saturating_sub(rhs.value), |
2340 | 3 | } |
2341 | 3 | } |
2342 | | } |
2343 | | |
2344 | | impl Mul<f64> for Price { |
2345 | | type Output = Result<Self, CommonTypeError>; |
2346 | 2 | fn mul(self, rhs: f64) -> Self::Output { |
2347 | 2 | Self::from_f64(self.to_f64() * rhs) |
2348 | 2 | } |
2349 | | } |
2350 | | |
2351 | | impl Div<f64> for Price { |
2352 | | type Output = Result<Self, CommonTypeError>; |
2353 | 4 | fn div(self, rhs: f64) -> Self::Output { |
2354 | 4 | if rhs == 0.0 { |
2355 | 2 | return Err(CommonTypeError::ConversionError { |
2356 | 2 | message: "Cannot divide price by zero".to_owned(), |
2357 | 2 | }); |
2358 | 2 | } |
2359 | 2 | Self::from_f64(self.to_f64() / rhs) |
2360 | 4 | } |
2361 | | } |
2362 | | |
2363 | | impl From<Decimal> for Price { |
2364 | 1 | fn from(decimal: Decimal) -> Self { |
2365 | 1 | let f64_val: f64 = TryInto::<f64>::try_into(decimal).unwrap_or_else(|_| {0 |
2366 | 0 | tracing::warn!("Failed to convert Decimal to f64, using 0.0 as fallback"); |
2367 | 0 | 0.0_f64 |
2368 | 0 | }); |
2369 | 1 | Self::from_f64(f64_val).unwrap_or_else(|_| {0 |
2370 | 0 | tracing::warn!( |
2371 | 0 | "Failed to create Price from f64 value {}, using ZERO", |
2372 | | f64_val |
2373 | | ); |
2374 | 0 | Self::ZERO |
2375 | 0 | }) |
2376 | 1 | } |
2377 | | } |
2378 | | |
2379 | | impl From<Price> for Decimal { |
2380 | 0 | fn from(price: Price) -> Self { |
2381 | 0 | price.to_decimal().unwrap_or(Decimal::ZERO) |
2382 | 0 | } |
2383 | | } |
2384 | | |
2385 | | // TryFrom<Quantity> for Decimal removed due to conflicting blanket implementation |
2386 | | // Use qty.to_decimal() directly instead |
2387 | | impl From<Quantity> for Decimal { |
2388 | 0 | fn from(qty: Quantity) -> Self { |
2389 | 0 | qty.to_decimal().unwrap_or(Decimal::ZERO) |
2390 | 0 | } |
2391 | | } |
2392 | | |
2393 | | // TryFrom<Quantity> for Decimal removed due to conflict with From implementation |
2394 | | // Use the From implementation instead which handles errors by returning ZERO |
2395 | | |
2396 | | impl Mul<Self> for Price { |
2397 | | type Output = Result<Self, CommonTypeError>; |
2398 | 1 | fn mul(self, rhs: Self) -> Self::Output { |
2399 | 1 | Self::from_f64(self.to_f64() * rhs.to_f64()) |
2400 | 1 | } |
2401 | | } |
2402 | | |
2403 | | impl TryFrom<String> for Price { |
2404 | | type Error = CommonTypeError; |
2405 | 0 | fn try_from(s: String) -> Result<Self, Self::Error> { |
2406 | 0 | Self::from_str(&s) |
2407 | 0 | } |
2408 | | } |
2409 | | |
2410 | | impl TryFrom<&str> for Price { |
2411 | | type Error = CommonTypeError; |
2412 | 0 | fn try_from(s: &str) -> Result<Self, Self::Error> { |
2413 | 0 | Self::from_str(s) |
2414 | 0 | } |
2415 | | } |
2416 | | |
2417 | | impl PartialEq<f64> for Price { |
2418 | 4 | fn eq(&self, other: &f64) -> bool { |
2419 | 4 | (self.to_f64() - other).abs() < f64::EPSILON |
2420 | 4 | } |
2421 | | } |
2422 | | |
2423 | | impl PartialEq<Price> for f64 { |
2424 | 1 | fn eq(&self, other: &Price) -> bool { |
2425 | 1 | (self - other.to_f64()).abs() < f64::EPSILON |
2426 | 1 | } |
2427 | | } |
2428 | | |
2429 | | impl AddAssign for Price { |
2430 | 1 | fn add_assign(&mut self, rhs: Self) { |
2431 | 1 | self.value = self.value.saturating_add(rhs.value); |
2432 | 1 | } |
2433 | | } |
2434 | | |
2435 | | impl SubAssign for Price { |
2436 | 0 | fn sub_assign(&mut self, rhs: Self) { |
2437 | 0 | self.value = self.value.saturating_sub(rhs.value); |
2438 | 0 | } |
2439 | | } |
2440 | | |
2441 | | impl MulAssign<f64> for Price { |
2442 | 0 | fn mul_assign(&mut self, rhs: f64) { |
2443 | 0 | if let Ok(result) = self.mul(rhs) { |
2444 | 0 | *self = result; |
2445 | 0 | } |
2446 | | // If multiplication fails, self remains unchanged |
2447 | 0 | } |
2448 | | } |
2449 | | |
2450 | | impl DivAssign<f64> for Price { |
2451 | 0 | fn div_assign(&mut self, rhs: f64) { |
2452 | 0 | if let Ok(result) = self.div(rhs) { |
2453 | 0 | *self = result; |
2454 | 0 | } |
2455 | | // If division fails, self remains unchanged |
2456 | 0 | } |
2457 | | } |
2458 | | |
2459 | | impl PartialOrd<f64> for Price { |
2460 | 2 | fn partial_cmp(&self, other: &f64) -> Option<std::cmp::Ordering> { |
2461 | 2 | self.to_f64().partial_cmp(other) |
2462 | 2 | } |
2463 | | } |
2464 | | |
2465 | | impl PartialOrd<Price> for f64 { |
2466 | 0 | fn partial_cmp(&self, other: &Price) -> Option<std::cmp::Ordering> { |
2467 | 0 | self.partial_cmp(&other.to_f64()) |
2468 | 0 | } |
2469 | | } |
2470 | | |
2471 | | /// Core Quantity type using fixed-point arithmetic |
2472 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] |
2473 | | pub struct Quantity { |
2474 | | value: u64, |
2475 | | } |
2476 | | |
2477 | | impl Quantity { |
2478 | | /// Zero quantity constant |
2479 | | pub const ZERO: Self = Self { value: 0 }; |
2480 | | /// One unit quantity constant |
2481 | | pub const ONE: Self = Self { value: 100_000_000 }; |
2482 | | /// Maximum possible quantity |
2483 | | pub const MAX: Self = Self { value: u64::MAX }; |
2484 | | |
2485 | | /// Create a Quantity from a floating point value |
2486 | 61 | pub fn from_f64(value: f64) -> Result<Self, CommonTypeError> { |
2487 | 61 | if value < 0.0 || !value.is_finite()59 { |
2488 | 4 | return Err(CommonTypeError::InvalidQuantity { |
2489 | 4 | value: value.to_string(), |
2490 | 4 | reason: "Quantity validation failed".to_owned(), |
2491 | 4 | }); |
2492 | 57 | } |
2493 | 57 | Ok(Self { |
2494 | 57 | value: (value * 100_000_000.0).round() as u64, |
2495 | 57 | }) |
2496 | 61 | } |
2497 | | |
2498 | | /// Convert quantity to floating point representation |
2499 | | #[must_use] |
2500 | 46 | pub fn to_f64(&self) -> f64 { |
2501 | 46 | self.value as f64 / 100_000_000.0 |
2502 | 46 | } |
2503 | | |
2504 | | /// Convert the quantity to a Decimal value |
2505 | 0 | pub fn to_decimal(&self) -> Result<Decimal, CommonTypeError> { |
2506 | 0 | Decimal::from_f64(self.to_f64()).ok_or_else(|| CommonTypeError::InvalidQuantity { |
2507 | 0 | value: "0.0".to_owned(), |
2508 | 0 | reason: "Quantity to Decimal conversion failed".to_owned(), |
2509 | 0 | }) |
2510 | 0 | } |
2511 | | |
2512 | | /// Get the internal value representation |
2513 | | #[must_use] |
2514 | 0 | pub const fn value(&self) -> u64 { |
2515 | 0 | self.value |
2516 | 0 | } |
2517 | | |
2518 | | /// Get the raw internal value representation |
2519 | | #[must_use] |
2520 | 1 | pub const fn raw_value(&self) -> u64 { |
2521 | 1 | self.value |
2522 | 1 | } |
2523 | | |
2524 | | /// Convert quantity to u64 representation |
2525 | | #[must_use] |
2526 | 0 | pub const fn as_u64(&self) -> u64 { |
2527 | 0 | self.value |
2528 | 0 | } |
2529 | | |
2530 | | /// Create quantity from raw u64 value |
2531 | | #[must_use] |
2532 | 0 | pub const fn from_raw(value: u64) -> Self { |
2533 | 0 | Self { value } |
2534 | 0 | } |
2535 | | |
2536 | | /// Create new quantity from f64 value |
2537 | 1 | pub fn new(value: f64) -> Result<Self, CommonTypeError> { |
2538 | 1 | Self::from_f64(value) |
2539 | 1 | } |
2540 | | |
2541 | | /// Create zero quantity |
2542 | | #[must_use] |
2543 | 0 | pub const fn zero() -> Self { |
2544 | 0 | Self::ZERO |
2545 | 0 | } |
2546 | | |
2547 | | /// Create a quantity from an i64 value |
2548 | 0 | pub fn from_i64(value: i64) -> Result<Self, CommonTypeError> { |
2549 | 0 | Self::from_f64(value as f64) |
2550 | 0 | } |
2551 | | |
2552 | | /// Create a quantity from a u64 value |
2553 | 0 | pub fn from_u64(value: u64) -> Result<Self, CommonTypeError> { |
2554 | 0 | Self::from_f64(value as f64) |
2555 | 0 | } |
2556 | | |
2557 | | /// Create a quantity from a Decimal value |
2558 | 0 | pub fn from_decimal(decimal: Decimal) -> Result<Self, CommonTypeError> { |
2559 | | use std::convert::TryFrom; |
2560 | 0 | Self::try_from(decimal).map_err(|_| CommonTypeError::InvalidQuantity { |
2561 | 0 | value: decimal.to_string(), |
2562 | 0 | reason: "Failed to convert Decimal to Quantity".to_owned(), |
2563 | 0 | }) |
2564 | 0 | } |
2565 | | |
2566 | | /// Check if quantity is zero |
2567 | | #[must_use] |
2568 | 10 | pub const fn is_zero(&self) -> bool { |
2569 | 10 | self.value == 0 |
2570 | 10 | } |
2571 | | |
2572 | | /// Check if quantity has a non-zero value |
2573 | | #[must_use] |
2574 | 0 | pub const fn is_some(&self) -> bool { |
2575 | 0 | !self.is_zero() |
2576 | 0 | } |
2577 | | |
2578 | | /// Check if quantity is zero (none) |
2579 | | #[must_use] |
2580 | 0 | pub const fn is_none(&self) -> bool { |
2581 | 0 | self.is_zero() |
2582 | 0 | } |
2583 | | |
2584 | | /// Get a reference to self |
2585 | | #[must_use] |
2586 | 0 | pub const fn as_ref(&self) -> &Self { |
2587 | 0 | self |
2588 | 0 | } |
2589 | | |
2590 | | /// Get absolute value (always positive for Quantity) |
2591 | | #[must_use] |
2592 | 0 | pub const fn abs(&self) -> Self { |
2593 | 0 | *self |
2594 | 0 | } |
2595 | | |
2596 | | /// Get the sign of the quantity (1.0 for positive, 0.0 for zero) |
2597 | | #[must_use] |
2598 | 0 | pub const fn signum(&self) -> f64 { |
2599 | 0 | if self.value > 0 { |
2600 | 0 | 1.0 |
2601 | | } else { |
2602 | 0 | 0.0 |
2603 | | } |
2604 | 0 | } |
2605 | | |
2606 | | /// Check if quantity is positive |
2607 | | #[must_use] |
2608 | 5 | pub const fn is_positive(&self) -> bool { |
2609 | 5 | self.value > 0 |
2610 | 5 | } |
2611 | | |
2612 | | /// Check if quantity is negative (always false for Quantity) |
2613 | | #[must_use] |
2614 | 2 | pub const fn is_negative(&self) -> bool { |
2615 | 2 | false |
2616 | 2 | } |
2617 | | |
2618 | | /// Convert quantity to f64 representation |
2619 | | #[must_use] |
2620 | 0 | pub fn as_f64(&self) -> f64 { |
2621 | 0 | self.to_f64() |
2622 | 0 | } |
2623 | | |
2624 | | /// Create quantity from number of shares |
2625 | | #[must_use] |
2626 | 2 | pub const fn from_shares(shares: u64) -> Self { |
2627 | 2 | Self { |
2628 | 2 | value: shares * 100_000_000, |
2629 | 2 | } |
2630 | 2 | } |
2631 | | |
2632 | | /// Convert quantity to number of shares |
2633 | | #[must_use] |
2634 | 2 | pub const fn to_shares(&self) -> u64 { |
2635 | 2 | self.value / 100_000_000 |
2636 | 2 | } |
2637 | | |
2638 | | /// Multiply this quantity by another quantity |
2639 | 0 | pub fn multiply(&self, other: Self) -> Result<Self, CommonTypeError> { |
2640 | 0 | Self::from_f64(self.to_f64() * other.to_f64()) |
2641 | 0 | } |
2642 | | |
2643 | | /// Subtract another quantity from this quantity |
2644 | | #[must_use] |
2645 | 0 | pub fn subtract(&self, other: Self) -> Self { |
2646 | 0 | *self - other |
2647 | 0 | } |
2648 | | } |
2649 | | |
2650 | | impl Default for Quantity { |
2651 | 0 | fn default() -> Self { |
2652 | 0 | Self::ZERO |
2653 | 0 | } |
2654 | | } |
2655 | | |
2656 | | impl FromStr for Quantity { |
2657 | | type Err = CommonTypeError; |
2658 | | |
2659 | 1 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
2660 | 1 | let parsed_value = s |
2661 | 1 | .parse::<f64>() |
2662 | 1 | .map_err(|_| CommonTypeError::InvalidQuantity { |
2663 | 0 | value: s.to_owned(), |
2664 | 0 | reason: format!("Cannot parse '{}' as quantity", s), |
2665 | 0 | })?; |
2666 | 1 | Self::from_f64(parsed_value) |
2667 | 1 | } |
2668 | | } |
2669 | | |
2670 | | impl fmt::Display for Quantity { |
2671 | | /// Format the quantity for display with 8 decimal places |
2672 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
2673 | 0 | write!(f, "{:.8}", self.to_f64()) |
2674 | 0 | } |
2675 | | } |
2676 | | |
2677 | | impl TryFrom<i32> for Quantity { |
2678 | | type Error = CommonTypeError; |
2679 | 1 | fn try_from(value: i32) -> Result<Self, Self::Error> { |
2680 | 1 | Self::new(f64::from(value)) |
2681 | 1 | } |
2682 | | } |
2683 | | |
2684 | | impl TryFrom<u64> for Quantity { |
2685 | | type Error = CommonTypeError; |
2686 | 0 | fn try_from(value: u64) -> Result<Self, Self::Error> { |
2687 | 0 | Self::new(value as f64) |
2688 | 0 | } |
2689 | | } |
2690 | | |
2691 | | impl TryFrom<f64> for Quantity { |
2692 | | type Error = CommonTypeError; |
2693 | 0 | fn try_from(value: f64) -> Result<Self, Self::Error> { |
2694 | 0 | Self::new(value) |
2695 | 0 | } |
2696 | | } |
2697 | | |
2698 | | impl TryFrom<Decimal> for Quantity { |
2699 | | type Error = CommonTypeError; |
2700 | 1 | fn try_from(decimal: Decimal) -> Result<Self, Self::Error> { |
2701 | 1 | let f64_val: f64 = |
2702 | 1 | TryInto::<f64>::try_into(decimal).map_err(|_| CommonTypeError::ConversionError { |
2703 | 0 | message: "Failed to convert Decimal to f64".to_owned(), |
2704 | 0 | })?; |
2705 | 1 | Self::from_f64(f64_val) |
2706 | 1 | } |
2707 | | } |
2708 | | |
2709 | | impl TryFrom<String> for Quantity { |
2710 | | type Error = CommonTypeError; |
2711 | 0 | fn try_from(s: String) -> Result<Self, Self::Error> { |
2712 | 0 | Self::from_str(&s) |
2713 | 0 | } |
2714 | | } |
2715 | | |
2716 | | impl TryFrom<&str> for Quantity { |
2717 | | type Error = CommonTypeError; |
2718 | 1 | fn try_from(s: &str) -> Result<Self, Self::Error> { |
2719 | 1 | Self::from_str(s) |
2720 | 1 | } |
2721 | | } |
2722 | | |
2723 | | impl PartialEq<f64> for Quantity { |
2724 | 0 | fn eq(&self, other: &f64) -> bool { |
2725 | 0 | (self.to_f64() - other).abs() < f64::EPSILON |
2726 | 0 | } |
2727 | | } |
2728 | | |
2729 | | impl PartialEq<Quantity> for f64 { |
2730 | 0 | fn eq(&self, other: &Quantity) -> bool { |
2731 | 0 | (self - other.to_f64()).abs() < f64::EPSILON |
2732 | 0 | } |
2733 | | } |
2734 | | |
2735 | | impl PartialOrd<f64> for Quantity { |
2736 | 0 | fn partial_cmp(&self, other: &f64) -> Option<std::cmp::Ordering> { |
2737 | 0 | self.to_f64().partial_cmp(other) |
2738 | 0 | } |
2739 | | } |
2740 | | |
2741 | | impl PartialOrd<Quantity> for f64 { |
2742 | 0 | fn partial_cmp(&self, other: &Quantity) -> Option<std::cmp::Ordering> { |
2743 | 0 | self.partial_cmp(&other.to_f64()) |
2744 | 0 | } |
2745 | | } |
2746 | | |
2747 | | impl Add for Quantity { |
2748 | | type Output = Self; |
2749 | 29 | fn add(self, rhs: Self) -> Self::Output { |
2750 | 29 | Self { |
2751 | 29 | value: self.value.saturating_add(rhs.value), |
2752 | 29 | } |
2753 | 29 | } |
2754 | | } |
2755 | | |
2756 | | impl Sub for Quantity { |
2757 | | type Output = Self; |
2758 | 14 | fn sub(self, rhs: Self) -> Self::Output { |
2759 | 14 | Self { |
2760 | 14 | value: self.value.saturating_sub(rhs.value), |
2761 | 14 | } |
2762 | 14 | } |
2763 | | } |
2764 | | |
2765 | | impl Mul<f64> for Quantity { |
2766 | | type Output = Result<Self, CommonTypeError>; |
2767 | 1 | fn mul(self, rhs: f64) -> Self::Output { |
2768 | 1 | Self::from_f64(self.to_f64() * rhs) |
2769 | 1 | } |
2770 | | } |
2771 | | |
2772 | | impl Div<f64> for Quantity { |
2773 | | type Output = Result<Self, CommonTypeError>; |
2774 | 2 | fn div(self, rhs: f64) -> Self::Output { |
2775 | 2 | if rhs == 0.0 { |
2776 | 1 | return Err(CommonTypeError::ConversionError { |
2777 | 1 | message: "Cannot divide quantity by zero".to_owned(), |
2778 | 1 | }); |
2779 | 1 | } |
2780 | 1 | Self::from_f64(self.to_f64() / rhs) |
2781 | 2 | } |
2782 | | } |
2783 | | |
2784 | | impl Sum for Quantity { |
2785 | 2 | fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { |
2786 | 6 | iter2 .fold2 (Self::ZERO, |acc, x| acc + x) |
2787 | 2 | } |
2788 | | } |
2789 | | |
2790 | | impl<'quantity> Sum<&'quantity Self> for Quantity { |
2791 | 0 | fn sum<I: Iterator<Item = &'quantity Self>>(iter: I) -> Self { |
2792 | 0 | iter.fold(Self::ZERO, |acc, x| acc + *x) |
2793 | 0 | } |
2794 | | } |
2795 | | |
2796 | | // ============================================================================= |
2797 | | // SQLX IMPLEMENTATIONS FOR FINANCIAL TYPES |
2798 | | // ============================================================================= |
2799 | | |
2800 | | #[cfg(feature = "database")] |
2801 | | mod sqlx_impls { |
2802 | | use super::{HftTimestamp, MarketRegime, OrderSide, OrderStatus, OrderType, Price, Quantity}; |
2803 | | use rust_decimal::Decimal as RustDecimal; |
2804 | | use sqlx::{ |
2805 | | decode::Decode, |
2806 | | encode::{Encode, IsNull}, |
2807 | | error::BoxDynError, |
2808 | | postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres}, |
2809 | | Type, |
2810 | | }; |
2811 | | |
2812 | | // SQLx implementations for Price |
2813 | | impl Type<Postgres> for Price { |
2814 | 0 | fn type_info() -> PgTypeInfo { |
2815 | 0 | PgTypeInfo::with_name("NUMERIC") |
2816 | 0 | } |
2817 | | } |
2818 | | |
2819 | | impl<'q> Encode<'q, Postgres> for Price { |
2820 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
2821 | | // Convert our fixed-point u64 to rust_decimal::Decimal with 8 decimal places |
2822 | 0 | let decimal_value = RustDecimal::new(self.raw_value() as i64, 8); |
2823 | 0 | decimal_value.encode_by_ref(buf) |
2824 | 0 | } |
2825 | | } |
2826 | | |
2827 | | impl<'r> Decode<'r, Postgres> for Price { |
2828 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
2829 | | // Decode from NUMERIC to rust_decimal::Decimal |
2830 | 0 | let decimal_value = <RustDecimal as Decode<Postgres>>::decode(value)?; |
2831 | | |
2832 | | // Validate scale matches our fixed-point precision (8 decimal places) |
2833 | 0 | if decimal_value.scale() != 8 { |
2834 | 0 | return Err(format!( |
2835 | 0 | "Invalid scale for Price: expected 8, got {}", |
2836 | 0 | decimal_value.scale() |
2837 | 0 | ) |
2838 | 0 | .into()); |
2839 | 0 | } |
2840 | | |
2841 | | // Extract mantissa and convert to our u64 representation |
2842 | 0 | let mantissa = decimal_value.mantissa(); |
2843 | 0 | let inner_val = u64::try_from(mantissa) |
2844 | 0 | .map_err(|_| "Failed to convert negative or overflowing NUMERIC to Price")?; |
2845 | | |
2846 | 0 | Ok(Price::from_raw(inner_val)) |
2847 | 0 | } |
2848 | | } |
2849 | | // SQLx implementations for Quantity |
2850 | | impl Type<Postgres> for Quantity { |
2851 | 0 | fn type_info() -> PgTypeInfo { |
2852 | 0 | PgTypeInfo::with_name("NUMERIC") |
2853 | 0 | } |
2854 | | } |
2855 | | |
2856 | | impl<'q> Encode<'q, Postgres> for Quantity { |
2857 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
2858 | | // Convert our fixed-point u64 to rust_decimal::Decimal with 8 decimal places |
2859 | 0 | let decimal_value = RustDecimal::new(self.raw_value() as i64, 8); |
2860 | 0 | decimal_value.encode_by_ref(buf) |
2861 | 0 | } |
2862 | | } |
2863 | | |
2864 | | impl<'r> Decode<'r, Postgres> for Quantity { |
2865 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
2866 | | // Decode from NUMERIC to rust_decimal::Decimal |
2867 | 0 | let decimal_value = <RustDecimal as Decode<Postgres>>::decode(value)?; |
2868 | | |
2869 | | // Validate scale matches our fixed-point precision (8 decimal places) |
2870 | 0 | if decimal_value.scale() != 8 { |
2871 | 0 | return Err(format!( |
2872 | 0 | "Invalid scale for Quantity: expected 8, got {}", |
2873 | 0 | decimal_value.scale() |
2874 | 0 | ) |
2875 | 0 | .into()); |
2876 | 0 | } |
2877 | | |
2878 | | // Extract mantissa and convert to our u64 representation |
2879 | 0 | let mantissa = decimal_value.mantissa(); |
2880 | 0 | let inner_val = u64::try_from(mantissa) |
2881 | 0 | .map_err(|_| "Failed to convert negative or overflowing NUMERIC to Quantity")?; |
2882 | | |
2883 | 0 | Ok(Quantity::from_raw(inner_val)) |
2884 | 0 | } |
2885 | | } |
2886 | | |
2887 | | // SQLx implementations for TimeInForce |
2888 | | impl Type<Postgres> for super::TimeInForce { |
2889 | 0 | fn type_info() -> PgTypeInfo { |
2890 | 0 | PgTypeInfo::with_name("TEXT") |
2891 | 0 | } |
2892 | | } |
2893 | | |
2894 | | impl<'q> Encode<'q, Postgres> for super::TimeInForce { |
2895 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
2896 | | // Use the Display trait to convert enum to string representation |
2897 | 0 | <&str as Encode<Postgres>>::encode(self.to_string().as_str(), buf) |
2898 | 0 | } |
2899 | | } |
2900 | | |
2901 | | impl<'r> Decode<'r, Postgres> for super::TimeInForce { |
2902 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
2903 | | // Decode from TEXT to string, then parse to enum |
2904 | 0 | let s = <&str as Decode<Postgres>>::decode(value)?; |
2905 | 0 | match s { |
2906 | 0 | "DAY" => Ok(super::TimeInForce::Day), |
2907 | 0 | "GTC" => Ok(super::TimeInForce::GoodTillCancel), |
2908 | 0 | "IOC" => Ok(super::TimeInForce::ImmediateOrCancel), |
2909 | 0 | "FOK" => Ok(super::TimeInForce::FillOrKill), |
2910 | 0 | _ => Err(format!("Invalid TimeInForce value: {}", s).into()), |
2911 | | } |
2912 | 0 | } |
2913 | | } |
2914 | | |
2915 | | // SQLx implementations for OrderStatus |
2916 | | impl Type<Postgres> for OrderStatus { |
2917 | 0 | fn type_info() -> PgTypeInfo { |
2918 | 0 | PgTypeInfo::with_name("TEXT") |
2919 | 0 | } |
2920 | | } |
2921 | | |
2922 | | impl<'q> Encode<'q, Postgres> for OrderStatus { |
2923 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
2924 | 0 | let value = match self { |
2925 | 0 | OrderStatus::Created => "CREATED", |
2926 | 0 | OrderStatus::Submitted => "SUBMITTED", |
2927 | 0 | OrderStatus::PartiallyFilled => "PARTIALLY_FILLED", |
2928 | 0 | OrderStatus::Filled => "FILLED", |
2929 | 0 | OrderStatus::Rejected => "REJECTED", |
2930 | 0 | OrderStatus::Cancelled => "CANCELLED", |
2931 | 0 | OrderStatus::New => "NEW", |
2932 | 0 | OrderStatus::Expired => "EXPIRED", |
2933 | 0 | OrderStatus::Pending => "PENDING", |
2934 | 0 | OrderStatus::Working => "WORKING", |
2935 | 0 | OrderStatus::Unknown => "UNKNOWN", |
2936 | 0 | OrderStatus::Suspended => "SUSPENDED", |
2937 | 0 | OrderStatus::PendingCancel => "PENDING_CANCEL", |
2938 | 0 | OrderStatus::PendingReplace => "PENDING_REPLACE", |
2939 | | }; |
2940 | 0 | <&str as Encode<Postgres>>::encode_by_ref(&value, buf) |
2941 | 0 | } |
2942 | | } |
2943 | | |
2944 | | impl<'r> Decode<'r, Postgres> for OrderStatus { |
2945 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
2946 | 0 | let s = <String as Decode<Postgres>>::decode(value)?; |
2947 | 0 | match s.as_str() { |
2948 | 0 | "CREATED" => Ok(OrderStatus::Created), |
2949 | 0 | "SUBMITTED" => Ok(OrderStatus::Submitted), |
2950 | 0 | "PARTIALLY_FILLED" => Ok(OrderStatus::PartiallyFilled), |
2951 | 0 | "FILLED" => Ok(OrderStatus::Filled), |
2952 | 0 | "REJECTED" => Ok(OrderStatus::Rejected), |
2953 | 0 | "CANCELLED" => Ok(OrderStatus::Cancelled), |
2954 | 0 | "NEW" => Ok(OrderStatus::New), |
2955 | 0 | "EXPIRED" => Ok(OrderStatus::Expired), |
2956 | 0 | "PENDING" => Ok(OrderStatus::Pending), |
2957 | 0 | "WORKING" => Ok(OrderStatus::Working), |
2958 | 0 | "UNKNOWN" => Ok(OrderStatus::Unknown), |
2959 | 0 | "SUSPENDED" => Ok(OrderStatus::Suspended), |
2960 | 0 | "PENDING_CANCEL" => Ok(OrderStatus::PendingCancel), |
2961 | 0 | "PENDING_REPLACE" => Ok(OrderStatus::PendingReplace), |
2962 | 0 | _ => Err(format!("Invalid OrderStatus value: {}", s).into()), |
2963 | | } |
2964 | 0 | } |
2965 | | } |
2966 | | |
2967 | | // SQLx implementations for OrderSide |
2968 | | impl Type<Postgres> for OrderSide { |
2969 | 0 | fn type_info() -> PgTypeInfo { |
2970 | 0 | PgTypeInfo::with_name("TEXT") |
2971 | 0 | } |
2972 | | } |
2973 | | |
2974 | | impl<'q> Encode<'q, Postgres> for OrderSide { |
2975 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
2976 | 0 | let value = match self { |
2977 | 0 | OrderSide::Buy => "BUY", |
2978 | 0 | OrderSide::Sell => "SELL", |
2979 | | }; |
2980 | 0 | <&str as Encode<Postgres>>::encode_by_ref(&value, buf) |
2981 | 0 | } |
2982 | | } |
2983 | | |
2984 | | impl<'r> Decode<'r, Postgres> for OrderSide { |
2985 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
2986 | 0 | let s = <String as Decode<Postgres>>::decode(value)?; |
2987 | 0 | match s.as_str() { |
2988 | 0 | "BUY" => Ok(OrderSide::Buy), |
2989 | 0 | "SELL" => Ok(OrderSide::Sell), |
2990 | 0 | _ => Err(format!("Invalid OrderSide value: {}", s).into()), |
2991 | | } |
2992 | 0 | } |
2993 | | } |
2994 | | |
2995 | | // SQLx implementations for OrderType |
2996 | | impl Type<Postgres> for OrderType { |
2997 | 0 | fn type_info() -> PgTypeInfo { |
2998 | 0 | PgTypeInfo::with_name("TEXT") |
2999 | 0 | } |
3000 | | } |
3001 | | |
3002 | | impl<'q> Encode<'q, Postgres> for OrderType { |
3003 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
3004 | 0 | let value = match self { |
3005 | 0 | OrderType::Market => "MARKET", |
3006 | 0 | OrderType::Limit => "LIMIT", |
3007 | 0 | OrderType::Stop => "STOP", |
3008 | 0 | OrderType::StopLimit => "STOP_LIMIT", |
3009 | 0 | OrderType::Iceberg => "ICEBERG", |
3010 | 0 | OrderType::TrailingStop => "TRAILING_STOP", |
3011 | 0 | OrderType::Hidden => "HIDDEN", |
3012 | | }; |
3013 | 0 | <&str as Encode<Postgres>>::encode_by_ref(&value, buf) |
3014 | 0 | } |
3015 | | } |
3016 | | |
3017 | | impl<'r> Decode<'r, Postgres> for OrderType { |
3018 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
3019 | 0 | let s = <String as Decode<Postgres>>::decode(value)?; |
3020 | 0 | match s.as_str() { |
3021 | 0 | "MARKET" => Ok(OrderType::Market), |
3022 | 0 | "LIMIT" => Ok(OrderType::Limit), |
3023 | 0 | "STOP" => Ok(OrderType::Stop), |
3024 | 0 | "STOP_LIMIT" => Ok(OrderType::StopLimit), |
3025 | 0 | "ICEBERG" => Ok(OrderType::Iceberg), |
3026 | 0 | "TRAILING_STOP" => Ok(OrderType::TrailingStop), |
3027 | 0 | "HIDDEN" => Ok(OrderType::Hidden), |
3028 | 0 | _ => Err(format!("Invalid OrderType value: {}", s).into()), |
3029 | | } |
3030 | 0 | } |
3031 | | } |
3032 | | |
3033 | | // SQLx implementations for MarketRegime |
3034 | | impl Type<Postgres> for MarketRegime { |
3035 | 0 | fn type_info() -> PgTypeInfo { |
3036 | 0 | PgTypeInfo::with_name("TEXT") |
3037 | 0 | } |
3038 | | } |
3039 | | |
3040 | | impl<'q> Encode<'q, Postgres> for MarketRegime { |
3041 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
3042 | 0 | let value = match self { |
3043 | 0 | MarketRegime::Normal => "NORMAL", |
3044 | 0 | MarketRegime::Crisis => "CRISIS", |
3045 | 0 | MarketRegime::Trending => "TRENDING", |
3046 | 0 | MarketRegime::Sideways => "SIDEWAYS", |
3047 | 0 | MarketRegime::Bull => "BULL", |
3048 | 0 | MarketRegime::Bear => "BEAR", |
3049 | 0 | MarketRegime::HighVolatility => "HIGH_VOLATILITY", |
3050 | 0 | MarketRegime::LowVolatility => "LOW_VOLATILITY", |
3051 | 0 | MarketRegime::Volatile => "VOLATILE", |
3052 | 0 | MarketRegime::Calm => "CALM", |
3053 | 0 | MarketRegime::Unknown => "UNKNOWN", |
3054 | 0 | MarketRegime::Recovery => "RECOVERY", |
3055 | 0 | MarketRegime::Bubble => "BUBBLE", |
3056 | 0 | MarketRegime::Correction => "CORRECTION", |
3057 | 0 | MarketRegime::Custom(id) => { |
3058 | 0 | return <String as Encode<Postgres>>::encode_by_ref( |
3059 | 0 | &format!("CUSTOM_{}", id), |
3060 | 0 | buf, |
3061 | | ) |
3062 | | }, |
3063 | | }; |
3064 | 0 | <&str as Encode<Postgres>>::encode_by_ref(&value, buf) |
3065 | 0 | } |
3066 | | } |
3067 | | |
3068 | | impl<'r> Decode<'r, Postgres> for MarketRegime { |
3069 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
3070 | 0 | let s = <String as Decode<Postgres>>::decode(value)?; |
3071 | 0 | match s.as_str() { |
3072 | 0 | "NORMAL" => Ok(MarketRegime::Normal), |
3073 | 0 | "CRISIS" => Ok(MarketRegime::Crisis), |
3074 | 0 | "TRENDING" => Ok(MarketRegime::Trending), |
3075 | 0 | "SIDEWAYS" => Ok(MarketRegime::Sideways), |
3076 | 0 | "BULL" => Ok(MarketRegime::Bull), |
3077 | 0 | "BEAR" => Ok(MarketRegime::Bear), |
3078 | 0 | "HIGH_VOLATILITY" => Ok(MarketRegime::HighVolatility), |
3079 | 0 | "LOW_VOLATILITY" => Ok(MarketRegime::LowVolatility), |
3080 | 0 | "VOLATILE" => Ok(MarketRegime::Volatile), |
3081 | 0 | "CALM" => Ok(MarketRegime::Calm), |
3082 | 0 | "UNKNOWN" => Ok(MarketRegime::Unknown), |
3083 | 0 | "RECOVERY" => Ok(MarketRegime::Recovery), |
3084 | 0 | "BUBBLE" => Ok(MarketRegime::Bubble), |
3085 | 0 | "CORRECTION" => Ok(MarketRegime::Correction), |
3086 | | _ => { |
3087 | | // Handle Custom(id) format |
3088 | 0 | if let Some(id_str) = s.strip_prefix("CUSTOM_") { |
3089 | 0 | if let Ok(id) = id_str.parse::<usize>() { |
3090 | 0 | Ok(MarketRegime::Custom(id)) |
3091 | | } else { |
3092 | 0 | Err(format!("Invalid MarketRegime Custom ID: {}", id_str).into()) |
3093 | | } |
3094 | | } else { |
3095 | 0 | Err(format!("Invalid MarketRegime value: {}", s).into()) |
3096 | | } |
3097 | | }, |
3098 | | } |
3099 | 0 | } |
3100 | | } |
3101 | | |
3102 | | // SQLx implementations for HftTimestamp |
3103 | | // Maps to PostgreSQL BIGINT (stores nanoseconds since Unix epoch) |
3104 | | // Note: Limited to i64::MAX nanoseconds (year 2262) due to PostgreSQL BIGINT constraints |
3105 | | impl<'q> Encode<'q, Postgres> for HftTimestamp { |
3106 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
3107 | | // Cast u64 to i64 for PostgreSQL BIGINT compatibility |
3108 | 0 | <i64 as Encode<Postgres>>::encode(self.nanos() as i64, buf) |
3109 | 0 | } |
3110 | | } |
3111 | | |
3112 | | impl<'r> Decode<'r, Postgres> for HftTimestamp { |
3113 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
3114 | 0 | let val = <i64 as Decode<Postgres>>::decode(value)?; |
3115 | | // Cast i64 back to u64 for internal representation |
3116 | 0 | Ok(HftTimestamp::from_nanos(val as u64)) |
3117 | 0 | } |
3118 | | } |
3119 | | |
3120 | | impl Type<Postgres> for HftTimestamp { |
3121 | 0 | fn type_info() -> <Postgres as sqlx::Database>::TypeInfo { |
3122 | 0 | <i64 as Type<Postgres>>::type_info() |
3123 | 0 | } |
3124 | | |
3125 | 0 | fn compatible(ty: &<Postgres as sqlx::Database>::TypeInfo) -> bool { |
3126 | 0 | <i64 as Type<Postgres>>::compatible(ty) |
3127 | 0 | } |
3128 | | } |
3129 | | |
3130 | | // SQLx implementations for OrderId (uses BIGINT for u64) |
3131 | | impl Type<Postgres> for super::OrderId { |
3132 | 0 | fn type_info() -> PgTypeInfo { |
3133 | 0 | PgTypeInfo::with_name("BIGINT") |
3134 | 0 | } |
3135 | | } |
3136 | | |
3137 | | impl<'q> Encode<'q, Postgres> for super::OrderId { |
3138 | 0 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { |
3139 | 0 | <i64 as Encode<Postgres>>::encode_by_ref(&(self.value() as i64), buf) |
3140 | 0 | } |
3141 | | } |
3142 | | |
3143 | | impl<'r> Decode<'r, Postgres> for super::OrderId { |
3144 | 0 | fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { |
3145 | 0 | let id = <i64 as Decode<Postgres>>::decode(value)?; |
3146 | 0 | Ok(super::OrderId::from_u64(id as u64)) |
3147 | 0 | } |
3148 | | } |
3149 | | } |
3150 | | |
3151 | | /// Volume type - alias for Quantity with the same fixed-point arithmetic |
3152 | | /// SQLx traits are automatically inherited from Quantity |
3153 | | pub type Volume = Quantity; |
3154 | | |
3155 | | // ORDER TYPES ALREADY DEFINED ABOVE - No need to re-export from trading_engine |
3156 | | // ============================================================================= |
3157 | | // CORE ID TYPES (MOVED FROM TRADING_ENGINE) |
3158 | | // ============================================================================= |
3159 | | |
3160 | | /// Order identifier with ultra-fast atomic generation |
3161 | | /// Replaces slow UUID generation (1ms+) with atomic increment (~5ns) |
3162 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] |
3163 | | pub struct OrderId(u64); |
3164 | | |
3165 | | impl Default for OrderId { |
3166 | 0 | fn default() -> Self { |
3167 | 0 | Self::new() |
3168 | 0 | } |
3169 | | } |
3170 | | |
3171 | | impl OrderId { |
3172 | | /// Generate next `OrderId` using atomic counter - <50ns performance |
3173 | 1.01k | pub fn new() -> Self { |
3174 | | use std::sync::atomic::{AtomicU64, Ordering}; |
3175 | | static COUNTER: AtomicU64 = AtomicU64::new(1); |
3176 | 1.01k | Self(COUNTER.fetch_add(1, Ordering::Relaxed)) |
3177 | 1.01k | } |
3178 | | |
3179 | | /// Create `OrderId` from u64 value |
3180 | | #[must_use] |
3181 | 2 | pub const fn from_u64(value: u64) -> Self { |
3182 | 2 | Self(value) |
3183 | 2 | } |
3184 | | |
3185 | | /// Get u64 value |
3186 | | #[must_use] |
3187 | 8 | pub const fn value(&self) -> u64 { |
3188 | 8 | self.0 |
3189 | 8 | } |
3190 | | |
3191 | | /// Get u64 value for performance-critical code (alias for value) |
3192 | | #[must_use] |
3193 | 1 | pub const fn as_u64(&self) -> u64 { |
3194 | 1 | self.0 |
3195 | 1 | } |
3196 | | |
3197 | | /// Get as string for compatibility |
3198 | | #[must_use] |
3199 | 0 | pub fn as_str(&self) -> String { |
3200 | 0 | self.0.to_string() |
3201 | 0 | } |
3202 | | } |
3203 | | |
3204 | | impl fmt::Display for OrderId { |
3205 | | /// Format the order ID for display |
3206 | 1 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3207 | 1 | write!(f, "{}", self.0) |
3208 | 1 | } |
3209 | | } |
3210 | | |
3211 | | impl From<u64> for OrderId { |
3212 | | /// Create an OrderId from a u64 value |
3213 | 0 | fn from(value: u64) -> Self { |
3214 | 0 | Self(value) |
3215 | 0 | } |
3216 | | } |
3217 | | |
3218 | | impl From<OrderId> for u64 { |
3219 | | /// Convert an OrderId to u64 |
3220 | 0 | fn from(order_id: OrderId) -> Self { |
3221 | 0 | order_id.0 |
3222 | 0 | } |
3223 | | } |
3224 | | |
3225 | | impl FromStr for OrderId { |
3226 | | type Err = ParseIntError; |
3227 | | |
3228 | 2 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
3229 | 2 | s.parse::<u64>().map(OrderId) |
3230 | 2 | } |
3231 | | } |
3232 | | |
3233 | | impl From<String> for OrderId { |
3234 | | /// Create an OrderId from a String, generating new ID if parsing fails |
3235 | 2 | fn from(s: String) -> Self { |
3236 | 2 | s.parse().unwrap_or_else(|_| Self::new1 ()) |
3237 | 2 | } |
3238 | | } |
3239 | | |
3240 | | impl From<&str> for OrderId { |
3241 | | /// Create an OrderId from a &str, generating new ID if parsing fails |
3242 | 0 | fn from(s: &str) -> Self { |
3243 | 0 | s.parse().unwrap_or_else(|_| Self::new()) |
3244 | 0 | } |
3245 | | } |
3246 | | |
3247 | | /// Execution identifier with validation |
3248 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3249 | | #[cfg_attr(feature = "database", derive(sqlx::Type))] |
3250 | | pub struct ExecutionId(String); |
3251 | | |
3252 | | impl ExecutionId { |
3253 | | /// Create a new execution ID with validation |
3254 | 3 | pub fn new<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> { |
3255 | 3 | let id = id.into(); |
3256 | 3 | if id.trim().is_empty() { |
3257 | 2 | return Err(CommonTypeError::ValidationError { |
3258 | 2 | field: "execution_id".to_owned(), |
3259 | 2 | reason: "Execution ID cannot be empty".to_owned(), |
3260 | 2 | }); |
3261 | 1 | } |
3262 | 1 | Ok(Self(id)) |
3263 | 3 | } |
3264 | | |
3265 | | /// Generate a new random execution ID |
3266 | 1 | pub fn generate() -> Self { |
3267 | 1 | Self(uuid::Uuid::new_v4().to_string()) |
3268 | 1 | } |
3269 | | |
3270 | | /// Get execution ID as string slice |
3271 | 3 | pub fn as_str(&self) -> &str { |
3272 | 3 | &self.0 |
3273 | 3 | } |
3274 | | |
3275 | | /// Convert execution ID into owned string |
3276 | 0 | pub fn into_string(self) -> String { |
3277 | 0 | self.0 |
3278 | 0 | } |
3279 | | } |
3280 | | |
3281 | | impl fmt::Display for ExecutionId { |
3282 | | /// Format the execution ID for display |
3283 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3284 | 0 | write!(f, "{}", self.0) |
3285 | 0 | } |
3286 | | } |
3287 | | |
3288 | | impl FromStr for ExecutionId { |
3289 | | type Err = CommonTypeError; |
3290 | | |
3291 | 0 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
3292 | 0 | Self::new(s) |
3293 | 0 | } |
3294 | | } |
3295 | | |
3296 | | /// Trade identifier with validation |
3297 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3298 | | pub struct TradeId(String); |
3299 | | |
3300 | | impl TradeId { |
3301 | | /// Create a new trade ID with validation |
3302 | 2 | pub fn new<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> { |
3303 | 2 | let id = id.into(); |
3304 | 2 | if id.is_empty() { |
3305 | 1 | return Err(CommonTypeError::ValidationError { |
3306 | 1 | field: "trade_id".to_owned(), |
3307 | 1 | reason: "Trade ID cannot be empty".to_owned(), |
3308 | 1 | }); |
3309 | 1 | } |
3310 | 1 | Ok(Self(id)) |
3311 | 2 | } |
3312 | | |
3313 | | /// Get the trade ID as a string slice |
3314 | 1 | pub fn as_str(&self) -> &str { |
3315 | 1 | &self.0 |
3316 | 1 | } |
3317 | | /// Convert the trade ID into an owned string |
3318 | 0 | pub fn into_string(self) -> String { |
3319 | 0 | self.0 |
3320 | 0 | } |
3321 | | } |
3322 | | |
3323 | | impl fmt::Display for TradeId { |
3324 | | /// Format the trade ID for display |
3325 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3326 | 0 | write!(f, "{}", self.0) |
3327 | 0 | } |
3328 | | } |
3329 | | |
3330 | | /// Trading symbol with validation |
3331 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3332 | | #[cfg_attr(feature = "database", derive(sqlx::Type))] |
3333 | | pub struct Symbol { |
3334 | | value: String, |
3335 | | } |
3336 | | |
3337 | | impl Symbol { |
3338 | | /// Create a new symbol from a string |
3339 | | #[must_use] |
3340 | 20 | pub const fn new(s: String) -> Self { |
3341 | 20 | Self { value: s } |
3342 | 20 | } |
3343 | | |
3344 | | /// Create a new Symbol with validation |
3345 | 6 | pub fn new_validated(s: String) -> Result<Self, CommonTypeError> { |
3346 | 6 | if s.trim().is_empty() { |
3347 | 4 | return Err(CommonTypeError::ValidationError { |
3348 | 4 | field: "symbol".to_string(), |
3349 | 4 | reason: "Symbol cannot be empty".to_string(), |
3350 | 4 | }); |
3351 | 2 | } |
3352 | 2 | Ok(Self { value: s }) |
3353 | 6 | } |
3354 | | |
3355 | | /// Create a Symbol from &str with validation |
3356 | 0 | pub fn from_str_validated(s: &str) -> Result<Self, CommonTypeError> { |
3357 | 0 | Self::new_validated(s.to_owned()) |
3358 | 0 | } |
3359 | | |
3360 | | /// Get the symbol as a string slice |
3361 | | #[must_use] |
3362 | 7 | pub fn as_str(&self) -> &str { |
3363 | 7 | &self.value |
3364 | 7 | } |
3365 | | /// Get the symbol value as a string slice |
3366 | | #[must_use] |
3367 | 0 | pub fn value(&self) -> &str { |
3368 | 0 | &self.value |
3369 | 0 | } |
3370 | | /// Get the symbol as bytes |
3371 | | #[must_use] |
3372 | 0 | pub fn as_bytes(&self) -> &[u8] { |
3373 | 0 | self.value.as_bytes() |
3374 | 0 | } |
3375 | | /// Check if the symbol is empty |
3376 | | #[must_use] |
3377 | 2 | pub fn is_empty(&self) -> bool { |
3378 | 2 | self.value.is_empty() |
3379 | 2 | } |
3380 | | /// Convert the symbol to uppercase |
3381 | | #[must_use] |
3382 | 2 | pub fn to_uppercase(&self) -> String { |
3383 | 2 | self.value.to_uppercase() |
3384 | 2 | } |
3385 | | /// Replace occurrences in the symbol |
3386 | | #[must_use] |
3387 | 2 | pub fn replace(&self, from: &str, to: &str) -> String { |
3388 | 2 | self.value.replace(from, to) |
3389 | 2 | } |
3390 | | |
3391 | | /// Helper for risk management - creates a 'NONE' symbol |
3392 | | #[must_use] |
3393 | 1 | pub fn none() -> Self { |
3394 | 1 | "NONE".parse().unwrap() |
3395 | 1 | } |
3396 | | |
3397 | | /// Check if the symbol contains a pattern |
3398 | | #[must_use] |
3399 | 4 | pub fn contains(&self, pattern: &str) -> bool { |
3400 | 4 | self.value.contains(pattern) |
3401 | 4 | } |
3402 | | } |
3403 | | |
3404 | | impl FromStr for Symbol { |
3405 | | type Err = std::convert::Infallible; |
3406 | | |
3407 | 6 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
3408 | 6 | Ok(Self { |
3409 | 6 | value: s.to_owned(), |
3410 | 6 | }) |
3411 | 6 | } |
3412 | | } |
3413 | | |
3414 | | // Additional implementation to support conversion from &Symbol to &str |
3415 | | impl AsRef<str> for Symbol { |
3416 | | /// Convert symbol to string reference |
3417 | 0 | fn as_ref(&self) -> &str { |
3418 | 0 | &self.value |
3419 | 0 | } |
3420 | | } |
3421 | | |
3422 | | impl fmt::Display for Symbol { |
3423 | | /// Format the symbol for display |
3424 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3425 | 0 | write!(f, "{}", self.value) |
3426 | 0 | } |
3427 | | } |
3428 | | |
3429 | | impl From<String> for Symbol { |
3430 | | /// Create a Symbol from a String |
3431 | 0 | fn from(s: String) -> Self { |
3432 | 0 | Self::new(s) |
3433 | 0 | } |
3434 | | } |
3435 | | impl From<&str> for Symbol { |
3436 | | /// Create a Symbol from a &str |
3437 | 19 | fn from(s: &str) -> Self { |
3438 | 19 | Self::new(s.to_owned()) |
3439 | 19 | } |
3440 | | } |
3441 | | |
3442 | | // TryFrom implementations removed due to conflicting blanket implementations |
3443 | | // Use Symbol::new_validated() or Symbol::from_validated() directly instead |
3444 | | |
3445 | | impl Default for Symbol { |
3446 | | /// Returns the default symbol (empty string) |
3447 | 0 | fn default() -> Self { |
3448 | 0 | Self::new(String::new()) |
3449 | 0 | } |
3450 | | } |
3451 | | |
3452 | | impl PartialEq<str> for Symbol { |
3453 | 0 | fn eq(&self, other: &str) -> bool { |
3454 | 0 | self.value == other |
3455 | 0 | } |
3456 | | } |
3457 | | |
3458 | | impl PartialEq<&str> for Symbol { |
3459 | 2 | fn eq(&self, other: &&str) -> bool { |
3460 | 2 | self.value == *other |
3461 | 2 | } |
3462 | | } |
3463 | | |
3464 | | impl PartialEq<String> for Symbol { |
3465 | 1 | fn eq(&self, other: &String) -> bool { |
3466 | 1 | &self.value == other |
3467 | 1 | } |
3468 | | } |
3469 | | |
3470 | | impl PartialEq<Symbol> for &str { |
3471 | 2 | fn eq(&self, other: &Symbol) -> bool { |
3472 | 2 | *self == other.value |
3473 | 2 | } |
3474 | | } |
3475 | | |
3476 | | impl PartialEq<Symbol> for String { |
3477 | 1 | fn eq(&self, other: &Symbol) -> bool { |
3478 | 1 | self == &other.value |
3479 | 1 | } |
3480 | | } |
3481 | | |
3482 | | // TimeInForce moved to canonical source: common::types::TimeInForce |
3483 | | |
3484 | | // Currency moved to canonical source: common::types::Currency |
3485 | | |
3486 | | // Price moved to canonical source: common::types::Price |
3487 | | |
3488 | | // Quantity moved to canonical source: common::types::Quantity |
3489 | | // Volume moved to canonical source: common::types::Quantity (as Volume alias) |
3490 | | |
3491 | | /// Money amount with currency |
3492 | | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
3493 | | pub struct Money { |
3494 | | /// The monetary amount |
3495 | | pub amount: Decimal, |
3496 | | /// The currency of the amount |
3497 | | pub currency: Currency, |
3498 | | } |
3499 | | |
3500 | | impl Money { |
3501 | | /// Create new money amount |
3502 | 3 | pub const fn new(amount: Decimal, currency: Currency) -> Self { |
3503 | 3 | Self { amount, currency } |
3504 | 3 | } |
3505 | | } |
3506 | | |
3507 | | impl fmt::Display for Money { |
3508 | 2 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3509 | 2 | write!(f, "{} {}", self.amount, self.currency) |
3510 | 2 | } |
3511 | | } |
3512 | | |
3513 | | // OrderId moved to canonical source: common::types::OrderId |
3514 | | |
3515 | | // TradeId moved to canonical source: common::types::TradeId |
3516 | | |
3517 | | // Symbol moved to canonical source: common::types::Symbol |
3518 | | |
3519 | | /// Type-safe account identifier |
3520 | | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3521 | | pub struct AccountId(String); |
3522 | | |
3523 | | impl AccountId { |
3524 | | /// Create a new account ID with validation |
3525 | 3 | pub fn new<S: Into<String>>(id: S) -> Result<Self, CommonTypeError> { |
3526 | 3 | let id = id.into(); |
3527 | 3 | if id.trim().is_empty() { |
3528 | 2 | return Err(CommonTypeError::InvalidIdentifier { |
3529 | 2 | field: "account_id".to_string(), |
3530 | 2 | reason: "Account ID cannot be empty".to_string(), |
3531 | 2 | }); |
3532 | 1 | } |
3533 | 1 | Ok(Self(id)) |
3534 | 3 | } |
3535 | | |
3536 | | /// Get the ID as a string slice |
3537 | 0 | pub fn as_str(&self) -> &str { |
3538 | 0 | &self.0 |
3539 | 0 | } |
3540 | | |
3541 | | /// Convert to owned String |
3542 | 0 | pub fn into_string(self) -> String { |
3543 | 0 | self.0 |
3544 | 0 | } |
3545 | | } |
3546 | | |
3547 | | impl fmt::Display for AccountId { |
3548 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3549 | 0 | write!(f, "{}", self.0) |
3550 | 0 | } |
3551 | | } |
3552 | | |
3553 | | /// High-precision timestamp for HFT applications - CANONICAL DEFINITION |
3554 | | /// Robust implementation with error handling for financial safety |
3555 | | #[derive( |
3556 | | Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default, |
3557 | | )] |
3558 | | pub struct HftTimestamp { |
3559 | | nanos: u64, |
3560 | | } |
3561 | | |
3562 | | impl HftTimestamp { |
3563 | | /// Get current timestamp with error handling for financial safety |
3564 | 26 | pub fn now() -> Result<Self, CommonError> { |
3565 | | use std::time::{SystemTime, UNIX_EPOCH}; |
3566 | 26 | let nanos = SystemTime::now() |
3567 | 26 | .duration_since(UNIX_EPOCH) |
3568 | 26 | .map_err(|e| CommonError::Service { |
3569 | 0 | category: CommonErrorCategory::System, |
3570 | 0 | message: format!("System time before UNIX epoch: {e}"), |
3571 | 0 | })? |
3572 | 26 | .as_nanos() as u64; |
3573 | 26 | Ok(Self { nanos }) |
3574 | 26 | } |
3575 | | |
3576 | | /// Get current timestamp with error handling for financial safety (CommonTypeError version) |
3577 | 1 | pub fn now_common() -> Result<Self, CommonTypeError> { |
3578 | | use std::time::{SystemTime, UNIX_EPOCH}; |
3579 | 1 | let nanos = SystemTime::now() |
3580 | 1 | .duration_since(UNIX_EPOCH) |
3581 | 1 | .map_err(|e| CommonTypeError::ConversionError { |
3582 | 0 | message: format!("System time before UNIX epoch: {e}"), |
3583 | 0 | })? |
3584 | 1 | .as_nanos() as u64; |
3585 | 1 | Ok(Self { nanos }) |
3586 | 1 | } |
3587 | | |
3588 | | /// Get current timestamp or zero if system time is invalid |
3589 | | #[must_use] |
3590 | 25 | pub fn now_or_zero() -> Self { |
3591 | 25 | Self::now().unwrap_or(Self { nanos: 0 }) |
3592 | 25 | } |
3593 | | |
3594 | | /// Get nanoseconds since epoch |
3595 | | #[must_use] |
3596 | 4 | pub const fn nanos(self) -> u64 { |
3597 | 4 | self.nanos |
3598 | 4 | } |
3599 | | |
3600 | | /// Create from nanoseconds since epoch |
3601 | | #[must_use] |
3602 | 2 | pub const fn from_nanos(nanos: u64) -> Self { |
3603 | 2 | Self { nanos } |
3604 | 2 | } |
3605 | | |
3606 | | /// Create from signed nanoseconds (cast to unsigned) |
3607 | | #[must_use] |
3608 | 0 | pub const fn from_nanos_i64(nanos: i64) -> Self { |
3609 | 0 | Self { |
3610 | 0 | nanos: nanos as u64, |
3611 | 0 | } |
3612 | 0 | } |
3613 | | |
3614 | | /// Get nanoseconds since epoch |
3615 | 0 | pub const fn as_nanos(&self) -> u64 { |
3616 | 0 | self.nanos |
3617 | 0 | } |
3618 | | |
3619 | | /// Convert to DateTime<Utc> |
3620 | 1 | pub fn to_datetime(&self) -> DateTime<Utc> { |
3621 | 1 | let secs = self.nanos / 1_000_000_000; |
3622 | 1 | let nsecs = (self.nanos % 1_000_000_000) as u32; |
3623 | 1 | DateTime::from_timestamp(secs as i64, nsecs).unwrap_or_default() |
3624 | 1 | } |
3625 | | } |
3626 | | |
3627 | | impl fmt::Display for HftTimestamp { |
3628 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3629 | 0 | write!(f, "{}", self.to_datetime()) |
3630 | 0 | } |
3631 | | } |
3632 | | |
3633 | | /// Generic timestamp for general use cases |
3634 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] |
3635 | | pub struct GenericTimestamp { |
3636 | | nanos: u64, |
3637 | | } |
3638 | | |
3639 | | impl GenericTimestamp { |
3640 | | /// Create from nanoseconds since epoch |
3641 | | #[must_use] |
3642 | 0 | pub const fn from_nanos(nanos: u64) -> Self { |
3643 | 0 | Self { nanos } |
3644 | 0 | } |
3645 | | |
3646 | | /// Get nanoseconds since epoch |
3647 | | #[must_use] |
3648 | 0 | pub const fn nanos(&self) -> u64 { |
3649 | 0 | self.nanos |
3650 | 0 | } |
3651 | | } |
3652 | | |
3653 | | // ============================================================================= |
3654 | | // MARKET TYPES (MIGRATED FROM TRADING_ENGINE) |
3655 | | // ============================================================================= |
3656 | | |
3657 | | /// Market regime enumeration for position sizing scaling and risk management |
3658 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3659 | | pub enum MarketRegime { |
3660 | | /// Normal market conditions |
3661 | | Normal, |
3662 | | /// Crisis/stress market conditions |
3663 | | Crisis, |
3664 | | /// Trending market (strong directional movement) |
3665 | | Trending, |
3666 | | /// Sideways/ranging market (low volatility) |
3667 | | Sideways, |
3668 | | /// Bull market (sustained upward trend) |
3669 | | Bull, |
3670 | | /// Bear market (sustained downward trend) |
3671 | | Bear, |
3672 | | /// High volatility market conditions |
3673 | | HighVolatility, |
3674 | | /// Low volatility market conditions |
3675 | | LowVolatility, |
3676 | | /// Volatile market conditions (alias for `HighVolatility`) |
3677 | | Volatile, |
3678 | | /// Calm market conditions (alias for `LowVolatility`) |
3679 | | Calm, |
3680 | | /// Unknown/unclassified regime |
3681 | | Unknown, |
3682 | | /// Recovery regime - transitioning from crisis |
3683 | | Recovery, |
3684 | | /// Bubble regime - unsustainable upward movement |
3685 | | Bubble, |
3686 | | /// Correction regime - temporary downward adjustment |
3687 | | Correction, |
3688 | | /// Custom regime with numeric identifier |
3689 | | Custom(usize), |
3690 | | } |
3691 | | |
3692 | | impl Default for MarketRegime { |
3693 | 0 | fn default() -> Self { |
3694 | 0 | Self::Normal |
3695 | 0 | } |
3696 | | } |
3697 | | |
3698 | | impl fmt::Display for MarketRegime { |
3699 | 6 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3700 | 6 | match self { |
3701 | 1 | Self::Normal => write!(f, "Normal"), |
3702 | 1 | Self::Crisis => write!(f, "Crisis"), |
3703 | 0 | Self::Trending => write!(f, "Trending"), |
3704 | 0 | Self::Sideways => write!(f, "Sideways"), |
3705 | 1 | Self::Bull => write!(f, "Bull"), |
3706 | 1 | Self::Bear => write!(f, "Bear"), |
3707 | 1 | Self::HighVolatility => write!(f, "HighVolatility"), |
3708 | 0 | Self::LowVolatility => write!(f, "LowVolatility"), |
3709 | 0 | Self::Volatile => write!(f, "Volatile"), |
3710 | 0 | Self::Calm => write!(f, "Calm"), |
3711 | 0 | Self::Unknown => write!(f, "Unknown"), |
3712 | 0 | Self::Recovery => write!(f, "Recovery"), |
3713 | 0 | Self::Bubble => write!(f, "Bubble"), |
3714 | 0 | Self::Correction => write!(f, "Correction"), |
3715 | 1 | Self::Custom(id) => write!(f, "Custom({id})"), |
3716 | | } |
3717 | 6 | } |
3718 | | } |
3719 | | |
3720 | | /// Tick type enumeration for market data |
3721 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3722 | | #[cfg_attr(feature = "database", derive(sqlx::Type))] |
3723 | | #[cfg_attr( |
3724 | | feature = "database", |
3725 | | sqlx(type_name = "tick_type", rename_all = "snake_case") |
3726 | | )] |
3727 | | pub enum TickType { |
3728 | | /// Trade execution tick |
3729 | | Trade, |
3730 | | /// Bid price update tick |
3731 | | Bid, |
3732 | | /// Ask price update tick |
3733 | | Ask, |
3734 | | /// Quote (bid/ask) update tick |
3735 | | Quote, |
3736 | | } |
3737 | | |
3738 | | /// Exchange enumeration for trading venues |
3739 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3740 | | pub enum Exchange { |
3741 | | /// New York Stock Exchange |
3742 | | NYSE, |
3743 | | /// NASDAQ |
3744 | | NASDAQ, |
3745 | | /// Chicago Mercantile Exchange |
3746 | | CME, |
3747 | | /// Intercontinental Exchange |
3748 | | ICE, |
3749 | | /// London Stock Exchange |
3750 | | LSE, |
3751 | | /// Tokyo Stock Exchange |
3752 | | TSE, |
3753 | | /// Hong Kong Stock Exchange |
3754 | | HKEX, |
3755 | | /// Shanghai Stock Exchange |
3756 | | SSE, |
3757 | | /// Shenzhen Stock Exchange |
3758 | | SZSE, |
3759 | | /// Euronext |
3760 | | EURONEXT, |
3761 | | /// Deutsche Börse |
3762 | | XETRA, |
3763 | | /// Chicago Board of Trade |
3764 | | CBOT, |
3765 | | /// Chicago Board Options Exchange |
3766 | | CBOE, |
3767 | | /// BATS Global Markets |
3768 | | BATS, |
3769 | | /// IEX Exchange |
3770 | | IEX, |
3771 | | /// Interactive Brokers |
3772 | | IBKR, |
3773 | | /// IC Markets |
3774 | | ICMARKETS, |
3775 | | /// Forex.com |
3776 | | FOREX, |
3777 | | /// Binance |
3778 | | BINANCE, |
3779 | | /// Coinbase |
3780 | | COINBASE, |
3781 | | /// Kraken |
3782 | | KRAKEN, |
3783 | | /// Unknown or unrecognized exchange |
3784 | | UNKNOWN, |
3785 | | } |
3786 | | |
3787 | | impl Default for Exchange { |
3788 | 0 | fn default() -> Self { |
3789 | 0 | Self::UNKNOWN |
3790 | 0 | } |
3791 | | } |
3792 | | |
3793 | | impl fmt::Display for Exchange { |
3794 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
3795 | 0 | match self { |
3796 | 0 | Self::NYSE => write!(f, "NYSE"), |
3797 | 0 | Self::NASDAQ => write!(f, "NASDAQ"), |
3798 | 0 | Self::CME => write!(f, "CME"), |
3799 | 0 | Self::ICE => write!(f, "ICE"), |
3800 | 0 | Self::LSE => write!(f, "LSE"), |
3801 | 0 | Self::TSE => write!(f, "TSE"), |
3802 | 0 | Self::HKEX => write!(f, "HKEX"), |
3803 | 0 | Self::SSE => write!(f, "SSE"), |
3804 | 0 | Self::SZSE => write!(f, "SZSE"), |
3805 | 0 | Self::EURONEXT => write!(f, "EURONEXT"), |
3806 | 0 | Self::XETRA => write!(f, "XETRA"), |
3807 | 0 | Self::CBOT => write!(f, "CBOT"), |
3808 | 0 | Self::CBOE => write!(f, "CBOE"), |
3809 | 0 | Self::BATS => write!(f, "BATS"), |
3810 | 0 | Self::IEX => write!(f, "IEX"), |
3811 | 0 | Self::IBKR => write!(f, "IBKR"), |
3812 | 0 | Self::ICMARKETS => write!(f, "ICMARKETS"), |
3813 | 0 | Self::FOREX => write!(f, "FOREX"), |
3814 | 0 | Self::BINANCE => write!(f, "BINANCE"), |
3815 | 0 | Self::COINBASE => write!(f, "COINBASE"), |
3816 | 0 | Self::KRAKEN => write!(f, "KRAKEN"), |
3817 | 0 | Self::UNKNOWN => write!(f, "UNKNOWN"), |
3818 | | } |
3819 | 0 | } |
3820 | | } |
3821 | | |
3822 | | impl FromStr for Exchange { |
3823 | | type Err = CommonTypeError; |
3824 | | |
3825 | 4 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
3826 | 4 | match s.to_uppercase().as_str() { |
3827 | 4 | "NYSE" => Ok(Self::NYSE)1 , |
3828 | 3 | "NASDAQ" => Ok(Self::NASDAQ)2 , |
3829 | 1 | "CME" => Ok(Self::CME)0 , |
3830 | 1 | "ICE" => Ok(Self::ICE)0 , |
3831 | 1 | "LSE" => Ok(Self::LSE)0 , |
3832 | 1 | "TSE" => Ok(Self::TSE)0 , |
3833 | 1 | "HKEX" => Ok(Self::HKEX)0 , |
3834 | 1 | "SSE" => Ok(Self::SSE)0 , |
3835 | 1 | "SZSE" => Ok(Self::SZSE)0 , |
3836 | 1 | "EURONEXT" => Ok(Self::EURONEXT)0 , |
3837 | 1 | "XETRA" => Ok(Self::XETRA)0 , |
3838 | 1 | "CBOT" => Ok(Self::CBOT)0 , |
3839 | 1 | "CBOE" => Ok(Self::CBOE)0 , |
3840 | 1 | "BATS" => Ok(Self::BATS)0 , |
3841 | 1 | "IEX" => Ok(Self::IEX)0 , |
3842 | 1 | "IBKR" => Ok(Self::IBKR)0 , |
3843 | 1 | "ICMARKETS" => Ok(Self::ICMARKETS)0 , |
3844 | 1 | "FOREX" => Ok(Self::FOREX)0 , |
3845 | 1 | "BINANCE" => Ok(Self::BINANCE)0 , |
3846 | 1 | "COINBASE" => Ok(Self::COINBASE)0 , |
3847 | 1 | "KRAKEN" => Ok(Self::KRAKEN)0 , |
3848 | 1 | "UNKNOWN" => Ok(Self::UNKNOWN)0 , |
3849 | 1 | _ => Ok(Self::UNKNOWN), // Default to UNKNOWN for unrecognized exchanges |
3850 | | } |
3851 | 4 | } |
3852 | | } |
3853 | | |
3854 | | /// Market tick data structure - CANONICAL SINGLE SOURCE OF TRUTH |
3855 | | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] |
3856 | | pub struct MarketTick { |
3857 | | /// Trading symbol |
3858 | | pub symbol: Symbol, |
3859 | | /// Tick price |
3860 | | pub price: Price, |
3861 | | /// Tick size/quantity |
3862 | | pub size: Quantity, |
3863 | | /// Tick timestamp |
3864 | | pub timestamp: HftTimestamp, |
3865 | | /// Type of tick (trade, bid, ask, quote) |
3866 | | pub tick_type: TickType, |
3867 | | /// Exchange where the tick occurred |
3868 | | pub exchange: Exchange, |
3869 | | /// Sequence number for ordering |
3870 | | pub sequence_number: u64, |
3871 | | } |
3872 | | |
3873 | | impl MarketTick { |
3874 | | /// Create a new market tick with current timestamp |
3875 | 0 | pub fn new( |
3876 | 0 | symbol: Symbol, |
3877 | 0 | price: Price, |
3878 | 0 | size: Quantity, |
3879 | 0 | tick_type: TickType, |
3880 | 0 | exchange: Exchange, |
3881 | 0 | sequence_number: u64, |
3882 | 0 | ) -> Result<Self, CommonError> { |
3883 | | Ok(Self { |
3884 | 0 | symbol, |
3885 | 0 | price, |
3886 | 0 | size, |
3887 | 0 | timestamp: HftTimestamp::now()?, |
3888 | 0 | tick_type, |
3889 | 0 | exchange, |
3890 | 0 | sequence_number, |
3891 | | }) |
3892 | 0 | } |
3893 | | |
3894 | | /// Create a new market tick with specified timestamp (for backtesting) |
3895 | | #[must_use] |
3896 | 0 | pub const fn with_timestamp( |
3897 | 0 | symbol: Symbol, |
3898 | 0 | price: Price, |
3899 | 0 | size: Quantity, |
3900 | 0 | timestamp: HftTimestamp, |
3901 | 0 | tick_type: TickType, |
3902 | 0 | exchange: Exchange, |
3903 | 0 | sequence_number: u64, |
3904 | 0 | ) -> Self { |
3905 | 0 | Self { |
3906 | 0 | symbol, |
3907 | 0 | price, |
3908 | 0 | size, |
3909 | 0 | timestamp, |
3910 | 0 | tick_type, |
3911 | 0 | exchange, |
3912 | 0 | sequence_number, |
3913 | 0 | } |
3914 | 0 | } |
3915 | | } |
3916 | | |
3917 | | /// Trading signal for algorithmic trading |
3918 | | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] |
3919 | | pub struct TradingSignal { |
3920 | | /// Signal ID |
3921 | | pub signal_id: Uuid, |
3922 | | /// Symbol this signal applies to |
3923 | | pub symbol: Symbol, |
3924 | | /// Signal strength (-1.0 to 1.0) |
3925 | | pub strength: f64, |
3926 | | /// Signal direction |
3927 | | pub direction: OrderSide, |
3928 | | /// Confidence level (0.0 to 1.0) |
3929 | | pub confidence: f64, |
3930 | | /// Signal generation timestamp |
3931 | | pub timestamp: HftTimestamp, |
3932 | | /// Signal source/strategy |
3933 | | pub source: String, |
3934 | | /// Additional metadata |
3935 | | pub metadata: std::collections::HashMap<String, String>, |
3936 | | } |
3937 | | |
3938 | | impl TradingSignal { |
3939 | | /// Create a new trading signal |
3940 | 3 | pub fn new( |
3941 | 3 | symbol: Symbol, |
3942 | 3 | strength: f64, |
3943 | 3 | direction: OrderSide, |
3944 | 3 | confidence: f64, |
3945 | 3 | source: String, |
3946 | 3 | ) -> Result<Self, CommonTypeError> { |
3947 | 3 | if !(0.0..=1.0).contains(&confidence) { |
3948 | 1 | return Err(CommonTypeError::ValidationError { |
3949 | 1 | field: "confidence".to_owned(), |
3950 | 1 | reason: "Confidence must be between 0.0 and 1.0".to_owned(), |
3951 | 1 | }); |
3952 | 2 | } |
3953 | 2 | if !(-1.0..=1.0).contains(&strength) { |
3954 | 1 | return Err(CommonTypeError::ValidationError { |
3955 | 1 | field: "strength".to_owned(), |
3956 | 1 | reason: "Strength must be between -1.0 and 1.0".to_owned(), |
3957 | 1 | }); |
3958 | 1 | } |
3959 | | |
3960 | | Ok(Self { |
3961 | 1 | signal_id: Uuid::new_v4(), |
3962 | 1 | symbol, |
3963 | 1 | strength, |
3964 | 1 | direction, |
3965 | 1 | confidence, |
3966 | 1 | timestamp: HftTimestamp::now_common()?0 , |
3967 | 1 | source, |
3968 | 1 | metadata: std::collections::HashMap::new(), |
3969 | | }) |
3970 | 3 | } |
3971 | | |
3972 | | /// Add metadata to the signal |
3973 | | #[must_use] |
3974 | 0 | pub fn with_metadata(mut self, key: String, value: String) -> Self { |
3975 | 0 | self.metadata.insert(key, value); |
3976 | 0 | self |
3977 | 0 | } |
3978 | | } |
3979 | | |
3980 | | // ============================================================================= |
3981 | | // HIGH-PERFORMANCE TYPES FOR COPY/CLONE OPTIMIZATION |
3982 | | // ============================================================================= |
3983 | | |
3984 | | /// Lightweight Order reference for high-performance contexts requiring Copy trait |
3985 | | /// |
3986 | | /// This struct contains only the essential order data needed for performance-critical |
3987 | | /// operations like `SmallBatchRing` processing, while maintaining Copy semantics. |
3988 | | /// For full order details, use the complete Order struct. |
3989 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
3990 | | pub struct OrderRef { |
3991 | | /// Order ID (u64 for performance) |
3992 | | pub id: u64, |
3993 | | /// Symbol hash for fast lookups |
3994 | | pub symbol_hash: i64, |
3995 | | /// Order side (Buy/Sell) |
3996 | | pub side: OrderSide, |
3997 | | /// Order type |
3998 | | pub order_type: OrderType, |
3999 | | /// Quantity (fixed-point u64) |
4000 | | pub quantity: u64, |
4001 | | /// Price (fixed-point u64, 0 for market orders) |
4002 | | pub price: u64, |
4003 | | /// Timestamp (nanoseconds since epoch) |
4004 | | pub timestamp: u64, |
4005 | | } |
4006 | | |
4007 | | impl OrderRef { |
4008 | | /// Create `OrderRef` from a full Order struct |
4009 | | #[must_use] |
4010 | 1 | pub fn from_order(order: &Order) -> Self { |
4011 | | Self { |
4012 | 1 | id: order.id.value(), |
4013 | 1 | symbol_hash: order.symbol_hash(), |
4014 | 1 | side: order.side, |
4015 | 1 | order_type: order.order_type, |
4016 | 1 | quantity: order.quantity.raw_value(), |
4017 | 1 | price: order.price.map_or(0, |p| p.raw_value()), |
4018 | 1 | timestamp: order.created_at.nanos(), |
4019 | | } |
4020 | 1 | } |
4021 | | |
4022 | | /// Create a limit order reference |
4023 | | #[must_use] |
4024 | 0 | pub fn limit(symbol_hash: i64, side: OrderSide, quantity: u64, price: u64) -> Self { |
4025 | 0 | Self { |
4026 | 0 | id: OrderId::new().value(), |
4027 | 0 | symbol_hash, |
4028 | 0 | side, |
4029 | 0 | order_type: OrderType::Limit, |
4030 | 0 | quantity, |
4031 | 0 | price, |
4032 | 0 | timestamp: HftTimestamp::now_or_zero().nanos(), |
4033 | 0 | } |
4034 | 0 | } |
4035 | | |
4036 | | /// Create a market order reference |
4037 | | #[must_use] |
4038 | 0 | pub fn market(symbol_hash: i64, side: OrderSide, quantity: u64) -> Self { |
4039 | 0 | Self { |
4040 | 0 | id: OrderId::new().value(), |
4041 | 0 | symbol_hash, |
4042 | 0 | side, |
4043 | 0 | order_type: OrderType::Market, |
4044 | 0 | quantity, |
4045 | 0 | price: 0, |
4046 | 0 | timestamp: HftTimestamp::now_or_zero().nanos(), |
4047 | 0 | } |
4048 | 0 | } |
4049 | | |
4050 | | /// Get quantity as Quantity type |
4051 | | #[must_use] |
4052 | 0 | pub const fn get_quantity(&self) -> Quantity { |
4053 | 0 | Quantity::from_raw(self.quantity) |
4054 | 0 | } |
4055 | | |
4056 | | /// Get price as Price type (None for market orders) |
4057 | | #[must_use] |
4058 | 0 | pub const fn get_price(&self) -> Option<Price> { |
4059 | 0 | if self.price == 0 { |
4060 | 0 | None |
4061 | | } else { |
4062 | 0 | Some(Price::from_raw(self.price)) |
4063 | | } |
4064 | 0 | } |
4065 | | |
4066 | | /// Check if this is a buy order |
4067 | | #[must_use] |
4068 | 0 | pub fn is_buy(&self) -> bool { |
4069 | 0 | self.side == OrderSide::Buy |
4070 | 0 | } |
4071 | | |
4072 | | /// Check if this is a sell order |
4073 | | #[must_use] |
4074 | 0 | pub fn is_sell(&self) -> bool { |
4075 | 0 | self.side == OrderSide::Sell |
4076 | 0 | } |
4077 | | |
4078 | | /// Check if this is a market order |
4079 | | #[must_use] |
4080 | 0 | pub fn is_market_order(&self) -> bool { |
4081 | 0 | self.order_type == OrderType::Market || self.price == 0 |
4082 | 0 | } |
4083 | | |
4084 | | /// Check if this is a limit order |
4085 | | #[must_use] |
4086 | 0 | pub fn is_limit_order(&self) -> bool { |
4087 | 0 | self.order_type == OrderType::Limit && self.price > 0 |
4088 | 0 | } |
4089 | | } |
4090 | | |
4091 | | impl Default for OrderRef { |
4092 | 0 | fn default() -> Self { |
4093 | 0 | Self { |
4094 | 0 | id: 0, |
4095 | 0 | symbol_hash: 0, |
4096 | 0 | side: OrderSide::Buy, |
4097 | 0 | order_type: OrderType::Market, |
4098 | 0 | quantity: 0, |
4099 | 0 | price: 0, |
4100 | 0 | timestamp: 0, |
4101 | 0 | } |
4102 | 0 | } |
4103 | | } |
4104 | | |
4105 | | // ============================================================================= |
4106 | | // COMPREHENSIVE TESTS |
4107 | | // ============================================================================= |
4108 | | |
4109 | | #[cfg(test)] |
4110 | | mod tests { |
4111 | | use super::*; |
4112 | | use std::str::FromStr; |
4113 | | |
4114 | | // ============================================================================= |
4115 | | // Price Tests |
4116 | | // ============================================================================= |
4117 | | |
4118 | | #[test] |
4119 | 1 | fn test_price_from_f64_valid() { |
4120 | 1 | let price = Price::from_f64(100.50).unwrap(); |
4121 | 1 | assert_eq!(price.to_f64(), 100.50); |
4122 | 1 | } |
4123 | | |
4124 | | #[test] |
4125 | 1 | fn test_price_from_f64_negative() { |
4126 | 1 | let result = Price::from_f64(-10.0); |
4127 | 1 | assert!(result.is_err()); |
4128 | 1 | } |
4129 | | |
4130 | | #[test] |
4131 | 1 | fn test_price_from_f64_nan() { |
4132 | 1 | let result = Price::from_f64(f64::NAN); |
4133 | 1 | assert!(result.is_err()); |
4134 | 1 | } |
4135 | | |
4136 | | #[test] |
4137 | 1 | fn test_price_from_f64_infinity() { |
4138 | 1 | let result = Price::from_f64(f64::INFINITY); |
4139 | 1 | assert!(result.is_err()); |
4140 | 1 | } |
4141 | | |
4142 | | #[test] |
4143 | 1 | fn test_price_constants() { |
4144 | 1 | assert_eq!(Price::ZERO.to_f64(), 0.0); |
4145 | 1 | assert_eq!(Price::ONE.to_f64(), 1.0); |
4146 | 1 | assert_eq!(Price::CENT.to_f64(), 0.01); |
4147 | 1 | } |
4148 | | |
4149 | | #[test] |
4150 | 1 | fn test_price_addition() { |
4151 | 1 | let p1 = Price::from_f64(10.0).unwrap(); |
4152 | 1 | let p2 = Price::from_f64(5.5).unwrap(); |
4153 | 1 | let result = p1 + p2; |
4154 | 1 | assert!((result.to_f64() - 15.5).abs() < 0.00001); |
4155 | 1 | } |
4156 | | |
4157 | | #[test] |
4158 | 1 | fn test_price_subtraction() { |
4159 | 1 | let p1 = Price::from_f64(10.0).unwrap(); |
4160 | 1 | let p2 = Price::from_f64(5.5).unwrap(); |
4161 | 1 | let result = p1 - p2; |
4162 | 1 | assert!((result.to_f64() - 4.5).abs() < 0.00001); |
4163 | 1 | } |
4164 | | |
4165 | | #[test] |
4166 | 1 | fn test_price_multiplication() { |
4167 | 1 | let price = Price::from_f64(10.0).unwrap(); |
4168 | 1 | let result = (price * 2.5).unwrap(); |
4169 | 1 | assert!((result.to_f64() - 25.0).abs() < 0.00001); |
4170 | 1 | } |
4171 | | |
4172 | | #[test] |
4173 | 1 | fn test_price_division() { |
4174 | 1 | let price = Price::from_f64(10.0).unwrap(); |
4175 | 1 | let result = (price / 2.0).unwrap(); |
4176 | 1 | assert!((result.to_f64() - 5.0).abs() < 0.00001); |
4177 | 1 | } |
4178 | | |
4179 | | #[test] |
4180 | 1 | fn test_price_division_by_zero() { |
4181 | 1 | let price = Price::from_f64(10.0).unwrap(); |
4182 | 1 | let result = price / 0.0; |
4183 | 1 | assert!(result.is_err()); |
4184 | 1 | } |
4185 | | |
4186 | | #[test] |
4187 | 1 | fn test_price_from_cents() { |
4188 | 1 | let price = Price::from_cents(150); |
4189 | 1 | assert!((price.to_f64() - 1.50).abs() < 0.00001); |
4190 | 1 | } |
4191 | | |
4192 | | #[test] |
4193 | 1 | fn test_price_to_cents() { |
4194 | 1 | let price = Price::from_f64(1.50).unwrap(); |
4195 | 1 | assert_eq!(price.to_cents(), 150); |
4196 | 1 | } |
4197 | | |
4198 | | #[test] |
4199 | 1 | fn test_price_is_zero() { |
4200 | 1 | assert!(Price::ZERO.is_zero()); |
4201 | 1 | assert!(!Price::from_f64(1.0).unwrap().is_zero()); |
4202 | 1 | } |
4203 | | |
4204 | | #[test] |
4205 | 1 | fn test_price_from_str() { |
4206 | 1 | let price = Price::from_str("123.45").unwrap(); |
4207 | 1 | assert!((price.to_f64() - 123.45).abs() < 0.00001); |
4208 | 1 | } |
4209 | | |
4210 | | #[test] |
4211 | 1 | fn test_price_from_str_invalid() { |
4212 | 1 | let result = Price::from_str("invalid"); |
4213 | 1 | assert!(result.is_err()); |
4214 | 1 | } |
4215 | | |
4216 | | #[test] |
4217 | 1 | fn test_price_display() { |
4218 | 1 | let price = Price::from_f64(123.456789).unwrap(); |
4219 | 1 | let display = format!("{}", price); |
4220 | 1 | assert!(display.starts_with("123.45678")); |
4221 | 1 | } |
4222 | | |
4223 | | #[test] |
4224 | 1 | fn test_price_partial_eq_f64() { |
4225 | 1 | let price = Price::from_f64(10.0).unwrap(); |
4226 | 1 | assert_eq!(price, 10.0); |
4227 | 1 | assert_eq!(10.0, price); |
4228 | 1 | } |
4229 | | |
4230 | | #[test] |
4231 | 1 | fn test_price_multiply_price() { |
4232 | 1 | let p1 = Price::from_f64(10.0).unwrap(); |
4233 | 1 | let p2 = Price::from_f64(2.5).unwrap(); |
4234 | 1 | let result = p1.multiply(p2).unwrap(); |
4235 | 1 | assert!((result.to_f64() - 25.0).abs() < 0.00001); |
4236 | 1 | } |
4237 | | |
4238 | | // ============================================================================= |
4239 | | // Quantity Tests |
4240 | | // ============================================================================= |
4241 | | |
4242 | | #[test] |
4243 | 1 | fn test_quantity_from_f64_valid() { |
4244 | 1 | let qty = Quantity::from_f64(100.5).unwrap(); |
4245 | 1 | assert_eq!(qty.to_f64(), 100.5); |
4246 | 1 | } |
4247 | | |
4248 | | #[test] |
4249 | 1 | fn test_quantity_from_f64_negative() { |
4250 | 1 | let result = Quantity::from_f64(-10.0); |
4251 | 1 | assert!(result.is_err()); |
4252 | 1 | } |
4253 | | |
4254 | | #[test] |
4255 | 1 | fn test_quantity_from_f64_nan() { |
4256 | 1 | let result = Quantity::from_f64(f64::NAN); |
4257 | 1 | assert!(result.is_err()); |
4258 | 1 | } |
4259 | | |
4260 | | #[test] |
4261 | 1 | fn test_quantity_constants() { |
4262 | 1 | assert_eq!(Quantity::ZERO.to_f64(), 0.0); |
4263 | 1 | assert_eq!(Quantity::ONE.to_f64(), 1.0); |
4264 | 1 | } |
4265 | | |
4266 | | #[test] |
4267 | 1 | fn test_quantity_addition() { |
4268 | 1 | let q1 = Quantity::from_f64(10.0).unwrap(); |
4269 | 1 | let q2 = Quantity::from_f64(5.5).unwrap(); |
4270 | 1 | let result = q1 + q2; |
4271 | 1 | assert!((result.to_f64() - 15.5).abs() < 0.00001); |
4272 | 1 | } |
4273 | | |
4274 | | #[test] |
4275 | 1 | fn test_quantity_subtraction() { |
4276 | 1 | let q1 = Quantity::from_f64(10.0).unwrap(); |
4277 | 1 | let q2 = Quantity::from_f64(5.5).unwrap(); |
4278 | 1 | let result = q1 - q2; |
4279 | 1 | assert!((result.to_f64() - 4.5).abs() < 0.00001); |
4280 | 1 | } |
4281 | | |
4282 | | #[test] |
4283 | 1 | fn test_quantity_multiplication() { |
4284 | 1 | let qty = Quantity::from_f64(10.0).unwrap(); |
4285 | 1 | let result = (qty * 2.5).unwrap(); |
4286 | 1 | assert!((result.to_f64() - 25.0).abs() < 0.00001); |
4287 | 1 | } |
4288 | | |
4289 | | #[test] |
4290 | 1 | fn test_quantity_division() { |
4291 | 1 | let qty = Quantity::from_f64(10.0).unwrap(); |
4292 | 1 | let result = (qty / 2.0).unwrap(); |
4293 | 1 | assert!((result.to_f64() - 5.0).abs() < 0.00001); |
4294 | 1 | } |
4295 | | |
4296 | | #[test] |
4297 | 1 | fn test_quantity_division_by_zero() { |
4298 | 1 | let qty = Quantity::from_f64(10.0).unwrap(); |
4299 | 1 | let result = qty / 0.0; |
4300 | 1 | assert!(result.is_err()); |
4301 | 1 | } |
4302 | | |
4303 | | #[test] |
4304 | 1 | fn test_quantity_is_zero() { |
4305 | 1 | assert!(Quantity::ZERO.is_zero()); |
4306 | 1 | assert!(!Quantity::from_f64(1.0).unwrap().is_zero()); |
4307 | 1 | } |
4308 | | |
4309 | | #[test] |
4310 | 1 | fn test_quantity_is_positive() { |
4311 | 1 | assert!(Quantity::from_f64(1.0).unwrap().is_positive()); |
4312 | 1 | assert!(!Quantity::ZERO.is_positive()); |
4313 | 1 | } |
4314 | | |
4315 | | #[test] |
4316 | 1 | fn test_quantity_is_negative() { |
4317 | | // Quantity is always non-negative |
4318 | 1 | assert!(!Quantity::from_f64(1.0).unwrap().is_negative()); |
4319 | 1 | assert!(!Quantity::ZERO.is_negative()); |
4320 | 1 | } |
4321 | | |
4322 | | #[test] |
4323 | 1 | fn test_quantity_from_shares() { |
4324 | 1 | let qty = Quantity::from_shares(100); |
4325 | 1 | assert_eq!(qty.to_shares(), 100); |
4326 | 1 | } |
4327 | | |
4328 | | #[test] |
4329 | 1 | fn test_quantity_sum() { |
4330 | 1 | let quantities = vec![ |
4331 | 1 | Quantity::from_f64(1.0).unwrap(), |
4332 | 1 | Quantity::from_f64(2.0).unwrap(), |
4333 | 1 | Quantity::from_f64(3.0).unwrap(), |
4334 | | ]; |
4335 | 1 | let sum: Quantity = quantities.into_iter().sum(); |
4336 | 1 | assert!((sum.to_f64() - 6.0).abs() < 0.00001); |
4337 | 1 | } |
4338 | | |
4339 | | #[test] |
4340 | 1 | fn test_quantity_try_from_i32() { |
4341 | 1 | let qty = Quantity::try_from(100i32).unwrap(); |
4342 | 1 | assert_eq!(qty.to_f64(), 100.0); |
4343 | 1 | } |
4344 | | |
4345 | | #[test] |
4346 | 1 | fn test_quantity_try_from_string() { |
4347 | 1 | let qty = Quantity::try_from("123.45").unwrap(); |
4348 | 1 | assert!((qty.to_f64() - 123.45).abs() < 0.00001); |
4349 | 1 | } |
4350 | | |
4351 | | // ============================================================================= |
4352 | | // Money Tests |
4353 | | // ============================================================================= |
4354 | | |
4355 | | #[test] |
4356 | 1 | fn test_money_new() { |
4357 | 1 | let amount = Decimal::from_f64(100.50).unwrap(); |
4358 | 1 | let money = Money::new(amount, Currency::USD); |
4359 | 1 | assert_eq!(money.currency, Currency::USD); |
4360 | 1 | assert_eq!(money.amount, amount); |
4361 | 1 | } |
4362 | | |
4363 | | #[test] |
4364 | 1 | fn test_money_display() { |
4365 | 1 | let amount = Decimal::from_f64(100.50).unwrap(); |
4366 | 1 | let money = Money::new(amount, Currency::USD); |
4367 | 1 | let display = format!("{}", money); |
4368 | 1 | assert!(display.contains("100.5")); |
4369 | 1 | assert!(display.contains("USD")); |
4370 | 1 | } |
4371 | | |
4372 | | // ============================================================================= |
4373 | | // Symbol Tests |
4374 | | // ============================================================================= |
4375 | | |
4376 | | #[test] |
4377 | 1 | fn test_symbol_new() { |
4378 | 1 | let symbol = Symbol::new("AAPL".to_string()); |
4379 | 1 | assert_eq!(symbol.as_str(), "AAPL"); |
4380 | 1 | } |
4381 | | |
4382 | | #[test] |
4383 | 1 | fn test_symbol_new_validated_valid() { |
4384 | 1 | let symbol = Symbol::new_validated("AAPL".to_string()).unwrap(); |
4385 | 1 | assert_eq!(symbol.as_str(), "AAPL"); |
4386 | 1 | } |
4387 | | |
4388 | | #[test] |
4389 | 1 | fn test_symbol_new_validated_empty() { |
4390 | 1 | let result = Symbol::new_validated("".to_string()); |
4391 | 1 | assert!(result.is_err()); |
4392 | 1 | } |
4393 | | |
4394 | | #[test] |
4395 | 1 | fn test_symbol_new_validated_whitespace() { |
4396 | 1 | let result = Symbol::new_validated(" ".to_string()); |
4397 | 1 | assert!(result.is_err()); |
4398 | 1 | } |
4399 | | |
4400 | | #[test] |
4401 | 1 | fn test_symbol_from_str() { |
4402 | 1 | let symbol = Symbol::from_str("AAPL").unwrap(); |
4403 | 1 | assert_eq!(symbol.as_str(), "AAPL"); |
4404 | 1 | } |
4405 | | |
4406 | | #[test] |
4407 | 1 | fn test_symbol_to_uppercase() { |
4408 | 1 | let symbol = Symbol::from_str("aapl").unwrap(); |
4409 | 1 | assert_eq!(symbol.to_uppercase(), "AAPL"); |
4410 | 1 | } |
4411 | | |
4412 | | #[test] |
4413 | 1 | fn test_symbol_replace() { |
4414 | 1 | let symbol = Symbol::from_str("AAPL.US").unwrap(); |
4415 | 1 | assert_eq!(symbol.replace(".US", ""), "AAPL"); |
4416 | 1 | } |
4417 | | |
4418 | | #[test] |
4419 | 1 | fn test_symbol_contains() { |
4420 | 1 | let symbol = Symbol::from_str("AAPL.US").unwrap(); |
4421 | 1 | assert!(symbol.contains("AAPL")); |
4422 | 1 | assert!(!symbol.contains("MSFT")); |
4423 | 1 | } |
4424 | | |
4425 | | #[test] |
4426 | 1 | fn test_symbol_partial_eq_str() { |
4427 | 1 | let symbol = Symbol::from_str("AAPL").unwrap(); |
4428 | 1 | assert_eq!("AAPL", symbol); |
4429 | 1 | assert_eq!(symbol.as_str(), "AAPL"); |
4430 | 1 | } |
4431 | | |
4432 | | #[test] |
4433 | 1 | fn test_symbol_none() { |
4434 | 1 | let symbol = Symbol::none(); |
4435 | 1 | assert_eq!(symbol.as_str(), "NONE"); |
4436 | 1 | } |
4437 | | |
4438 | | // ============================================================================= |
4439 | | // TimeInForce Tests |
4440 | | // ============================================================================= |
4441 | | |
4442 | | #[test] |
4443 | 1 | fn test_time_in_force_display() { |
4444 | 1 | assert_eq!(format!("{}", TimeInForce::Day), "DAY"); |
4445 | 1 | assert_eq!(format!("{}", TimeInForce::GoodTillCancel), "GTC"); |
4446 | 1 | assert_eq!(format!("{}", TimeInForce::ImmediateOrCancel), "IOC"); |
4447 | 1 | assert_eq!(format!("{}", TimeInForce::FillOrKill), "FOK"); |
4448 | 1 | } |
4449 | | |
4450 | | #[test] |
4451 | 1 | fn test_time_in_force_default() { |
4452 | 1 | assert_eq!(TimeInForce::default(), TimeInForce::Day); |
4453 | 1 | } |
4454 | | |
4455 | | // ============================================================================= |
4456 | | // OrderType Tests |
4457 | | // ============================================================================= |
4458 | | |
4459 | | #[test] |
4460 | 1 | fn test_order_type_display() { |
4461 | 1 | assert_eq!(format!("{}", OrderType::Market), "MARKET"); |
4462 | 1 | assert_eq!(format!("{}", OrderType::Limit), "LIMIT"); |
4463 | 1 | assert_eq!(format!("{}", OrderType::Stop), "STOP"); |
4464 | 1 | assert_eq!(format!("{}", OrderType::StopLimit), "STOP_LIMIT"); |
4465 | 1 | } |
4466 | | |
4467 | | #[test] |
4468 | 1 | fn test_order_type_try_from_i32_valid() { |
4469 | 1 | assert_eq!(OrderType::try_from(0).unwrap(), OrderType::Market); |
4470 | 1 | assert_eq!(OrderType::try_from(1).unwrap(), OrderType::Limit); |
4471 | 1 | assert_eq!(OrderType::try_from(2).unwrap(), OrderType::Stop); |
4472 | 1 | } |
4473 | | |
4474 | | #[test] |
4475 | 1 | fn test_order_type_try_from_i32_invalid() { |
4476 | 1 | let result = OrderType::try_from(99); |
4477 | 1 | assert!(result.is_err()); |
4478 | 1 | } |
4479 | | |
4480 | | #[test] |
4481 | 1 | fn test_order_type_default() { |
4482 | 1 | assert_eq!(OrderType::default(), OrderType::Market); |
4483 | 1 | } |
4484 | | |
4485 | | // ============================================================================= |
4486 | | // OrderStatus Tests |
4487 | | // ============================================================================= |
4488 | | |
4489 | | #[test] |
4490 | 1 | fn test_order_status_display() { |
4491 | 1 | assert_eq!(format!("{}", OrderStatus::Created), "CREATED"); |
4492 | 1 | assert_eq!(format!("{}", OrderStatus::Filled), "FILLED"); |
4493 | 1 | assert_eq!(format!("{}", OrderStatus::Cancelled), "CANCELLED"); |
4494 | 1 | } |
4495 | | |
4496 | | #[test] |
4497 | 1 | fn test_order_status_try_from_i32_valid() { |
4498 | 1 | assert_eq!(OrderStatus::try_from(0).unwrap(), OrderStatus::Created); |
4499 | 1 | assert_eq!(OrderStatus::try_from(3).unwrap(), OrderStatus::Filled); |
4500 | 1 | assert_eq!(OrderStatus::try_from(5).unwrap(), OrderStatus::Cancelled); |
4501 | 1 | } |
4502 | | |
4503 | | #[test] |
4504 | 1 | fn test_order_status_try_from_i32_invalid() { |
4505 | 1 | let result = OrderStatus::try_from(99); |
4506 | 1 | assert!(result.is_err()); |
4507 | 1 | } |
4508 | | |
4509 | | // ============================================================================= |
4510 | | // OrderSide Tests |
4511 | | // ============================================================================= |
4512 | | |
4513 | | #[test] |
4514 | 1 | fn test_order_side_display() { |
4515 | 1 | assert_eq!(format!("{}", OrderSide::Buy), "BUY"); |
4516 | 1 | assert_eq!(format!("{}", OrderSide::Sell), "SELL"); |
4517 | 1 | } |
4518 | | |
4519 | | #[test] |
4520 | 1 | fn test_order_side_try_from_i32_valid() { |
4521 | 1 | assert_eq!(OrderSide::try_from(0).unwrap(), OrderSide::Buy); |
4522 | 1 | assert_eq!(OrderSide::try_from(1).unwrap(), OrderSide::Sell); |
4523 | 1 | } |
4524 | | |
4525 | | #[test] |
4526 | 1 | fn test_order_side_try_from_i32_invalid() { |
4527 | 1 | let result = OrderSide::try_from(99); |
4528 | 1 | assert!(result.is_err()); |
4529 | 1 | } |
4530 | | |
4531 | | #[test] |
4532 | 1 | fn test_order_side_default() { |
4533 | 1 | assert_eq!(OrderSide::default(), OrderSide::Buy); |
4534 | 1 | } |
4535 | | |
4536 | | // ============================================================================= |
4537 | | // Currency Tests |
4538 | | // ============================================================================= |
4539 | | |
4540 | | #[test] |
4541 | 1 | fn test_currency_display() { |
4542 | 1 | assert_eq!(format!("{}", Currency::USD), "USD"); |
4543 | 1 | assert_eq!(format!("{}", Currency::EUR), "EUR"); |
4544 | 1 | assert_eq!(format!("{}", Currency::BTC), "BTC"); |
4545 | 1 | } |
4546 | | |
4547 | | #[test] |
4548 | 1 | fn test_currency_default() { |
4549 | 1 | assert_eq!(Currency::default(), Currency::USD); |
4550 | 1 | } |
4551 | | |
4552 | | // ============================================================================= |
4553 | | // Error Type Tests |
4554 | | // ============================================================================= |
4555 | | |
4556 | | #[test] |
4557 | 1 | fn test_common_type_error_invalid_price() { |
4558 | 1 | let error = CommonTypeError::InvalidPrice { |
4559 | 1 | value: "abc".to_string(), |
4560 | 1 | reason: "not a number".to_string(), |
4561 | 1 | }; |
4562 | 1 | let display = format!("{}", error); |
4563 | 1 | assert!(display.contains("abc")); |
4564 | 1 | } |
4565 | | |
4566 | | #[test] |
4567 | 1 | fn test_common_type_error_invalid_quantity() { |
4568 | 1 | let error = CommonTypeError::InvalidQuantity { |
4569 | 1 | value: "xyz".to_string(), |
4570 | 1 | reason: "not a number".to_string(), |
4571 | 1 | }; |
4572 | 1 | let display = format!("{}", error); |
4573 | 1 | assert!(display.contains("xyz")); |
4574 | 1 | } |
4575 | | |
4576 | | #[test] |
4577 | 1 | fn test_common_type_error_validation() { |
4578 | 1 | let error = CommonTypeError::ValidationError { |
4579 | 1 | field: "symbol".to_string(), |
4580 | 1 | reason: "cannot be empty".to_string(), |
4581 | 1 | }; |
4582 | 1 | let display = format!("{}", error); |
4583 | 1 | assert!(display.contains("symbol")); |
4584 | 1 | } |
4585 | | } |