polyfill_rs/
fill.rs

1//! Trade execution and fill handling for Polymarket client
2//!
3//! This module provides high-performance trade execution logic and
4//! fill event processing for latency-sensitive trading environments.
5
6use 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/// Fill execution result
16#[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/// Fill execution status
29#[derive(Debug, Clone, PartialEq)]
30pub enum FillStatus {
31    /// Order was fully filled
32    Filled,
33    /// Order was partially filled
34    Partial,
35    /// Order was not filled (insufficient liquidity)
36    Unfilled,
37    /// Order was rejected
38    Rejected,
39}
40
41/// Fill execution engine
42#[derive(Debug)]
43pub struct FillEngine {
44    /// Minimum fill size for market orders
45    min_fill_size: Decimal,
46    /// Maximum slippage tolerance (as percentage)
47    max_slippage_pct: Decimal,
48    /// Fee rate in basis points
49    fee_rate_bps: u32,
50    /// Track fills by order ID
51    fills: HashMap<String, Vec<FillEvent>>,
52}
53
54impl FillEngine {
55    /// Create a new fill engine
56    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    /// Execute a market order against an order book
66    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        // Validate order
74        self.validate_market_order(order)?;
75
76        // Get available liquidity
77        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        // Execute fills
99        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            // Calculate fee
113            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, // TODO: Get from level
127                taker_address: Address::ZERO, // TODO: Get from order
128                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        // Check slippage
138        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        // Determine status
161        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        // Store fills for tracking
192        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    /// Execute a limit order (simulation)
209    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        // Validate order
217        self.validate_limit_order(order)?;
218
219        // Check if order can be filled immediately
220        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        // Simulate immediate fill
254        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        // Store fills for tracking
285        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    /// Calculate slippage for a market order
299    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        // Get reference price (best bid/ask)
313        let reference_price = match order.side {
314            Side::BUY => fills.first()?.price,  // Best ask
315            Side::SELL => fills.first()?.price, // Best bid
316        };
317
318        Some(math::calculate_slippage(
319            reference_price,
320            average_price,
321            order.side,
322        ))
323    }
324
325    /// Calculate fee for a trade
326    fn calculate_fee(&self, notional: Decimal) -> Decimal {
327        notional * Decimal::from(self.fee_rate_bps) / Decimal::from(10_000)
328    }
329
330    /// Validate market order parameters
331    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    /// Validate limit order parameters
353    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    /// Get fills for an order
382    pub fn get_fills(&self, order_id: &str) -> Option<&[FillEvent]> {
383        self.fills.get(order_id).map(|f| f.as_slice())
384    }
385
386    /// Get all fills
387    pub fn get_all_fills(&self) -> Vec<&FillEvent> {
388        self.fills.values().flatten().collect()
389    }
390
391    /// Clear fills for an order
392    pub fn clear_fills(&mut self, order_id: &str) {
393        self.fills.remove(order_id);
394    }
395
396    /// Get fill statistics
397    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/// Fill statistics
412#[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/// Fill event processor for real-time updates
421#[derive(Debug)]
422pub struct FillProcessor {
423    /// Pending fills by order ID
424    pending_fills: HashMap<String, Vec<FillEvent>>,
425    /// Processed fills
426    processed_fills: Vec<FillEvent>,
427    /// Maximum pending fills to keep in memory
428    max_pending: usize,
429}
430
431impl FillProcessor {
432    /// Create a new fill processor
433    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    /// Process a fill event
442    pub fn process_fill(&mut self, fill: FillEvent) -> Result<()> {
443        // Validate fill
444        self.validate_fill(&fill)?;
445
446        // Add to pending fills
447        self.pending_fills
448            .entry(fill.order_id.clone())
449            .or_default()
450            .push(fill.clone());
451
452        // Move to processed if complete
453        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        // Cleanup if too many pending
460        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    /// Validate a fill event
475    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    /// Check if an order is complete
494    fn is_order_complete(&self, _order_id: &str) -> bool {
495        // Simplified implementation - in practice you'd check against order book
496        false
497    }
498
499    /// Cleanup old pending fills
500    fn cleanup_old_pending(&mut self) {
501        // Remove oldest pending fills
502        let to_remove = self.pending_fills.len() - self.max_pending;
503        let mut keys: Vec<_> = self.pending_fills.keys().cloned().collect();
504        keys.sort(); // Simple ordering - in practice you'd use timestamps
505
506        for key in keys.iter().take(to_remove) {
507            self.pending_fills.remove(key);
508        }
509    }
510
511    /// Get pending fills for an order
512    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    /// Get processed fills
517    pub fn get_processed_fills(&self) -> &[FillEvent] {
518        &self.processed_fills
519    }
520
521    /// Get fill statistics
522    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/// Fill processor statistics
537#[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)); // 10 bps = 0.1% = 1 on 1000
587    }
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        // Test that we can create a fill engine with parameters
613        let _engine = FillEngine::new(dec!(1.0), dec!(0.05), 50); // min_fill_size, max_slippage, fee_rate_bps
614
615        // Test basic properties exist (we can't access private fields directly)
616        // But we can test that the engine was created successfully
617        // Engine creation successful
618    }
619
620    #[test]
621    fn test_fill_processor_basic_operations() {
622        let mut processor = FillProcessor::new(100); // max_pending
623
624        // Test that we can create a fill event and process it
625        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        // Check that the fill was added to pending
642        assert_eq!(processor.pending_fills.len(), 1);
643    }
644}