Skip to main content

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