nautilus_model/python/data/
status.rs1use std::{
17 collections::{HashMap, hash_map::DefaultHasher},
18 hash::{Hash, Hasher},
19};
20
21use nautilus_core::{
22 python::{
23 IntoPyObjectNautilusExt,
24 serialization::{from_dict_pyo3, to_dict_pyo3},
25 to_pyvalue_err,
26 },
27 serialization::{
28 Serializable,
29 msgpack::{FromMsgPack, ToMsgPack},
30 },
31};
32use pyo3::{IntoPyObjectExt, prelude::*, pyclass::CompareOp, types::PyDict};
33use ustr::Ustr;
34
35use crate::{
36 data::status::InstrumentStatus, enums::MarketStatusAction, identifiers::InstrumentId,
37 python::common::PY_MODULE_MODEL,
38};
39
40#[pymethods]
41#[pyo3_stub_gen::derive::gen_stub_pymethods]
42impl InstrumentStatus {
43 #[new]
45 #[expect(clippy::too_many_arguments)]
46 #[pyo3(signature = (instrument_id, action, ts_event, ts_init, reason=None, trading_event=None, is_trading=None, is_quoting=None, is_short_sell_restricted=None))]
47 fn py_new(
48 instrument_id: InstrumentId,
49 action: MarketStatusAction,
50 ts_event: u64,
51 ts_init: u64,
52 reason: Option<String>,
53 trading_event: Option<String>,
54 is_trading: Option<bool>,
55 is_quoting: Option<bool>,
56 is_short_sell_restricted: Option<bool>,
57 ) -> Self {
58 Self::new(
59 instrument_id,
60 action,
61 ts_event.into(),
62 ts_init.into(),
63 reason.map(|s| Ustr::from(&s)),
64 trading_event.map(|s| Ustr::from(&s)),
65 is_trading,
66 is_quoting,
67 is_short_sell_restricted,
68 )
69 }
70
71 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
72 match op {
73 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
74 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
75 _ => py.NotImplemented(),
76 }
77 }
78
79 fn __hash__(&self) -> isize {
80 let mut h = DefaultHasher::new();
81 self.hash(&mut h);
82 h.finish() as isize
83 }
84
85 fn __repr__(&self) -> String {
86 format!("{}({})", stringify!(InstrumentStatus), self)
87 }
88
89 fn __str__(&self) -> String {
90 self.to_string()
91 }
92
93 #[getter]
94 #[pyo3(name = "instrument_id")]
95 fn py_instrument_id(&self) -> InstrumentId {
96 self.instrument_id
97 }
98
99 #[getter]
100 #[pyo3(name = "action")]
101 fn py_action(&self) -> MarketStatusAction {
102 self.action
103 }
104
105 #[getter]
106 #[pyo3(name = "ts_event")]
107 fn py_ts_event(&self) -> u64 {
108 self.ts_event.as_u64()
109 }
110
111 #[getter]
112 #[pyo3(name = "ts_init")]
113 fn py_ts_init(&self) -> u64 {
114 self.ts_init.as_u64()
115 }
116
117 #[getter]
118 #[pyo3(name = "reason")]
119 fn py_reason(&self) -> Option<String> {
120 self.reason.map(|x| x.to_string())
121 }
122
123 #[getter]
124 #[pyo3(name = "trading_event")]
125 fn py_trading_event(&self) -> Option<String> {
126 self.trading_event.map(|x| x.to_string())
127 }
128
129 #[getter]
130 #[pyo3(name = "is_trading")]
131 fn py_is_trading(&self) -> Option<bool> {
132 self.is_trading
133 }
134
135 #[getter]
136 #[pyo3(name = "is_quoting")]
137 fn py_is_quoting(&self) -> Option<bool> {
138 self.is_quoting
139 }
140
141 #[getter]
142 #[pyo3(name = "is_short_sell_restricted")]
143 fn py_is_short_sell_restricted(&self) -> Option<bool> {
144 self.is_short_sell_restricted
145 }
146
147 #[staticmethod]
148 #[pyo3(name = "fully_qualified_name")]
149 fn py_fully_qualified_name() -> String {
150 format!("{}:{}", PY_MODULE_MODEL, stringify!(InstrumentStatus))
151 }
152
153 #[staticmethod]
155 #[pyo3(name = "from_dict")]
156 fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
157 from_dict_pyo3(py, values)
158 }
159
160 #[staticmethod]
162 #[pyo3(name = "get_metadata")]
163 fn py_get_metadata(instrument_id: &InstrumentId) -> HashMap<String, String> {
164 Self::get_metadata(instrument_id)
165 }
166
167 #[pyo3(name = "to_dict")]
169 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
170 to_dict_pyo3(py, self)
171 }
172
173 #[pyo3(name = "to_json_bytes")]
175 fn py_to_json_bytes(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
176 self.to_json_bytes()
177 .map_err(to_pyvalue_err)?
178 .into_py_any(py)
179 }
180
181 #[pyo3(name = "to_msgpack_bytes")]
183 fn py_to_msgpack_bytes(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
184 self.to_msgpack_bytes()
185 .map_err(to_pyvalue_err)?
186 .into_py_any(py)
187 }
188}
189
190#[pymethods]
191impl InstrumentStatus {
192 #[staticmethod]
193 #[pyo3(name = "from_json")]
194 fn py_from_json(data: &[u8]) -> PyResult<Self> {
195 Self::from_json_bytes(data).map_err(to_pyvalue_err)
196 }
197
198 #[staticmethod]
199 #[pyo3(name = "from_msgpack")]
200 fn py_from_msgpack(data: &[u8]) -> PyResult<Self> {
201 Self::from_msgpack_bytes(data).map_err(to_pyvalue_err)
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use pyo3::Python;
208 use rstest::rstest;
209
210 use crate::data::{status::InstrumentStatus, stubs::stub_instrument_status};
211
212 #[rstest]
213 fn test_to_dict(stub_instrument_status: InstrumentStatus) {
214 Python::initialize();
215 Python::attach(|py| {
216 let dict_string = stub_instrument_status.py_to_dict(py).unwrap().to_string();
217 let expected_string = "{'type': 'InstrumentStatus', 'instrument_id': 'MSFT.XNAS', 'action': 'TRADING', 'ts_event': 1, 'ts_init': 2, 'reason': None, 'trading_event': None, 'is_trading': None, 'is_quoting': None, 'is_short_sell_restricted': None}";
218 assert_eq!(dict_string, expected_string);
219 });
220 }
221
222 #[rstest]
223 fn test_from_dict(stub_instrument_status: InstrumentStatus) {
224 Python::initialize();
225 Python::attach(|py| {
226 let dict = stub_instrument_status.py_to_dict(py).unwrap();
227 let parsed = InstrumentStatus::py_from_dict(py, dict).unwrap();
228 assert_eq!(parsed, stub_instrument_status);
229 });
230 }
231}