Skip to main content

nautilus_model/python/orders/
trailing_stop_limit.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
16use indexmap::IndexMap;
17use nautilus_core::{
18    UUID4, UnixNanos,
19    python::{
20        IntoPyObjectNautilusExt,
21        parsing::{get_optional, get_optional_parsed, get_required, get_required_parsed},
22        to_pyruntime_err, to_pyvalue_err,
23    },
24};
25use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
26use rust_decimal::Decimal;
27use ustr::Ustr;
28
29use crate::{
30    enums::{
31        ContingencyType, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce,
32        TrailingOffsetType, TriggerType,
33    },
34    events::order::initialized::OrderInitialized,
35    identifiers::{
36        ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
37    },
38    orders::{Order, OrderCore, TrailingStopLimitOrder, str_indexmap_to_ustr},
39    python::{
40        common::commissions_from_indexmap,
41        events::order::{order_event_to_pyobject, pyobject_to_order_event},
42    },
43    types::{Currency, Money, Price, Quantity},
44};
45
46#[pymethods]
47#[pyo3_stub_gen::derive::gen_stub_pymethods]
48impl TrailingStopLimitOrder {
49    /// Creates a new `TrailingStopLimitOrder` instance.
50    #[new]
51    #[expect(clippy::too_many_arguments)]
52    #[pyo3(signature = (trader_id, strategy_id, instrument_id, client_order_id, order_side, quantity, price, trigger_price, trigger_type, limit_offset, trailing_offset, trailing_offset_type, time_in_force, post_only, reduce_only, quote_quantity, init_id, ts_init, activation_price=None, expire_time=None, display_qty=None, emulation_trigger=None, trigger_instrument_id=None, contingency_type=None, order_list_id=None, linked_order_ids=None, parent_order_id=None, exec_algorithm_id=None, exec_algorithm_params=None, exec_spawn_id=None, tags=None))]
53    fn py_new(
54        trader_id: TraderId,
55        strategy_id: StrategyId,
56        instrument_id: InstrumentId,
57        client_order_id: ClientOrderId,
58        order_side: OrderSide,
59        quantity: Quantity,
60        price: Option<Price>,
61        trigger_price: Option<Price>,
62        trigger_type: TriggerType,
63        limit_offset: Decimal,
64        trailing_offset: Decimal,
65        trailing_offset_type: TrailingOffsetType,
66        time_in_force: TimeInForce,
67        post_only: bool,
68        reduce_only: bool,
69        quote_quantity: bool,
70        init_id: UUID4,
71        ts_init: u64,
72        activation_price: Option<Price>,
73        expire_time: Option<u64>,
74        display_qty: Option<Quantity>,
75        emulation_trigger: Option<TriggerType>,
76        trigger_instrument_id: Option<InstrumentId>,
77        contingency_type: Option<ContingencyType>,
78        order_list_id: Option<OrderListId>,
79        linked_order_ids: Option<Vec<ClientOrderId>>,
80        parent_order_id: Option<ClientOrderId>,
81        exec_algorithm_id: Option<ExecAlgorithmId>,
82        exec_algorithm_params: Option<IndexMap<String, String>>,
83        exec_spawn_id: Option<ClientOrderId>,
84        tags: Option<Vec<String>>,
85    ) -> PyResult<Self> {
86        Self::new_checked(
87            trader_id,
88            strategy_id,
89            instrument_id,
90            client_order_id,
91            order_side,
92            quantity,
93            activation_price,
94            price,
95            trigger_price,
96            trigger_type,
97            limit_offset,
98            trailing_offset,
99            trailing_offset_type,
100            time_in_force,
101            expire_time.map(std::convert::Into::into),
102            post_only,
103            reduce_only,
104            quote_quantity,
105            display_qty,
106            emulation_trigger,
107            trigger_instrument_id,
108            contingency_type,
109            order_list_id,
110            linked_order_ids,
111            parent_order_id,
112            exec_algorithm_id,
113            exec_algorithm_params.map(str_indexmap_to_ustr),
114            exec_spawn_id,
115            tags.map(|vec| vec.into_iter().map(|s| Ustr::from(s.as_str())).collect()),
116            init_id,
117            ts_init.into(),
118        )
119        .map_err(to_pyvalue_err)
120    }
121
122    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
123        match op {
124            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
125            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
126            _ => py.NotImplemented(),
127        }
128    }
129
130    fn __repr__(&self) -> String {
131        self.to_string()
132    }
133
134    fn __str__(&self) -> String {
135        self.to_string()
136    }
137
138    #[staticmethod]
139    #[pyo3(name = "create")]
140    fn py_create(init: OrderInitialized) -> PyResult<Self> {
141        Self::try_from(init).map_err(to_pyvalue_err)
142    }
143
144    #[staticmethod]
145    #[pyo3(name = "opposite_side")]
146    fn py_opposite_side(side: OrderSide) -> OrderSide {
147        OrderCore::opposite_side(side)
148    }
149
150    #[staticmethod]
151    #[pyo3(name = "closing_side")]
152    fn py_closing_side(side: PositionSide) -> Option<OrderSide> {
153        OrderCore::closing_side(side)
154    }
155
156    #[getter]
157    #[pyo3(name = "status")]
158    fn py_status(&self) -> OrderStatus {
159        self.status
160    }
161
162    #[getter]
163    #[pyo3(name = "trader_id")]
164    fn py_trader_id(&self) -> TraderId {
165        self.trader_id
166    }
167
168    #[getter]
169    #[pyo3(name = "strategy_id")]
170    fn py_strategy_id(&self) -> StrategyId {
171        self.strategy_id
172    }
173
174    #[getter]
175    #[pyo3(name = "instrument_id")]
176    fn py_instrument_id(&self) -> InstrumentId {
177        self.instrument_id
178    }
179
180    #[getter]
181    #[pyo3(name = "client_order_id")]
182    fn py_client_order_id(&self) -> ClientOrderId {
183        self.client_order_id
184    }
185
186    #[getter]
187    #[pyo3(name = "side")]
188    fn py_order_side(&self) -> OrderSide {
189        self.side
190    }
191
192    #[getter]
193    #[pyo3(name = "quantity")]
194    fn py_quantity(&self) -> Quantity {
195        self.quantity
196    }
197
198    #[getter]
199    #[pyo3(name = "price")]
200    fn py_price(&self) -> Option<Price> {
201        self.price
202    }
203
204    #[getter]
205    #[pyo3(name = "trigger_price")]
206    fn py_trigger_price(&self) -> Option<Price> {
207        self.trigger_price
208    }
209
210    #[getter]
211    #[pyo3(name = "trigger_type")]
212    fn py_trigger_type(&self) -> TriggerType {
213        self.trigger_type
214    }
215
216    #[getter]
217    #[pyo3(name = "limit_offset")]
218    fn py_limit_offset(&self) -> Decimal {
219        self.limit_offset
220    }
221
222    #[getter]
223    #[pyo3(name = "trailing_offset")]
224    fn py_trailing_offset(&self) -> Decimal {
225        self.trailing_offset
226    }
227
228    #[getter]
229    #[pyo3(name = "trailing_offset_type")]
230    fn py_trailing_offset_type(&self) -> TrailingOffsetType {
231        self.trailing_offset_type
232    }
233
234    #[getter]
235    #[pyo3(name = "activation_price")]
236    fn py_activation_price(&self) -> Option<Price> {
237        self.activation_price
238    }
239
240    #[getter]
241    #[pyo3(name = "order_type")]
242    fn py_order_type(&self) -> OrderType {
243        self.order_type
244    }
245
246    #[getter]
247    #[pyo3(name = "time_in_force")]
248    fn py_time_in_force(&self) -> TimeInForce {
249        self.time_in_force
250    }
251
252    #[getter]
253    #[pyo3(name = "expire_time")]
254    fn py_expire_time(&self) -> Option<u64> {
255        self.expire_time.map(std::convert::Into::into)
256    }
257
258    #[getter]
259    #[pyo3(name = "init_id")]
260    fn py_init_id(&self) -> UUID4 {
261        self.init_id
262    }
263
264    #[getter]
265    #[pyo3(name = "ts_init")]
266    fn py_ts_init(&self) -> u64 {
267        self.ts_init.as_u64()
268    }
269
270    #[getter]
271    #[pyo3(name = "init_event")]
272    fn py_init_event(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
273        match self.init_event() {
274            Some(event) => order_event_to_pyobject(py, event),
275            None => Ok(py.None()),
276        }
277    }
278
279    #[getter]
280    #[pyo3(name = "has_price")]
281    fn py_has_price(&self) -> bool {
282        self.price.is_some()
283    }
284
285    #[getter]
286    #[pyo3(name = "has_trigger_price")]
287    fn py_has_trigger_price(&self) -> bool {
288        true
289    }
290
291    #[getter]
292    #[pyo3(name = "is_passive")]
293    fn py_is_passive(&self) -> bool {
294        self.is_passive()
295    }
296
297    #[getter]
298    #[pyo3(name = "is_aggressive")]
299    fn py_is_aggressive(&self) -> bool {
300        self.is_aggressive()
301    }
302
303    #[getter]
304    #[pyo3(name = "is_closed")]
305    fn py_is_closed(&self) -> bool {
306        self.is_closed()
307    }
308
309    #[getter]
310    #[pyo3(name = "is_open")]
311    fn py_is_open(&self) -> bool {
312        self.is_open()
313    }
314
315    #[getter]
316    #[pyo3(name = "is_post_only")]
317    fn py_post_only(&self) -> bool {
318        self.is_post_only
319    }
320
321    #[getter]
322    #[pyo3(name = "is_reduce_only")]
323    fn py_reduce_only(&self) -> bool {
324        self.is_reduce_only
325    }
326
327    #[getter]
328    #[pyo3(name = "is_quote_quantity")]
329    fn py_quote_quantity(&self) -> bool {
330        self.is_quote_quantity
331    }
332
333    #[getter]
334    #[pyo3(name = "display_qty")]
335    fn py_display_qty(&self) -> Option<Quantity> {
336        self.display_qty
337    }
338
339    #[getter]
340    #[pyo3(name = "emulation_trigger")]
341    fn py_emulation_trigger(&self) -> Option<TriggerType> {
342        self.emulation_trigger
343    }
344
345    #[getter]
346    #[pyo3(name = "trigger_instrument_id")]
347    fn py_trigger_instrument_id(&self) -> Option<InstrumentId> {
348        self.trigger_instrument_id
349    }
350
351    #[getter]
352    #[pyo3(name = "contingency_type")]
353    fn py_contingency_type(&self) -> Option<ContingencyType> {
354        self.contingency_type
355    }
356
357    #[getter]
358    #[pyo3(name = "order_list_id")]
359    fn py_order_list_id(&self) -> Option<OrderListId> {
360        self.order_list_id
361    }
362
363    #[getter]
364    #[pyo3(name = "linked_order_ids")]
365    fn py_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
366        self.linked_order_ids.clone()
367    }
368
369    #[getter]
370    #[pyo3(name = "parent_order_id")]
371    fn py_parent_order_id(&self) -> Option<ClientOrderId> {
372        self.parent_order_id
373    }
374
375    #[getter]
376    #[pyo3(name = "exec_algorithm_id")]
377    fn py_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
378        self.exec_algorithm_id
379    }
380
381    #[getter]
382    #[pyo3(name = "exec_algorithm_params")]
383    fn py_exec_algorithm_params(&self) -> Option<IndexMap<&str, &str>> {
384        self.exec_algorithm_params
385            .as_ref()
386            .map(|x| x.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect())
387    }
388
389    #[getter]
390    #[pyo3(name = "exec_spawn_id")]
391    fn py_exec_spawn_id(&self) -> Option<ClientOrderId> {
392        self.exec_spawn_id
393    }
394
395    #[getter]
396    #[pyo3(name = "account_id")]
397    fn py_account_id(&self) -> Option<crate::identifiers::AccountId> {
398        self.account_id
399    }
400
401    #[getter]
402    #[pyo3(name = "tags")]
403    fn py_tags(&self) -> Option<Vec<&str>> {
404        self.tags
405            .as_ref()
406            .map(|vec| vec.iter().map(Ustr::as_str).collect())
407    }
408
409    #[pyo3(name = "commission")]
410    fn py_commission(&self, currency: &Currency) -> Option<Money> {
411        self.commission(currency)
412    }
413
414    #[pyo3(name = "commissions")]
415    fn py_commissions(&self) -> IndexMap<Currency, Money> {
416        self.commissions().clone()
417    }
418
419    #[pyo3(name = "events")]
420    fn py_events(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
421        self.events()
422            .into_iter()
423            .map(|event| order_event_to_pyobject(py, event.clone()))
424            .collect()
425    }
426
427    #[pyo3(name = "signed_decimal_qty")]
428    fn py_signed_decimal_qty(&self) -> Decimal {
429        self.signed_decimal_qty()
430    }
431
432    #[pyo3(name = "would_reduce_only")]
433    fn py_would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
434        self.would_reduce_only(side, position_qty)
435    }
436
437    #[pyo3(name = "apply")]
438    fn py_apply(&mut self, event: Py<PyAny>, py: Python<'_>) -> PyResult<()> {
439        let event_any = pyobject_to_order_event(py, event)?;
440        self.apply(event_any).map_err(to_pyruntime_err)
441    }
442
443    #[staticmethod]
444    #[pyo3(name = "from_dict")]
445    fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
446        let trader_id = get_required_parsed(values, "trader_id", |s| {
447            TraderId::new_checked(s).map_err(|e| e.to_string())
448        })?;
449
450        let strategy_id = get_required_parsed(values, "strategy_id", |s| {
451            StrategyId::new_checked(s).map_err(|e| e.to_string())
452        })?;
453
454        let instrument_id = get_required_parsed(values, "instrument_id", |s| {
455            s.parse::<InstrumentId>().map_err(|e| e.to_string())
456        })?;
457
458        let client_order_id = get_required_parsed(values, "client_order_id", |s| {
459            ClientOrderId::new_checked(s).map_err(|e| e.to_string())
460        })?;
461
462        let order_side = get_required_parsed(values, "side", |s| {
463            s.parse::<OrderSide>().map_err(|e| e.to_string())
464        })?;
465
466        let quantity = get_required_parsed(values, "quantity", |s| s.parse::<Quantity>())?;
467        let activation_price =
468            get_optional_parsed(values, "activation_price", |s| s.parse::<Price>())?;
469        let price = get_optional_parsed(values, "price", |s| s.parse::<Price>())?;
470        let trigger_price = get_optional_parsed(values, "trigger_price", |s| s.parse::<Price>())?;
471
472        let trigger_type = get_required_parsed(values, "trigger_type", |s| {
473            s.parse::<TriggerType>().map_err(|e| e.to_string())
474        })?;
475        let limit_offset = get_required_parsed(values, "limit_offset", |s| {
476            s.parse::<Decimal>().map_err(|e| e.to_string())
477        })?;
478        let trailing_offset = get_required_parsed(values, "trailing_offset", |s| {
479            s.parse::<Decimal>().map_err(|e| e.to_string())
480        })?;
481        let trailing_offset_type = get_required_parsed(values, "trailing_offset_type", |s| {
482            s.parse::<TrailingOffsetType>().map_err(|e| e.to_string())
483        })?;
484        let time_in_force = get_required_parsed(values, "time_in_force", |s| {
485            s.parse::<TimeInForce>().map_err(|e| e.to_string())
486        })?;
487        let post_only = get_required::<bool>(values, "is_post_only")?;
488        let reduce_only = get_required::<bool>(values, "is_reduce_only")?;
489        let quote_quantity = get_required::<bool>(values, "is_quote_quantity")?;
490        let expire_time = get_optional::<u64>(values, "expire_time_ns")?.map(UnixNanos::from);
491        let display_quantity =
492            get_optional_parsed(values, "display_qty", |s| s.parse::<Quantity>())?;
493
494        let emulation_trigger = get_optional_parsed(values, "emulation_trigger", |s| {
495            s.parse::<TriggerType>().map_err(|e| e.to_string())
496        })?;
497        let trigger_instrument_id = get_optional_parsed(values, "trigger_instrument_id", |s| {
498            s.parse::<InstrumentId>().map_err(|e| e.to_string())
499        })?;
500        let contingency_type = get_optional_parsed(values, "contingency_type", |s| {
501            s.parse::<ContingencyType>().map_err(|e| e.to_string())
502        })?;
503
504        let order_list_id = get_optional_parsed(values, "order_list_id", |s| {
505            OrderListId::new_checked(s).map_err(|e| e.to_string())
506        })?;
507
508        let linked_order_ids = get_optional::<Vec<String>>(values, "linked_order_ids")?
509            .map(|vec| {
510                vec.into_iter()
511                    .map(ClientOrderId::new_checked)
512                    .collect::<Result<Vec<_>, _>>()
513            })
514            .transpose()
515            .map_err(|e| to_pyvalue_err(format!("Failed to parse 'linked_order_ids': {e}")))?;
516
517        let parent_order_id = get_optional_parsed(values, "parent_order_id", |s| {
518            ClientOrderId::new_checked(s).map_err(|e| e.to_string())
519        })?;
520
521        let exec_algorithm_id = get_optional_parsed(values, "exec_algorithm_id", |s| {
522            ExecAlgorithmId::new_checked(s).map_err(|e| e.to_string())
523        })?;
524        let exec_algorithm_params =
525            get_optional::<IndexMap<String, String>>(values, "exec_algorithm_params")?
526                .map(str_indexmap_to_ustr);
527
528        let exec_spawn_id = get_optional_parsed(values, "exec_spawn_id", |s| {
529            ClientOrderId::new_checked(s).map_err(|e| e.to_string())
530        })?;
531        let tags = get_optional::<Vec<String>>(values, "tags")?
532            .map(|vec| vec.iter().map(|s| Ustr::from(s)).collect());
533        let init_id = get_required_parsed(values, "init_id", |s| s.parse::<UUID4>())?;
534        let ts_init = get_required::<u64>(values, "ts_init")?;
535        let order = Self::new_checked(
536            trader_id,
537            strategy_id,
538            instrument_id,
539            client_order_id,
540            order_side,
541            quantity,
542            activation_price,
543            price,
544            trigger_price,
545            trigger_type,
546            limit_offset,
547            trailing_offset,
548            trailing_offset_type,
549            time_in_force,
550            expire_time,
551            post_only,
552            reduce_only,
553            quote_quantity,
554            display_quantity,
555            emulation_trigger,
556            trigger_instrument_id,
557            contingency_type,
558            order_list_id,
559            linked_order_ids,
560            parent_order_id,
561            exec_algorithm_id,
562            exec_algorithm_params,
563            exec_spawn_id,
564            tags,
565            init_id,
566            ts_init.into(),
567        )
568        .map_err(to_pyvalue_err)?;
569        Ok(order)
570    }
571
572    #[pyo3(name = "to_dict")]
573    fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
574        let dict = PyDict::new(py);
575        dict.set_item("trader_id", self.trader_id.to_string())?;
576        dict.set_item("strategy_id", self.strategy_id.to_string())?;
577        dict.set_item("instrument_id", self.instrument_id.to_string())?;
578        dict.set_item("client_order_id", self.client_order_id.to_string())?;
579        dict.set_item("side", self.side.to_string())?;
580        dict.set_item("type", self.order_type.to_string())?;
581        dict.set_item("quantity", self.quantity.to_string())?;
582        dict.set_item("status", self.status.to_string())?;
583        self.price.map_or_else(
584            || dict.set_item("price", py.None()),
585            |x| dict.set_item("price", x.to_string()),
586        )?;
587        self.trigger_price.map_or_else(
588            || dict.set_item("trigger_price", py.None()),
589            |x| dict.set_item("trigger_price", x.to_string()),
590        )?;
591        dict.set_item("trigger_type", self.trigger_type.to_string())?;
592        dict.set_item("limit_offset", self.limit_offset.to_string())?;
593        dict.set_item("trailing_offset", self.trailing_offset.to_string())?;
594        dict.set_item(
595            "trailing_offset_type",
596            self.trailing_offset_type.to_string(),
597        )?;
598        dict.set_item("filled_qty", self.filled_qty.to_string())?;
599        dict.set_item("time_in_force", self.time_in_force.to_string())?;
600        dict.set_item("is_post_only", self.is_post_only)?;
601        dict.set_item("is_reduce_only", self.is_reduce_only)?;
602        dict.set_item("is_quote_quantity", self.is_quote_quantity)?;
603        dict.set_item("init_id", self.init_id.to_string())?;
604        dict.set_item(
605            "expire_time_ns",
606            self.expire_time.filter(|&t| t != 0).map(|t| t.as_u64()),
607        )?;
608        dict.set_item("ts_init", self.ts_init.as_u64())?;
609        dict.set_item("ts_last", self.ts_last.as_u64())?;
610        dict.set_item(
611            "commissions",
612            commissions_from_indexmap(py, self.commissions())?,
613        )?;
614        self.activation_price.map_or_else(
615            || dict.set_item("activation_price", py.None()),
616            |x| dict.set_item("activation_price", x.to_string()),
617        )?;
618        self.last_trade_id.map_or_else(
619            || dict.set_item("last_trade_id", py.None()),
620            |x| dict.set_item("last_trade_id", x.to_string()),
621        )?;
622        self.avg_px.map_or_else(
623            || dict.set_item("avg_px", py.None()),
624            |x| dict.set_item("avg_px", x.to_string()),
625        )?;
626        self.position_id.map_or_else(
627            || dict.set_item("position_id", py.None()),
628            |x| dict.set_item("position_id", x.to_string()),
629        )?;
630        self.liquidity_side.map_or_else(
631            || dict.set_item("liquidity_side", py.None()),
632            |x| dict.set_item("liquidity_side", x.to_string()),
633        )?;
634        self.slippage.map_or_else(
635            || dict.set_item("slippage", py.None()),
636            |x| dict.set_item("slippage", x.to_string()),
637        )?;
638        self.account_id.map_or_else(
639            || dict.set_item("account_id", py.None()),
640            |x| dict.set_item("account_id", x.to_string()),
641        )?;
642        self.venue_order_id.map_or_else(
643            || dict.set_item("venue_order_id", py.None()),
644            |x| dict.set_item("venue_order_id", x.to_string()),
645        )?;
646        self.display_qty.map_or_else(
647            || dict.set_item("display_qty", py.None()),
648            |x| dict.set_item("display_qty", x.to_string()),
649        )?;
650        self.emulation_trigger.map_or_else(
651            || dict.set_item("emulation_trigger", py.None()),
652            |x| dict.set_item("emulation_trigger", x.to_string()),
653        )?;
654        self.trigger_instrument_id.map_or_else(
655            || dict.set_item("trigger_instrument_id", py.None()),
656            |x| dict.set_item("trigger_instrument_id", x.to_string()),
657        )?;
658        self.contingency_type.map_or_else(
659            || dict.set_item("contingency_type", py.None()),
660            |x| dict.set_item("contingency_type", x.to_string()),
661        )?;
662        self.order_list_id.map_or_else(
663            || dict.set_item("order_list_id", py.None()),
664            |x| dict.set_item("order_list_id", x.to_string()),
665        )?;
666        dict.set_item(
667            "linked_order_ids",
668            self.linked_order_ids
669                .as_ref()
670                .map(|x| x.iter().map(ToString::to_string).collect::<Vec<String>>()),
671        )?;
672        self.parent_order_id.map_or_else(
673            || dict.set_item("parent_order_id", py.None()),
674            |x| dict.set_item("parent_order_id", x.to_string()),
675        )?;
676        self.exec_algorithm_id.map_or_else(
677            || dict.set_item("exec_algorithm_id", py.None()),
678            |x| dict.set_item("exec_algorithm_id", x.to_string()),
679        )?;
680        dict.set_item(
681            "exec_algorithm_params",
682            self.exec_algorithm_params.as_ref().map(|x| {
683                x.iter()
684                    .map(|(k, v)| (k.to_string(), v.to_string()))
685                    .collect::<IndexMap<String, String>>()
686            }),
687        )?;
688        self.exec_spawn_id.map_or_else(
689            || dict.set_item("exec_spawn_id", py.None()),
690            |x| dict.set_item("exec_spawn_id", x.to_string()),
691        )?;
692        dict.set_item(
693            "tags",
694            self.tags
695                .as_ref()
696                .map(|vec| vec.iter().map(ToString::to_string).collect::<Vec<String>>()),
697        )?;
698        Ok(dict.into())
699    }
700}