nautilus_model/python/orders/
limit.rs

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