nautilus_model/python/orders/
market.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,
19    python::{
20        IntoPyObjectPoseiExt,
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::{
29    Bound, Py, PyAny, PyObject, PyResult, Python,
30    basic::CompareOp,
31    pymethods,
32    types::{PyAnyMethods, PyDict, PyList},
33};
34use rust_decimal::Decimal;
35use ustr::Ustr;
36
37use crate::{
38    enums::{ContingencyType, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce},
39    events::OrderInitialized,
40    identifiers::{
41        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
42    },
43    orders::{MarketOrder, 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, Quantity},
49};
50
51#[pymethods]
52impl MarketOrder {
53    #[new]
54    #[allow(clippy::too_many_arguments)]
55    #[pyo3(signature = (trader_id, strategy_id, instrument_id, client_order_id, order_side, quantity, init_id, ts_init, time_in_force, reduce_only, quote_quantity, 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))]
56    fn py_new(
57        trader_id: TraderId,
58        strategy_id: StrategyId,
59        instrument_id: InstrumentId,
60        client_order_id: ClientOrderId,
61        order_side: OrderSide,
62        quantity: Quantity,
63        init_id: UUID4,
64        ts_init: u64,
65        time_in_force: TimeInForce,
66        reduce_only: bool,
67        quote_quantity: bool,
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            init_id,
86            ts_init.into(),
87            reduce_only,
88            quote_quantity,
89            contingency_type,
90            order_list_id,
91            linked_order_ids,
92            parent_order_id,
93            exec_algorithm_id,
94            exec_algorithm_params.map(str_indexmap_to_ustr),
95            exec_spawn_id,
96            tags.map(|vec| vec.into_iter().map(|s| Ustr::from(s.as_str())).collect()),
97        )
98        .map_err(to_pyvalue_err)
99    }
100
101    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
102        match op {
103            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
104            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
105            _ => py.NotImplemented(),
106        }
107    }
108
109    fn __repr__(&self) -> String {
110        self.to_string()
111    }
112
113    fn __str__(&self) -> String {
114        self.to_string()
115    }
116
117    #[staticmethod]
118    #[pyo3(name = "create")]
119    fn py_create(init: OrderInitialized) -> PyResult<Self> {
120        Ok(MarketOrder::from(init))
121    }
122
123    #[staticmethod]
124    #[pyo3(name = "opposite_side")]
125    fn py_opposite_side(side: OrderSide) -> OrderSide {
126        OrderCore::opposite_side(side)
127    }
128
129    #[staticmethod]
130    #[pyo3(name = "closing_side")]
131    fn py_closing_side(side: PositionSide) -> OrderSide {
132        OrderCore::closing_side(side)
133    }
134
135    #[getter]
136    #[pyo3(name = "status")]
137    fn py_status(&self) -> OrderStatus {
138        self.status
139    }
140
141    #[pyo3(name = "commission")]
142    fn py_commission(&self, currency: &Currency) -> Option<Money> {
143        self.commission(currency)
144    }
145
146    #[pyo3(name = "commissions")]
147    fn py_commissions(&self) -> IndexMap<Currency, Money> {
148        self.commissions().clone()
149    }
150
151    #[getter]
152    #[pyo3(name = "account_id")]
153    fn py_account_id(&self) -> Option<AccountId> {
154        self.account_id
155    }
156
157    #[getter]
158    #[pyo3(name = "instrument_id")]
159    fn py_instrument_id(&self) -> InstrumentId {
160        self.instrument_id
161    }
162
163    #[getter]
164    #[pyo3(name = "trader_id")]
165    fn py_trader_id(&self) -> TraderId {
166        self.trader_id
167    }
168
169    #[getter]
170    #[pyo3(name = "strategy_id")]
171    fn py_strategy_id(&self) -> StrategyId {
172        self.strategy_id
173    }
174
175    #[getter]
176    #[pyo3(name = "init_id")]
177    fn py_init_id(&self) -> UUID4 {
178        self.init_id
179    }
180
181    #[getter]
182    #[pyo3(name = "ts_init")]
183    fn py_ts_init(&self) -> u64 {
184        self.ts_init.as_u64()
185    }
186
187    #[getter]
188    #[pyo3(name = "client_order_id")]
189    fn py_client_order_id(&self) -> ClientOrderId {
190        self.client_order_id
191    }
192
193    #[getter]
194    #[pyo3(name = "order_list_id")]
195    fn py_order_list_id(&self) -> Option<OrderListId> {
196        self.order_list_id
197    }
198
199    #[getter]
200    #[pyo3(name = "linked_order_ids")]
201    fn py_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
202        self.linked_order_ids.clone()
203    }
204
205    #[getter]
206    #[pyo3(name = "parent_order_id")]
207    fn py_parent_order_id(&self) -> Option<ClientOrderId> {
208        self.parent_order_id
209    }
210
211    #[getter]
212    #[pyo3(name = "exec_algorithm_id")]
213    fn py_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
214        self.exec_algorithm_id
215    }
216
217    #[getter]
218    #[pyo3(name = "exec_algorithm_params")]
219    fn py_exec_algorithm_params(&self) -> Option<IndexMap<&str, &str>> {
220        self.exec_algorithm_params
221            .as_ref()
222            .map(|x| x.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect())
223    }
224
225    #[getter]
226    #[pyo3(name = "exec_spawn_id")]
227    fn py_exec_spawn_id(&self) -> Option<ClientOrderId> {
228        self.exec_spawn_id
229    }
230
231    #[getter]
232    #[pyo3(name = "is_reduce_only")]
233    fn py_is_reduce_only(&self) -> bool {
234        self.is_reduce_only
235    }
236
237    #[getter]
238    #[pyo3(name = "is_quote_quantity")]
239    fn py_is_quote_quantity(&self) -> bool {
240        self.is_quote_quantity
241    }
242
243    #[getter]
244    #[pyo3(name = "contingency_type")]
245    fn py_contingency_type(&self) -> Option<ContingencyType> {
246        self.contingency_type
247    }
248
249    #[getter]
250    #[pyo3(name = "quantity")]
251    fn py_quantity(&self) -> Quantity {
252        self.quantity
253    }
254
255    #[getter]
256    #[pyo3(name = "side")]
257    fn py_side(&self) -> OrderSide {
258        self.side
259    }
260
261    #[getter]
262    #[pyo3(name = "order_type")]
263    fn py_order_type(&self) -> OrderType {
264        self.order_type
265    }
266
267    #[getter]
268    #[pyo3(name = "emulation_trigger")]
269    fn py_emulation_trigger(&self) -> Option<String> {
270        self.emulation_trigger.map(|x| x.to_string())
271    }
272
273    #[getter]
274    #[pyo3(name = "time_in_force")]
275    fn py_time_in_force(&self) -> TimeInForce {
276        self.time_in_force
277    }
278
279    #[getter]
280    #[pyo3(name = "tags")]
281    fn py_tags(&self) -> Option<Vec<&str>> {
282        self.tags
283            .as_ref()
284            .map(|vec| vec.iter().map(|s| s.as_str()).collect())
285    }
286
287    #[getter]
288    #[pyo3(name = "events")]
289    fn py_events(&self, py: Python<'_>) -> PyResult<Vec<PyObject>> {
290        self.events()
291            .into_iter()
292            .map(|event| order_event_to_pyobject(py, event.clone()))
293            .collect()
294    }
295
296    #[pyo3(name = "signed_decimal_qty")]
297    fn py_signed_decimal_qty(&self) -> Decimal {
298        self.signed_decimal_qty()
299    }
300
301    #[pyo3(name = "would_reduce_only")]
302    fn py_would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
303        self.would_reduce_only(side, position_qty)
304    }
305
306    #[pyo3(name = "apply")]
307    fn py_apply(&mut self, event: PyObject, py: Python<'_>) -> PyResult<()> {
308        let event_any = pyobject_to_order_event(py, event).unwrap();
309        self.apply(event_any).map(|_| ()).map_err(to_pyruntime_err)
310    }
311
312    #[staticmethod]
313    #[pyo3(name = "from_dict")]
314    fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
315        let trader_id = TraderId::from(get_required_string(values, "trader_id")?.as_str());
316        let strategy_id = StrategyId::from(get_required_string(values, "strategy_id")?.as_str());
317        let instrument_id =
318            InstrumentId::from(get_required_string(values, "instrument_id")?.as_str());
319        let client_order_id =
320            ClientOrderId::from(get_required_string(values, "client_order_id")?.as_str());
321        let order_side = get_required_parsed(values, "side", |s| {
322            s.parse::<OrderSide>().map_err(|e| e.to_string())
323        })?;
324        let quantity = Quantity::from(get_required_string(values, "quantity")?.as_str());
325        let time_in_force = get_required_parsed(values, "time_in_force", |s| {
326            s.parse::<TimeInForce>().map_err(|e| e.to_string())
327        })?;
328        let init_id = get_required_parsed(values, "init_id", |s| {
329            s.parse::<UUID4>().map_err(|e| e.to_string())
330        })?;
331        let ts_init = get_required::<u64>(values, "ts_init")?;
332        let is_reduce_only = get_required::<bool>(values, "is_reduce_only")?;
333        let is_quote_quantity = get_required::<bool>(values, "is_quote_quantity")?;
334        let contingency_type = get_optional_parsed(values, "contingency_type", |s| {
335            s.parse::<ContingencyType>().map_err(|e| e.to_string())
336        })?;
337        let order_list_id = get_optional_parsed(values, "order_list_id", |s| {
338            Ok(OrderListId::from(s.as_str()))
339        })?;
340        let linked_order_ids =
341            get_optional::<Vec<String>>(values, "linked_order_ids")?.map(|vec| {
342                vec.iter()
343                    .map(|s| ClientOrderId::from(s.as_str()))
344                    .collect()
345            });
346        let parent_order_id = get_optional_parsed(values, "parent_order_id", |s| {
347            Ok(ClientOrderId::from(s.as_str()))
348        })?;
349        let exec_algorithm_id = get_optional_parsed(values, "exec_algorithm_id", |s| {
350            Ok(ExecAlgorithmId::from(s.as_str()))
351        })?;
352        let exec_algorithm_params =
353            get_optional::<IndexMap<String, String>>(values, "exec_algorithm_params")?
354                .map(str_indexmap_to_ustr);
355        let exec_spawn_id = get_optional_parsed(values, "exec_spawn_id", |s| {
356            Ok(ClientOrderId::from(s.as_str()))
357        })?;
358        let tags = get_optional::<Vec<String>>(values, "tags")?
359            .map(|vec| vec.iter().map(|s| Ustr::from(s)).collect());
360        Self::new_checked(
361            trader_id,
362            strategy_id,
363            instrument_id,
364            client_order_id,
365            order_side,
366            quantity,
367            time_in_force,
368            init_id,
369            ts_init.into(),
370            is_reduce_only,
371            is_quote_quantity,
372            contingency_type,
373            order_list_id,
374            linked_order_ids,
375            parent_order_id,
376            exec_algorithm_id,
377            exec_algorithm_params,
378            exec_spawn_id,
379            tags,
380        )
381        .map_err(to_pyvalue_err)
382    }
383
384    #[pyo3(name = "to_dict")]
385    fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
386        let dict = PyDict::new(py);
387        dict.set_item("trader_id", self.trader_id.to_string())?;
388        dict.set_item("strategy_id", self.strategy_id.to_string())?;
389        dict.set_item("instrument_id", self.instrument_id.to_string())?;
390        dict.set_item("client_order_id", self.client_order_id.to_string())?;
391        dict.set_item("side", self.side.to_string())?;
392        dict.set_item("type", self.order_type.to_string())?;
393        dict.set_item("quantity", self.quantity.to_string())?;
394        dict.set_item("status", self.status.to_string())?;
395        dict.set_item("time_in_force", self.time_in_force.to_string())?;
396        dict.set_item("is_reduce_only", self.is_reduce_only)?;
397        dict.set_item("is_quote_quantity", self.is_quote_quantity)?;
398        dict.set_item("filled_qty", self.filled_qty.to_string())?;
399        dict.set_item("init_id", self.init_id.to_string())?;
400        dict.set_item("ts_init", self.ts_init.as_u64())?;
401        dict.set_item("ts_last", self.ts_last.as_u64())?;
402        dict.set_item(
403            "commissions",
404            commissions_from_indexmap(py, self.commissions().clone())?,
405        )?;
406        self.venue_order_id.map_or_else(
407            || dict.set_item("venue_order_id", py.None()),
408            |x| dict.set_item("venue_order_id", x.to_string()),
409        )?;
410        self.emulation_trigger.map_or_else(
411            || dict.set_item("emulation_trigger", py.None()),
412            |x| dict.set_item("emulation_trigger", x.to_string()),
413        )?;
414        self.contingency_type.map_or_else(
415            || dict.set_item("contingency_type", py.None()),
416            |x| dict.set_item("contingency_type", x.to_string()),
417        )?;
418        self.order_list_id.map_or_else(
419            || dict.set_item("order_list_id", py.None()),
420            |x| dict.set_item("order_list_id", x.to_string()),
421        )?;
422        self.linked_order_ids.clone().map_or_else(
423            || dict.set_item("linked_order_ids", py.None()),
424            |linked_order_ids| {
425                let linked_order_ids_list =
426                    PyList::new(py, linked_order_ids.iter().map(ToString::to_string))
427                        .expect("Invalid `ExactSizeIterator`");
428                dict.set_item("linked_order_ids", linked_order_ids_list)
429            },
430        )?;
431        self.parent_order_id.map_or_else(
432            || dict.set_item("parent_order_id", py.None()),
433            |x| dict.set_item("parent_order_id", x.to_string()),
434        )?;
435        self.exec_algorithm_id.map_or_else(
436            || dict.set_item("exec_algorithm_id", py.None()),
437            |x| dict.set_item("exec_algorithm_id", x.to_string()),
438        )?;
439        match &self.exec_algorithm_params {
440            Some(exec_algorithm_params) => {
441                let py_exec_algorithm_params = PyDict::new(py);
442                for (key, value) in exec_algorithm_params {
443                    py_exec_algorithm_params.set_item(key.to_string(), value.to_string())?;
444                }
445                dict.set_item("exec_algorithm_params", py_exec_algorithm_params)?;
446            }
447            None => dict.set_item("exec_algorithm_params", py.None())?,
448        }
449        self.exec_spawn_id.map_or_else(
450            || dict.set_item("exec_spawn_id", py.None()),
451            |x| dict.set_item("exec_spawn_id", x.to_string()),
452        )?;
453        self.tags.clone().map_or_else(
454            || dict.set_item("tags", py.None()),
455            |x| {
456                dict.set_item(
457                    "tags",
458                    x.iter().map(|x| x.to_string()).collect::<Vec<String>>(),
459                )
460            },
461        )?;
462        self.account_id.map_or_else(
463            || dict.set_item("account_id", py.None()),
464            |x| dict.set_item("account_id", x.to_string()),
465        )?;
466        self.slippage.map_or_else(
467            || dict.set_item("slippage", py.None()),
468            |x| dict.set_item("slippage", x.to_string()),
469        )?;
470        self.position_id.map_or_else(
471            || dict.set_item("position_id", py.None()),
472            |x| dict.set_item("position_id", x.to_string()),
473        )?;
474        self.liquidity_side.map_or_else(
475            || dict.set_item("liquidity_side", py.None()),
476            |x| dict.set_item("liquidity_side", x.to_string()),
477        )?;
478        self.last_trade_id.map_or_else(
479            || dict.set_item("last_trade_id", py.None()),
480            |x| dict.set_item("last_trade_id", x.to_string()),
481        )?;
482        self.avg_px.map_or_else(
483            || dict.set_item("avg_px", py.None()),
484            |x| dict.set_item("avg_px", x.to_string()),
485        )?;
486        Ok(dict.into())
487    }
488}