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