1use crate::errors::{PolyfillError, Result};
7use crate::types::*;
8use crate::utils::math;
9use alloy_primitives::Address;
10use chrono::{DateTime, Utc};
11use rust_decimal::Decimal;
12use std::collections::HashMap;
13use tracing::{debug, info, warn};
14
15#[derive(Debug, Clone)]
17pub struct FillResult {
18 pub order_id: String,
19 pub fills: Vec<FillEvent>,
20 pub total_size: Decimal,
21 pub average_price: Decimal,
22 pub total_cost: Decimal,
23 pub fees: Decimal,
24 pub status: FillStatus,
25 pub timestamp: DateTime<Utc>,
26}
27
28#[derive(Debug, Clone, PartialEq)]
30pub enum FillStatus {
31 Filled,
33 Partial,
35 Unfilled,
37 Rejected,
39}
40
41#[derive(Debug)]
43pub struct FillEngine {
44 min_fill_size: Decimal,
46 max_slippage_pct: Decimal,
48 fee_rate_bps: u32,
50 fills: HashMap<String, Vec<FillEvent>>,
52}
53
54impl FillEngine {
55 pub fn new(min_fill_size: Decimal, max_slippage_pct: Decimal, fee_rate_bps: u32) -> Self {
57 Self {
58 min_fill_size,
59 max_slippage_pct,
60 fee_rate_bps,
61 fills: HashMap::new(),
62 }
63 }
64
65 pub fn execute_market_order(
67 &mut self,
68 order: &MarketOrderRequest,
69 book: &crate::book::OrderBook,
70 ) -> Result<FillResult> {
71 let start_time = Utc::now();
72
73 self.validate_market_order(order)?;
75
76 let levels = match order.side {
78 Side::BUY => book.asks(None),
79 Side::SELL => book.bids(None),
80 };
81
82 if levels.is_empty() {
83 return Ok(FillResult {
84 order_id: order.client_id.clone().unwrap_or_else(|| "market_order".to_string()),
85 fills: Vec::new(),
86 total_size: Decimal::ZERO,
87 average_price: Decimal::ZERO,
88 total_cost: Decimal::ZERO,
89 fees: Decimal::ZERO,
90 status: FillStatus::Unfilled,
91 timestamp: start_time,
92 });
93 }
94
95 let mut fills = Vec::new();
97 let mut remaining_size = order.amount;
98 let mut total_cost = Decimal::ZERO;
99 let mut total_size = Decimal::ZERO;
100
101 for level in levels {
102 if remaining_size.is_zero() {
103 break;
104 }
105
106 let fill_size = std::cmp::min(remaining_size, level.size);
107 let fill_cost = fill_size * level.price;
108
109 let fee = self.calculate_fee(fill_cost);
111
112 let fill = FillEvent {
113 id: uuid::Uuid::new_v4().to_string(),
114 order_id: order.client_id.clone().unwrap_or_else(|| "market_order".to_string()),
115 token_id: order.token_id.clone(),
116 side: order.side,
117 price: level.price,
118 size: fill_size,
119 timestamp: Utc::now(),
120 maker_address: Address::ZERO, taker_address: Address::ZERO, fee,
123 };
124
125 fills.push(fill);
126 total_cost += fill_cost;
127 total_size += fill_size;
128 remaining_size -= fill_size;
129 }
130
131 if let Some(slippage) = self.calculate_slippage(order, &fills) {
133 if slippage > self.max_slippage_pct {
134 warn!(
135 "Slippage {}% exceeds maximum {}%",
136 slippage, self.max_slippage_pct
137 );
138 return Ok(FillResult {
139 order_id: order.client_id.clone().unwrap_or_else(|| "market_order".to_string()),
140 fills: Vec::new(),
141 total_size: Decimal::ZERO,
142 average_price: Decimal::ZERO,
143 total_cost: Decimal::ZERO,
144 fees: Decimal::ZERO,
145 status: FillStatus::Rejected,
146 timestamp: start_time,
147 });
148 }
149 }
150
151 let status = if remaining_size.is_zero() {
153 FillStatus::Filled
154 } else if total_size >= self.min_fill_size {
155 FillStatus::Partial
156 } else {
157 FillStatus::Unfilled
158 };
159
160 let average_price = if total_size.is_zero() {
161 Decimal::ZERO
162 } else {
163 total_cost / total_size
164 };
165
166 let total_fees: Decimal = fills.iter().map(|f| f.fee).sum();
167
168 let result = FillResult {
169 order_id: order.client_id.clone().unwrap_or_else(|| "market_order".to_string()),
170 fills,
171 total_size,
172 average_price,
173 total_cost,
174 fees: total_fees,
175 status,
176 timestamp: start_time,
177 };
178
179 if !result.fills.is_empty() {
181 self.fills.insert(result.order_id.clone(), result.fills.clone());
182 }
183
184 info!(
185 "Market order executed: {} {} @ {} (avg: {})",
186 result.total_size,
187 order.side.as_str(),
188 order.amount,
189 result.average_price
190 );
191
192 Ok(result)
193 }
194
195 pub fn execute_limit_order(
197 &mut self,
198 order: &OrderRequest,
199 book: &crate::book::OrderBook,
200 ) -> Result<FillResult> {
201 let start_time = Utc::now();
202
203 self.validate_limit_order(order)?;
205
206 let can_fill = match order.side {
208 Side::BUY => {
209 if let Some(best_ask) = book.best_ask() {
210 order.price >= best_ask.price
211 } else {
212 false
213 }
214 }
215 Side::SELL => {
216 if let Some(best_bid) = book.best_bid() {
217 order.price <= best_bid.price
218 } else {
219 false
220 }
221 }
222 };
223
224 if !can_fill {
225 return Ok(FillResult {
226 order_id: order.client_id.clone().unwrap_or_else(|| "limit_order".to_string()),
227 fills: Vec::new(),
228 total_size: Decimal::ZERO,
229 average_price: Decimal::ZERO,
230 total_cost: Decimal::ZERO,
231 fees: Decimal::ZERO,
232 status: FillStatus::Unfilled,
233 timestamp: start_time,
234 });
235 }
236
237 let fill = FillEvent {
239 id: uuid::Uuid::new_v4().to_string(),
240 order_id: order.client_id.clone().unwrap_or_else(|| "limit_order".to_string()),
241 token_id: order.token_id.clone(),
242 side: order.side,
243 price: order.price,
244 size: order.size,
245 timestamp: Utc::now(),
246 maker_address: Address::ZERO,
247 taker_address: Address::ZERO,
248 fee: self.calculate_fee(order.price * order.size),
249 };
250
251 let result = FillResult {
252 order_id: order.client_id.clone().unwrap_or_else(|| "limit_order".to_string()),
253 fills: vec![fill],
254 total_size: order.size,
255 average_price: order.price,
256 total_cost: order.price * order.size,
257 fees: self.calculate_fee(order.price * order.size),
258 status: FillStatus::Filled,
259 timestamp: start_time,
260 };
261
262 self.fills.insert(result.order_id.clone(), result.fills.clone());
264
265 info!(
266 "Limit order executed: {} {} @ {}",
267 result.total_size,
268 order.side.as_str(),
269 result.average_price
270 );
271
272 Ok(result)
273 }
274
275 fn calculate_slippage(&self, order: &MarketOrderRequest, fills: &[FillEvent]) -> Option<Decimal> {
277 if fills.is_empty() {
278 return None;
279 }
280
281 let total_cost: Decimal = fills.iter().map(|f| f.price * f.size).sum();
282 let total_size: Decimal = fills.iter().map(|f| f.size).sum();
283 let average_price = total_cost / total_size;
284
285 let reference_price = match order.side {
287 Side::BUY => fills.first()?.price, Side::SELL => fills.first()?.price, };
290
291 Some(math::calculate_slippage(reference_price, average_price, order.side))
292 }
293
294 fn calculate_fee(&self, notional: Decimal) -> Decimal {
296 notional * Decimal::from(self.fee_rate_bps) / Decimal::from(10_000)
297 }
298
299 fn validate_market_order(&self, order: &MarketOrderRequest) -> Result<()> {
301 if order.amount.is_zero() {
302 return Err(PolyfillError::order(
303 "Market order amount cannot be zero",
304 crate::errors::OrderErrorKind::InvalidSize,
305 ));
306 }
307
308 if order.amount < self.min_fill_size {
309 return Err(PolyfillError::order(
310 format!("Order size {} below minimum {}", order.amount, self.min_fill_size),
311 crate::errors::OrderErrorKind::SizeConstraint,
312 ));
313 }
314
315 Ok(())
316 }
317
318 fn validate_limit_order(&self, order: &OrderRequest) -> Result<()> {
320 if order.size.is_zero() {
321 return Err(PolyfillError::order(
322 "Limit order size cannot be zero",
323 crate::errors::OrderErrorKind::InvalidSize,
324 ));
325 }
326
327 if order.price.is_zero() {
328 return Err(PolyfillError::order(
329 "Limit order price cannot be zero",
330 crate::errors::OrderErrorKind::InvalidPrice,
331 ));
332 }
333
334 if order.size < self.min_fill_size {
335 return Err(PolyfillError::order(
336 format!("Order size {} below minimum {}", order.size, self.min_fill_size),
337 crate::errors::OrderErrorKind::SizeConstraint,
338 ));
339 }
340
341 Ok(())
342 }
343
344 pub fn get_fills(&self, order_id: &str) -> Option<&[FillEvent]> {
346 self.fills.get(order_id).map(|f| f.as_slice())
347 }
348
349 pub fn get_all_fills(&self) -> Vec<&FillEvent> {
351 self.fills.values().flatten().collect()
352 }
353
354 pub fn clear_fills(&mut self, order_id: &str) {
356 self.fills.remove(order_id);
357 }
358
359 pub fn get_stats(&self) -> FillStats {
361 let total_fills = self.fills.values().flatten().count();
362 let total_volume: Decimal = self.fills.values().flatten().map(|f| f.size).sum();
363 let total_fees: Decimal = self.fills.values().flatten().map(|f| f.fee).sum();
364
365 FillStats {
366 total_orders: self.fills.len(),
367 total_fills,
368 total_volume,
369 total_fees,
370 }
371 }
372}
373
374#[derive(Debug, Clone)]
376pub struct FillStats {
377 pub total_orders: usize,
378 pub total_fills: usize,
379 pub total_volume: Decimal,
380 pub total_fees: Decimal,
381}
382
383#[derive(Debug)]
385pub struct FillProcessor {
386 pending_fills: HashMap<String, Vec<FillEvent>>,
388 processed_fills: Vec<FillEvent>,
390 max_pending: usize,
392}
393
394impl FillProcessor {
395 pub fn new(max_pending: usize) -> Self {
397 Self {
398 pending_fills: HashMap::new(),
399 processed_fills: Vec::new(),
400 max_pending,
401 }
402 }
403
404 pub fn process_fill(&mut self, fill: FillEvent) -> Result<()> {
406 self.validate_fill(&fill)?;
408
409 self.pending_fills
411 .entry(fill.order_id.clone())
412 .or_insert_with(Vec::new)
413 .push(fill.clone());
414
415 if self.is_order_complete(&fill.order_id) {
417 if let Some(fills) = self.pending_fills.remove(&fill.order_id) {
418 self.processed_fills.extend(fills);
419 }
420 }
421
422 if self.pending_fills.len() > self.max_pending {
424 self.cleanup_old_pending();
425 }
426
427 debug!("Processed fill: {} {} @ {}", fill.size, fill.side.as_str(), fill.price);
428
429 Ok(())
430 }
431
432 fn validate_fill(&self, fill: &FillEvent) -> Result<()> {
434 if fill.size.is_zero() {
435 return Err(PolyfillError::order(
436 "Fill size cannot be zero",
437 crate::errors::OrderErrorKind::InvalidSize,
438 ));
439 }
440
441 if fill.price.is_zero() {
442 return Err(PolyfillError::order(
443 "Fill price cannot be zero",
444 crate::errors::OrderErrorKind::InvalidPrice,
445 ));
446 }
447
448 Ok(())
449 }
450
451 fn is_order_complete(&self, _order_id: &str) -> bool {
453 false
455 }
456
457 fn cleanup_old_pending(&mut self) {
459 let to_remove = self.pending_fills.len() - self.max_pending;
461 let mut keys: Vec<_> = self.pending_fills.keys().cloned().collect();
462 keys.sort(); for key in keys.iter().take(to_remove) {
465 self.pending_fills.remove(key);
466 }
467 }
468
469 pub fn get_pending_fills(&self, order_id: &str) -> Option<&[FillEvent]> {
471 self.pending_fills.get(order_id).map(|f| f.as_slice())
472 }
473
474 pub fn get_processed_fills(&self) -> &[FillEvent] {
476 &self.processed_fills
477 }
478
479 pub fn get_stats(&self) -> FillProcessorStats {
481 let total_pending: Decimal = self.pending_fills.values().flatten().map(|f| f.size).sum();
482 let total_processed: Decimal = self.processed_fills.iter().map(|f| f.size).sum();
483
484 FillProcessorStats {
485 pending_orders: self.pending_fills.len(),
486 pending_fills: self.pending_fills.values().flatten().count(),
487 pending_volume: total_pending,
488 processed_fills: self.processed_fills.len(),
489 processed_volume: total_processed,
490 }
491 }
492}
493
494#[derive(Debug, Clone)]
496pub struct FillProcessorStats {
497 pub pending_orders: usize,
498 pub pending_fills: usize,
499 pub pending_volume: Decimal,
500 pub processed_fills: usize,
501 pub processed_volume: Decimal,
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507 use rust_decimal_macros::dec;
508
509 #[test]
510 fn test_fill_engine_creation() {
511 let engine = FillEngine::new(dec!(1), dec!(5), 10);
512 assert_eq!(engine.min_fill_size, dec!(1));
513 assert_eq!(engine.max_slippage_pct, dec!(5));
514 assert_eq!(engine.fee_rate_bps, 10);
515 }
516
517 #[test]
518 fn test_market_order_validation() {
519 let engine = FillEngine::new(dec!(1), dec!(5), 10);
520
521 let valid_order = MarketOrderRequest {
522 token_id: "test".to_string(),
523 side: Side::BUY,
524 amount: dec!(100),
525 slippage_tolerance: None,
526 client_id: None,
527 };
528 assert!(engine.validate_market_order(&valid_order).is_ok());
529
530 let invalid_order = MarketOrderRequest {
531 token_id: "test".to_string(),
532 side: Side::BUY,
533 amount: dec!(0),
534 slippage_tolerance: None,
535 client_id: None,
536 };
537 assert!(engine.validate_market_order(&invalid_order).is_err());
538 }
539
540 #[test]
541 fn test_fee_calculation() {
542 let engine = FillEngine::new(dec!(1), dec!(5), 10);
543 let fee = engine.calculate_fee(dec!(1000));
544 assert_eq!(fee, dec!(1)); }
546
547 #[test]
548 fn test_fill_processor() {
549 let mut processor = FillProcessor::new(100);
550
551 let fill = FillEvent {
552 id: "fill1".to_string(),
553 order_id: "order1".to_string(),
554 token_id: "test".to_string(),
555 side: Side::BUY,
556 price: dec!(0.5),
557 size: dec!(100),
558 timestamp: Utc::now(),
559 maker_address: Address::ZERO,
560 taker_address: Address::ZERO,
561 fee: dec!(0.1),
562 };
563
564 assert!(processor.process_fill(fill).is_ok());
565 assert_eq!(processor.pending_fills.len(), 1);
566 }
567
568 #[test]
569 fn test_fill_engine_advanced_creation() {
570 let _engine = FillEngine::new(dec!(1.0), dec!(0.05), 50); assert!(true); }
577
578 #[test]
579 fn test_fill_processor_basic_operations() {
580 let mut processor = FillProcessor::new(100); let fill_event = FillEvent {
584 id: "fill_1".to_string(),
585 order_id: "order_1".to_string(),
586 side: Side::BUY,
587 size: dec!(25),
588 price: dec!(0.75),
589 timestamp: chrono::Utc::now(),
590 token_id: "token_1".to_string(),
591 maker_address: alloy_primitives::Address::ZERO,
592 taker_address: alloy_primitives::Address::ZERO,
593 fee: dec!(0.01),
594 };
595
596 let result = processor.process_fill(fill_event);
597 assert!(result.is_ok());
598
599 assert_eq!(processor.pending_fills.len(), 1);
601 }
602}