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#[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 let removed_qty = active_order.remaining_quantity() - order.remaining_quantity();
42 assert!(removed_qty > Q::new_zero());
43 }
44 }
45
46 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 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 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 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 #[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 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}