Skip to main content

nautilus_common/python/
order_factory.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for the [`OrderFactory`].
17
18use std::{cell::RefCell, rc::Rc};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, python::to_pyvalue_err};
22use nautilus_model::{
23    enums::{ContingencyType, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
24    identifiers::{
25        ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
26    },
27    orders::{OrderAny, str_indexmap_to_ustr},
28    python::orders::order_any_to_pyobject,
29    types::{Price, Quantity},
30};
31use pyo3::{prelude::*, types::PyList};
32use rust_decimal::Decimal;
33use ustr::Ustr;
34
35use crate::{factories::OrderFactory, python::clock::PyClock};
36
37/// Wrapper providing shared access to [`OrderFactory`] from Python.
38#[allow(non_camel_case_types)]
39#[pyo3::pyclass(
40    module = "nautilus_trader.core.nautilus_pyo3.common",
41    name = "OrderFactory",
42    unsendable,
43    from_py_object
44)]
45#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")]
46#[derive(Debug, Clone)]
47pub struct PyOrderFactory(Rc<RefCell<OrderFactory>>);
48
49impl PyOrderFactory {
50    /// Creates a `PyOrderFactory` from an `Rc<RefCell<OrderFactory>>`.
51    #[must_use]
52    pub fn from_rc(rc: Rc<RefCell<OrderFactory>>) -> Self {
53        Self(rc)
54    }
55
56    /// Gets the inner `Rc<RefCell<OrderFactory>>` for use in Rust code.
57    #[must_use]
58    pub fn order_factory_rc(&self) -> Rc<RefCell<OrderFactory>> {
59        self.0.clone()
60    }
61}
62
63#[pymethods]
64#[pyo3_stub_gen::derive::gen_stub_pymethods]
65#[expect(
66    clippy::too_many_arguments,
67    reason = "factory methods mirror the Python order factory API"
68)]
69impl PyOrderFactory {
70    #[new]
71    #[pyo3(signature = (
72        trader_id,
73        strategy_id,
74        clock,
75        use_uuid_client_order_ids=false,
76        use_hyphens_in_client_order_ids=true
77    ))]
78    #[allow(
79        clippy::needless_pass_by_value,
80        reason = "PyO3 extracts pyclass constructor arguments by value"
81    )]
82    fn py_new(
83        trader_id: TraderId,
84        strategy_id: StrategyId,
85        clock: PyClock,
86        use_uuid_client_order_ids: bool,
87        use_hyphens_in_client_order_ids: bool,
88    ) -> Self {
89        Self(Rc::new(RefCell::new(OrderFactory::new(
90            trader_id,
91            strategy_id,
92            None,
93            None,
94            clock.clock_rc(),
95            use_uuid_client_order_ids,
96            use_hyphens_in_client_order_ids,
97        ))))
98    }
99
100    #[pyo3(name = "get_client_order_id_count")]
101    fn py_get_client_order_id_count(&self) -> usize {
102        self.0.borrow().client_order_id_count()
103    }
104
105    #[pyo3(name = "get_order_list_id_count")]
106    fn py_get_order_list_id_count(&self) -> usize {
107        self.0.borrow().order_list_id_count()
108    }
109
110    #[pyo3(name = "generate_client_order_id")]
111    fn py_generate_client_order_id(&mut self) -> ClientOrderId {
112        self.0.borrow_mut().generate_client_order_id()
113    }
114
115    #[pyo3(name = "generate_order_list_id")]
116    fn py_generate_order_list_id(&mut self) -> OrderListId {
117        self.0.borrow_mut().generate_order_list_id()
118    }
119
120    #[pyo3(name = "reset")]
121    fn py_reset(&mut self) {
122        self.0.borrow_mut().reset_factory();
123    }
124
125    #[pyo3(name = "market", signature = (
126        instrument_id,
127        order_side,
128        quantity,
129        time_in_force=None,
130        reduce_only=None,
131        quote_quantity=None,
132        exec_algorithm_id=None,
133        exec_algorithm_params=None,
134        tags=None,
135        client_order_id=None
136    ))]
137    fn py_market(
138        &mut self,
139        py: Python<'_>,
140        instrument_id: InstrumentId,
141        order_side: OrderSide,
142        quantity: Quantity,
143        time_in_force: Option<TimeInForce>,
144        reduce_only: Option<bool>,
145        quote_quantity: Option<bool>,
146        exec_algorithm_id: Option<ExecAlgorithmId>,
147        exec_algorithm_params: Option<IndexMap<String, String>>,
148        tags: Option<Vec<String>>,
149        client_order_id: Option<ClientOrderId>,
150    ) -> PyResult<Py<PyAny>> {
151        py_order_from_factory(py, || {
152            self.0.borrow_mut().try_market(
153                instrument_id,
154                order_side,
155                quantity,
156                time_in_force,
157                reduce_only,
158                quote_quantity,
159                exec_algorithm_id,
160                exec_algorithm_params.map(str_indexmap_to_ustr),
161                tags.map(strings_to_ustrs),
162                client_order_id,
163            )
164        })
165    }
166
167    #[pyo3(name = "limit", signature = (
168        instrument_id,
169        order_side,
170        quantity,
171        price,
172        time_in_force=None,
173        expire_time=None,
174        post_only=None,
175        reduce_only=None,
176        quote_quantity=None,
177        display_qty=None,
178        emulation_trigger=None,
179        trigger_instrument_id=None,
180        exec_algorithm_id=None,
181        exec_algorithm_params=None,
182        tags=None,
183        client_order_id=None
184    ))]
185    fn py_limit(
186        &mut self,
187        py: Python<'_>,
188        instrument_id: InstrumentId,
189        order_side: OrderSide,
190        quantity: Quantity,
191        price: Price,
192        time_in_force: Option<TimeInForce>,
193        expire_time: Option<u64>,
194        post_only: Option<bool>,
195        reduce_only: Option<bool>,
196        quote_quantity: Option<bool>,
197        display_qty: Option<Quantity>,
198        emulation_trigger: Option<TriggerType>,
199        trigger_instrument_id: Option<InstrumentId>,
200        exec_algorithm_id: Option<ExecAlgorithmId>,
201        exec_algorithm_params: Option<IndexMap<String, String>>,
202        tags: Option<Vec<String>>,
203        client_order_id: Option<ClientOrderId>,
204    ) -> PyResult<Py<PyAny>> {
205        py_order_from_factory(py, || {
206            self.0.borrow_mut().try_limit(
207                instrument_id,
208                order_side,
209                quantity,
210                price,
211                time_in_force,
212                expire_time.map(UnixNanos::from),
213                post_only,
214                reduce_only,
215                quote_quantity,
216                display_qty,
217                emulation_trigger,
218                trigger_instrument_id,
219                exec_algorithm_id,
220                exec_algorithm_params.map(str_indexmap_to_ustr),
221                tags.map(strings_to_ustrs),
222                client_order_id,
223            )
224        })
225    }
226
227    #[pyo3(name = "stop_market", signature = (
228        instrument_id,
229        order_side,
230        quantity,
231        trigger_price,
232        trigger_type=None,
233        time_in_force=None,
234        expire_time=None,
235        reduce_only=None,
236        quote_quantity=None,
237        display_qty=None,
238        emulation_trigger=None,
239        trigger_instrument_id=None,
240        exec_algorithm_id=None,
241        exec_algorithm_params=None,
242        tags=None,
243        client_order_id=None
244    ))]
245    fn py_stop_market(
246        &mut self,
247        py: Python<'_>,
248        instrument_id: InstrumentId,
249        order_side: OrderSide,
250        quantity: Quantity,
251        trigger_price: Price,
252        trigger_type: Option<TriggerType>,
253        time_in_force: Option<TimeInForce>,
254        expire_time: Option<u64>,
255        reduce_only: Option<bool>,
256        quote_quantity: Option<bool>,
257        display_qty: Option<Quantity>,
258        emulation_trigger: Option<TriggerType>,
259        trigger_instrument_id: Option<InstrumentId>,
260        exec_algorithm_id: Option<ExecAlgorithmId>,
261        exec_algorithm_params: Option<IndexMap<String, String>>,
262        tags: Option<Vec<String>>,
263        client_order_id: Option<ClientOrderId>,
264    ) -> PyResult<Py<PyAny>> {
265        py_order_from_factory(py, || {
266            self.0.borrow_mut().try_stop_market(
267                instrument_id,
268                order_side,
269                quantity,
270                trigger_price,
271                trigger_type,
272                time_in_force,
273                expire_time.map(UnixNanos::from),
274                reduce_only,
275                quote_quantity,
276                display_qty,
277                emulation_trigger,
278                trigger_instrument_id,
279                exec_algorithm_id,
280                exec_algorithm_params.map(str_indexmap_to_ustr),
281                tags.map(strings_to_ustrs),
282                client_order_id,
283            )
284        })
285    }
286
287    #[pyo3(name = "stop_limit", signature = (
288        instrument_id,
289        order_side,
290        quantity,
291        price,
292        trigger_price,
293        trigger_type=None,
294        time_in_force=None,
295        expire_time=None,
296        post_only=None,
297        reduce_only=None,
298        quote_quantity=None,
299        display_qty=None,
300        emulation_trigger=None,
301        trigger_instrument_id=None,
302        exec_algorithm_id=None,
303        exec_algorithm_params=None,
304        tags=None,
305        client_order_id=None
306    ))]
307    fn py_stop_limit(
308        &mut self,
309        py: Python<'_>,
310        instrument_id: InstrumentId,
311        order_side: OrderSide,
312        quantity: Quantity,
313        price: Price,
314        trigger_price: Price,
315        trigger_type: Option<TriggerType>,
316        time_in_force: Option<TimeInForce>,
317        expire_time: Option<u64>,
318        post_only: Option<bool>,
319        reduce_only: Option<bool>,
320        quote_quantity: Option<bool>,
321        display_qty: Option<Quantity>,
322        emulation_trigger: Option<TriggerType>,
323        trigger_instrument_id: Option<InstrumentId>,
324        exec_algorithm_id: Option<ExecAlgorithmId>,
325        exec_algorithm_params: Option<IndexMap<String, String>>,
326        tags: Option<Vec<String>>,
327        client_order_id: Option<ClientOrderId>,
328    ) -> PyResult<Py<PyAny>> {
329        py_order_from_factory(py, || {
330            self.0.borrow_mut().try_stop_limit(
331                instrument_id,
332                order_side,
333                quantity,
334                price,
335                trigger_price,
336                trigger_type,
337                time_in_force,
338                expire_time.map(UnixNanos::from),
339                post_only,
340                reduce_only,
341                quote_quantity,
342                display_qty,
343                emulation_trigger,
344                trigger_instrument_id,
345                exec_algorithm_id,
346                exec_algorithm_params.map(str_indexmap_to_ustr),
347                tags.map(strings_to_ustrs),
348                client_order_id,
349            )
350        })
351    }
352
353    #[pyo3(name = "market_to_limit", signature = (
354        instrument_id,
355        order_side,
356        quantity,
357        time_in_force=None,
358        expire_time=None,
359        reduce_only=None,
360        quote_quantity=None,
361        display_qty=None,
362        exec_algorithm_id=None,
363        exec_algorithm_params=None,
364        tags=None,
365        client_order_id=None
366    ))]
367    fn py_market_to_limit(
368        &mut self,
369        py: Python<'_>,
370        instrument_id: InstrumentId,
371        order_side: OrderSide,
372        quantity: Quantity,
373        time_in_force: Option<TimeInForce>,
374        expire_time: Option<u64>,
375        reduce_only: Option<bool>,
376        quote_quantity: Option<bool>,
377        display_qty: Option<Quantity>,
378        exec_algorithm_id: Option<ExecAlgorithmId>,
379        exec_algorithm_params: Option<IndexMap<String, String>>,
380        tags: Option<Vec<String>>,
381        client_order_id: Option<ClientOrderId>,
382    ) -> PyResult<Py<PyAny>> {
383        py_order_from_factory(py, || {
384            self.0.borrow_mut().try_market_to_limit(
385                instrument_id,
386                order_side,
387                quantity,
388                time_in_force,
389                expire_time.map(UnixNanos::from),
390                reduce_only,
391                quote_quantity,
392                display_qty,
393                exec_algorithm_id,
394                exec_algorithm_params.map(str_indexmap_to_ustr),
395                tags.map(strings_to_ustrs),
396                client_order_id,
397            )
398        })
399    }
400
401    #[pyo3(name = "market_if_touched", signature = (
402        instrument_id,
403        order_side,
404        quantity,
405        trigger_price,
406        trigger_type=None,
407        time_in_force=None,
408        expire_time=None,
409        reduce_only=None,
410        quote_quantity=None,
411        emulation_trigger=None,
412        trigger_instrument_id=None,
413        exec_algorithm_id=None,
414        exec_algorithm_params=None,
415        tags=None,
416        client_order_id=None
417    ))]
418    fn py_market_if_touched(
419        &mut self,
420        py: Python<'_>,
421        instrument_id: InstrumentId,
422        order_side: OrderSide,
423        quantity: Quantity,
424        trigger_price: Price,
425        trigger_type: Option<TriggerType>,
426        time_in_force: Option<TimeInForce>,
427        expire_time: Option<u64>,
428        reduce_only: Option<bool>,
429        quote_quantity: Option<bool>,
430        emulation_trigger: Option<TriggerType>,
431        trigger_instrument_id: Option<InstrumentId>,
432        exec_algorithm_id: Option<ExecAlgorithmId>,
433        exec_algorithm_params: Option<IndexMap<String, String>>,
434        tags: Option<Vec<String>>,
435        client_order_id: Option<ClientOrderId>,
436    ) -> PyResult<Py<PyAny>> {
437        py_order_from_factory(py, || {
438            self.0.borrow_mut().try_market_if_touched(
439                instrument_id,
440                order_side,
441                quantity,
442                trigger_price,
443                trigger_type,
444                time_in_force,
445                expire_time.map(UnixNanos::from),
446                reduce_only,
447                quote_quantity,
448                emulation_trigger,
449                trigger_instrument_id,
450                exec_algorithm_id,
451                exec_algorithm_params.map(str_indexmap_to_ustr),
452                tags.map(strings_to_ustrs),
453                client_order_id,
454            )
455        })
456    }
457
458    #[pyo3(name = "limit_if_touched", signature = (
459        instrument_id,
460        order_side,
461        quantity,
462        price,
463        trigger_price,
464        trigger_type=None,
465        time_in_force=None,
466        expire_time=None,
467        post_only=None,
468        reduce_only=None,
469        quote_quantity=None,
470        display_qty=None,
471        emulation_trigger=None,
472        trigger_instrument_id=None,
473        exec_algorithm_id=None,
474        exec_algorithm_params=None,
475        tags=None,
476        client_order_id=None
477    ))]
478    fn py_limit_if_touched(
479        &mut self,
480        py: Python<'_>,
481        instrument_id: InstrumentId,
482        order_side: OrderSide,
483        quantity: Quantity,
484        price: Price,
485        trigger_price: Price,
486        trigger_type: Option<TriggerType>,
487        time_in_force: Option<TimeInForce>,
488        expire_time: Option<u64>,
489        post_only: Option<bool>,
490        reduce_only: Option<bool>,
491        quote_quantity: Option<bool>,
492        display_qty: Option<Quantity>,
493        emulation_trigger: Option<TriggerType>,
494        trigger_instrument_id: Option<InstrumentId>,
495        exec_algorithm_id: Option<ExecAlgorithmId>,
496        exec_algorithm_params: Option<IndexMap<String, String>>,
497        tags: Option<Vec<String>>,
498        client_order_id: Option<ClientOrderId>,
499    ) -> PyResult<Py<PyAny>> {
500        py_order_from_factory(py, || {
501            self.0.borrow_mut().try_limit_if_touched(
502                instrument_id,
503                order_side,
504                quantity,
505                price,
506                trigger_price,
507                trigger_type,
508                time_in_force,
509                expire_time.map(UnixNanos::from),
510                post_only,
511                reduce_only,
512                quote_quantity,
513                display_qty,
514                emulation_trigger,
515                trigger_instrument_id,
516                exec_algorithm_id,
517                exec_algorithm_params.map(str_indexmap_to_ustr),
518                tags.map(strings_to_ustrs),
519                client_order_id,
520            )
521        })
522    }
523
524    #[pyo3(name = "trailing_stop_market", signature = (
525        instrument_id,
526        order_side,
527        quantity,
528        trailing_offset,
529        trailing_offset_type=None,
530        activation_price=None,
531        trigger_price=None,
532        trigger_type=None,
533        time_in_force=None,
534        expire_time=None,
535        reduce_only=None,
536        quote_quantity=None,
537        display_qty=None,
538        emulation_trigger=None,
539        trigger_instrument_id=None,
540        exec_algorithm_id=None,
541        exec_algorithm_params=None,
542        tags=None,
543        client_order_id=None
544    ))]
545    fn py_trailing_stop_market(
546        &mut self,
547        py: Python<'_>,
548        instrument_id: InstrumentId,
549        order_side: OrderSide,
550        quantity: Quantity,
551        trailing_offset: Decimal,
552        trailing_offset_type: Option<TrailingOffsetType>,
553        activation_price: Option<Price>,
554        trigger_price: Option<Price>,
555        trigger_type: Option<TriggerType>,
556        time_in_force: Option<TimeInForce>,
557        expire_time: Option<u64>,
558        reduce_only: Option<bool>,
559        quote_quantity: Option<bool>,
560        display_qty: Option<Quantity>,
561        emulation_trigger: Option<TriggerType>,
562        trigger_instrument_id: Option<InstrumentId>,
563        exec_algorithm_id: Option<ExecAlgorithmId>,
564        exec_algorithm_params: Option<IndexMap<String, String>>,
565        tags: Option<Vec<String>>,
566        client_order_id: Option<ClientOrderId>,
567    ) -> PyResult<Py<PyAny>> {
568        py_order_from_factory(py, || {
569            self.0.borrow_mut().try_trailing_stop_market(
570                instrument_id,
571                order_side,
572                quantity,
573                trailing_offset,
574                trailing_offset_type,
575                activation_price,
576                trigger_price,
577                trigger_type,
578                time_in_force,
579                expire_time.map(UnixNanos::from),
580                reduce_only,
581                quote_quantity,
582                display_qty,
583                emulation_trigger,
584                trigger_instrument_id,
585                exec_algorithm_id,
586                exec_algorithm_params.map(str_indexmap_to_ustr),
587                tags.map(strings_to_ustrs),
588                client_order_id,
589            )
590        })
591    }
592
593    #[pyo3(name = "trailing_stop_limit", signature = (
594        instrument_id,
595        order_side,
596        quantity,
597        price,
598        limit_offset,
599        trailing_offset,
600        trailing_offset_type=None,
601        activation_price=None,
602        trigger_price=None,
603        trigger_type=None,
604        time_in_force=None,
605        expire_time=None,
606        post_only=None,
607        reduce_only=None,
608        quote_quantity=None,
609        display_qty=None,
610        emulation_trigger=None,
611        trigger_instrument_id=None,
612        exec_algorithm_id=None,
613        exec_algorithm_params=None,
614        tags=None,
615        client_order_id=None
616    ))]
617    fn py_trailing_stop_limit(
618        &mut self,
619        py: Python<'_>,
620        instrument_id: InstrumentId,
621        order_side: OrderSide,
622        quantity: Quantity,
623        price: Price,
624        limit_offset: Decimal,
625        trailing_offset: Decimal,
626        trailing_offset_type: Option<TrailingOffsetType>,
627        activation_price: Option<Price>,
628        trigger_price: Option<Price>,
629        trigger_type: Option<TriggerType>,
630        time_in_force: Option<TimeInForce>,
631        expire_time: Option<u64>,
632        post_only: Option<bool>,
633        reduce_only: Option<bool>,
634        quote_quantity: Option<bool>,
635        display_qty: Option<Quantity>,
636        emulation_trigger: Option<TriggerType>,
637        trigger_instrument_id: Option<InstrumentId>,
638        exec_algorithm_id: Option<ExecAlgorithmId>,
639        exec_algorithm_params: Option<IndexMap<String, String>>,
640        tags: Option<Vec<String>>,
641        client_order_id: Option<ClientOrderId>,
642    ) -> PyResult<Py<PyAny>> {
643        py_order_from_factory(py, || {
644            self.0.borrow_mut().try_trailing_stop_limit(
645                instrument_id,
646                order_side,
647                quantity,
648                price,
649                limit_offset,
650                trailing_offset,
651                trailing_offset_type,
652                activation_price,
653                trigger_price,
654                trigger_type,
655                time_in_force,
656                expire_time.map(UnixNanos::from),
657                post_only,
658                reduce_only,
659                quote_quantity,
660                display_qty,
661                emulation_trigger,
662                trigger_instrument_id,
663                exec_algorithm_id,
664                exec_algorithm_params.map(str_indexmap_to_ustr),
665                tags.map(strings_to_ustrs),
666                client_order_id,
667            )
668        })
669    }
670
671    #[pyo3(name = "bracket", signature = (
672        instrument_id,
673        order_side,
674        quantity,
675        quote_quantity=false,
676        emulation_trigger=None,
677        trigger_instrument_id=None,
678        contingency_type=ContingencyType::Ouo,
679        entry_order_type=OrderType::Market,
680        entry_price=None,
681        entry_trigger_price=None,
682        expire_time=None,
683        time_in_force=TimeInForce::Gtc,
684        entry_post_only=false,
685        entry_exec_algorithm_id=None,
686        entry_exec_algorithm_params=None,
687        entry_tags=None,
688        entry_client_order_id=None,
689        tp_order_type=OrderType::Limit,
690        tp_price=None,
691        tp_trigger_price=None,
692        tp_trigger_type=TriggerType::Default,
693        tp_activation_price=None,
694        tp_trailing_offset=None,
695        tp_trailing_offset_type=TrailingOffsetType::Price,
696        tp_limit_offset=None,
697        tp_time_in_force=TimeInForce::Gtc,
698        tp_post_only=true,
699        tp_exec_algorithm_id=None,
700        tp_exec_algorithm_params=None,
701        tp_tags=None,
702        tp_client_order_id=None,
703        sl_order_type=OrderType::StopMarket,
704        sl_trigger_price=None,
705        sl_trigger_type=TriggerType::Default,
706        sl_activation_price=None,
707        sl_trailing_offset=None,
708        sl_trailing_offset_type=TrailingOffsetType::Price,
709        sl_time_in_force=TimeInForce::Gtc,
710        sl_exec_algorithm_id=None,
711        sl_exec_algorithm_params=None,
712        sl_tags=None,
713        sl_client_order_id=None
714    ))]
715    fn py_bracket(
716        &mut self,
717        py: Python<'_>,
718        instrument_id: InstrumentId,
719        order_side: OrderSide,
720        quantity: Quantity,
721        quote_quantity: bool,
722        emulation_trigger: Option<TriggerType>,
723        trigger_instrument_id: Option<InstrumentId>,
724        contingency_type: ContingencyType,
725        entry_order_type: OrderType,
726        entry_price: Option<Price>,
727        entry_trigger_price: Option<Price>,
728        expire_time: Option<u64>,
729        time_in_force: TimeInForce,
730        entry_post_only: bool,
731        entry_exec_algorithm_id: Option<ExecAlgorithmId>,
732        entry_exec_algorithm_params: Option<IndexMap<String, String>>,
733        entry_tags: Option<Vec<String>>,
734        entry_client_order_id: Option<ClientOrderId>,
735        tp_order_type: OrderType,
736        tp_price: Option<Price>,
737        tp_trigger_price: Option<Price>,
738        tp_trigger_type: TriggerType,
739        tp_activation_price: Option<Price>,
740        tp_trailing_offset: Option<Decimal>,
741        tp_trailing_offset_type: TrailingOffsetType,
742        tp_limit_offset: Option<Decimal>,
743        tp_time_in_force: TimeInForce,
744        tp_post_only: bool,
745        tp_exec_algorithm_id: Option<ExecAlgorithmId>,
746        tp_exec_algorithm_params: Option<IndexMap<String, String>>,
747        tp_tags: Option<Vec<String>>,
748        tp_client_order_id: Option<ClientOrderId>,
749        sl_order_type: OrderType,
750        sl_trigger_price: Option<Price>,
751        sl_trigger_type: TriggerType,
752        sl_activation_price: Option<Price>,
753        sl_trailing_offset: Option<Decimal>,
754        sl_trailing_offset_type: TrailingOffsetType,
755        sl_time_in_force: TimeInForce,
756        sl_exec_algorithm_id: Option<ExecAlgorithmId>,
757        sl_exec_algorithm_params: Option<IndexMap<String, String>>,
758        sl_tags: Option<Vec<String>>,
759        sl_client_order_id: Option<ClientOrderId>,
760    ) -> PyResult<Py<PyList>> {
761        let orders = py_orders_from_factory(|| {
762            self.0
763                .borrow_mut()
764                .try_bracket()
765                .instrument_id(instrument_id)
766                .order_side(order_side)
767                .quantity(quantity)
768                .quote_quantity(quote_quantity)
769                .maybe_emulation_trigger(emulation_trigger)
770                .maybe_trigger_instrument_id(trigger_instrument_id)
771                .contingency_type(contingency_type)
772                .entry_order_type(entry_order_type)
773                .maybe_entry_price(entry_price)
774                .maybe_entry_trigger_price(entry_trigger_price)
775                .maybe_expire_time(expire_time.map(UnixNanos::from))
776                .time_in_force(time_in_force)
777                .entry_post_only(entry_post_only)
778                .maybe_entry_exec_algorithm_id(entry_exec_algorithm_id)
779                .maybe_entry_exec_algorithm_params(
780                    entry_exec_algorithm_params.map(str_indexmap_to_ustr),
781                )
782                .maybe_entry_tags(entry_tags.map(strings_to_ustrs))
783                .maybe_entry_client_order_id(entry_client_order_id)
784                .tp_order_type(tp_order_type)
785                .maybe_tp_price(tp_price)
786                .maybe_tp_trigger_price(tp_trigger_price)
787                .tp_trigger_type(tp_trigger_type)
788                .maybe_tp_activation_price(tp_activation_price)
789                .maybe_tp_trailing_offset(tp_trailing_offset)
790                .tp_trailing_offset_type(tp_trailing_offset_type)
791                .maybe_tp_limit_offset(tp_limit_offset)
792                .tp_time_in_force(tp_time_in_force)
793                .tp_post_only(tp_post_only)
794                .maybe_tp_exec_algorithm_id(tp_exec_algorithm_id)
795                .maybe_tp_exec_algorithm_params(tp_exec_algorithm_params.map(str_indexmap_to_ustr))
796                .maybe_tp_tags(tp_tags.map(strings_to_ustrs))
797                .maybe_tp_client_order_id(tp_client_order_id)
798                .sl_order_type(sl_order_type)
799                .maybe_sl_trigger_price(sl_trigger_price)
800                .sl_trigger_type(sl_trigger_type)
801                .maybe_sl_activation_price(sl_activation_price)
802                .maybe_sl_trailing_offset(sl_trailing_offset)
803                .sl_trailing_offset_type(sl_trailing_offset_type)
804                .sl_time_in_force(sl_time_in_force)
805                .maybe_sl_exec_algorithm_id(sl_exec_algorithm_id)
806                .maybe_sl_exec_algorithm_params(sl_exec_algorithm_params.map(str_indexmap_to_ustr))
807                .maybe_sl_tags(sl_tags.map(strings_to_ustrs))
808                .maybe_sl_client_order_id(sl_client_order_id)
809                .call()
810        })?;
811        let py_orders = orders
812            .into_iter()
813            .map(|order| order_any_to_pyobject(py, order))
814            .collect::<PyResult<Vec<_>>>()?;
815        Ok(PyList::new(py, py_orders)?.unbind())
816    }
817}
818
819fn py_order_from_factory<F>(py: Python<'_>, create: F) -> PyResult<Py<PyAny>>
820where
821    F: FnOnce() -> anyhow::Result<OrderAny>,
822{
823    let order = create().map_err(to_pyvalue_err)?;
824    order_any_to_pyobject(py, order)
825}
826
827fn py_orders_from_factory<F>(create: F) -> PyResult<Vec<OrderAny>>
828where
829    F: FnOnce() -> anyhow::Result<Vec<OrderAny>>,
830{
831    create().map_err(to_pyvalue_err)
832}
833
834fn strings_to_ustrs(values: Vec<String>) -> Vec<Ustr> {
835    values
836        .into_iter()
837        .map(|value| Ustr::from(value.as_str()))
838        .collect()
839}