1use std::cell::RefCell;
19
20use ahash::AHashMap;
21use indexmap::IndexMap;
22use nautilus_analysis::snapshot::PortfolioStatistics;
23use nautilus_common::factories::OrderFactory;
24use nautilus_core::UnixNanos;
25use nautilus_model::{
26 enums::{ContingencyType, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
27 events::PortfolioSnapshot,
28 identifiers::{
29 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId, Venue,
30 },
31 orders::{OrderAny, OrderList},
32 types::{Currency, Money, Price, Quantity},
33};
34use nautilus_portfolio::Portfolio;
35use rust_decimal::Decimal;
36use ustr::Ustr;
37
38#[derive(Debug)]
40pub struct OrderApi<'a> {
41 order_factory: &'a RefCell<OrderFactory>,
42}
43
44#[bon::bon]
45impl<'a> OrderApi<'a> {
46 pub(crate) const fn new(order_factory: &'a RefCell<OrderFactory>) -> Self {
47 Self { order_factory }
48 }
49
50 #[must_use]
56 pub fn generate_client_order_id(&self) -> ClientOrderId {
57 self.order_factory.borrow_mut().generate_client_order_id()
58 }
59
60 #[must_use]
66 pub fn generate_order_list_id(&self) -> OrderListId {
67 self.order_factory.borrow_mut().generate_order_list_id()
68 }
69
70 #[must_use]
77 #[expect(clippy::too_many_arguments)]
78 pub fn market(
79 &self,
80 instrument_id: InstrumentId,
81 order_side: OrderSide,
82 quantity: Quantity,
83 time_in_force: Option<TimeInForce>,
84 reduce_only: Option<bool>,
85 quote_quantity: Option<bool>,
86 exec_algorithm_id: Option<ExecAlgorithmId>,
87 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
88 tags: Option<Vec<Ustr>>,
89 client_order_id: Option<ClientOrderId>,
90 ) -> OrderAny {
91 self.order_factory.borrow_mut().market(
92 instrument_id,
93 order_side,
94 quantity,
95 time_in_force,
96 reduce_only,
97 quote_quantity,
98 exec_algorithm_id,
99 exec_algorithm_params,
100 tags,
101 client_order_id,
102 )
103 }
104
105 #[must_use]
112 #[expect(clippy::too_many_arguments)]
113 pub fn limit(
114 &self,
115 instrument_id: InstrumentId,
116 order_side: OrderSide,
117 quantity: Quantity,
118 price: Price,
119 time_in_force: Option<TimeInForce>,
120 expire_time: Option<UnixNanos>,
121 post_only: Option<bool>,
122 reduce_only: Option<bool>,
123 quote_quantity: Option<bool>,
124 display_qty: Option<Quantity>,
125 emulation_trigger: Option<TriggerType>,
126 trigger_instrument_id: Option<InstrumentId>,
127 exec_algorithm_id: Option<ExecAlgorithmId>,
128 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
129 tags: Option<Vec<Ustr>>,
130 client_order_id: Option<ClientOrderId>,
131 ) -> OrderAny {
132 self.order_factory.borrow_mut().limit(
133 instrument_id,
134 order_side,
135 quantity,
136 price,
137 time_in_force,
138 expire_time,
139 post_only,
140 reduce_only,
141 quote_quantity,
142 display_qty,
143 emulation_trigger,
144 trigger_instrument_id,
145 exec_algorithm_id,
146 exec_algorithm_params,
147 tags,
148 client_order_id,
149 )
150 }
151
152 #[must_use]
159 #[expect(clippy::too_many_arguments)]
160 pub fn stop_market(
161 &self,
162 instrument_id: InstrumentId,
163 order_side: OrderSide,
164 quantity: Quantity,
165 trigger_price: Price,
166 trigger_type: Option<TriggerType>,
167 time_in_force: Option<TimeInForce>,
168 expire_time: Option<UnixNanos>,
169 reduce_only: Option<bool>,
170 quote_quantity: Option<bool>,
171 display_qty: Option<Quantity>,
172 emulation_trigger: Option<TriggerType>,
173 trigger_instrument_id: Option<InstrumentId>,
174 exec_algorithm_id: Option<ExecAlgorithmId>,
175 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
176 tags: Option<Vec<Ustr>>,
177 client_order_id: Option<ClientOrderId>,
178 ) -> OrderAny {
179 self.order_factory.borrow_mut().stop_market(
180 instrument_id,
181 order_side,
182 quantity,
183 trigger_price,
184 trigger_type,
185 time_in_force,
186 expire_time,
187 reduce_only,
188 quote_quantity,
189 display_qty,
190 emulation_trigger,
191 trigger_instrument_id,
192 exec_algorithm_id,
193 exec_algorithm_params,
194 tags,
195 client_order_id,
196 )
197 }
198
199 #[must_use]
206 #[expect(clippy::too_many_arguments)]
207 pub fn stop_limit(
208 &self,
209 instrument_id: InstrumentId,
210 order_side: OrderSide,
211 quantity: Quantity,
212 price: Price,
213 trigger_price: Price,
214 trigger_type: Option<TriggerType>,
215 time_in_force: Option<TimeInForce>,
216 expire_time: Option<UnixNanos>,
217 post_only: Option<bool>,
218 reduce_only: Option<bool>,
219 quote_quantity: Option<bool>,
220 display_qty: Option<Quantity>,
221 emulation_trigger: Option<TriggerType>,
222 trigger_instrument_id: Option<InstrumentId>,
223 exec_algorithm_id: Option<ExecAlgorithmId>,
224 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
225 tags: Option<Vec<Ustr>>,
226 client_order_id: Option<ClientOrderId>,
227 ) -> OrderAny {
228 self.order_factory.borrow_mut().stop_limit(
229 instrument_id,
230 order_side,
231 quantity,
232 price,
233 trigger_price,
234 trigger_type,
235 time_in_force,
236 expire_time,
237 post_only,
238 reduce_only,
239 quote_quantity,
240 display_qty,
241 emulation_trigger,
242 trigger_instrument_id,
243 exec_algorithm_id,
244 exec_algorithm_params,
245 tags,
246 client_order_id,
247 )
248 }
249
250 #[must_use]
257 #[expect(clippy::too_many_arguments)]
258 pub fn market_to_limit(
259 &self,
260 instrument_id: InstrumentId,
261 order_side: OrderSide,
262 quantity: Quantity,
263 time_in_force: Option<TimeInForce>,
264 expire_time: Option<UnixNanos>,
265 reduce_only: Option<bool>,
266 quote_quantity: Option<bool>,
267 display_qty: Option<Quantity>,
268 exec_algorithm_id: Option<ExecAlgorithmId>,
269 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
270 tags: Option<Vec<Ustr>>,
271 client_order_id: Option<ClientOrderId>,
272 ) -> OrderAny {
273 self.order_factory.borrow_mut().market_to_limit(
274 instrument_id,
275 order_side,
276 quantity,
277 time_in_force,
278 expire_time,
279 reduce_only,
280 quote_quantity,
281 display_qty,
282 exec_algorithm_id,
283 exec_algorithm_params,
284 tags,
285 client_order_id,
286 )
287 }
288
289 #[must_use]
296 #[expect(clippy::too_many_arguments)]
297 pub fn market_if_touched(
298 &self,
299 instrument_id: InstrumentId,
300 order_side: OrderSide,
301 quantity: Quantity,
302 trigger_price: Price,
303 trigger_type: Option<TriggerType>,
304 time_in_force: Option<TimeInForce>,
305 expire_time: Option<UnixNanos>,
306 reduce_only: Option<bool>,
307 quote_quantity: Option<bool>,
308 emulation_trigger: Option<TriggerType>,
309 trigger_instrument_id: Option<InstrumentId>,
310 exec_algorithm_id: Option<ExecAlgorithmId>,
311 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
312 tags: Option<Vec<Ustr>>,
313 client_order_id: Option<ClientOrderId>,
314 ) -> OrderAny {
315 self.order_factory.borrow_mut().market_if_touched(
316 instrument_id,
317 order_side,
318 quantity,
319 trigger_price,
320 trigger_type,
321 time_in_force,
322 expire_time,
323 reduce_only,
324 quote_quantity,
325 emulation_trigger,
326 trigger_instrument_id,
327 exec_algorithm_id,
328 exec_algorithm_params,
329 tags,
330 client_order_id,
331 )
332 }
333
334 #[must_use]
341 #[expect(clippy::too_many_arguments)]
342 pub fn limit_if_touched(
343 &self,
344 instrument_id: InstrumentId,
345 order_side: OrderSide,
346 quantity: Quantity,
347 price: Price,
348 trigger_price: Price,
349 trigger_type: Option<TriggerType>,
350 time_in_force: Option<TimeInForce>,
351 expire_time: Option<UnixNanos>,
352 post_only: Option<bool>,
353 reduce_only: Option<bool>,
354 quote_quantity: Option<bool>,
355 display_qty: Option<Quantity>,
356 emulation_trigger: Option<TriggerType>,
357 trigger_instrument_id: Option<InstrumentId>,
358 exec_algorithm_id: Option<ExecAlgorithmId>,
359 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
360 tags: Option<Vec<Ustr>>,
361 client_order_id: Option<ClientOrderId>,
362 ) -> OrderAny {
363 self.order_factory.borrow_mut().limit_if_touched(
364 instrument_id,
365 order_side,
366 quantity,
367 price,
368 trigger_price,
369 trigger_type,
370 time_in_force,
371 expire_time,
372 post_only,
373 reduce_only,
374 quote_quantity,
375 display_qty,
376 emulation_trigger,
377 trigger_instrument_id,
378 exec_algorithm_id,
379 exec_algorithm_params,
380 tags,
381 client_order_id,
382 )
383 }
384
385 #[must_use]
392 #[expect(clippy::too_many_arguments)]
393 pub fn trailing_stop_market(
394 &self,
395 instrument_id: InstrumentId,
396 order_side: OrderSide,
397 quantity: Quantity,
398 trailing_offset: Decimal,
399 trailing_offset_type: Option<TrailingOffsetType>,
400 activation_price: Option<Price>,
401 trigger_price: Option<Price>,
402 trigger_type: Option<TriggerType>,
403 time_in_force: Option<TimeInForce>,
404 expire_time: Option<UnixNanos>,
405 reduce_only: Option<bool>,
406 quote_quantity: Option<bool>,
407 display_qty: Option<Quantity>,
408 emulation_trigger: Option<TriggerType>,
409 trigger_instrument_id: Option<InstrumentId>,
410 exec_algorithm_id: Option<ExecAlgorithmId>,
411 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
412 tags: Option<Vec<Ustr>>,
413 client_order_id: Option<ClientOrderId>,
414 ) -> OrderAny {
415 self.order_factory.borrow_mut().trailing_stop_market(
416 instrument_id,
417 order_side,
418 quantity,
419 trailing_offset,
420 trailing_offset_type,
421 activation_price,
422 trigger_price,
423 trigger_type,
424 time_in_force,
425 expire_time,
426 reduce_only,
427 quote_quantity,
428 display_qty,
429 emulation_trigger,
430 trigger_instrument_id,
431 exec_algorithm_id,
432 exec_algorithm_params,
433 tags,
434 client_order_id,
435 )
436 }
437
438 #[must_use]
445 #[expect(clippy::too_many_arguments)]
446 pub fn trailing_stop_limit(
447 &self,
448 instrument_id: InstrumentId,
449 order_side: OrderSide,
450 quantity: Quantity,
451 price: Price,
452 limit_offset: Decimal,
453 trailing_offset: Decimal,
454 trailing_offset_type: Option<TrailingOffsetType>,
455 activation_price: Option<Price>,
456 trigger_price: Option<Price>,
457 trigger_type: Option<TriggerType>,
458 time_in_force: Option<TimeInForce>,
459 expire_time: Option<UnixNanos>,
460 post_only: Option<bool>,
461 reduce_only: Option<bool>,
462 quote_quantity: Option<bool>,
463 display_qty: Option<Quantity>,
464 emulation_trigger: Option<TriggerType>,
465 trigger_instrument_id: Option<InstrumentId>,
466 exec_algorithm_id: Option<ExecAlgorithmId>,
467 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
468 tags: Option<Vec<Ustr>>,
469 client_order_id: Option<ClientOrderId>,
470 ) -> OrderAny {
471 self.order_factory.borrow_mut().trailing_stop_limit(
472 instrument_id,
473 order_side,
474 quantity,
475 Some(price),
476 limit_offset,
477 trailing_offset,
478 trailing_offset_type,
479 activation_price,
480 trigger_price,
481 trigger_type,
482 time_in_force,
483 expire_time,
484 post_only,
485 reduce_only,
486 quote_quantity,
487 display_qty,
488 emulation_trigger,
489 trigger_instrument_id,
490 exec_algorithm_id,
491 exec_algorithm_params,
492 tags,
493 client_order_id,
494 )
495 }
496
497 #[must_use]
504 pub fn create_list(&self, orders: &mut [OrderAny], ts_init: UnixNanos) -> OrderList {
505 self.order_factory.borrow_mut().create_list(orders, ts_init)
506 }
507
508 #[must_use]
515 #[builder]
516 pub fn bracket(
517 &self,
518 instrument_id: InstrumentId,
519 order_side: OrderSide,
520 quantity: Quantity,
521 #[builder(default = false)] quote_quantity: bool,
522 emulation_trigger: Option<TriggerType>,
523 trigger_instrument_id: Option<InstrumentId>,
524 #[builder(default = ContingencyType::Ouo)] contingency_type: ContingencyType,
525 #[builder(default = OrderType::Market)] entry_order_type: OrderType,
526 entry_price: Option<Price>,
527 entry_trigger_price: Option<Price>,
528 expire_time: Option<UnixNanos>,
529 #[builder(default = TimeInForce::Gtc)] time_in_force: TimeInForce,
530 #[builder(default = false)] entry_post_only: bool,
531 entry_exec_algorithm_id: Option<ExecAlgorithmId>,
532 entry_exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
533 #[builder(default = vec![Ustr::from("ENTRY")])] entry_tags: Vec<Ustr>,
534 entry_client_order_id: Option<ClientOrderId>,
535 #[builder(default = OrderType::Limit)] tp_order_type: OrderType,
536 tp_price: Option<Price>,
537 tp_trigger_price: Option<Price>,
538 #[builder(default = TriggerType::Default)] tp_trigger_type: TriggerType,
539 tp_activation_price: Option<Price>,
540 tp_trailing_offset: Option<Decimal>,
541 #[builder(default = TrailingOffsetType::Price)] tp_trailing_offset_type: TrailingOffsetType,
542 tp_limit_offset: Option<Decimal>,
543 #[builder(default = TimeInForce::Gtc)] tp_time_in_force: TimeInForce,
544 #[builder(default = true)] tp_post_only: bool,
545 tp_exec_algorithm_id: Option<ExecAlgorithmId>,
546 tp_exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
547 #[builder(default = vec![Ustr::from("TAKE_PROFIT")])] tp_tags: Vec<Ustr>,
548 tp_client_order_id: Option<ClientOrderId>,
549 #[builder(default = OrderType::StopMarket)] sl_order_type: OrderType,
550 sl_trigger_price: Option<Price>,
551 #[builder(default = TriggerType::Default)] sl_trigger_type: TriggerType,
552 sl_activation_price: Option<Price>,
553 sl_trailing_offset: Option<Decimal>,
554 #[builder(default = TrailingOffsetType::Price)] sl_trailing_offset_type: TrailingOffsetType,
555 #[builder(default = TimeInForce::Gtc)] sl_time_in_force: TimeInForce,
556 sl_exec_algorithm_id: Option<ExecAlgorithmId>,
557 sl_exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
558 #[builder(default = vec![Ustr::from("STOP_LOSS")])] sl_tags: Vec<Ustr>,
559 sl_client_order_id: Option<ClientOrderId>,
560 ) -> Vec<OrderAny> {
561 let mut order_factory = self.order_factory.borrow_mut();
562 order_factory
563 .bracket()
564 .instrument_id(instrument_id)
565 .order_side(order_side)
566 .quantity(quantity)
567 .quote_quantity(quote_quantity)
568 .maybe_emulation_trigger(emulation_trigger)
569 .maybe_trigger_instrument_id(trigger_instrument_id)
570 .contingency_type(contingency_type)
571 .entry_order_type(entry_order_type)
572 .maybe_entry_price(entry_price)
573 .maybe_entry_trigger_price(entry_trigger_price)
574 .maybe_expire_time(expire_time)
575 .time_in_force(time_in_force)
576 .entry_post_only(entry_post_only)
577 .maybe_entry_exec_algorithm_id(entry_exec_algorithm_id)
578 .maybe_entry_exec_algorithm_params(entry_exec_algorithm_params)
579 .entry_tags(entry_tags)
580 .maybe_entry_client_order_id(entry_client_order_id)
581 .tp_order_type(tp_order_type)
582 .maybe_tp_price(tp_price)
583 .maybe_tp_trigger_price(tp_trigger_price)
584 .tp_trigger_type(tp_trigger_type)
585 .maybe_tp_activation_price(tp_activation_price)
586 .maybe_tp_trailing_offset(tp_trailing_offset)
587 .tp_trailing_offset_type(tp_trailing_offset_type)
588 .maybe_tp_limit_offset(tp_limit_offset)
589 .tp_time_in_force(tp_time_in_force)
590 .tp_post_only(tp_post_only)
591 .maybe_tp_exec_algorithm_id(tp_exec_algorithm_id)
592 .maybe_tp_exec_algorithm_params(tp_exec_algorithm_params)
593 .tp_tags(tp_tags)
594 .maybe_tp_client_order_id(tp_client_order_id)
595 .sl_order_type(sl_order_type)
596 .maybe_sl_trigger_price(sl_trigger_price)
597 .sl_trigger_type(sl_trigger_type)
598 .maybe_sl_activation_price(sl_activation_price)
599 .maybe_sl_trailing_offset(sl_trailing_offset)
600 .sl_trailing_offset_type(sl_trailing_offset_type)
601 .sl_time_in_force(sl_time_in_force)
602 .maybe_sl_exec_algorithm_id(sl_exec_algorithm_id)
603 .maybe_sl_exec_algorithm_params(sl_exec_algorithm_params)
604 .sl_tags(sl_tags)
605 .maybe_sl_client_order_id(sl_client_order_id)
606 .call()
607 }
608}
609
610#[derive(Debug)]
612pub struct PortfolioApi<'a> {
613 portfolio: &'a RefCell<Portfolio>,
614}
615
616impl<'a> PortfolioApi<'a> {
617 pub(crate) const fn new(portfolio: &'a RefCell<Portfolio>) -> Self {
618 Self { portfolio }
619 }
620
621 #[must_use]
627 pub fn is_initialized(&self) -> bool {
628 self.portfolio.borrow().is_initialized()
629 }
630
631 #[must_use]
637 pub fn balances_locked(&self, venue: &Venue) -> IndexMap<Currency, Money> {
638 self.portfolio.borrow().balances_locked(venue)
639 }
640
641 #[must_use]
647 pub fn instrument_initial_margins(&self, venue: &Venue) -> IndexMap<InstrumentId, Money> {
648 self.portfolio.borrow().instrument_initial_margins(venue)
649 }
650
651 #[must_use]
657 pub fn instrument_maintenance_margins(&self, venue: &Venue) -> IndexMap<InstrumentId, Money> {
658 self.portfolio
659 .borrow()
660 .instrument_maintenance_margins(venue)
661 }
662
663 #[must_use]
669 pub fn unrealized_pnls(
670 &self,
671 venue: &Venue,
672 account_id: Option<&AccountId>,
673 ) -> Option<IndexMap<Currency, Money>> {
674 self.portfolio
675 .borrow_mut()
676 .unrealized_pnls(venue, account_id, None)
677 }
678
679 #[must_use]
685 pub fn realized_pnls(
686 &self,
687 venue: &Venue,
688 account_id: Option<&AccountId>,
689 ) -> Option<IndexMap<Currency, Money>> {
690 self.portfolio
691 .borrow_mut()
692 .realized_pnls(venue, account_id, None)
693 }
694
695 #[must_use]
701 pub fn net_exposures(
702 &self,
703 venue: &Venue,
704 account_id: Option<&AccountId>,
705 ) -> Option<IndexMap<Currency, Money>> {
706 self.portfolio
707 .borrow()
708 .net_exposures(venue, account_id, None)
709 }
710
711 #[must_use]
717 pub fn unrealized_pnl(&self, instrument_id: &InstrumentId) -> Option<Money> {
718 self.portfolio.borrow_mut().unrealized_pnl(instrument_id)
719 }
720
721 #[must_use]
727 pub fn unrealized_pnl_for_account(
728 &self,
729 instrument_id: &InstrumentId,
730 account_id: Option<&AccountId>,
731 ) -> Option<Money> {
732 self.portfolio.borrow_mut().unrealized_pnl_for_account(
733 instrument_id,
734 None,
735 account_id,
736 None,
737 )
738 }
739
740 #[must_use]
746 pub fn realized_pnl(&self, instrument_id: &InstrumentId) -> Option<Money> {
747 self.portfolio.borrow_mut().realized_pnl(instrument_id)
748 }
749
750 #[must_use]
756 pub fn realized_pnl_for_account(
757 &self,
758 instrument_id: &InstrumentId,
759 account_id: Option<&AccountId>,
760 ) -> Option<Money> {
761 self.portfolio
762 .borrow_mut()
763 .realized_pnl_for_account(instrument_id, account_id, None)
764 }
765
766 #[must_use]
772 pub fn total_pnl(&self, instrument_id: &InstrumentId) -> Option<Money> {
773 self.portfolio.borrow_mut().total_pnl(instrument_id)
774 }
775
776 #[must_use]
782 pub fn total_pnl_for_account(
783 &self,
784 instrument_id: &InstrumentId,
785 account_id: Option<&AccountId>,
786 ) -> Option<Money> {
787 self.portfolio
788 .borrow_mut()
789 .total_pnl_for_account(instrument_id, None, account_id, None)
790 }
791
792 #[must_use]
798 pub fn total_pnls(
799 &self,
800 venue: &Venue,
801 account_id: Option<&AccountId>,
802 ) -> Option<IndexMap<Currency, Money>> {
803 self.portfolio
804 .borrow_mut()
805 .total_pnls(venue, account_id, None)
806 }
807
808 #[must_use]
814 pub fn mark_values(
815 &self,
816 venue: &Venue,
817 account_id: Option<&AccountId>,
818 ) -> IndexMap<Currency, Money> {
819 self.portfolio.borrow_mut().mark_values(venue, account_id)
820 }
821
822 #[must_use]
828 pub fn equity(
829 &self,
830 venue: &Venue,
831 account_id: Option<&AccountId>,
832 ) -> IndexMap<Currency, Money> {
833 self.portfolio.borrow_mut().equity(venue, account_id)
834 }
835
836 #[must_use]
842 pub fn build_snapshot(&self, account_id: &AccountId) -> Option<PortfolioSnapshot> {
843 self.portfolio.borrow_mut().build_snapshot(account_id)
844 }
845
846 #[must_use]
852 pub fn statistics(&self) -> PortfolioStatistics {
853 self.portfolio.borrow().statistics()
854 }
855
856 #[must_use]
862 pub fn snapshots(&self, account_id: &AccountId) -> Vec<PortfolioSnapshot> {
863 self.portfolio.borrow().snapshots(account_id)
864 }
865
866 #[must_use]
872 pub fn missing_price_instruments(&self, venue: &Venue) -> Vec<InstrumentId> {
873 self.portfolio
874 .borrow()
875 .missing_price_instruments(venue, None)
876 }
877
878 #[must_use]
884 pub fn net_exposure(
885 &self,
886 instrument_id: &InstrumentId,
887 account_id: Option<&AccountId>,
888 ) -> Option<Money> {
889 self.portfolio
890 .borrow()
891 .net_exposure(instrument_id, None, account_id, None)
892 }
893
894 #[must_use]
900 pub fn net_position(&self, instrument_id: &InstrumentId) -> Decimal {
901 self.portfolio.borrow().net_position(instrument_id)
902 }
903
904 #[must_use]
910 pub fn is_net_long(&self, instrument_id: &InstrumentId) -> bool {
911 self.portfolio.borrow().is_net_long(instrument_id)
912 }
913
914 #[must_use]
920 pub fn is_net_short(&self, instrument_id: &InstrumentId) -> bool {
921 self.portfolio.borrow().is_net_short(instrument_id)
922 }
923
924 #[must_use]
930 pub fn is_net_flat(&self, instrument_id: &InstrumentId) -> bool {
931 self.portfolio.borrow().is_net_flat(instrument_id)
932 }
933
934 #[must_use]
940 pub fn is_completely_net_flat(&self) -> bool {
941 self.portfolio.borrow().is_completely_net_flat()
942 }
943
944 #[must_use]
952 pub fn recorded_realized_pnls(&self) -> AHashMap<Currency, Vec<(PositionId, UnixNanos, f64)>> {
953 self.portfolio.borrow().recorded_realized_pnls()
954 }
955}
956
957#[cfg(test)]
958mod tests {
959 use std::{cell::RefCell, rc::Rc};
960
961 use nautilus_common::{cache::Cache, clock::TestClock, factories::OrderFactory};
962 use nautilus_model::{
963 enums::{OrderSide, OrderType},
964 identifiers::{AccountId, InstrumentId, StrategyId, TraderId, Venue},
965 orders::Order,
966 };
967 use rstest::rstest;
968
969 use super::*;
970
971 #[rstest]
972 fn test_order_api_creates_market_order() {
973 let trader_id = TraderId::from("TRADER-001");
974 let strategy_id = StrategyId::from("S-001");
975 let clock = Rc::new(RefCell::new(TestClock::new()));
976 let order_factory = RefCell::new(OrderFactory::new(
977 trader_id,
978 strategy_id,
979 None,
980 None,
981 clock,
982 false,
983 true,
984 ));
985 let api = OrderApi::new(&order_factory);
986 let instrument_id = InstrumentId::from("AUD/USD.SIM");
987
988 let order = api.market(
989 instrument_id,
990 OrderSide::Buy,
991 Quantity::from("100000"),
992 None,
993 None,
994 None,
995 None,
996 None,
997 None,
998 None,
999 );
1000
1001 assert_eq!(order.order_type(), OrderType::Market);
1002 assert_eq!(order.instrument_id(), instrument_id);
1003 assert_eq!(order.order_side(), OrderSide::Buy);
1004 assert_eq!(order.quantity(), Quantity::from("100000"));
1005 assert_eq!(order.trader_id(), trader_id);
1006 assert_eq!(order.strategy_id(), strategy_id);
1007 }
1008
1009 #[rstest]
1010 fn test_order_api_creates_bracket_orders() {
1011 let trader_id = TraderId::from("TRADER-001");
1012 let strategy_id = StrategyId::from("S-001");
1013 let clock = Rc::new(RefCell::new(TestClock::new()));
1014 let order_factory = RefCell::new(OrderFactory::new(
1015 trader_id,
1016 strategy_id,
1017 None,
1018 None,
1019 clock,
1020 false,
1021 true,
1022 ));
1023 let api = OrderApi::new(&order_factory);
1024 let instrument_id = InstrumentId::from("AUD/USD.SIM");
1025
1026 let orders = api
1027 .bracket()
1028 .instrument_id(instrument_id)
1029 .order_side(OrderSide::Buy)
1030 .quantity(Quantity::from("100000"))
1031 .tp_price(Price::from("1.10000"))
1032 .sl_trigger_price(Price::from("0.90000"))
1033 .call();
1034
1035 assert_eq!(orders.len(), 3);
1036 assert!(
1037 orders
1038 .iter()
1039 .all(|order| order.instrument_id() == instrument_id)
1040 );
1041 assert!(orders.iter().all(|order| order.trader_id() == trader_id));
1042 assert!(
1043 orders
1044 .iter()
1045 .all(|order| order.strategy_id() == strategy_id)
1046 );
1047 assert_eq!(orders[0].order_type(), OrderType::Market);
1048 assert_eq!(orders[0].order_side(), OrderSide::Buy);
1049 assert_eq!(orders[0].quantity(), Quantity::from("100000"));
1050 assert_eq!(orders[1].order_type(), OrderType::StopMarket);
1051 assert_eq!(orders[1].order_side(), OrderSide::Sell);
1052 assert_eq!(orders[1].trigger_price(), Some(Price::from("0.90000")));
1053 assert_eq!(orders[2].order_type(), OrderType::Limit);
1054 assert_eq!(orders[2].order_side(), OrderSide::Sell);
1055 assert_eq!(orders[2].price(), Some(Price::from("1.10000")));
1056 }
1057
1058 #[rstest]
1059 fn test_portfolio_api_empty_reads_return_empty_values() {
1060 let cache = Rc::new(RefCell::new(Cache::default()));
1061 let clock = Rc::new(RefCell::new(TestClock::new()));
1062 let portfolio = RefCell::new(Portfolio::new(clock, cache, None));
1063 let api = PortfolioApi::new(&portfolio);
1064 let venue = Venue::from("SIM");
1065 let account_id = AccountId::from("SIM-001");
1066 let instrument_id = InstrumentId::from("AUD/USD.SIM");
1067
1068 assert!(!api.is_initialized());
1069 assert!(api.balances_locked(&venue).is_empty());
1070 assert!(api.instrument_initial_margins(&venue).is_empty());
1071 assert!(api.instrument_maintenance_margins(&venue).is_empty());
1072 assert_eq!(api.unrealized_pnls(&venue, None), Some(IndexMap::new()));
1073 assert_eq!(api.realized_pnls(&venue, None), Some(IndexMap::new()));
1074 assert_eq!(api.net_exposures(&venue, None), None);
1075 assert_eq!(api.unrealized_pnl(&instrument_id), None);
1076 assert_eq!(
1077 api.unrealized_pnl_for_account(&instrument_id, Some(&account_id)),
1078 None
1079 );
1080 assert_eq!(api.realized_pnl(&instrument_id), None);
1081 assert_eq!(
1082 api.realized_pnl_for_account(&instrument_id, Some(&account_id)),
1083 None
1084 );
1085 assert_eq!(api.total_pnl(&instrument_id), None);
1086 assert_eq!(
1087 api.total_pnl_for_account(&instrument_id, Some(&account_id)),
1088 None
1089 );
1090 assert_eq!(api.total_pnls(&venue, None), Some(IndexMap::new()));
1091 assert!(api.mark_values(&venue, None).is_empty());
1092 assert!(api.equity(&venue, None).is_empty());
1093 assert_eq!(api.build_snapshot(&account_id), None);
1094 assert!(api.snapshots(&account_id).is_empty());
1095 assert!(api.missing_price_instruments(&venue).is_empty());
1096 assert_eq!(api.net_exposure(&instrument_id, None), None);
1097 assert_eq!(api.net_position(&instrument_id), Decimal::ZERO);
1098 assert!(!api.is_net_long(&instrument_id));
1099 assert!(!api.is_net_short(&instrument_id));
1100 assert!(api.is_net_flat(&instrument_id));
1101 assert!(api.is_completely_net_flat());
1102 assert!(api.recorded_realized_pnls().is_empty());
1103
1104 let _statistics = api.statistics();
1105 }
1106}