nautilus_model/python/orders/
limit.rs

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