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, 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::{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#[pyo3_stub_gen::derive::gen_stub_pymethods]
40#[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, activation_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        activation_price: Option<Price>,
73        trigger_price: Option<Price>,
74        trigger_type: Option<TriggerType>,
75        limit_offset: Option<Decimal>,
76        trailing_offset: Option<Decimal>,
77        trailing_offset_type: Option<TrailingOffsetType>,
78        expire_time: Option<u64>,
79        display_qty: Option<Quantity>,
80        emulation_trigger: Option<TriggerType>,
81        trigger_instrument_id: Option<InstrumentId>,
82        contingency_type: Option<ContingencyType>,
83        order_list_id: Option<OrderListId>,
84        linked_order_ids: Option<Vec<ClientOrderId>>,
85        parent_order_id: Option<ClientOrderId>,
86        exec_algorithm_id: Option<ExecAlgorithmId>,
87        exec_algorithm_params: Option<IndexMap<String, String>>,
88        exec_spawn_id: Option<ClientOrderId>,
89        tags: Option<Vec<String>>,
90    ) -> PyResult<Self> {
91        Self::new_checked(
92            trader_id,
93            strategy_id,
94            instrument_id,
95            client_order_id,
96            order_side,
97            order_type,
98            quantity,
99            time_in_force,
100            post_only,
101            reduce_only,
102            quote_quantity,
103            reconciliation,
104            event_id,
105            ts_event.into(),
106            ts_init.into(),
107            price,
108            activation_price,
109            trigger_price,
110            trigger_type,
111            limit_offset,
112            trailing_offset,
113            trailing_offset_type,
114            expire_time.map(UnixNanos::from),
115            display_qty,
116            emulation_trigger,
117            trigger_instrument_id,
118            contingency_type,
119            order_list_id,
120            linked_order_ids,
121            parent_order_id,
122            exec_algorithm_id,
123            exec_algorithm_params.map(str_indexmap_to_ustr),
124            exec_spawn_id,
125            tags.map(|vec| vec.iter().map(|s| Ustr::from(s)).collect()),
126        )
127        .map_err(to_pyvalue_err)
128    }
129
130    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
131        match op {
132            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
133            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
134            _ => py.NotImplemented(),
135        }
136    }
137
138    fn __repr__(&self) -> String {
139        format!("{self:?}")
140    }
141
142    fn __str__(&self) -> String {
143        self.to_string()
144    }
145
146    #[getter]
147    #[pyo3(name = "trader_id")]
148    fn py_trader_id(&self) -> TraderId {
149        self.trader_id
150    }
151
152    #[getter]
153    #[pyo3(name = "strategy_id")]
154    fn py_strategy_id(&self) -> StrategyId {
155        self.strategy_id
156    }
157
158    #[getter]
159    #[pyo3(name = "instrument_id")]
160    fn py_instrument_id(&self) -> InstrumentId {
161        self.instrument_id
162    }
163
164    #[getter]
165    #[pyo3(name = "client_order_id")]
166    fn py_client_order_id(&self) -> ClientOrderId {
167        self.client_order_id
168    }
169
170    #[getter]
171    #[pyo3(name = "order_side")]
172    fn py_order_side(&self) -> OrderSide {
173        self.order_side
174    }
175
176    #[getter]
177    #[pyo3(name = "order_type")]
178    fn py_order_type(&self) -> OrderType {
179        self.order_type
180    }
181
182    #[getter]
183    #[pyo3(name = "quantity")]
184    fn py_quantity(&self) -> Quantity {
185        self.quantity
186    }
187
188    #[getter]
189    #[pyo3(name = "time_in_force")]
190    fn py_time_in_force(&self) -> TimeInForce {
191        self.time_in_force
192    }
193
194    #[getter]
195    #[pyo3(name = "post_only")]
196    fn py_post_only(&self) -> bool {
197        self.post_only
198    }
199
200    #[getter]
201    #[pyo3(name = "reduce_only")]
202    fn py_reduce_only(&self) -> bool {
203        self.reduce_only
204    }
205
206    #[getter]
207    #[pyo3(name = "quote_quantity")]
208    fn py_quote_quantity(&self) -> bool {
209        self.quote_quantity
210    }
211
212    #[getter]
213    #[pyo3(name = "reconciliation")]
214    fn py_reconciliation(&self) -> bool {
215        self.reconciliation
216    }
217
218    #[getter]
219    #[pyo3(name = "event_id")]
220    fn py_event_id(&self) -> UUID4 {
221        self.event_id
222    }
223
224    #[getter]
225    #[pyo3(name = "ts_event")]
226    fn py_ts_event(&self) -> u64 {
227        self.ts_event.as_u64()
228    }
229
230    #[getter]
231    #[pyo3(name = "ts_init")]
232    fn py_ts_init(&self) -> u64 {
233        self.ts_init.as_u64()
234    }
235
236    #[getter]
237    #[pyo3(name = "price")]
238    fn py_price(&self) -> Option<Price> {
239        self.price
240    }
241
242    #[getter]
243    #[pyo3(name = "activation_price")]
244    fn py_activation_price(&self) -> Option<Price> {
245        self.activation_price
246    }
247
248    #[getter]
249    #[pyo3(name = "trigger_price")]
250    fn py_trigger_price(&self) -> Option<Price> {
251        self.trigger_price
252    }
253
254    #[getter]
255    #[pyo3(name = "trigger_type")]
256    fn py_trigger_type(&self) -> Option<TriggerType> {
257        self.trigger_type
258    }
259
260    #[getter]
261    #[pyo3(name = "limit_offset")]
262    fn py_limit_offset(&self) -> Option<Decimal> {
263        self.limit_offset
264    }
265
266    #[getter]
267    #[pyo3(name = "trailing_offset")]
268    fn py_trailing_offset(&self) -> Option<Decimal> {
269        self.trailing_offset
270    }
271
272    #[getter]
273    #[pyo3(name = "trailing_offset_type")]
274    fn py_trailing_offset_type(&self) -> Option<TrailingOffsetType> {
275        self.trailing_offset_type
276    }
277
278    #[getter]
279    #[pyo3(name = "expire_time")]
280    fn py_expire_time(&self) -> Option<u64> {
281        self.expire_time.map(|ts| ts.as_u64())
282    }
283
284    #[getter]
285    #[pyo3(name = "display_qty")]
286    fn py_display_qty(&self) -> Option<Quantity> {
287        self.display_qty
288    }
289
290    #[getter]
291    #[pyo3(name = "emulation_trigger")]
292    fn py_emulation_trigger(&self) -> Option<TriggerType> {
293        self.emulation_trigger
294    }
295
296    #[getter]
297    #[pyo3(name = "trigger_instrument_id")]
298    fn py_trigger_instrument_id(&self) -> Option<InstrumentId> {
299        self.trigger_instrument_id
300    }
301
302    #[getter]
303    #[pyo3(name = "contingency_type")]
304    fn py_contingency_type(&self) -> Option<ContingencyType> {
305        self.contingency_type
306    }
307
308    #[getter]
309    #[pyo3(name = "order_list_id")]
310    fn py_order_list_id(&self) -> Option<OrderListId> {
311        self.order_list_id
312    }
313
314    #[getter]
315    #[pyo3(name = "linked_order_ids")]
316    fn py_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
317        self.linked_order_ids.clone()
318    }
319
320    #[getter]
321    #[pyo3(name = "parent_order_id")]
322    fn py_parent_order_id(&self) -> Option<ClientOrderId> {
323        self.parent_order_id
324    }
325
326    #[getter]
327    #[pyo3(name = "exec_algorithm_id")]
328    fn py_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
329        self.exec_algorithm_id
330    }
331
332    #[getter]
333    #[pyo3(name = "exec_algorithm_params")]
334    fn py_exec_algorithm_params(&self) -> Option<IndexMap<String, String>> {
335        self.exec_algorithm_params.as_ref().map(|params| {
336            params
337                .iter()
338                .map(|(key, value)| (key.to_string(), value.to_string()))
339                .collect()
340        })
341    }
342
343    #[getter]
344    #[pyo3(name = "exec_spawn_id")]
345    fn py_exec_spawn_id(&self) -> Option<ClientOrderId> {
346        self.exec_spawn_id
347    }
348
349    #[getter]
350    #[pyo3(name = "tags")]
351    fn py_tags(&self) -> Option<Vec<String>> {
352        self.tags
353            .as_ref()
354            .map(|tags| tags.iter().map(ToString::to_string).collect())
355    }
356
357    #[getter]
358    #[pyo3(name = "causation_id")]
359    fn py_causation_id(&self) -> Option<UUID4> {
360        self.causation_id
361    }
362
363    #[staticmethod]
364    #[pyo3(name = "from_dict")]
365    fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
366        from_dict_pyo3(py, values)
367    }
368
369    #[pyo3(name = "to_dict")]
370    fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
371        let dict = PyDict::new(py);
372        dict.set_item("type", stringify!(OrderInitialized))?;
373        dict.set_item("trader_id", self.trader_id.to_string())?;
374        dict.set_item("strategy_id", self.strategy_id.to_string())?;
375        dict.set_item("instrument_id", self.instrument_id.to_string())?;
376        dict.set_item("client_order_id", self.client_order_id.to_string())?;
377        dict.set_item("order_side", self.order_side.to_string())?;
378        dict.set_item("order_type", self.order_type.to_string())?;
379        dict.set_item("quantity", self.quantity.to_string())?;
380        dict.set_item("time_in_force", self.time_in_force.to_string())?;
381        dict.set_item("post_only", self.post_only)?;
382        dict.set_item("reduce_only", self.reduce_only)?;
383        dict.set_item("quote_quantity", self.quote_quantity)?;
384        dict.set_item("reconciliation", self.reconciliation)?;
385        dict.set_item("event_id", self.event_id.to_string())?;
386        dict.set_item("ts_event", self.ts_event.as_u64())?;
387        dict.set_item("ts_init", self.ts_init.as_u64())?;
388        match self.price {
389            Some(price) => dict.set_item("price", price.to_string())?,
390            None => dict.set_item("price", py.None())?,
391        }
392
393        match self.activation_price {
394            Some(activation_price) => {
395                dict.set_item("activation_price", activation_price.to_string())?;
396            }
397            None => dict.set_item("activation_price", py.None())?,
398        }
399
400        match self.trigger_price {
401            Some(trigger_price) => dict.set_item("trigger_price", trigger_price.to_string())?,
402            None => dict.set_item("trigger_price", py.None())?,
403        }
404
405        match self.trigger_type {
406            Some(trigger_type) => dict.set_item("trigger_type", trigger_type.to_string())?,
407            None => dict.set_item("trigger_type", py.None())?,
408        }
409
410        match self.limit_offset {
411            Some(limit_offset) => dict.set_item("limit_offset", limit_offset.to_string())?,
412            None => dict.set_item("limit_offset", py.None())?,
413        }
414
415        match self.trailing_offset {
416            Some(trailing_offset) => {
417                dict.set_item("trailing_offset", trailing_offset.to_string())?;
418            }
419            None => dict.set_item("trailing_offset", py.None())?,
420        }
421
422        match self.trailing_offset_type {
423            Some(trailing_offset_type) => {
424                dict.set_item("trailing_offset_type", trailing_offset_type.to_string())?;
425            }
426            None => dict.set_item("trailing_offset_type", py.None())?,
427        }
428
429        match self.expire_time {
430            Some(expire_time) => dict.set_item("expire_time", expire_time.as_u64())?,
431            None => dict.set_item("expire_time", py.None())?,
432        }
433
434        match self.display_qty {
435            Some(display_qty) => dict.set_item("display_qty", display_qty.to_string())?,
436            None => dict.set_item("display_qty", py.None())?,
437        }
438
439        match self.emulation_trigger {
440            Some(emulation_trigger) => {
441                dict.set_item("emulation_trigger", emulation_trigger.to_string())?;
442            }
443            None => dict.set_item("emulation_trigger", py.None())?,
444        }
445
446        match self.trigger_instrument_id {
447            Some(trigger_instrument_id) => {
448                dict.set_item("trigger_instrument_id", trigger_instrument_id.to_string())?;
449            }
450            None => dict.set_item("trigger_instrument_id", py.None())?,
451        }
452
453        match self.contingency_type {
454            Some(contingency_type) => {
455                dict.set_item("contingency_type", contingency_type.to_string())?;
456            }
457            None => dict.set_item("contingency_type", py.None())?,
458        }
459
460        match self.order_list_id {
461            Some(order_list_id) => dict.set_item("order_list_id", order_list_id.to_string())?,
462            None => dict.set_item("order_list_id", py.None())?,
463        }
464
465        match &self.linked_order_ids {
466            Some(linked_order_ids) => {
467                let py_linked_order_ids = PyList::empty(py);
468                for linked_order_id in linked_order_ids {
469                    py_linked_order_ids.append(linked_order_id.to_string())?;
470                }
471                dict.set_item("linked_order_ids", py_linked_order_ids)?;
472            }
473            None => dict.set_item("linked_order_ids", py.None())?,
474        }
475
476        match self.parent_order_id {
477            Some(parent_order_id) => {
478                dict.set_item("parent_order_id", parent_order_id.to_string())?;
479            }
480            None => dict.set_item("parent_order_id", py.None())?,
481        }
482
483        match self.exec_algorithm_id {
484            Some(exec_algorithm_id) => {
485                dict.set_item("exec_algorithm_id", exec_algorithm_id.to_string())?;
486            }
487            None => dict.set_item("exec_algorithm_id", py.None())?,
488        }
489
490        match &self.exec_algorithm_params {
491            Some(exec_algorithm_params) => {
492                let py_exec_algorithm_params = PyDict::new(py);
493                for (key, value) in exec_algorithm_params {
494                    py_exec_algorithm_params.set_item(key.to_string(), value.to_string())?;
495                }
496                dict.set_item("exec_algorithm_params", py_exec_algorithm_params)?;
497            }
498            None => dict.set_item("exec_algorithm_params", py.None())?,
499        }
500
501        match self.exec_spawn_id {
502            Some(exec_spawn_id) => dict.set_item("exec_spawn_id", exec_spawn_id.to_string())?,
503            None => dict.set_item("exec_spawn_id", py.None())?,
504        }
505
506        match &self.tags {
507            Some(tags) => dict.set_item(
508                "tags",
509                tags.iter()
510                    .map(ToString::to_string)
511                    .collect::<Vec<String>>(),
512            )?,
513            None => dict.set_item("tags", py.None())?,
514        }
515
516        match self.causation_id {
517            Some(causation_id) => dict.set_item("causation_id", causation_id.to_string())?,
518            None => dict.set_item("causation_id", py.None())?,
519        }
520        Ok(dict.into())
521    }
522}