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 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 margins_init(&self, venue: &Venue) -> IndexMap<InstrumentId, Money> {
648 self.portfolio.borrow().margins_init(venue)
649 }
650
651 #[must_use]
657 pub fn margins_maint(&self, venue: &Venue) -> IndexMap<InstrumentId, Money> {
658 self.portfolio.borrow().margins_maint(venue)
659 }
660
661 #[must_use]
667 pub fn unrealized_pnls(
668 &self,
669 venue: &Venue,
670 account_id: Option<&AccountId>,
671 ) -> IndexMap<Currency, Money> {
672 self.portfolio
673 .borrow_mut()
674 .unrealized_pnls(venue, account_id)
675 }
676
677 #[must_use]
683 pub fn realized_pnls(
684 &self,
685 venue: &Venue,
686 account_id: Option<&AccountId>,
687 ) -> IndexMap<Currency, Money> {
688 self.portfolio.borrow_mut().realized_pnls(venue, account_id)
689 }
690
691 #[must_use]
697 pub fn net_exposures(
698 &self,
699 venue: &Venue,
700 account_id: Option<&AccountId>,
701 ) -> Option<IndexMap<Currency, Money>> {
702 self.portfolio.borrow().net_exposures(venue, account_id)
703 }
704
705 #[must_use]
711 pub fn unrealized_pnl(&self, instrument_id: &InstrumentId) -> Option<Money> {
712 self.portfolio.borrow_mut().unrealized_pnl(instrument_id)
713 }
714
715 #[must_use]
721 pub fn unrealized_pnl_for_account(
722 &self,
723 instrument_id: &InstrumentId,
724 account_id: Option<&AccountId>,
725 ) -> Option<Money> {
726 self.portfolio
727 .borrow_mut()
728 .unrealized_pnl_for_account(instrument_id, account_id)
729 }
730
731 #[must_use]
737 pub fn realized_pnl(&self, instrument_id: &InstrumentId) -> Option<Money> {
738 self.portfolio.borrow_mut().realized_pnl(instrument_id)
739 }
740
741 #[must_use]
747 pub fn realized_pnl_for_account(
748 &self,
749 instrument_id: &InstrumentId,
750 account_id: Option<&AccountId>,
751 ) -> Option<Money> {
752 self.portfolio
753 .borrow_mut()
754 .realized_pnl_for_account(instrument_id, account_id)
755 }
756
757 #[must_use]
763 pub fn total_pnl(&self, instrument_id: &InstrumentId) -> Option<Money> {
764 self.portfolio.borrow_mut().total_pnl(instrument_id)
765 }
766
767 #[must_use]
773 pub fn total_pnl_for_account(
774 &self,
775 instrument_id: &InstrumentId,
776 account_id: Option<&AccountId>,
777 ) -> Option<Money> {
778 self.portfolio
779 .borrow_mut()
780 .total_pnl_for_account(instrument_id, account_id)
781 }
782
783 #[must_use]
789 pub fn total_pnls(
790 &self,
791 venue: &Venue,
792 account_id: Option<&AccountId>,
793 ) -> IndexMap<Currency, Money> {
794 self.portfolio.borrow_mut().total_pnls(venue, account_id)
795 }
796
797 #[must_use]
803 pub fn mark_values(
804 &self,
805 venue: &Venue,
806 account_id: Option<&AccountId>,
807 ) -> IndexMap<Currency, Money> {
808 self.portfolio.borrow_mut().mark_values(venue, account_id)
809 }
810
811 #[must_use]
817 pub fn equity(
818 &self,
819 venue: &Venue,
820 account_id: Option<&AccountId>,
821 ) -> IndexMap<Currency, Money> {
822 self.portfolio.borrow_mut().equity(venue, account_id)
823 }
824
825 #[must_use]
831 pub fn build_snapshot(&self, account_id: &AccountId) -> Option<PortfolioSnapshot> {
832 self.portfolio.borrow_mut().build_snapshot(account_id)
833 }
834
835 #[must_use]
841 pub fn statistics(&self) -> PortfolioStatistics {
842 self.portfolio.borrow().statistics()
843 }
844
845 #[must_use]
851 pub fn snapshots(&self, account_id: &AccountId) -> Vec<PortfolioSnapshot> {
852 self.portfolio.borrow().snapshots(account_id)
853 }
854
855 #[must_use]
861 pub fn missing_price_instruments(&self, venue: &Venue) -> Vec<InstrumentId> {
862 self.portfolio.borrow().missing_price_instruments(venue)
863 }
864
865 #[must_use]
871 pub fn net_exposure(
872 &self,
873 instrument_id: &InstrumentId,
874 account_id: Option<&AccountId>,
875 ) -> Option<Money> {
876 self.portfolio
877 .borrow()
878 .net_exposure(instrument_id, account_id)
879 }
880
881 #[must_use]
887 pub fn net_position(&self, instrument_id: &InstrumentId) -> Decimal {
888 self.portfolio.borrow().net_position(instrument_id)
889 }
890
891 #[must_use]
897 pub fn is_net_long(&self, instrument_id: &InstrumentId) -> bool {
898 self.portfolio.borrow().is_net_long(instrument_id)
899 }
900
901 #[must_use]
907 pub fn is_net_short(&self, instrument_id: &InstrumentId) -> bool {
908 self.portfolio.borrow().is_net_short(instrument_id)
909 }
910
911 #[must_use]
917 pub fn is_flat(&self, instrument_id: &InstrumentId) -> bool {
918 self.portfolio.borrow().is_flat(instrument_id)
919 }
920
921 #[must_use]
927 pub fn is_completely_flat(&self) -> bool {
928 self.portfolio.borrow().is_completely_flat()
929 }
930
931 #[must_use]
939 pub fn recorded_realized_pnls(&self) -> AHashMap<Currency, Vec<(PositionId, UnixNanos, f64)>> {
940 self.portfolio.borrow().recorded_realized_pnls()
941 }
942}
943
944#[cfg(test)]
945mod tests {
946 use std::{cell::RefCell, rc::Rc};
947
948 use nautilus_common::{cache::Cache, clock::TestClock, factories::OrderFactory};
949 use nautilus_model::{
950 enums::{OrderSide, OrderType},
951 identifiers::{AccountId, InstrumentId, StrategyId, TraderId, Venue},
952 orders::Order,
953 };
954 use rstest::rstest;
955
956 use super::*;
957
958 #[rstest]
959 fn test_order_api_creates_market_order() {
960 let trader_id = TraderId::from("TRADER-001");
961 let strategy_id = StrategyId::from("S-001");
962 let clock = Rc::new(RefCell::new(TestClock::new()));
963 let order_factory = RefCell::new(OrderFactory::new(
964 trader_id,
965 strategy_id,
966 None,
967 None,
968 clock,
969 false,
970 true,
971 ));
972 let api = OrderApi::new(&order_factory);
973 let instrument_id = InstrumentId::from("AUD/USD.SIM");
974
975 let order = api.market(
976 instrument_id,
977 OrderSide::Buy,
978 Quantity::from("100000"),
979 None,
980 None,
981 None,
982 None,
983 None,
984 None,
985 None,
986 );
987
988 assert_eq!(order.order_type(), OrderType::Market);
989 assert_eq!(order.instrument_id(), instrument_id);
990 assert_eq!(order.order_side(), OrderSide::Buy);
991 assert_eq!(order.quantity(), Quantity::from("100000"));
992 assert_eq!(order.trader_id(), trader_id);
993 assert_eq!(order.strategy_id(), strategy_id);
994 }
995
996 #[rstest]
997 fn test_order_api_creates_bracket_orders() {
998 let trader_id = TraderId::from("TRADER-001");
999 let strategy_id = StrategyId::from("S-001");
1000 let clock = Rc::new(RefCell::new(TestClock::new()));
1001 let order_factory = RefCell::new(OrderFactory::new(
1002 trader_id,
1003 strategy_id,
1004 None,
1005 None,
1006 clock,
1007 false,
1008 true,
1009 ));
1010 let api = OrderApi::new(&order_factory);
1011 let instrument_id = InstrumentId::from("AUD/USD.SIM");
1012
1013 let orders = api
1014 .bracket()
1015 .instrument_id(instrument_id)
1016 .order_side(OrderSide::Buy)
1017 .quantity(Quantity::from("100000"))
1018 .tp_price(Price::from("1.10000"))
1019 .sl_trigger_price(Price::from("0.90000"))
1020 .call();
1021
1022 assert_eq!(orders.len(), 3);
1023 assert!(
1024 orders
1025 .iter()
1026 .all(|order| order.instrument_id() == instrument_id)
1027 );
1028 assert!(orders.iter().all(|order| order.trader_id() == trader_id));
1029 assert!(
1030 orders
1031 .iter()
1032 .all(|order| order.strategy_id() == strategy_id)
1033 );
1034 assert_eq!(orders[0].order_type(), OrderType::Market);
1035 assert_eq!(orders[0].order_side(), OrderSide::Buy);
1036 assert_eq!(orders[0].quantity(), Quantity::from("100000"));
1037 assert_eq!(orders[1].order_type(), OrderType::StopMarket);
1038 assert_eq!(orders[1].order_side(), OrderSide::Sell);
1039 assert_eq!(orders[1].trigger_price(), Some(Price::from("0.90000")));
1040 assert_eq!(orders[2].order_type(), OrderType::Limit);
1041 assert_eq!(orders[2].order_side(), OrderSide::Sell);
1042 assert_eq!(orders[2].price(), Some(Price::from("1.10000")));
1043 }
1044
1045 #[rstest]
1046 fn test_portfolio_api_empty_reads_return_empty_values() {
1047 let cache = Rc::new(RefCell::new(Cache::default()));
1048 let clock = Rc::new(RefCell::new(TestClock::new()));
1049 let portfolio = RefCell::new(Portfolio::new(clock, cache, None));
1050 let api = PortfolioApi::new(&portfolio);
1051 let venue = Venue::from("SIM");
1052 let account_id = AccountId::from("SIM-001");
1053 let instrument_id = InstrumentId::from("AUD/USD.SIM");
1054
1055 assert!(!api.is_initialized());
1056 assert!(api.balances_locked(&venue).is_empty());
1057 assert!(api.margins_init(&venue).is_empty());
1058 assert!(api.margins_maint(&venue).is_empty());
1059 assert!(api.unrealized_pnls(&venue, None).is_empty());
1060 assert!(api.realized_pnls(&venue, None).is_empty());
1061 assert_eq!(api.net_exposures(&venue, None), None);
1062 assert_eq!(api.unrealized_pnl(&instrument_id), None);
1063 assert_eq!(
1064 api.unrealized_pnl_for_account(&instrument_id, Some(&account_id)),
1065 None
1066 );
1067 assert_eq!(api.realized_pnl(&instrument_id), None);
1068 assert_eq!(
1069 api.realized_pnl_for_account(&instrument_id, Some(&account_id)),
1070 None
1071 );
1072 assert_eq!(api.total_pnl(&instrument_id), None);
1073 assert_eq!(
1074 api.total_pnl_for_account(&instrument_id, Some(&account_id)),
1075 None
1076 );
1077 assert!(api.total_pnls(&venue, None).is_empty());
1078 assert!(api.mark_values(&venue, None).is_empty());
1079 assert!(api.equity(&venue, None).is_empty());
1080 assert_eq!(api.build_snapshot(&account_id), None);
1081 assert!(api.snapshots(&account_id).is_empty());
1082 assert!(api.missing_price_instruments(&venue).is_empty());
1083 assert_eq!(api.net_exposure(&instrument_id, None), None);
1084 assert_eq!(api.net_position(&instrument_id), Decimal::ZERO);
1085 assert!(!api.is_net_long(&instrument_id));
1086 assert!(!api.is_net_short(&instrument_id));
1087 assert!(api.is_flat(&instrument_id));
1088 assert!(api.is_completely_flat());
1089 assert!(api.recorded_realized_pnls().is_empty());
1090
1091 let _statistics = api.statistics();
1092 }
1093}