1use super::ExecutionVenue;
2use crate::Dir;
3use derive_more::Display;
4use rust_decimal::Decimal;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use strum_macros::{EnumString, IntoStaticStr};
8
9#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
11pub struct ExecutionInfo {
12 pub execution_venue: ExecutionVenue,
13 pub exchange_symbol: Option<String>,
15 pub tick_size: TickSize,
16 pub step_size: Decimal,
17 pub min_order_quantity: Decimal,
18 pub min_order_quantity_unit: MinOrderQuantityUnit,
19 pub is_delisted: bool,
20 pub initial_margin: Option<Decimal>,
21 pub maintenance_margin: Option<Decimal>,
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
25#[serde(rename_all = "snake_case")]
26pub enum TickSize {
27 #[schemars(title = "Simple|Decimal")]
28 Simple(Decimal),
29 #[schemars(title = "Varying")]
37 Varying { thresholds: Vec<(Decimal, Decimal)> },
38}
39
40impl TickSize {
41 pub fn simple(tick_size: Decimal) -> Self {
42 Self::Simple(tick_size)
43 }
44
45 pub fn increment(&self, mut price: Decimal, mut n: i32) -> Option<Decimal> {
58 if n == 0 {
59 return Some(price);
60 } else if n < 0 {
61 return self.decrement(price, -n);
62 }
63 let thresholds = match self {
65 TickSize::Simple(step) => {
66 return Some(price + *step * Decimal::from(n));
67 }
68 TickSize::Varying { thresholds } => thresholds,
69 };
70 if thresholds.is_empty() {
71 return None;
72 }
73 let mut i = thresholds.len() - 1;
75 let mut t_bound;
76 let mut t_step;
77 let mut u_bound;
78 loop {
79 (t_bound, t_step) = thresholds[i];
80 u_bound = thresholds.get(i + 1).map(|(b, _)| *b);
81 if price >= t_bound {
82 break;
83 }
84 if i == 0 {
85 return None;
87 }
88 i -= 1;
89 }
90 while n > 0 {
91 price += t_step;
92 if u_bound.is_some_and(|u| price >= u) {
93 i += 1;
96 (_, t_step) = thresholds[i];
97 u_bound = thresholds.get(i + 1).map(|(b, _)| *b);
98 }
99 n -= 1;
100 }
101 Some(price)
102 }
103
104 pub fn decrement(&self, mut price: Decimal, mut n: i32) -> Option<Decimal> {
105 if n == 0 {
106 return Some(price);
107 } else if n < 0 {
108 return self.increment(price, -n);
109 }
110 let thresholds = match self {
112 TickSize::Simple(step) => {
113 return Some(price - *step * Decimal::from(n));
114 }
115 TickSize::Varying { thresholds } => thresholds,
116 };
117 if thresholds.is_empty() {
118 return None;
119 }
120 let mut i = thresholds.len() - 1;
122 let mut t_bound;
123 let mut t_step;
124 loop {
125 (t_bound, t_step) = thresholds[i];
126 if price >= t_bound {
127 break;
128 }
129 if i == 0 {
130 return None;
132 }
133 i -= 1;
134 }
135 while n > 0 {
136 if price == t_bound {
137 if i == 0 {
139 return None;
140 }
141 i -= 1;
142 (t_bound, t_step) = thresholds[i];
143 }
144 price -= t_step;
145 n -= 1;
146 }
147 Some(price)
148 }
149
150 pub fn round_aggressive(&self, price: Decimal, dir: Dir) -> Option<Decimal> {
152 match dir {
153 Dir::Buy => self.round_up(price),
154 Dir::Sell => self.round_down(price),
155 }
156 }
157
158 pub fn round_passive(&self, price: Decimal, dir: Dir) -> Option<Decimal> {
160 match dir {
161 Dir::Buy => self.round_down(price),
162 Dir::Sell => self.round_up(price),
163 }
164 }
165
166 pub fn round_up(&self, price: Decimal) -> Option<Decimal> {
167 match self {
168 TickSize::Simple(tick_size) => {
169 if tick_size.is_zero() {
170 return None;
171 }
172 let remainder = price % tick_size;
173 if remainder.is_zero() {
174 Some(price)
176 } else if remainder > Decimal::ZERO {
177 Some(price - remainder + tick_size)
179 } else {
180 Some(price - remainder)
182 }
183 }
184 TickSize::Varying { thresholds } => {
185 if thresholds.is_empty() {
186 return None;
187 }
188
189 let mut tick_size = thresholds[0].1;
191 for (threshold, size) in thresholds {
192 if price >= *threshold {
193 tick_size = *size;
194 } else {
195 break;
196 }
197 }
198
199 if tick_size.is_zero() {
200 return None;
201 }
202
203 let remainder = price % tick_size;
204 if remainder.is_zero() {
205 Some(price)
207 } else if remainder > Decimal::ZERO {
208 Some(price - remainder + tick_size)
210 } else {
211 Some(price - remainder)
213 }
214 }
215 }
216 }
217
218 pub fn round_down(&self, price: Decimal) -> Option<Decimal> {
219 match self {
220 TickSize::Simple(tick_size) => {
221 if tick_size.is_zero() {
222 return None;
223 }
224 let remainder = price % tick_size;
225 if remainder.is_zero() {
226 Some(price)
228 } else if remainder > Decimal::ZERO {
229 Some(price - remainder)
231 } else {
232 Some(price - remainder - tick_size)
234 }
235 }
236 TickSize::Varying { thresholds } => {
237 if thresholds.is_empty() {
238 return None;
239 }
240
241 let mut tick_size = thresholds[0].1;
243 for (threshold, size) in thresholds {
244 if price >= *threshold {
245 tick_size = *size;
246 } else {
247 break;
248 }
249 }
250
251 if tick_size.is_zero() {
252 return None;
253 }
254
255 let remainder = price % tick_size;
256 if remainder.is_zero() {
257 Some(price)
259 } else if remainder > Decimal::ZERO {
260 Some(price - remainder)
262 } else {
263 Some(price - remainder - tick_size)
265 }
266 }
267 }
268 }
269
270 pub fn signed_tick_distance(&self, from: Decimal, to: Decimal) -> Option<Decimal> {
277 if from == to {
278 return Some(Decimal::ZERO);
279 } else if from > to {
280 return self.signed_tick_distance(to, from).map(|d| -d);
281 }
282 match self {
284 TickSize::Simple(step) => {
285 if step.is_zero() {
286 None
287 } else {
288 Some((to - from) / *step)
289 }
290 }
291 TickSize::Varying { thresholds } => {
292 let mut ticks = Decimal::ZERO;
293 let mut price = from;
294 let mut i = thresholds.iter();
295 let mut step = None;
296 while let Some((lower, lower_step)) = i.next() {
298 if price >= *lower {
299 step = Some(*lower_step);
300 break;
301 }
302 }
303 let mut step = step?; loop {
305 if step.is_zero() {
306 return None;
307 }
308 match i.next() {
309 Some((upper, next_step)) => {
310 if to >= *upper {
312 ticks += (upper - price) / step;
313 price = *upper;
314 step = *next_step;
315 } else {
316 ticks += (to - price) / step;
318 break;
319 }
320 }
321 None => {
322 ticks += (to - price) / step;
324 break;
325 }
326 }
327 }
328 Some(ticks)
329 }
330 }
331 }
332}
333
334impl ExecutionInfo {
335 pub fn min_quantity_in_base_units(&self, price: Option<Decimal>) -> Option<Decimal> {
339 match self.min_order_quantity_unit {
340 MinOrderQuantityUnit::Base => Some(self.min_order_quantity),
341 MinOrderQuantityUnit::Quote => {
342 if let Some(p) = price {
343 if p.is_zero() {
344 None
345 } else {
346 Some(self.min_order_quantity / p)
347 }
348 } else {
349 None
351 }
352 }
353 }
354 }
355
356 pub fn round_quantity(&self, quantity: Decimal) -> Decimal {
358 round_quantity_with_step(quantity, self.step_size)
359 }
360
361 pub fn round_quantity_up(&self, quantity: Decimal) -> Decimal {
363 round_quantity_up_with_step(quantity, self.step_size)
364 }
365
366 pub fn round_quantity_down(&self, quantity: Decimal) -> Decimal {
368 round_quantity_down_with_step(quantity, self.step_size)
369 }
370}
371
372pub fn round_quantity_with_step(quantity: Decimal, step_size: Decimal) -> Decimal {
374 if step_size.is_zero() {
375 return quantity;
376 }
377 let remainder = quantity % step_size;
378 if remainder.is_zero() {
379 quantity
380 } else {
381 let half_step = step_size / Decimal::from(2);
382 if remainder >= half_step {
383 quantity - remainder + step_size
385 } else {
386 quantity - remainder
388 }
389 }
390}
391
392pub fn round_quantity_up_with_step(quantity: Decimal, step_size: Decimal) -> Decimal {
394 if step_size.is_zero() {
395 return quantity;
396 }
397
398 let remainder = quantity % step_size;
399 if remainder.is_zero() {
400 quantity
401 } else {
402 quantity - remainder + step_size
403 }
404}
405
406pub fn round_quantity_down_with_step(quantity: Decimal, step_size: Decimal) -> Decimal {
408 if step_size.is_zero() {
409 return quantity;
410 }
411
412 let remainder = quantity % step_size;
413 if remainder.is_zero() {
414 quantity
415 } else {
416 quantity - remainder
417 }
418}
419
420#[derive(
422 Default,
423 Debug,
424 Display,
425 Clone,
426 Copy,
427 EnumString,
428 IntoStaticStr,
429 Serialize,
430 Deserialize,
431 JsonSchema,
432)]
433#[cfg_attr(feature = "juniper", derive(juniper::GraphQLEnum))]
434#[serde(tag = "unit", rename_all = "snake_case")]
435#[strum(serialize_all = "snake_case")]
436pub enum MinOrderQuantityUnit {
437 #[default]
438 #[schemars(title = "Base")]
439 Base,
440 #[schemars(title = "Quote")]
441 Quote,
442}
443
444#[cfg(feature = "postgres")]
445crate::to_sql_str!(MinOrderQuantityUnit);
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use rust_decimal_macros::dec;
451
452 #[test]
453 fn test_round_quantity() {
454 let step_size = dec!(0.1);
455
456 assert_eq!(round_quantity_with_step(dec!(1.24), step_size), dec!(1.2));
457 assert_eq!(round_quantity_with_step(dec!(1.25), step_size), dec!(1.3));
458 assert_eq!(round_quantity_with_step(dec!(0.04), step_size), dec!(0.0));
459
460 assert_eq!(round_quantity_up_with_step(dec!(1.21), step_size), dec!(1.3));
461 assert_eq!(round_quantity_up_with_step(dec!(0.01), step_size), dec!(0.1));
462 assert_eq!(round_quantity_up_with_step(dec!(0.0), step_size), dec!(0.0));
463
464 assert_eq!(round_quantity_down_with_step(dec!(1.29), step_size), dec!(1.2));
465 assert_eq!(round_quantity_down_with_step(dec!(0.09), step_size), dec!(0.0));
466 assert_eq!(round_quantity_down_with_step(dec!(0.0), step_size), dec!(0.0));
467 }
468
469 #[test]
470 fn test_tick_size_decrement() {
471 let tick = TickSize::Simple(dec!(0.01));
472 assert_eq!(tick.increment(dec!(100.00), -1), Some(dec!(99.99)));
473
474 let tick_varying = TickSize::Varying {
475 thresholds: vec![
476 (dec!(0), dec!(0.01)),
477 (dec!(100), dec!(0.05)),
478 (dec!(500), dec!(0.10)),
479 ],
480 };
481
482 assert_eq!(tick_varying.increment(dec!(100.00), -1), Some(dec!(99.99)));
484 assert_eq!(tick_varying.increment(dec!(100.05), -1), Some(dec!(100)));
485 assert_eq!(tick_varying.increment(dec!(500.00), -1), Some(dec!(499.95)));
486 }
487
488 #[test]
489 fn test_tick_size_zero() {
490 let tick = TickSize::Varying {
491 thresholds: vec![
492 (dec!(0), dec!(0.01)),
493 (dec!(100), dec!(0.05)),
494 (dec!(500), dec!(0.10)),
495 ],
496 };
497
498 assert_eq!(tick.increment(dec!(100.00), 0), Some(dec!(100.00)));
499 assert_eq!(tick.increment(dec!(150.00), 0), Some(dec!(150.00)));
500 assert_eq!(tick.increment(dec!(50.00), 0), Some(dec!(50.00)));
501
502 }
505
506 #[test]
507 fn test_tick_size_boundary_behavior() {
508 let tick = TickSize::Varying {
509 thresholds: vec![
510 (dec!(0), dec!(0.01)),
511 (dec!(100), dec!(0.05)),
512 (dec!(500), dec!(0.10)),
513 ],
514 };
515
516 assert_eq!(tick.increment(dec!(100.00), 1), Some(dec!(100.05)));
518 assert_eq!(tick.increment(dec!(500.00), 1), Some(dec!(500.10)));
519
520 assert_eq!(tick.increment(dec!(100.00), -1), Some(dec!(99.99)));
522 assert_eq!(tick.increment(dec!(500.00), -1), Some(dec!(499.95)));
523 }
524
525 #[test]
526 fn test_tick_size_multiple_crossings() {
527 let tick = TickSize::Varying {
528 thresholds: vec![
529 (dec!(0), dec!(0.01)),
530 (dec!(100), dec!(0.05)),
531 (dec!(500), dec!(0.10)),
532 ],
533 };
534
535 let result = tick.increment(dec!(50), 12000);
537 assert_eq!(result, Some(dec!(450)));
538
539 let result = tick.increment(dec!(600), -5100);
547 assert_eq!(result, Some(dec!(295.00)));
548 }
549
550 #[test]
551 fn test_round_up_simple() {
552 let tick = TickSize::Simple(dec!(0.01));
553
554 assert_eq!(tick.round_up(dec!(100.00)), Some(dec!(100.00)));
556
557 assert_eq!(tick.round_up(dec!(100.001)), Some(dec!(100.01)));
559 assert_eq!(tick.round_up(dec!(100.005)), Some(dec!(100.01)));
560 assert_eq!(tick.round_up(dec!(100.009)), Some(dec!(100.01)));
561
562 assert_eq!(tick.round_up(dec!(-99.995)), Some(dec!(-99.99)));
564 assert_eq!(tick.round_up(dec!(-100.00)), Some(dec!(-100.00)));
565
566 let zero_tick = TickSize::Simple(dec!(0));
568 assert_eq!(zero_tick.round_up(dec!(100.123)), None);
569 }
570
571 #[test]
572 fn test_round_down_simple() {
573 let tick = TickSize::Simple(dec!(0.01));
574
575 assert_eq!(tick.round_down(dec!(100.00)), Some(dec!(100.00)));
577
578 assert_eq!(tick.round_down(dec!(100.001)), Some(dec!(100.00)));
580 assert_eq!(tick.round_down(dec!(100.005)), Some(dec!(100.00)));
581 assert_eq!(tick.round_down(dec!(100.009)), Some(dec!(100.00)));
582
583 assert_eq!(tick.round_down(dec!(-99.995)), Some(dec!(-100.00)));
585 assert_eq!(tick.round_down(dec!(-100.00)), Some(dec!(-100.00)));
586
587 let zero_tick = TickSize::Simple(dec!(0));
589 assert_eq!(zero_tick.round_down(dec!(100.123)), None);
590 }
591
592 #[test]
593 fn test_round_up_varying() {
594 let tick = TickSize::Varying {
595 thresholds: vec![
596 (dec!(0), dec!(0.01)),
597 (dec!(100), dec!(0.05)),
598 (dec!(500), dec!(0.10)),
599 ],
600 };
601
602 assert_eq!(tick.round_up(dec!(50.00)), Some(dec!(50.00)));
604 assert_eq!(tick.round_up(dec!(50.001)), Some(dec!(50.01)));
605 assert_eq!(tick.round_up(dec!(99.996)), Some(dec!(100.00)));
606
607 assert_eq!(tick.round_up(dec!(100.00)), Some(dec!(100.00)));
609 assert_eq!(tick.round_up(dec!(100.01)), Some(dec!(100.05)));
610 assert_eq!(tick.round_up(dec!(100.03)), Some(dec!(100.05)));
611 assert_eq!(tick.round_up(dec!(100.05)), Some(dec!(100.05)));
612
613 assert_eq!(tick.round_up(dec!(500.00)), Some(dec!(500.00)));
615 assert_eq!(tick.round_up(dec!(500.01)), Some(dec!(500.10)));
616 assert_eq!(tick.round_up(dec!(500.05)), Some(dec!(500.10)));
617 assert_eq!(tick.round_up(dec!(500.09)), Some(dec!(500.10)));
618 assert_eq!(tick.round_up(dec!(500.10)), Some(dec!(500.10)));
619 }
620
621 #[test]
622 fn test_round_down_varying() {
623 let tick = TickSize::Varying {
624 thresholds: vec![
625 (dec!(0), dec!(0.01)),
626 (dec!(100), dec!(0.05)),
627 (dec!(500), dec!(0.10)),
628 ],
629 };
630
631 assert_eq!(tick.round_down(dec!(50.00)), Some(dec!(50.00)));
633 assert_eq!(tick.round_down(dec!(50.001)), Some(dec!(50.00)));
634 assert_eq!(tick.round_down(dec!(99.999)), Some(dec!(99.99)));
635
636 assert_eq!(tick.round_down(dec!(100.00)), Some(dec!(100.00)));
638 assert_eq!(tick.round_down(dec!(100.01)), Some(dec!(100.00)));
639 assert_eq!(tick.round_down(dec!(100.04)), Some(dec!(100.00)));
640 assert_eq!(tick.round_down(dec!(100.05)), Some(dec!(100.05)));
641 assert_eq!(tick.round_down(dec!(100.09)), Some(dec!(100.05)));
642
643 assert_eq!(tick.round_down(dec!(500.00)), Some(dec!(500.00)));
645 assert_eq!(tick.round_down(dec!(500.01)), Some(dec!(500.00)));
646 assert_eq!(tick.round_down(dec!(500.09)), Some(dec!(500.00)));
647 assert_eq!(tick.round_down(dec!(500.10)), Some(dec!(500.10)));
648 assert_eq!(tick.round_down(dec!(500.19)), Some(dec!(500.10)));
649 }
650
651 #[test]
652 fn test_round_aggressive() {
653 let tick = TickSize::Simple(dec!(0.01));
654
655 assert_eq!(tick.round_aggressive(dec!(100.001), Dir::Buy), Some(dec!(100.01)));
657 assert_eq!(tick.round_aggressive(dec!(100.00), Dir::Buy), Some(dec!(100.00)));
658
659 assert_eq!(tick.round_aggressive(dec!(100.009), Dir::Sell), Some(dec!(100.00)));
661 assert_eq!(tick.round_aggressive(dec!(100.00), Dir::Sell), Some(dec!(100.00)));
662 }
663
664 #[test]
665 fn test_round_passive() {
666 let tick = TickSize::Simple(dec!(0.01));
667
668 assert_eq!(tick.round_passive(dec!(100.009), Dir::Buy), Some(dec!(100.00)));
670 assert_eq!(tick.round_passive(dec!(100.00), Dir::Buy), Some(dec!(100.00)));
671
672 assert_eq!(tick.round_passive(dec!(100.001), Dir::Sell), Some(dec!(100.01)));
674 assert_eq!(tick.round_passive(dec!(100.00), Dir::Sell), Some(dec!(100.00)));
675 }
676
677 #[test]
678 fn test_rounding_with_large_tick_sizes() {
679 let tick = TickSize::Simple(dec!(0.25));
681
682 assert_eq!(tick.round_up(dec!(10.00)), Some(dec!(10.00)));
683 assert_eq!(tick.round_up(dec!(10.10)), Some(dec!(10.25)));
684 assert_eq!(tick.round_up(dec!(10.25)), Some(dec!(10.25)));
685 assert_eq!(tick.round_up(dec!(10.30)), Some(dec!(10.50)));
686
687 assert_eq!(tick.round_down(dec!(10.00)), Some(dec!(10.00)));
688 assert_eq!(tick.round_down(dec!(10.10)), Some(dec!(10.00)));
689 assert_eq!(tick.round_down(dec!(10.25)), Some(dec!(10.25)));
690 assert_eq!(tick.round_down(dec!(10.30)), Some(dec!(10.25)));
691 assert_eq!(tick.round_down(dec!(10.50)), Some(dec!(10.50)));
692 assert_eq!(tick.round_down(dec!(10.60)), Some(dec!(10.50)));
693 }
694}