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    #[staticmethod]
358    #[pyo3(name = "from_dict")]
359    fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
360        from_dict_pyo3(py, values)
361    }
362
363    #[pyo3(name = "to_dict")]
364    fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
365        let dict = PyDict::new(py);
366        dict.set_item("type", stringify!(OrderInitialized))?;
367        dict.set_item("trader_id", self.trader_id.to_string())?;
368        dict.set_item("strategy_id", self.strategy_id.to_string())?;
369        dict.set_item("instrument_id", self.instrument_id.to_string())?;
370        dict.set_item("client_order_id", self.client_order_id.to_string())?;
371        dict.set_item("order_side", self.order_side.to_string())?;
372        dict.set_item("order_type", self.order_type.to_string())?;
373        dict.set_item("quantity", self.quantity.to_string())?;
374        dict.set_item("time_in_force", self.time_in_force.to_string())?;
375        dict.set_item("post_only", self.post_only)?;
376        dict.set_item("reduce_only", self.reduce_only)?;
377        dict.set_item("quote_quantity", self.quote_quantity)?;
378        dict.set_item("reconciliation", self.reconciliation)?;
379        dict.set_item("event_id", self.event_id.to_string())?;
380        dict.set_item("ts_event", self.ts_event.as_u64())?;
381        dict.set_item("ts_init", self.ts_init.as_u64())?;
382        match self.price {
383            Some(price) => dict.set_item("price", price.to_string())?,
384            None => dict.set_item("price", py.None())?,
385        }
386
387        match self.activation_price {
388            Some(activation_price) => {
389                dict.set_item("activation_price", activation_price.to_string())?;
390            }
391            None => dict.set_item("activation_price", py.None())?,
392        }
393
394        match self.trigger_price {
395            Some(trigger_price) => dict.set_item("trigger_price", trigger_price.to_string())?,
396            None => dict.set_item("trigger_price", py.None())?,
397        }
398
399        match self.trigger_type {
400            Some(trigger_type) => dict.set_item("trigger_type", trigger_type.to_string())?,
401            None => dict.set_item("trigger_type", py.None())?,
402        }
403
404        match self.limit_offset {
405            Some(limit_offset) => dict.set_item("limit_offset", limit_offset.to_string())?,
406            None => dict.set_item("limit_offset", py.None())?,
407        }
408
409        match self.trailing_offset {
410            Some(trailing_offset) => {
411                dict.set_item("trailing_offset", trailing_offset.to_string())?;
412            }
413            None => dict.set_item("trailing_offset", py.None())?,
414        }
415
416        match self.trailing_offset_type {
417            Some(trailing_offset_type) => {
418                dict.set_item("trailing_offset_type", trailing_offset_type.to_string())?;
419            }
420            None => dict.set_item("trailing_offset_type", py.None())?,
421        }
422
423        match self.expire_time {
424            Some(expire_time) => dict.set_item("expire_time", expire_time.as_u64())?,
425            None => dict.set_item("expire_time", py.None())?,
426        }
427
428        match self.display_qty {
429            Some(display_qty) => dict.set_item("display_qty", display_qty.to_string())?,
430            None => dict.set_item("display_qty", py.None())?,
431        }
432
433        match self.emulation_trigger {
434            Some(emulation_trigger) => {
435                dict.set_item("emulation_trigger", emulation_trigger.to_string())?;
436            }
437            None => dict.set_item("emulation_trigger", py.None())?,
438        }
439
440        match self.trigger_instrument_id {
441            Some(trigger_instrument_id) => {
442                dict.set_item("trigger_instrument_id", trigger_instrument_id.to_string())?;
443            }
444            None => dict.set_item("trigger_instrument_id", py.None())?,
445        }
446
447        match self.contingency_type {
448            Some(contingency_type) => {
449                dict.set_item("contingency_type", contingency_type.to_string())?;
450            }
451            None => dict.set_item("contingency_type", py.None())?,
452        }
453
454        match self.order_list_id {
455            Some(order_list_id) => dict.set_item("order_list_id", order_list_id.to_string())?,
456            None => dict.set_item("order_list_id", py.None())?,
457        }
458
459        match &self.linked_order_ids {
460            Some(linked_order_ids) => {
461                let py_linked_order_ids = PyList::empty(py);
462                for linked_order_id in linked_order_ids {
463                    py_linked_order_ids.append(linked_order_id.to_string())?;
464                }
465                dict.set_item("linked_order_ids", py_linked_order_ids)?;
466            }
467            None => dict.set_item("linked_order_ids", py.None())?,
468        }
469
470        match self.parent_order_id {
471            Some(parent_order_id) => {
472                dict.set_item("parent_order_id", parent_order_id.to_string())?;
473            }
474            None => dict.set_item("parent_order_id", py.None())?,
475        }
476
477        match self.exec_algorithm_id {
478            Some(exec_algorithm_id) => {
479                dict.set_item("exec_algorithm_id", exec_algorithm_id.to_string())?;
480            }
481            None => dict.set_item("exec_algorithm_id", py.None())?,
482        }
483
484        match &self.exec_algorithm_params {
485            Some(exec_algorithm_params) => {
486                let py_exec_algorithm_params = PyDict::new(py);
487                for (key, value) in exec_algorithm_params {
488                    py_exec_algorithm_params.set_item(key.to_string(), value.to_string())?;
489                }
490                dict.set_item("exec_algorithm_params", py_exec_algorithm_params)?;
491            }
492            None => dict.set_item("exec_algorithm_params", py.None())?,
493        }
494
495        match self.exec_spawn_id {
496            Some(exec_spawn_id) => dict.set_item("exec_spawn_id", exec_spawn_id.to_string())?,
497            None => dict.set_item("exec_spawn_id", py.None())?,
498        }
499
500        match &self.tags {
501            Some(tags) => dict.set_item(
502                "tags",
503                tags.iter()
504                    .map(ToString::to_string)
505                    .collect::<Vec<String>>(),
506            )?,
507            None => dict.set_item("tags", py.None())?,
508        }
509
510        match self.causation_id {
511            Some(causation_id) => dict.set_item("causation_id", causation_id.to_string())?,
512            None => dict.set_item("causation_id", py.None())?,
513        }
514        Ok(dict.into())
515    }
516}