nautilus_model/python/data/
status.rs1use std::{
17 collections::{HashMap, hash_map::DefaultHasher},
18 hash::{Hash, Hasher},
19 str::FromStr,
20};
21
22use nautilus_core::{
23 python::{
24 IntoPyObjectPoseiExt,
25 serialization::{from_dict_pyo3, to_dict_pyo3},
26 to_pyvalue_err,
27 },
28 serialization::Serializable,
29};
30use pyo3::{prelude::*, pyclass::CompareOp, types::PyDict};
31use ustr::Ustr;
32
33use crate::{
34 data::status::InstrumentStatus,
35 enums::{FromU16, MarketStatusAction},
36 identifiers::InstrumentId,
37 python::common::PY_MODULE_MODEL,
38};
39
40impl InstrumentStatus {
41 pub fn from_pyobject(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
51 let instrument_id_obj: Bound<'_, PyAny> = obj.getattr("instrument_id")?.extract()?;
52 let instrument_id_str: String = instrument_id_obj.getattr("value")?.extract()?;
53 let instrument_id =
54 InstrumentId::from_str(instrument_id_str.as_str()).map_err(to_pyvalue_err)?;
55
56 let action_obj: Bound<'_, PyAny> = obj.getattr("action")?.extract()?;
57 let action_u16: u16 = action_obj.getattr("value")?.extract()?;
58 let action = MarketStatusAction::from_u16(action_u16).unwrap();
59
60 let ts_event: u64 = obj.getattr("ts_event")?.extract()?;
61 let ts_init: u64 = obj.getattr("ts_init")?.extract()?;
62
63 let reason_str: Option<String> = obj.getattr("reason")?.extract()?;
64 let reason = reason_str.map(|reason_str| Ustr::from(&reason_str));
65
66 let trading_event_str: Option<String> = obj.getattr("trading_event")?.extract()?;
67 let trading_event =
68 trading_event_str.map(|trading_event_str| Ustr::from(&trading_event_str));
69
70 let is_trading: Option<bool> = obj.getattr("is_trading")?.extract()?;
71 let is_quoting: Option<bool> = obj.getattr("is_quoting")?.extract()?;
72 let is_short_sell_restricted: Option<bool> =
73 obj.getattr("is_short_sell_restricted")?.extract()?;
74
75 Ok(Self::new(
76 instrument_id,
77 action,
78 ts_event.into(),
79 ts_init.into(),
80 reason,
81 trading_event,
82 is_trading,
83 is_quoting,
84 is_short_sell_restricted,
85 ))
86 }
87}
88
89#[pymethods]
90impl InstrumentStatus {
91 #[new]
92 #[allow(clippy::too_many_arguments)]
93 #[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))]
94 fn py_new(
95 instrument_id: InstrumentId,
96 action: MarketStatusAction,
97 ts_event: u64,
98 ts_init: u64,
99 reason: Option<String>,
100 trading_event: Option<String>,
101 is_trading: Option<bool>,
102 is_quoting: Option<bool>,
103 is_short_sell_restricted: Option<bool>,
104 ) -> Self {
105 Self::new(
106 instrument_id,
107 action,
108 ts_event.into(),
109 ts_init.into(),
110 reason.map(|s| Ustr::from(&s)),
111 trading_event.map(|s| Ustr::from(&s)),
112 is_trading,
113 is_quoting,
114 is_short_sell_restricted,
115 )
116 }
117
118 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
119 match op {
120 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
121 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
122 _ => py.NotImplemented(),
123 }
124 }
125
126 fn __hash__(&self) -> isize {
127 let mut h = DefaultHasher::new();
128 self.hash(&mut h);
129 h.finish() as isize
130 }
131
132 fn __repr__(&self) -> String {
133 format!("{}({})", stringify!(InstrumentStatus), self)
134 }
135
136 fn __str__(&self) -> String {
137 self.to_string()
138 }
139
140 #[getter]
141 #[pyo3(name = "instrument_id")]
142 fn py_instrument_id(&self) -> InstrumentId {
143 self.instrument_id
144 }
145
146 #[getter]
147 #[pyo3(name = "action")]
148 fn py_action(&self) -> MarketStatusAction {
149 self.action
150 }
151
152 #[getter]
153 #[pyo3(name = "ts_event")]
154 fn py_ts_event(&self) -> u64 {
155 self.ts_event.as_u64()
156 }
157
158 #[getter]
159 #[pyo3(name = "ts_init")]
160 fn py_ts_init(&self) -> u64 {
161 self.ts_init.as_u64()
162 }
163
164 #[getter]
165 #[pyo3(name = "reason")]
166 fn py_reason(&self) -> Option<String> {
167 self.reason.map(|x| x.to_string())
168 }
169
170 #[getter]
171 #[pyo3(name = "trading_event")]
172 fn py_trading_event(&self) -> Option<String> {
173 self.trading_event.map(|x| x.to_string())
174 }
175
176 #[getter]
177 #[pyo3(name = "is_trading")]
178 fn py_is_trading(&self) -> Option<bool> {
179 self.is_trading
180 }
181
182 #[getter]
183 #[pyo3(name = "is_quoting")]
184 fn py_is_quoting(&self) -> Option<bool> {
185 self.is_quoting
186 }
187
188 #[getter]
189 #[pyo3(name = "is_short_sell_restricted")]
190 fn py_is_short_sell_restricted(&self) -> Option<bool> {
191 self.is_short_sell_restricted
192 }
193
194 #[staticmethod]
195 #[pyo3(name = "fully_qualified_name")]
196 fn py_fully_qualified_name() -> String {
197 format!("{}:{}", PY_MODULE_MODEL, stringify!(InstrumentStatus))
198 }
199
200 #[staticmethod]
202 #[pyo3(name = "from_dict")]
203 fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
204 from_dict_pyo3(py, values)
205 }
206
207 #[staticmethod]
208 #[pyo3(name = "get_metadata")]
209 fn py_get_metadata(instrument_id: &InstrumentId) -> PyResult<HashMap<String, String>> {
210 Ok(Self::get_metadata(instrument_id))
211 }
212
213 #[staticmethod]
214 #[pyo3(name = "from_json")]
215 fn py_from_json(data: Vec<u8>) -> PyResult<Self> {
216 Self::from_json_bytes(&data).map_err(to_pyvalue_err)
217 }
218
219 #[staticmethod]
220 #[pyo3(name = "from_msgpack")]
221 fn py_from_msgpack(data: Vec<u8>) -> PyResult<Self> {
222 Self::from_msgpack_bytes(&data).map_err(to_pyvalue_err)
223 }
224
225 #[pyo3(name = "to_dict")]
227 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
228 to_dict_pyo3(py, self)
229 }
230
231 #[pyo3(name = "to_json_bytes")]
233 fn py_to_json_bytes(&self, py: Python<'_>) -> Py<PyAny> {
234 self.to_json_bytes().unwrap().into_py_any_unwrap(py)
236 }
237
238 #[pyo3(name = "to_msgpack_bytes")]
240 fn py_to_msgpack_bytes(&self, py: Python<'_>) -> Py<PyAny> {
241 self.to_msgpack_bytes().unwrap().into_py_any_unwrap(py)
243 }
244}
245
246#[cfg(test)]
250mod tests {
251 use nautilus_core::python::IntoPyObjectPoseiExt;
252 use pyo3::Python;
253 use rstest::rstest;
254
255 use crate::data::{status::InstrumentStatus, stubs::stub_instrument_status};
256
257 #[rstest]
258 fn test_to_dict(stub_instrument_status: InstrumentStatus) {
259 pyo3::prepare_freethreaded_python();
260
261 Python::with_gil(|py| {
262 let dict_string = stub_instrument_status.py_to_dict(py).unwrap().to_string();
263 let expected_string = r"{'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}";
264 assert_eq!(dict_string, expected_string);
265 });
266 }
267
268 #[rstest]
269 fn test_from_dict(stub_instrument_status: InstrumentStatus) {
270 pyo3::prepare_freethreaded_python();
271
272 Python::with_gil(|py| {
273 let dict = stub_instrument_status.py_to_dict(py).unwrap();
274 let parsed = InstrumentStatus::py_from_dict(py, dict).unwrap();
275 assert_eq!(parsed, stub_instrument_status);
276 });
277 }
278
279 #[rstest]
280 fn test_from_pyobject(stub_instrument_status: InstrumentStatus) {
281 pyo3::prepare_freethreaded_python();
282
283 Python::with_gil(|py| {
284 let status_pyobject = stub_instrument_status.into_py_any_unwrap(py);
285 let parsed_status = InstrumentStatus::from_pyobject(status_pyobject.bind(py)).unwrap();
286 assert_eq!(parsed_status, stub_instrument_status);
287 });
288 }
289}