Skip to main content

nautilus_model/python/orders/
list.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 nautilus_core::python::IntoPyObjectNautilusExt;
17use pyo3::{basic::CompareOp, prelude::*};
18
19use crate::{
20    identifiers::{ClientOrderId, InstrumentId, OrderListId, StrategyId},
21    orders::OrderList,
22};
23
24#[pyo3_stub_gen::derive::gen_stub_pymethods]
25#[pymethods]
26impl OrderList {
27    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
28        match op {
29            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
30            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
31            _ => py.NotImplemented(),
32        }
33    }
34
35    fn __hash__(&self) -> isize {
36        self.id.inner().precomputed_hash() as isize
37    }
38
39    fn __len__(&self) -> usize {
40        self.len()
41    }
42
43    fn __repr__(&self) -> String {
44        self.to_string()
45    }
46
47    fn __str__(&self) -> String {
48        self.to_string()
49    }
50
51    #[getter]
52    #[pyo3(name = "id")]
53    fn py_id(&self) -> OrderListId {
54        self.id
55    }
56
57    #[getter]
58    #[pyo3(name = "instrument_id")]
59    fn py_instrument_id(&self) -> InstrumentId {
60        self.instrument_id
61    }
62
63    #[getter]
64    #[pyo3(name = "strategy_id")]
65    fn py_strategy_id(&self) -> StrategyId {
66        self.strategy_id
67    }
68
69    /// Returns the client order IDs contained in the order list.
70    #[pyo3(name = "client_order_ids")]
71    fn py_client_order_ids(&self) -> Vec<ClientOrderId> {
72        self.client_order_ids.clone()
73    }
74
75    #[getter]
76    #[pyo3(name = "first_client_order_id")]
77    fn py_first_client_order_id(&self) -> Option<ClientOrderId> {
78        self.first().copied()
79    }
80
81    #[getter]
82    #[pyo3(name = "ts_init")]
83    fn py_ts_init(&self) -> u64 {
84        self.ts_init.as_u64()
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use nautilus_core::UnixNanos;
91    use pyo3::{
92        Py, Python,
93        types::{PyAnyMethods, PyStringMethods},
94    };
95    use rstest::rstest;
96
97    use crate::{
98        identifiers::{ClientOrderId, InstrumentId, OrderListId, StrategyId},
99        orders::OrderList,
100    };
101
102    fn create_order_list(order_list_id: &str) -> OrderList {
103        OrderList::new(
104            OrderListId::from(order_list_id),
105            InstrumentId::from("AUD/USD.SIM"),
106            StrategyId::from("S-001"),
107            vec![ClientOrderId::from("O-001"), ClientOrderId::from("O-002")],
108            UnixNanos::from(42_u64),
109        )
110    }
111
112    #[rstest]
113    fn test_python_order_list_exposes_readonly_api() {
114        Python::initialize();
115        Python::attach(|py| {
116            let order_list = create_order_list("OL-001");
117            let py_order_list = Py::new(py, order_list.clone()).unwrap();
118            let bound = py_order_list.bind(py);
119
120            assert_eq!(
121                bound
122                    .getattr("id")
123                    .unwrap()
124                    .extract::<OrderListId>()
125                    .unwrap(),
126                order_list.id,
127            );
128            assert_eq!(
129                bound
130                    .getattr("instrument_id")
131                    .unwrap()
132                    .extract::<InstrumentId>()
133                    .unwrap(),
134                order_list.instrument_id,
135            );
136            assert_eq!(
137                bound
138                    .getattr("strategy_id")
139                    .unwrap()
140                    .extract::<StrategyId>()
141                    .unwrap(),
142                order_list.strategy_id,
143            );
144            assert_eq!(
145                bound.getattr("ts_init").unwrap().extract::<u64>().unwrap(),
146                order_list.ts_init.as_u64(),
147            );
148            assert_eq!(
149                bound
150                    .call_method0("client_order_ids")
151                    .unwrap()
152                    .extract::<Vec<ClientOrderId>>()
153                    .unwrap(),
154                order_list.client_order_ids,
155            );
156            assert_eq!(
157                bound
158                    .getattr("first_client_order_id")
159                    .unwrap()
160                    .extract::<ClientOrderId>()
161                    .unwrap(),
162                order_list.client_order_ids[0],
163            );
164            assert_eq!(bound.len().unwrap(), order_list.len());
165            assert_eq!(
166                bound.str().unwrap().to_str().unwrap(),
167                order_list.to_string(),
168            );
169            assert_eq!(
170                bound.repr().unwrap().to_str().unwrap(),
171                order_list.to_string(),
172            );
173            assert_eq!(
174                bound.hash().unwrap(),
175                bound.getattr("id").unwrap().hash().unwrap(),
176            );
177
178            let same = Py::new(py, order_list).unwrap();
179            assert!(
180                bound
181                    .call_method1("__eq__", (same,))
182                    .unwrap()
183                    .extract::<bool>()
184                    .unwrap(),
185            );
186
187            let different = Py::new(py, create_order_list("OL-002")).unwrap();
188            assert!(
189                !bound
190                    .call_method1("__eq__", (different,))
191                    .unwrap()
192                    .extract::<bool>()
193                    .unwrap(),
194            );
195            assert!(bound.setattr("id", OrderListId::from("OL-003")).is_err());
196        });
197    }
198}