Skip to main content

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