lfest/
order_margin.rs

1use fpdec::{Dec, Decimal};
2use getset::{CopyGetters, Getters};
3use tracing::trace;
4
5use crate::{
6    exchange::ActiveLimitOrders,
7    prelude::{OrderId, Position},
8    types::{Currency, LimitOrder, MarginCurrency, Pending, Side},
9    utils::{max, min},
10};
11
12/// An implementation for computing the order margin online, aka with every change to the active orders.
13#[derive(Debug, Clone, Default, CopyGetters, Getters)]
14pub(crate) struct OrderMargin<Q, UserOrderId>
15where
16    Q: Currency,
17    UserOrderId: Clone + Default,
18{
19    #[getset(get = "pub(crate)")]
20    active_limit_orders: ActiveLimitOrders<Q, UserOrderId>,
21}
22
23impl<Q, UserOrderId> OrderMargin<Q, UserOrderId>
24where
25    Q: Currency,
26    Q::PairedCurrency: MarginCurrency,
27    UserOrderId: Clone + std::fmt::Debug + std::cmp::PartialEq + Default,
28{
29    pub(crate) fn update(&mut self, order: &LimitOrder<Q, UserOrderId, Pending<Q>>) {
30        assert!(order.remaining_quantity() > Q::new_zero());
31        trace!("update_order: order: {order:?}");
32        if let Some(active_order) = self.active_limit_orders.insert(order.id(), order.clone()) {
33            assert_ne!(
34                &active_order, order,
35                "An update to an order should not be the same as the existing one"
36            );
37            assert!(order.remaining_quantity() < active_order.remaining_quantity(), "An update to an existing order must mean the new order has less quantity than the tracked order.");
38            debug_assert_eq!(order.id(), active_order.id());
39
40            // when an existing limit order is updated for margin purposes here, its quantity is always reduced.
41            let removed_qty = active_order.remaining_quantity() - order.remaining_quantity();
42            assert!(removed_qty > Q::new_zero());
43        }
44    }
45
46    /// Remove an order from being tracked for margin purposes.
47    pub(crate) fn remove(&mut self, order_id: OrderId) {
48        self.active_limit_orders
49            .remove(&order_id)
50            .expect("Its an internal method call; it must work");
51    }
52
53    /// The margin requirement for all the tracked orders.
54    pub(crate) fn order_margin(
55        &self,
56        init_margin_req: Decimal,
57        position: &Position<Q>,
58    ) -> Q::PairedCurrency {
59        Self::order_margin_internal(&self.active_limit_orders, init_margin_req, position)
60    }
61
62    /// The margin requirement for all the tracked orders.
63    fn order_margin_internal(
64        active_limit_orders: &ActiveLimitOrders<Q, UserOrderId>,
65        init_margin_req: Decimal,
66        position: &Position<Q>,
67    ) -> Q::PairedCurrency {
68        debug_assert!(init_margin_req <= Dec!(1));
69        trace!("order_margin_internal: position: {position:?}, active_limit_orders: {active_limit_orders:?}");
70
71        let mut buy_orders = Vec::from_iter(
72            active_limit_orders
73                .values()
74                .filter(|order| matches!(order.side(), Side::Buy))
75                .map(|order| (order.limit_price(), order.remaining_quantity())),
76        );
77        buy_orders.sort_by_key(|order| order.0.into_negative());
78
79        let mut sell_orders = Vec::from_iter(
80            active_limit_orders
81                .values()
82                .filter(|order| matches!(order.side(), Side::Sell))
83                .map(|order| (order.limit_price(), order.remaining_quantity())),
84        );
85        sell_orders.sort_by_key(|order| order.0);
86
87        match position {
88            Position::Neutral => {}
89            Position::Long(inner) => {
90                let mut outstanding_pos_qty = inner.quantity();
91                let mut i = 0;
92                while outstanding_pos_qty > Q::new_zero() {
93                    if i >= sell_orders.len() {
94                        break;
95                    }
96                    let new_qty = max(sell_orders[i].1 - outstanding_pos_qty, Q::new_zero());
97                    trace!("sells order_qty: {}, outstanding_pos_qty: {outstanding_pos_qty} new_qty: {new_qty}", sell_orders[i].1);
98                    outstanding_pos_qty -= min(sell_orders[i].1, outstanding_pos_qty);
99                    sell_orders[i].1 = new_qty;
100                    i += 1;
101                }
102            }
103            Position::Short(inner) => {
104                let mut outstanding_pos_qty = inner.quantity();
105                let mut i = 0;
106                while outstanding_pos_qty > Q::new_zero() {
107                    if i >= buy_orders.len() {
108                        break;
109                    }
110                    let new_qty = max(buy_orders[i].1 - outstanding_pos_qty, Q::new_zero());
111                    trace!("buys order_qty: {}, outstanding_pos_qty: {outstanding_pos_qty} new_qty: {new_qty}", buy_orders[i].1);
112                    outstanding_pos_qty -= min(buy_orders[i].1, outstanding_pos_qty);
113                    buy_orders[i].1 = new_qty;
114                    i += 1;
115                }
116            }
117        }
118
119        let mut buy_value = Q::PairedCurrency::new_zero();
120        buy_orders
121            .iter()
122            .for_each(|(price, qty)| buy_value += qty.convert(*price));
123
124        let mut sell_value = Q::PairedCurrency::new_zero();
125        sell_orders
126            .iter()
127            .for_each(|(price, qty)| sell_value += qty.convert(*price));
128
129        max(buy_value, sell_value) * init_margin_req
130    }
131
132    /// Get the order margin if a new order were to be added.
133    pub(crate) fn order_margin_with_order(
134        &self,
135        order: &LimitOrder<Q, UserOrderId, Pending<Q>>,
136        init_margin_req: Decimal,
137        position: &Position<Q>,
138    ) -> Q::PairedCurrency {
139        let mut active_orders = self.active_limit_orders.clone();
140        assert!(active_orders.insert(order.id(), order.clone()).is_none());
141        Self::order_margin_internal(&active_orders, init_margin_req, position)
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::{prelude::*, MockTransactionAccounting, TEST_FEE_MAKER};
149
150    #[test_case::test_matrix(
151        [1, 2, 5]
152    )]
153    fn order_margin_neutral_no_orders(leverage: u32) {
154        let order_margin = OrderMargin::<_, ()>::default();
155
156        let init_margin_req = Dec!(1.0) / Decimal::from(leverage);
157
158        let position = Position::<BaseCurrency>::Neutral;
159        assert_eq!(
160            order_margin.order_margin(init_margin_req, &position),
161            quote!(0)
162        );
163    }
164
165    #[test_case::test_matrix(
166        [1, 2, 5],
167        [1, 2, 5],
168        [100, 200, 300]
169    )]
170    fn order_margin_long_no_orders(leverage: u32, position_qty: u32, entry_price: u32) {
171        let order_margin = OrderMargin::<_, ()>::default();
172
173        let mut accounting = MockTransactionAccounting::default();
174        let qty = BaseCurrency::new(Decimal::from(position_qty));
175        let entry_price = QuoteCurrency::new(Decimal::from(entry_price));
176        let init_margin_req = Dec!(1.0) / Decimal::from(leverage);
177        let position = Position::Long(PositionInner::new(
178            qty,
179            entry_price,
180            &mut accounting,
181            init_margin_req,
182            quote!(0),
183        ));
184
185        assert_eq!(
186            order_margin.order_margin(init_margin_req, &position),
187            quote!(0)
188        );
189    }
190
191    #[test_case::test_matrix(
192        [1, 2, 5],
193        [1, 2, 5],
194        [100, 200, 300]
195    )]
196    fn order_margin_short_no_orders(leverage: u32, position_qty: u32, entry_price: u32) {
197        let order_margin = OrderMargin::<_, ()>::default();
198
199        let mut accounting = MockTransactionAccounting::default();
200        let qty = BaseCurrency::new(Decimal::from(position_qty));
201        let entry_price = QuoteCurrency::new(Decimal::from(entry_price));
202        let init_margin_req = Dec!(1.0) / Decimal::from(leverage);
203        let position = Position::Short(PositionInner::new(
204            qty,
205            entry_price,
206            &mut accounting,
207            init_margin_req,
208            quote!(0),
209        ));
210
211        assert_eq!(
212            order_margin.order_margin(init_margin_req, &position),
213            quote!(0)
214        );
215    }
216
217    #[test_case::test_matrix(
218        [1, 2, 5],
219        [Side::Buy, Side::Sell],
220        [100, 150, 200],
221        [1, 2, 3],
222        [1, 2, 3]
223    )]
224    fn order_margin_neutral_orders_of_same_side(
225        leverage: u32,
226        side: Side,
227        limit_price: u32,
228        qty: u32,
229        n: usize,
230    ) {
231        let mut order_margin = OrderMargin::<_, ()>::default();
232
233        let init_margin_req = Dec!(1.0) / Decimal::from(leverage);
234
235        let qty = BaseCurrency::new(Decimal::from(qty));
236        let limit_price = QuoteCurrency::new(Decimal::from(limit_price));
237
238        let orders = Vec::from_iter((0..n).map(|i| {
239            let order = LimitOrder::new(side, limit_price, qty).unwrap();
240            let meta =
241                ExchangeOrderMeta::new((i as u64).into(), Into::<TimestampNs>::into(i as i64));
242            order.into_pending(meta)
243        }));
244        orders.iter().for_each(|order| order_margin.update(&order));
245
246        let mult = QuoteCurrency::new(Decimal::from(n as u64));
247        assert_eq!(
248            order_margin.order_margin(init_margin_req, &Position::<BaseCurrency>::Neutral),
249            mult * qty.convert(limit_price) * init_margin_req
250        );
251
252        orders
253            .iter()
254            .for_each(|order| order_margin.remove(order.id()));
255        assert_eq!(
256            order_margin.order_margin(init_margin_req, &Position::<BaseCurrency>::Neutral),
257            quote!(0)
258        );
259    }
260
261    #[test_case::test_matrix(
262        [1, 2, 5],
263        [Side::Buy],
264        [100, 150, 200],
265        [1, 2, 3],
266        [1, 2, 3]
267    )]
268    fn order_margin_neutral_orders_of_opposite_side(
269        leverage: u32,
270        side: Side,
271        limit_price: u32,
272        qty: u32,
273        n: usize,
274    ) {
275        let mut order_margin = OrderMargin::<_, ()>::default();
276
277        let init_margin_req = Dec!(1.0) / Decimal::from(leverage);
278
279        let qty = BaseCurrency::new(Decimal::from(qty));
280        let limit_price = QuoteCurrency::new(Decimal::from(limit_price));
281
282        let buy_orders = Vec::from_iter((0..n).map(|i| {
283            let order = LimitOrder::new(side, limit_price, qty).unwrap();
284            let meta = ExchangeOrderMeta::new((i as u64).into(), (i as i64).into());
285            order.into_pending(meta)
286        }));
287        buy_orders.iter().for_each(|order| {
288            order_margin.update(&order);
289        });
290
291        let sell_orders = Vec::from_iter((0..n).map(|i| {
292            let order = LimitOrder::new(side.inverted(), limit_price, qty).unwrap();
293            let meta = ExchangeOrderMeta::new(((n + i) as u64).into(), ((n + i) as i64).into());
294            order.into_pending(meta)
295        }));
296        sell_orders.iter().for_each(|order| {
297            order_margin.update(&order);
298        });
299
300        let mult = QuoteCurrency::new(Decimal::from(n as u64));
301        assert_eq!(
302            order_margin.order_margin(init_margin_req, &Position::<BaseCurrency>::Neutral,),
303            mult * qty.convert(limit_price) * init_margin_req
304        );
305
306        buy_orders
307            .iter()
308            .for_each(|order| order_margin.remove(order.id()));
309        sell_orders
310            .iter()
311            .for_each(|order| order_margin.remove(order.id()));
312        assert_eq!(
313            order_margin.order_margin(init_margin_req, &Position::<BaseCurrency>::Neutral),
314            quote!(0)
315        );
316    }
317
318    /// The position always cancels out the orders, so the order margin is zero.
319    #[test_case::test_matrix(
320        [1, 2, 5],
321        [Side::Buy, Side::Sell],
322        [70, 90, 110],
323        [1, 2, 3],
324        [85, 100, 125]
325    )]
326    fn order_margin_long_orders_of_same_qty(
327        leverage: u32,
328        side: Side,
329        limit_price: u32,
330        qty: u32,
331        pos_entry_price: u32,
332    ) {
333        let mut order_margin = OrderMargin::<_, ()>::default();
334
335        let init_margin_req = Dec!(1.0) / Decimal::from(leverage);
336
337        let qty = BaseCurrency::new(Decimal::from(qty));
338        let limit_price = QuoteCurrency::new(Decimal::from(limit_price));
339
340        let order = LimitOrder::new(side, limit_price, qty).unwrap();
341        let meta = ExchangeOrderMeta::new(0.into(), 0.into());
342        let order = order.into_pending(meta);
343        order_margin.update(&order);
344
345        let mut accounting = MockTransactionAccounting::default();
346        let pos_entry_price = QuoteCurrency::new(Decimal::from(pos_entry_price));
347        let fees = qty.convert(limit_price);
348        let position = match side {
349            Side::Buy => Position::Short(PositionInner::new(
350                qty,
351                pos_entry_price,
352                &mut accounting,
353                init_margin_req,
354                fees,
355            )),
356            Side::Sell => Position::Long(PositionInner::new(
357                qty,
358                pos_entry_price,
359                &mut accounting,
360                init_margin_req,
361                fees,
362            )),
363        };
364
365        assert_eq!(
366            order_margin.order_margin(init_margin_req, &position),
367            quote!(0),
368            "The position quantity always cancels out the limit orders. So margin requirement is 0."
369        );
370    }
371
372    #[test_case::test_matrix(
373        [1, 2, 5],
374        [Side::Buy, Side::Sell],
375        [70, 90, 110],
376        [1, 2, 3]
377    )]
378    fn order_margin_neutral_update_partial_fills(
379        leverage: u32,
380        side: Side,
381        limit_price: u32,
382        qty: u32,
383    ) {
384        let mut order_margin = OrderMargin::<_, ()>::default();
385
386        let init_margin_req = Dec!(1.0) / Decimal::from(leverage);
387
388        let qty = BaseCurrency::new(Decimal::from(qty));
389        let limit_price = QuoteCurrency::new(Decimal::from(limit_price));
390
391        let order = LimitOrder::new(side, limit_price, qty).unwrap();
392        let meta = ExchangeOrderMeta::new(0.into(), 0.into());
393        let mut order = order.into_pending(meta);
394        order_margin.update(&order);
395
396        // Now partially fill the order
397        let filled_qty = qty / base!(2);
398        assert!(order.fill(filled_qty, 0.into()).is_none());
399        order_margin.update(&order);
400
401        let remaining_qty = order.remaining_quantity();
402        assert_eq!(remaining_qty, filled_qty);
403        assert_eq!(
404            order_margin.order_margin(init_margin_req, &Position::Neutral),
405            remaining_qty.convert(limit_price) * init_margin_req
406        );
407    }
408
409    #[test]
410    #[tracing_test::traced_test]
411    fn order_margin_no_position() {
412        let position = Position::default();
413        let init_margin_req = Dec!(1);
414        let mut order_margin = OrderMargin::default();
415
416        assert_eq!(
417            order_margin.order_margin(init_margin_req, &position),
418            quote!(0)
419        );
420
421        let qty = base!(1);
422        let limit_price = quote!(90);
423        let order = LimitOrder::new(Side::Buy, limit_price, qty).unwrap();
424        let meta = ExchangeOrderMeta::new(0.into(), 0.into());
425        let order = order.into_pending(meta);
426        order_margin.update(&order);
427        assert_eq!(
428            order_margin.order_margin(init_margin_req, &position),
429            quote!(90)
430        );
431
432        let limit_price = quote!(100);
433        let order = LimitOrder::new(Side::Sell, limit_price, qty).unwrap();
434        let meta = ExchangeOrderMeta::new(1.into(), 0.into());
435        let order = order.into_pending(meta);
436        order_margin.update(&order);
437        assert_eq!(
438            order_margin.order_margin(init_margin_req, &position),
439            quote!(100)
440        );
441
442        let limit_price = quote!(120);
443        let qty = base!(1);
444        let order = LimitOrder::new(Side::Sell, limit_price, qty).unwrap();
445        let meta = ExchangeOrderMeta::new(2.into(), 0.into());
446        let order = order.into_pending(meta);
447        order_margin.update(&order);
448        assert_eq!(
449            order_margin.order_margin(init_margin_req, &position),
450            quote!(220)
451        );
452    }
453
454    #[test]
455    #[tracing_test::traced_test]
456    fn order_margin_with_long() {
457        let mut accounting = InMemoryTransactionAccounting::new(quote!(1000));
458        let mut order_margin = OrderMargin::default();
459        let init_margin_req = Dec!(1);
460
461        let qty = base!(1);
462        let entry_price = quote!(100);
463        let fee = qty.convert(entry_price) * TEST_FEE_MAKER;
464        let position = Position::Long(PositionInner::new(
465            qty,
466            entry_price,
467            &mut accounting,
468            init_margin_req,
469            fee,
470        ));
471        let init_margin_req = Dec!(1);
472
473        assert_eq!(
474            order_margin.order_margin(init_margin_req, &position),
475            quote!(0)
476        );
477
478        let limit_price = quote!(90);
479        let qty = base!(1);
480        let order = LimitOrder::new(Side::Buy, limit_price, qty).unwrap();
481        let meta = ExchangeOrderMeta::new(0.into(), 0.into());
482        let order = order.into_pending(meta);
483        order_margin.update(&order);
484        assert_eq!(
485            order_margin.order_margin(init_margin_req, &position),
486            quote!(90)
487        );
488
489        let limit_price = quote!(100);
490        let order = LimitOrder::new(Side::Sell, limit_price, qty).unwrap();
491        let meta = ExchangeOrderMeta::new(1.into(), 0.into());
492        let order = order.into_pending(meta);
493        order_margin.update(&order);
494        assert_eq!(
495            order_margin.order_margin(init_margin_req, &position),
496            quote!(90)
497        );
498
499        let limit_price = quote!(120);
500        let order = LimitOrder::new(Side::Sell, limit_price, qty).unwrap();
501        let meta = ExchangeOrderMeta::new(2.into(), 0.into());
502        let order = order.into_pending(meta);
503        order_margin.update(&order);
504        assert_eq!(
505            order_margin.order_margin(init_margin_req, &position),
506            quote!(120)
507        );
508
509        let limit_price = quote!(95);
510        let order = LimitOrder::new(Side::Buy, limit_price, qty).unwrap();
511        let meta = ExchangeOrderMeta::new(3.into(), 0.into());
512        let order = order.into_pending(meta);
513        order_margin.update(&order);
514        assert_eq!(
515            order_margin.order_margin(init_margin_req, &position),
516            quote!(185)
517        );
518    }
519
520    #[test]
521    #[tracing_test::traced_test]
522    fn order_margin_with_short() {
523        let mut accounting = InMemoryTransactionAccounting::new(quote!(1000));
524        let mut order_margin = OrderMargin::default();
525        let init_margin_req = Dec!(1);
526
527        let qty = base!(1);
528        let entry_price = quote!(100);
529        let fee = qty.convert(entry_price) * TEST_FEE_MAKER;
530        let position = Position::Short(PositionInner::new(
531            qty,
532            entry_price,
533            &mut accounting,
534            init_margin_req,
535            fee,
536        ));
537        let init_margin_req = Dec!(1);
538
539        assert_eq!(
540            order_margin.order_margin(init_margin_req, &position),
541            quote!(0)
542        );
543
544        let limit_price = quote!(90);
545        let qty = base!(1);
546        let order = LimitOrder::new(Side::Buy, limit_price, qty).unwrap();
547        let meta = ExchangeOrderMeta::new(0.into(), 0.into());
548        let order = order.into_pending(meta);
549        order_margin.update(&order);
550        assert_eq!(
551            order_margin.order_margin(init_margin_req, &position),
552            quote!(0)
553        );
554
555        let limit_price = quote!(100);
556        let order = LimitOrder::new(Side::Sell, limit_price, qty).unwrap();
557        let meta = ExchangeOrderMeta::new(1.into(), 0.into());
558        let order = order.into_pending(meta);
559        order_margin.update(&order);
560        assert_eq!(
561            order_margin.order_margin(init_margin_req, &position),
562            quote!(100)
563        );
564
565        let limit_price = quote!(120);
566        let order = LimitOrder::new(Side::Sell, limit_price, qty).unwrap();
567        let meta = ExchangeOrderMeta::new(2.into(), 0.into());
568        let order = order.into_pending(meta);
569        order_margin.update(&order);
570        assert_eq!(
571            order_margin.order_margin(init_margin_req, &position),
572            quote!(220)
573        );
574
575        let limit_price = quote!(95);
576        let order = LimitOrder::new(Side::Buy, limit_price, qty).unwrap();
577        let meta = ExchangeOrderMeta::new(3.into(), 0.into());
578        let order = order.into_pending(meta);
579        order_margin.update(&order);
580        assert_eq!(
581            order_margin.order_margin(init_margin_req, &position),
582            quote!(220)
583        );
584    }
585}