Skip to main content

nautilus_model/python/events/order/
initialized.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::{IntoPyObjectNautilusExt, serialization::from_dict_pyo3},
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::{ContingencyType, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
31    events::OrderInitialized,
32    identifiers::{
33        ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
34    },
35    orders::str_indexmap_to_ustr,
36    types::{Price, Quantity},
37};
38
39#[pymethods]
40#[pyo3_stub_gen::derive::gen_stub_pymethods]
41impl OrderInitialized {
42    /// Represents an event where an order has been initialized.
43    ///
44    /// This is a seed event which can instantiate any order through a creation
45    /// method. This event should contain enough information to be able to send it
46    /// 'over the wire' and have a valid order created with exactly the same
47    /// properties as if it had been instantiated locally.
48    #[expect(clippy::too_many_arguments)]
49    #[expect(
50        clippy::fn_params_excessive_bools,
51        reason = "domain event constructor requires multiple boolean flags"
52    )]
53    #[new]
54    #[pyo3(signature = (trader_id, strategy_id, instrument_id, client_order_id, order_side, order_type, quantity, time_in_force, post_only, reduce_only, quote_quantity, reconciliation, event_id, ts_event, ts_init, price=None, trigger_price=None, trigger_type=None, limit_offset=None, trailing_offset=None, trailing_offset_type=None, 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))]
55    fn py_new(
56        trader_id: TraderId,
57        strategy_id: StrategyId,
58        instrument_id: InstrumentId,
59        client_order_id: ClientOrderId,
60        order_side: OrderSide,
61        order_type: OrderType,
62        quantity: Quantity,
63        time_in_force: TimeInForce,
64        post_only: bool,
65        reduce_only: bool,
66        quote_quantity: bool,
67        reconciliation: bool,
68        event_id: UUID4,
69        ts_event: u64,
70        ts_init: u64,
71        price: Option<Price>,
72        trigger_price: Option<Price>,
73        trigger_type: Option<TriggerType>,
74        limit_offset: Option<Decimal>,
75        trailing_offset: Option<Decimal>,
76        trailing_offset_type: Option<TrailingOffsetType>,
77        expire_time: Option<u64>,
78        display_qty: Option<Quantity>,
79        emulation_trigger: Option<TriggerType>,
80        trigger_instrument_id: Option<InstrumentId>,
81        contingency_type: Option<ContingencyType>,
82        order_list_id: Option<OrderListId>,
83        linked_order_ids: Option<Vec<ClientOrderId>>,
84        parent_order_id: Option<ClientOrderId>,
85        exec_algorithm_id: Option<ExecAlgorithmId>,
86        exec_algorithm_params: Option<IndexMap<String, String>>,
87        exec_spawn_id: Option<ClientOrderId>,
88        tags: Option<Vec<String>>,
89    ) -> Self {
90        Self::new(
91            trader_id,
92            strategy_id,
93            instrument_id,
94            client_order_id,
95            order_side,
96            order_type,
97            quantity,
98            time_in_force,
99            post_only,
100            reduce_only,
101            quote_quantity,
102            reconciliation,
103            event_id,
104            ts_event.into(),
105            ts_init.into(),
106            price,
107            trigger_price,
108            trigger_type,
109            limit_offset,
110            trailing_offset,
111            trailing_offset_type,
112            expire_time.map(UnixNanos::from),
113            display_qty,
114            emulation_trigger,
115            trigger_instrument_id,
116            contingency_type,
117            order_list_id,
118            linked_order_ids,
119            parent_order_id,
120            exec_algorithm_id,
121            exec_algorithm_params.map(str_indexmap_to_ustr),
122            exec_spawn_id,
123            tags.map(|vec| vec.iter().map(|s| Ustr::from(s)).collect()),
124        )
125    }
126
127    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
128        match op {
129            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
130            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
131            _ => py.NotImplemented(),
132        }
133    }
134
135    fn __repr__(&self) -> String {
136        format!("{self:?}")
137    }
138
139    fn __str__(&self) -> String {
140        self.to_string()
141    }
142
143    #[getter]
144    #[pyo3(name = "order_type")]
145    fn py_order_type(&self) -> OrderType {
146        self.order_type
147    }
148
149    #[staticmethod]
150    #[pyo3(name = "from_dict")]
151    fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
152        from_dict_pyo3(py, values)
153    }
154
155    #[pyo3(name = "to_dict")]
156    fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
157        let dict = PyDict::new(py);
158        dict.set_item("type", stringify!(OrderInitialized))?;
159        dict.set_item("trader_id", self.trader_id.to_string())?;
160        dict.set_item("strategy_id", self.strategy_id.to_string())?;
161        dict.set_item("instrument_id", self.instrument_id.to_string())?;
162        dict.set_item("client_order_id", self.client_order_id.to_string())?;
163        dict.set_item("order_side", self.order_side.to_string())?;
164        dict.set_item("order_type", self.order_type.to_string())?;
165        dict.set_item("quantity", self.quantity.to_string())?;
166        dict.set_item("time_in_force", self.time_in_force.to_string())?;
167        dict.set_item("post_only", self.post_only)?;
168        dict.set_item("reduce_only", self.reduce_only)?;
169        dict.set_item("quote_quantity", self.quote_quantity)?;
170        dict.set_item("reconciliation", self.reconciliation)?;
171        // TODO remove options as in legacy cython only
172        let options = PyDict::new(py);
173
174        if self.order_type == OrderType::StopMarket {
175            options.set_item("trigger_type", self.trigger_type.map(|x| x.to_string()))?;
176            options.set_item("trigger_price", self.trigger_price.map(|x| x.to_string()))?;
177            options.set_item("expire_time_ns", self.expire_time.map(|x| x.to_string()))?;
178        }
179        dict.set_item("options", options)?;
180        dict.set_item("event_id", self.event_id.to_string())?;
181        dict.set_item("ts_event", self.ts_event.as_u64())?;
182        dict.set_item("ts_init", self.ts_init.as_u64())?;
183        match self.price {
184            Some(price) => dict.set_item("price", price.to_string())?,
185            None => dict.set_item("price", py.None())?,
186        }
187
188        match self.trigger_price {
189            Some(trigger_price) => dict.set_item("trigger_price", trigger_price.to_string())?,
190            None => dict.set_item("trigger_price", py.None())?,
191        }
192
193        match self.trigger_type {
194            Some(trigger_type) => dict.set_item("trigger_type", trigger_type.to_string())?,
195            None => dict.set_item("trigger_type", py.None())?,
196        }
197
198        match self.limit_offset {
199            Some(limit_offset) => dict.set_item("limit_offset", limit_offset.to_string())?,
200            None => dict.set_item("limit_offset", py.None())?,
201        }
202
203        match self.trailing_offset {
204            Some(trailing_offset) => {
205                dict.set_item("trailing_offset", trailing_offset.to_string())?;
206            }
207            None => dict.set_item("trailing_offset", py.None())?,
208        }
209
210        match self.trailing_offset_type {
211            Some(trailing_offset_type) => {
212                dict.set_item("trailing_offset_type", trailing_offset_type.to_string())?;
213            }
214            None => dict.set_item("trailing_offset_type", py.None())?,
215        }
216
217        match self.expire_time {
218            Some(expire_time) => dict.set_item("expire_time", expire_time.as_u64())?,
219            None => dict.set_item("expire_time", py.None())?,
220        }
221
222        match self.display_qty {
223            Some(display_qty) => dict.set_item("display_qty", display_qty.to_string())?,
224            None => dict.set_item("display_qty", py.None())?,
225        }
226
227        match self.emulation_trigger {
228            Some(emulation_trigger) => {
229                dict.set_item("emulation_trigger", emulation_trigger.to_string())?;
230            }
231            None => dict.set_item("emulation_trigger", py.None())?,
232        }
233
234        match self.trigger_instrument_id {
235            Some(trigger_instrument_id) => {
236                dict.set_item("trigger_instrument_id", trigger_instrument_id.to_string())?;
237            }
238            None => dict.set_item("trigger_instrument_id", py.None())?,
239        }
240
241        match self.contingency_type {
242            Some(contingency_type) => {
243                dict.set_item("contingency_type", contingency_type.to_string())?;
244            }
245            None => dict.set_item("contingency_type", py.None())?,
246        }
247
248        match self.order_list_id {
249            Some(order_list_id) => dict.set_item("order_list_id", order_list_id.to_string())?,
250            None => dict.set_item("order_list_id", py.None())?,
251        }
252
253        match &self.linked_order_ids {
254            Some(linked_order_ids) => {
255                let py_linked_order_ids = PyList::empty(py);
256                for linked_order_id in linked_order_ids {
257                    py_linked_order_ids.append(linked_order_id.to_string())?;
258                }
259                dict.set_item("linked_order_ids", py_linked_order_ids)?;
260            }
261            None => dict.set_item("linked_order_ids", py.None())?,
262        }
263
264        match self.parent_order_id {
265            Some(parent_order_id) => {
266                dict.set_item("parent_order_id", parent_order_id.to_string())?;
267            }
268            None => dict.set_item("parent_order_id", py.None())?,
269        }
270
271        match self.exec_algorithm_id {
272            Some(exec_algorithm_id) => {
273                dict.set_item("exec_algorithm_id", exec_algorithm_id.to_string())?;
274            }
275            None => dict.set_item("exec_algorithm_id", py.None())?,
276        }
277
278        match &self.exec_algorithm_params {
279            Some(exec_algorithm_params) => {
280                let py_exec_algorithm_params = PyDict::new(py);
281                for (key, value) in exec_algorithm_params {
282                    py_exec_algorithm_params.set_item(key.to_string(), value.to_string())?;
283                }
284                dict.set_item("exec_algorithm_params", py_exec_algorithm_params)?;
285            }
286            None => dict.set_item("exec_algorithm_params", py.None())?,
287        }
288
289        match self.exec_spawn_id {
290            Some(exec_spawn_id) => dict.set_item("exec_spawn_id", exec_spawn_id.to_string())?,
291            None => dict.set_item("exec_spawn_id", py.None())?,
292        }
293
294        match &self.tags {
295            Some(tags) => dict.set_item(
296                "tags",
297                tags.iter().map(|x| x.to_string()).collect::<Vec<String>>(),
298            )?,
299            None => dict.set_item("tags", py.None())?,
300        }
301        Ok(dict.into())
302    }
303}