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
85 .client_id
86 .clone()
87 .unwrap_or_else(|| "market_order".to_string()),
88 fills: Vec::new(),
89 total_size: Decimal::ZERO,
90 average_price: Decimal::ZERO,
91 total_cost: Decimal::ZERO,
92 fees: Decimal::ZERO,
93 status: FillStatus::Unfilled,
94 timestamp: start_time,
95 });
96 }
97
98 let mut fills = Vec::new();
100 let mut remaining_size = order.amount;
101 let mut total_cost = Decimal::ZERO;
102 let mut total_size = Decimal::ZERO;
103
104 for level in levels {
105 if remaining_size.is_zero() {
106 break;
107 }
108
109 let fill_size = std::cmp::min(remaining_size, level.size);
110 let fill_cost = fill_size * level.price;
111
112 let fee = self.calculate_fee(fill_cost);
114
115 let fill = FillEvent {
116 id: uuid::Uuid::new_v4().to_string(),
117 order_id: order
118 .client_id
119 .clone()
120 .unwrap_or_else(|| "market_order".to_string()),
121 token_id: order.token_id.clone(),
122 side: order.side,
123 price: level.price,
124 size: fill_size,
125 timestamp: Utc::now(),
126 maker_address: Address::ZERO, taker_address: Address::ZERO, fee,
129 };
130
131 fills.push(fill);
132 total_cost += fill_cost;
133 total_size += fill_size;
134 remaining_size -= fill_size;
135 }
136
137 if let Some(slippage) = self.calculate_slippage(order, &fills) {
139 if slippage > self.max_slippage_pct {
140 warn!(
141 "Slippage {}% exceeds maximum {}%",
142 slippage, self.max_slippage_pct
143 );
144 return Ok(FillResult {
145 order_id: order
146 .client_id
147 .clone()
148 .unwrap_or_else(|| "market_order".to_string()),
149 fills: Vec::new(),
150 total_size: Decimal::ZERO,
151 average_price: Decimal::ZERO,
152 total_cost: Decimal::ZERO,
153 fees: Decimal::ZERO,
154 status: FillStatus::Rejected,
155 timestamp: start_time,
156 });
157 }
158 }
159
160 let status = if remaining_size.is_zero() {
162 FillStatus::Filled
163 } else if total_size >= self.min_fill_size {
164 FillStatus::Partial
165 } else {
166 FillStatus::Unfilled
167 };
168
169 let average_price = if total_size.is_zero() {
170 Decimal::ZERO
171 } else {
172 total_cost / total_size
173 };
174
175 let total_fees: Decimal = fills.iter().map(|f| f.fee).sum();
176
177 let result = FillResult {
178 order_id: order
179 .client_id
180 .clone()
181 .unwrap_or_else(|| "market_order".to_string()),
182 fills,
183 total_size,
184 average_price,
185 total_cost,
186 fees: total_fees,
187 status,
188 timestamp: start_time,
189 };
190
191 if !result.fills.is_empty() {
193 self.fills
194 .insert(result.order_id.clone(), result.fills.clone());
195 }
196
197 info!(
198 "Market order executed: {} {} @ {} (avg: {})",
199 result.total_size,
200 order.side.as_str(),
201 order.amount,
202 result.average_price
203 );
204
205 Ok(result)
206 }
207
208 pub fn execute_limit_order(
210 &mut self,
211 order: &OrderRequest,
212 book: &crate::book::OrderBook,
213 ) -> Result<FillResult> {
214 let start_time = Utc::now();
215
216 self.validate_limit_order(order)?;
218
219 let can_fill = match order.side {
221 Side::BUY => {
222 if let Some(best_ask) = book.best_ask() {
223 order.price >= best_ask.price
224 } else {
225 false
226 }
227 },
228 Side::SELL => {
229 if let Some(best_bid) = book.best_bid() {
230 order.price <= best_bid.price
231 } else {
232 false
233 }
234 },
235 };
236
237 if !can_fill {
238 return Ok(FillResult {
239 order_id: order
240 .client_id
241 .clone()
242 .unwrap_or_else(|| "limit_order".to_string()),
243 fills: Vec::new(),
244 total_size: Decimal::ZERO,
245 average_price: Decimal::ZERO,
246 total_cost: Decimal::ZERO,
247 fees: Decimal::ZERO,
248 status: FillStatus::Unfilled,
249 timestamp: start_time,
250 });
251 }
252
253 let fill = FillEvent {
255 id: uuid::Uuid::new_v4().to_string(),
256 order_id: order
257 .client_id
258 .clone()
259 .unwrap_or_else(|| "limit_order".to_string()),
260 token_id: order.token_id.clone(),
261 side: order.side,
262 price: order.price,
263 size: order.size,
264 timestamp: Utc::now(),
265 maker_address: Address::ZERO,
266 taker_address: Address::ZERO,
267 fee: self.calculate_fee(order.price * order.size),
268 };
269
270 let result = FillResult {
271 order_id: order
272 .client_id
273 .clone()
274 .unwrap_or_else(|| "limit_order".to_string()),
275 fills: vec![fill],
276 total_size: order.size,
277 average_price: order.price,
278 total_cost: order.price * order.size,
279 fees: self.calculate_fee(order.price * order.size),
280 status: FillStatus::Filled,
281 timestamp: start_time,
282 };
283
284 self.fills
286 .insert(result.order_id.clone(), result.fills.clone());
287
288 info!(
289 "Limit order executed: {} {} @ {}",
290 result.total_size,
291 order.side.as_str(),
292 result.average_price
293 );
294
295 Ok(result)
296 }
297
298 fn calculate_slippage(
300 &self,
301 order: &MarketOrderRequest,
302 fills: &[FillEvent],
303 ) -> Option<Decimal> {
304 if fills.is_empty() {
305 return None;
306 }
307
308 let total_cost: Decimal = fills.iter().map(|f| f.price * f.size).sum();
309 let total_size: Decimal = fills.iter().map(|f| f.size).sum();
310 let average_price = total_cost / total_size;
311
312 let reference_price = match order.side {
314 Side::BUY => fills.first()?.price, Side::SELL => fills.first()?.price, };
317
318 Some(math::calculate_slippage(
319 reference_price,
320 average_price,
321 order.side,
322 ))
323 }
324
325 fn calculate_fee(&self, notional: Decimal) -> Decimal {
327 notional * Decimal::from(self.fee_rate_bps) / Decimal::from(10_000)
328 }
329
330 fn validate_market_order(&self, order: &MarketOrderRequest) -> Result<()> {
332 if order.amount.is_zero() {
333 return Err(PolyfillError::order(
334 "Market order amount cannot be zero",
335 crate::errors::OrderErrorKind::InvalidSize,
336 ));
337 }
338
339 if order.amount < self.min_fill_size {
340 return Err(PolyfillError::order(
341 format!(
342 "Order size {} below minimum {}",
343 order.amount, self.min_fill_size
344 ),
345 crate::errors::OrderErrorKind::SizeConstraint,
346 ));
347 }
348
349 Ok(())
350 }
351
352 fn validate_limit_order(&self, order: &OrderRequest) -> Result<()> {
354 if order.size.is_zero() {
355 return Err(PolyfillError::order(
356 "Limit order size cannot be zero",
357 crate::errors::OrderErrorKind::InvalidSize,
358 ));
359 }
360
361 if order.price.is_zero() {
362 return Err(PolyfillError::order(
363 "Limit order price cannot be zero",
364 crate::errors::OrderErrorKind::InvalidPrice,
365 ));
366 }
367
368 if order.size < self.min_fill_size {
369 return Err(PolyfillError::order(
370 format!(
371 "Order size {} below minimum {}",
372 order.size, self.min_fill_size
373 ),
374 crate::errors::OrderErrorKind::SizeConstraint,
375 ));
376 }
377
378 Ok(())
379 }
380
381 pub fn get_fills(&self, order_id: &str) -> Option<&[FillEvent]> {
383 self.fills.get(order_id).map(|f| f.as_slice())
384 }
385
386 pub fn get_all_fills(&self) -> Vec<&FillEvent> {
388 self.fills.values().flatten().collect()
389 }
390
391 pub fn clear_fills(&mut self, order_id: &str) {
393 self.fills.remove(order_id);
394 }
395
396 pub fn get_stats(&self) -> FillStats {
398 let total_fills = self.fills.values().flatten().count();
399 let total_volume: Decimal = self.fills.values().flatten().map(|f| f.size).sum();
400 let total_fees: Decimal = self.fills.values().flatten().map(|f| f.fee).sum();
401
402 FillStats {
403 total_orders: self.fills.len(),
404 total_fills,
405 total_volume,
406 total_fees,
407 }
408 }
409}
410
411#[derive(Debug, Clone)]
413pub struct FillStats {
414 pub total_orders: usize,
415 pub total_fills: usize,
416 pub total_volume: Decimal,
417 pub total_fees: Decimal,
418}
419
420#[derive(Debug)]
422pub struct FillProcessor {
423 pending_fills: HashMap<String, Vec<FillEvent>>,
425 processed_fills: Vec<FillEvent>,
427 max_pending: usize,
429}
430
431impl FillProcessor {
432 pub fn new(max_pending: usize) -> Self {
434 Self {
435 pending_fills: HashMap::new(),
436 processed_fills: Vec::new(),
437 max_pending,
438 }
439 }
440
441 pub fn process_fill(&mut self, fill: FillEvent) -> Result<()> {
443 self.validate_fill(&fill)?;
445
446 self.pending_fills
448 .entry(fill.order_id.clone())
449 .or_default()
450 .push(fill.clone());
451
452 if self.is_order_complete(&fill.order_id) {
454 if let Some(fills) = self.pending_fills.remove(&fill.order_id) {
455 self.processed_fills.extend(fills);
456 }
457 }
458
459 if self.pending_fills.len() > self.max_pending {
461 self.cleanup_old_pending();
462 }
463
464 debug!(
465 "Processed fill: {} {} @ {}",
466 fill.size,
467 fill.side.as_str(),
468 fill.price
469 );
470
471 Ok(())
472 }
473
474 fn validate_fill(&self, fill: &FillEvent) -> Result<()> {
476 if fill.size.is_zero() {
477 return Err(PolyfillError::order(
478 "Fill size cannot be zero",
479 crate::errors::OrderErrorKind::InvalidSize,
480 ));
481 }
482
483 if fill.price.is_zero() {
484 return Err(PolyfillError::order(
485 "Fill price cannot be zero",
486 crate::errors::OrderErrorKind::InvalidPrice,
487 ));
488 }
489
490 Ok(())
491 }
492
493 fn is_order_complete(&self, _order_id: &str) -> bool {
495 false
497 }
498
499 fn cleanup_old_pending(&mut self) {
501 let to_remove = self.pending_fills.len() - self.max_pending;
503 let mut keys: Vec<_> = self.pending_fills.keys().cloned().collect();
504 keys.sort(); for key in keys.iter().take(to_remove) {
507 self.pending_fills.remove(key);
508 }
509 }
510
511 pub fn get_pending_fills(&self, order_id: &str) -> Option<&[FillEvent]> {
513 self.pending_fills.get(order_id).map(|f| f.as_slice())
514 }
515
516 pub fn get_processed_fills(&self) -> &[FillEvent] {
518 &self.processed_fills
519 }
520
521 pub fn get_stats(&self) -> FillProcessorStats {
523 let total_pending: Decimal = self.pending_fills.values().flatten().map(|f| f.size).sum();
524 let total_processed: Decimal = self.processed_fills.iter().map(|f| f.size).sum();
525
526 FillProcessorStats {
527 pending_orders: self.pending_fills.len(),
528 pending_fills: self.pending_fills.values().flatten().count(),
529 pending_volume: total_pending,
530 processed_fills: self.processed_fills.len(),
531 processed_volume: total_processed,
532 }
533 }
534}
535
536#[derive(Debug, Clone)]
538pub struct FillProcessorStats {
539 pub pending_orders: usize,
540 pub pending_fills: usize,
541 pub pending_volume: Decimal,
542 pub processed_fills: usize,
543 pub processed_volume: Decimal,
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549 use rust_decimal_macros::dec;
550
551 #[test]
552 fn test_fill_engine_creation() {
553 let engine = FillEngine::new(dec!(1), dec!(5), 10);
554 assert_eq!(engine.min_fill_size, dec!(1));
555 assert_eq!(engine.max_slippage_pct, dec!(5));
556 assert_eq!(engine.fee_rate_bps, 10);
557 }
558
559 #[test]
560 fn test_market_order_validation() {
561 let engine = FillEngine::new(dec!(1), dec!(5), 10);
562
563 let valid_order = MarketOrderRequest {
564 token_id: "test".to_string(),
565 side: Side::BUY,
566 amount: dec!(100),
567 slippage_tolerance: None,
568 client_id: None,
569 };
570 assert!(engine.validate_market_order(&valid_order).is_ok());
571
572 let invalid_order = MarketOrderRequest {
573 token_id: "test".to_string(),
574 side: Side::BUY,
575 amount: dec!(0),
576 slippage_tolerance: None,
577 client_id: None,
578 };
579 assert!(engine.validate_market_order(&invalid_order).is_err());
580 }
581
582 #[test]
583 fn test_fee_calculation() {
584 let engine = FillEngine::new(dec!(1), dec!(5), 10);
585 let fee = engine.calculate_fee(dec!(1000));
586 assert_eq!(fee, dec!(1)); }
588
589 #[test]
590 fn test_fill_processor() {
591 let mut processor = FillProcessor::new(100);
592
593 let fill = FillEvent {
594 id: "fill1".to_string(),
595 order_id: "order1".to_string(),
596 token_id: "test".to_string(),
597 side: Side::BUY,
598 price: dec!(0.5),
599 size: dec!(100),
600 timestamp: Utc::now(),
601 maker_address: Address::ZERO,
602 taker_address: Address::ZERO,
603 fee: dec!(0.1),
604 };
605
606 assert!(processor.process_fill(fill).is_ok());
607 assert_eq!(processor.pending_fills.len(), 1);
608 }
609
610 #[test]
611 fn test_fill_engine_advanced_creation() {
612 let _engine = FillEngine::new(dec!(1.0), dec!(0.05), 50); }
619
620 #[test]
621 fn test_fill_processor_basic_operations() {
622 let mut processor = FillProcessor::new(100); let fill_event = FillEvent {
626 id: "fill_1".to_string(),
627 order_id: "order_1".to_string(),
628 side: Side::BUY,
629 size: dec!(25),
630 price: dec!(0.75),
631 timestamp: chrono::Utc::now(),
632 token_id: "token_1".to_string(),
633 maker_address: alloy_primitives::Address::ZERO,
634 taker_address: alloy_primitives::Address::ZERO,
635 fee: dec!(0.01),
636 };
637
638 let result = processor.process_fill(fill_event);
639 assert!(result.is_ok());
640
641 assert_eq!(processor.pending_fills.len(), 1);
643 }
644}