1use crate::matcher::Matcher;
2use crate::types::{FillEvent, MarketState, Order, OrderState, Side};
3use rand::prelude::*;
4use rand::rng;
5use rand::rngs::StdRng;
6
7pub struct StochasticMatcher {
8 orders: Vec<Order>,
9 dt: f64,
10 k: f64,
11 a: f64,
12 rng: StdRng,
13 hawkes_alpha: f64,
17 hawkes_beta: f64,
18 hawkes_excitation: f64,
19 bilateral_hawkes: bool,
24 hawkes_buy_excitation: f64,
25 hawkes_sell_excitation: f64,
26}
27
28impl StochasticMatcher {
29 pub fn new(dt: f64, k: f64, a: f64) -> Self {
30 Self {
31 orders: Vec::new(),
32 dt,
33 k,
34 a,
35 rng: StdRng::from_rng(&mut rng()),
36 hawkes_alpha: 0.0,
37 hawkes_beta: 0.0,
38 hawkes_excitation: 0.0,
39 bilateral_hawkes: false,
40 hawkes_buy_excitation: 0.0,
41 hawkes_sell_excitation: 0.0,
42 }
43 }
44
45 pub fn with_hawkes(mut self, alpha: f64, beta: f64) -> Self {
51 self.hawkes_alpha = alpha;
52 self.hawkes_beta = beta;
53 self
54 }
55
56 pub fn with_bilateral_hawkes(mut self, alpha: f64, beta: f64) -> Self {
64 self.hawkes_alpha = alpha;
65 self.hawkes_beta = beta;
66 self.bilateral_hawkes = true;
67 self
68 }
69
70 pub fn with_initial_hawkes_intensity(mut self, lambda0: f64) -> Self {
74 self.hawkes_excitation = (lambda0 - self.a).max(0.0);
75 self
76 }
77
78 pub fn with_initial_bilateral_hawkes_intensities(
83 mut self,
84 lambda_buy0: f64,
85 lambda_sell0: f64,
86 ) -> Self {
87 self.hawkes_buy_excitation = (lambda_buy0 - self.a).max(0.0);
88 self.hawkes_sell_excitation = (lambda_sell0 - self.a).max(0.0);
89 self
90 }
91
92 pub fn with_rng(mut self, rng: StdRng) -> Self {
94 self.rng = rng;
95 self
96 }
97
98 #[inline]
100 pub fn effective_a(&self) -> f64 {
101 self.a + self.hawkes_excitation
102 }
103
104 #[inline]
106 pub fn hawkes_excitation(&self) -> f64 {
107 self.hawkes_excitation
108 }
109
110 #[inline]
112 pub fn effective_a_buy(&self) -> f64 {
113 self.a + self.hawkes_buy_excitation
114 }
115
116 #[inline]
118 pub fn effective_a_sell(&self) -> f64 {
119 self.a + self.hawkes_sell_excitation
120 }
121}
122
123impl Matcher for StochasticMatcher {
124 fn add_order(&mut self, order: Order) {
125 self.orders.push(order);
126 }
127
128 fn cancel_order(&mut self, order_id: u64) {
129 if let Some(order) = self.orders.iter_mut().find(|o| o.id == order_id) {
130 order.state = OrderState::Cancelled;
131 }
132 self.orders.retain(|o| o.state == OrderState::Open);
133 }
134
135 fn cancel_all(&mut self) {
136 self.orders.clear();
137 }
138
139 fn process_quote(&mut self, state: &MarketState) -> Vec<FillEvent> {
140 if self.bilateral_hawkes {
142 let decay = (-self.hawkes_beta * self.dt).exp();
143 self.hawkes_buy_excitation *= decay;
144 self.hawkes_sell_excitation *= decay;
145 } else if self.hawkes_alpha > 0.0 {
146 self.hawkes_excitation *= (-self.hawkes_beta * self.dt).exp();
147 }
148 let a_eff = self.a + self.hawkes_excitation;
149
150 let mut fills = Vec::new();
151 let mid_price = (state.best_bid + state.best_ask) / 2.0;
155 let timestamp = state.timestamp;
156 let best_ask = state.best_ask;
157 let best_bid = state.best_bid;
158
159 for order in self.orders.iter_mut() {
160 if order.state != OrderState::Open {
161 continue;
162 }
163
164 let mut filled = false;
165 let mut fill_price = 0.0;
166
167 let aggressive = match order.side {
198 Side::Buy => order.limit_price >= best_ask,
199 Side::Sell => order.limit_price <= best_bid,
200 };
201
202 if aggressive {
203 filled = true;
204 fill_price = match order.side {
205 Side::Buy => best_ask,
206 Side::Sell => best_bid,
207 };
208 } else {
209 let swept = match order.side {
211 Side::Buy => mid_price <= order.limit_price,
212 Side::Sell => mid_price >= order.limit_price,
213 };
214
215 if swept {
216 filled = true;
217 fill_price = order.limit_price;
218 } else {
219 let delta = match order.side {
222 Side::Buy => mid_price - order.limit_price, Side::Sell => order.limit_price - mid_price, };
225
226 let side_a_eff = if self.bilateral_hawkes {
227 match order.side {
228 Side::Buy => self.a + self.hawkes_buy_excitation,
229 Side::Sell => self.a + self.hawkes_sell_excitation,
230 }
231 } else {
232 a_eff
233 };
234
235 let lambda = side_a_eff * (-self.k * delta).exp();
236 let prob = 1.0 - (-lambda * self.dt).exp();
237
238 if self.rng.random_bool(prob.clamp(0.0, 1.0)) {
239 filled = true;
240 fill_price = order.limit_price;
241 }
242 }
243 }
244
245 if filled {
246 order.state = OrderState::Filled;
247 fills.push(FillEvent {
248 timestamp,
249 order_id: order.id,
250 side: order.side,
251 price: fill_price,
252 quantity: order.quantity,
253 });
254 }
255 }
256
257 if self.bilateral_hawkes && !fills.is_empty() {
259 for fill in &fills {
260 match fill.side {
261 Side::Buy => self.hawkes_buy_excitation += self.hawkes_alpha,
262 Side::Sell => self.hawkes_sell_excitation += self.hawkes_alpha,
263 }
264 }
265 } else if self.hawkes_alpha > 0.0 && !fills.is_empty() {
266 self.hawkes_excitation += self.hawkes_alpha * fills.len() as f64;
267 }
268
269 self.orders.retain(|o| o.state == OrderState::Open);
271
272 fills
273 }
274
275 fn get_orders(&self) -> &Vec<Order> {
276 &self.orders
277 }
278
279 fn augment_parameters(&self) -> std::collections::HashMap<String, f64> {
280 let mut params = std::collections::HashMap::with_capacity(2);
281 if self.bilateral_hawkes {
282 params.insert("hawkes_buy_intensity".to_string(), self.effective_a_sell());
292 params.insert("hawkes_sell_intensity".to_string(), self.effective_a_buy());
293 } else if self.hawkes_alpha > 0.0 {
294 params.insert("hawkes_intensity".to_string(), self.effective_a());
295 }
296 params
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::types::Side;
304
305 fn state(bid: f64, ask: f64) -> MarketState {
306 MarketState {
307 timestamp: 0.0,
308 best_bid: bid,
309 best_ask: ask,
310 last_price: None,
311 true_volatility: None,
312 true_drift: None,
313 parameters: None,
314 }
315 }
316
317 #[test]
320 fn test_aggressive_buy_fills_at_ask_not_at_limit() {
321 let mut m = StochasticMatcher::new(1.0, 1.5, 140.0);
324 m.add_order(Order::new(1, Side::Buy, 101.0, 1.0));
325 let fills = m.process_quote(&state(99.0, 100.0));
326 assert_eq!(fills.len(), 1);
327 assert_eq!(fills[0].price, 100.0); }
329
330 #[test]
331 fn test_aggressive_sell_fills_at_bid_not_at_limit() {
332 let mut m = StochasticMatcher::new(1.0, 1.5, 140.0);
335 m.add_order(Order::new(1, Side::Sell, 98.0, 1.0));
336 let fills = m.process_quote(&state(99.0, 100.0));
337 assert_eq!(fills.len(), 1);
338 assert_eq!(fills[0].price, 99.0); }
340
341 #[test]
344 fn test_sweep_buy_fills_when_mid_drops_through_limit() {
345 let mut m = StochasticMatcher::new(1.0, 1.5, 140.0);
348 m.add_order(Order::new(1, Side::Buy, 99.8, 1.0));
349 let fills = m.process_quote(&state(98.5, 100.0));
351 assert_eq!(fills.len(), 1);
352 assert_eq!(fills[0].price, 99.8);
353 }
354
355 #[test]
356 fn test_sweep_sell_fills_when_mid_rises_through_limit() {
357 let mut m = StochasticMatcher::new(1.0, 1.5, 140.0);
360 m.add_order(Order::new(1, Side::Sell, 99.5, 1.0));
361 let fills = m.process_quote(&state(99.0, 101.0));
363 assert_eq!(fills.len(), 1);
364 assert_eq!(fills[0].price, 99.5);
365 }
366
367 #[test]
370 fn test_passive_far_from_mid_has_near_zero_fill_probability() {
371 let a = 1.0;
374 let k = 1.5;
375 let mut fill_count = 0u32;
376 let trials = 1000;
377 for i in 0..trials {
378 let mut m = StochasticMatcher::new(1.0, k, a);
379 m.add_order(Order::new(i as u64 + 1, Side::Buy, 80.0, 1.0));
380 if !m.process_quote(&state(89.5, 90.5)).is_empty() {
382 fill_count += 1;
383 }
384 }
385 assert!(
386 fill_count <= 10,
387 "Expected near-zero fills, got {fill_count}/{trials}"
388 );
389 }
390
391 #[test]
392 fn test_passive_at_zero_delta_has_near_certain_fill() {
393 let a = 500.0;
396 let k = 1.5;
397 let mut miss_count = 0u32;
398 let trials = 100;
399 for i in 0..trials {
400 let mut m = StochasticMatcher::new(1.0, k, a);
401 m.add_order(Order::new(i as u64 + 1, Side::Buy, 100.0, 1.0));
403 if m.process_quote(&state(99.99, 100.01)).is_empty() {
404 miss_count += 1;
405 }
406 }
407 assert!(
408 miss_count == 0,
409 "Expected all fills, missed {miss_count}/{trials}"
410 );
411 }
412
413 #[test]
416 fn test_multiple_passive_bids_only_swept_one_fills() {
417 let mut m = StochasticMatcher::new(1.0, 1.5, 0.001);
426 m.add_order(Order::new(1, Side::Buy, 99.05, 1.0));
427 m.add_order(Order::new(2, Side::Buy, 98.0, 1.0));
428 m.add_order(Order::new(3, Side::Buy, 95.0, 1.0));
429 let fills = m.process_quote(&state(98.9, 99.1));
430 let ids: Vec<u64> = fills.iter().map(|f| f.order_id).collect();
431 assert!(ids.contains(&1), "Swept order must fill");
432 assert!(
433 !ids.contains(&2),
434 "Passive order at delta=1 must not fill with a=0.001"
435 );
436 assert!(
437 !ids.contains(&3),
438 "Passive order at delta=4 must not fill with a=0.001"
439 );
440 }
441
442 #[test]
443 fn test_two_aggressive_bids_both_fill_at_bbo() {
444 let mut m = StochasticMatcher::new(1.0, 1.5, 140.0);
446 m.add_order(Order::new(1, Side::Buy, 101.0, 1.0));
447 m.add_order(Order::new(2, Side::Buy, 102.0, 2.0));
448 let fills = m.process_quote(&state(99.0, 100.0));
449 assert_eq!(fills.len(), 2);
450 for f in &fills {
451 assert_eq!(f.price, 100.0);
452 }
453 }
454
455 #[test]
458 fn test_hawkes_disabled_by_default() {
459 let m = StochasticMatcher::new(1.0, 1.5, 10.0);
460 assert_eq!(m.hawkes_excitation(), 0.0);
461 assert_eq!(m.effective_a(), 10.0);
462 }
463
464 #[test]
465 fn test_hawkes_builder_sets_params() {
466 let m = StochasticMatcher::new(1.0, 1.5, 10.0).with_hawkes(0.5, 2.0);
467 assert_eq!(m.hawkes_excitation(), 0.0);
468 assert_eq!(m.effective_a(), 10.0); }
470
471 #[test]
472 fn test_hawkes_excitation_after_fills() {
473 let mut m = StochasticMatcher::new(1.0, 1.5, 500.0).with_hawkes(3.0, 2.0);
475
476 m.add_order(Order::new(1, Side::Buy, 101.0, 1.0));
478 let fills = m.process_quote(&state(99.0, 100.0));
479 assert_eq!(fills.len(), 1);
480 assert!((m.hawkes_excitation() - 3.0).abs() < 1e-10);
482 assert!((m.effective_a() - 503.0).abs() < 1e-10);
483 }
484
485 #[test]
486 fn test_hawkes_excitation_decays() {
487 let mut m = StochasticMatcher::new(1.0, 1.5, 500.0).with_hawkes(3.0, 2.0);
488
489 m.add_order(Order::new(1, Side::Buy, 101.0, 1.0));
491 m.process_quote(&state(99.0, 100.0));
492 assert!((m.hawkes_excitation() - 3.0).abs() < 1e-10);
493
494 m.process_quote(&state(99.0, 100.0)); let expected = 3.0 * (-2.0_f64).exp(); assert!((m.hawkes_excitation() - expected).abs() < 1e-10);
498 }
499
500 #[test]
501 fn test_hawkes_increases_fill_rate_statistically() {
502 const MAX_ATTEMPTS: usize = 3;
511 let mut best_margin: i32 = i32::MIN;
512 let mut best_base = 0u32;
513 let mut best_hawkes = 0u32;
514
515 for attempt in 0..MAX_ATTEMPTS {
516 let trials = 2000;
517 let mut base_fills = 0u32;
518 let mut hawkes_fills = 0u32;
519
520 for i in 0..trials {
522 let mut m = StochasticMatcher::new(1.0, 1.5, 1.0);
523 m.add_order(Order::new(i as u64 + 1, Side::Buy, 99.5, 1.0));
524 if !m.process_quote(&state(99.9, 100.1)).is_empty() {
525 base_fills += 1;
526 }
527 }
528
529 for i in 0..trials {
531 let mut m = StochasticMatcher::new(1.0, 1.5, 1.0).with_hawkes(10.0, 5.0);
532 m.add_order(Order::new(1000000 + i as u64, Side::Buy, 101.0, 1.0));
534 m.process_quote(&state(99.0, 100.0)); m.add_order(Order::new(2000000 + i as u64, Side::Buy, 101.0, 1.0));
536 m.process_quote(&state(99.0, 100.0)); m.add_order(Order::new(3000000 + i as u64, Side::Buy, 99.5, 1.0));
540 if !m.process_quote(&state(99.9, 100.1)).is_empty() {
541 hawkes_fills += 1;
542 }
543 }
544
545 let margin = hawkes_fills as i32 - base_fills as i32;
546 if margin > best_margin {
547 best_margin = margin;
548 best_base = base_fills;
549 best_hawkes = hawkes_fills;
550 }
551
552 if hawkes_fills > base_fills {
553 return; }
555
556 eprintln!(
557 "attempt {} failed: base={}, hawkes={}; retrying…",
558 attempt + 1,
559 base_fills,
560 hawkes_fills,
561 );
562 }
563
564 panic!(
565 "Hawkes should produce more fills after {} attempts. \
566 Best result: base={}, hawkes={} (margin={})",
567 MAX_ATTEMPTS, best_base, best_hawkes, best_margin,
568 );
569 }
570}