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