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.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        // Execute fills
96        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            // Calculate fee
110            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, // TODO: Get from level
121                taker_address: Address::ZERO, // TODO: Get from order
122                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        // Check slippage
132        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        // Determine status
152        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        // Store fills for tracking
180        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    /// Execute a limit order (simulation)
196    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        // Validate order
204        self.validate_limit_order(order)?;
205
206        // Check if order can be filled immediately
207        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        // Simulate immediate fill
238        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        // Store fills for tracking
263        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    /// Calculate slippage for a market order
276    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        // Get reference price (best bid/ask)
286        let reference_price = match order.side {
287            Side::BUY => fills.first()?.price, // Best ask
288            Side::SELL => fills.first()?.price, // Best bid
289        };
290
291        Some(math::calculate_slippage(reference_price, average_price, order.side))
292    }
293
294    /// Calculate fee for a trade
295    fn calculate_fee(&self, notional: Decimal) -> Decimal {
296        notional * Decimal::from(self.fee_rate_bps) / Decimal::from(10_000)
297    }
298
299    /// Validate market order parameters
300    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    /// Validate limit order parameters
319    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    /// Get fills for an order
345    pub fn get_fills(&self, order_id: &str) -> Option<&[FillEvent]> {
346        self.fills.get(order_id).map(|f| f.as_slice())
347    }
348
349    /// Get all fills
350    pub fn get_all_fills(&self) -> Vec<&FillEvent> {
351        self.fills.values().flatten().collect()
352    }
353
354    /// Clear fills for an order
355    pub fn clear_fills(&mut self, order_id: &str) {
356        self.fills.remove(order_id);
357    }
358
359    /// Get fill statistics
360    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/// Fill statistics
375#[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/// Fill event processor for real-time updates
384#[derive(Debug)]
385pub struct FillProcessor {
386    /// Pending fills by order ID
387    pending_fills: HashMap<String, Vec<FillEvent>>,
388    /// Processed fills
389    processed_fills: Vec<FillEvent>,
390    /// Maximum pending fills to keep in memory
391    max_pending: usize,
392}
393
394impl FillProcessor {
395    /// Create a new fill processor
396    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    /// Process a fill event
405    pub fn process_fill(&mut self, fill: FillEvent) -> Result<()> {
406        // Validate fill
407        self.validate_fill(&fill)?;
408
409        // Add to pending fills
410        self.pending_fills
411            .entry(fill.order_id.clone())
412            .or_insert_with(Vec::new)
413            .push(fill.clone());
414
415        // Move to processed if complete
416        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        // Cleanup if too many pending
423        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    /// Validate a fill event
433    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    /// Check if an order is complete
452    fn is_order_complete(&self, _order_id: &str) -> bool {
453        // Simplified implementation - in practice you'd check against order book
454        false
455    }
456
457    /// Cleanup old pending fills
458    fn cleanup_old_pending(&mut self) {
459        // Remove oldest pending fills
460        let to_remove = self.pending_fills.len() - self.max_pending;
461        let mut keys: Vec<_> = self.pending_fills.keys().cloned().collect();
462        keys.sort(); // Simple ordering - in practice you'd use timestamps
463
464        for key in keys.iter().take(to_remove) {
465            self.pending_fills.remove(key);
466        }
467    }
468
469    /// Get pending fills for an order
470    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    /// Get processed fills
475    pub fn get_processed_fills(&self) -> &[FillEvent] {
476        &self.processed_fills
477    }
478
479    /// Get fill statistics
480    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/// Fill processor statistics
495#[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)); // 10 bps = 0.1% = 1 on 1000
545    }
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        // Test that we can create a fill engine with parameters
571        let _engine = FillEngine::new(dec!(1.0), dec!(0.05), 50); // min_fill_size, max_slippage, fee_rate_bps
572        
573        // Test basic properties exist (we can't access private fields directly)
574        // But we can test that the engine was created successfully
575        assert!(true); // Engine creation successful
576    }
577
578    #[test]
579    fn test_fill_processor_basic_operations() {
580        let mut processor = FillProcessor::new(100); // max_pending
581        
582        // Test that we can create a fill event and process it
583        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        // Check that the fill was added to pending
600        assert_eq!(processor.pending_fills.len(), 1);
601    }
602}