1use 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}