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]
98#[pyo3_stub_gen::derive::gen_stub_pymethods]
99impl InstrumentStatus {
100 #[new]
102 #[allow(clippy::too_many_arguments)]
103 #[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))]
104 fn py_new(
105 instrument_id: InstrumentId,
106 action: MarketStatusAction,
107 ts_event: u64,
108 ts_init: u64,
109 reason: Option<String>,
110 trading_event: Option<String>,
111 is_trading: Option<bool>,
112 is_quoting: Option<bool>,
113 is_short_sell_restricted: Option<bool>,
114 ) -> Self {
115 Self::new(
116 instrument_id,
117 action,
118 ts_event.into(),
119 ts_init.into(),
120 reason.map(|s| Ustr::from(&s)),
121 trading_event.map(|s| Ustr::from(&s)),
122 is_trading,
123 is_quoting,
124 is_short_sell_restricted,
125 )
126 }
127
128 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
129 match op {
130 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
131 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
132 _ => py.NotImplemented(),
133 }
134 }
135
136 fn __hash__(&self) -> isize {
137 let mut h = DefaultHasher::new();
138 self.hash(&mut h);
139 h.finish() as isize
140 }
141
142 fn __repr__(&self) -> String {
143 format!("{}({})", stringify!(InstrumentStatus), self)
144 }
145
146 fn __str__(&self) -> String {
147 self.to_string()
148 }
149
150 #[getter]
151 #[pyo3(name = "instrument_id")]
152 fn py_instrument_id(&self) -> InstrumentId {
153 self.instrument_id
154 }
155
156 #[getter]
157 #[pyo3(name = "action")]
158 fn py_action(&self) -> MarketStatusAction {
159 self.action
160 }
161
162 #[getter]
163 #[pyo3(name = "ts_event")]
164 fn py_ts_event(&self) -> u64 {
165 self.ts_event.as_u64()
166 }
167
168 #[getter]
169 #[pyo3(name = "ts_init")]
170 fn py_ts_init(&self) -> u64 {
171 self.ts_init.as_u64()
172 }
173
174 #[getter]
175 #[pyo3(name = "reason")]
176 fn py_reason(&self) -> Option<String> {
177 self.reason.map(|x| x.to_string())
178 }
179
180 #[getter]
181 #[pyo3(name = "trading_event")]
182 fn py_trading_event(&self) -> Option<String> {
183 self.trading_event.map(|x| x.to_string())
184 }
185
186 #[getter]
187 #[pyo3(name = "is_trading")]
188 fn py_is_trading(&self) -> Option<bool> {
189 self.is_trading
190 }
191
192 #[getter]
193 #[pyo3(name = "is_quoting")]
194 fn py_is_quoting(&self) -> Option<bool> {
195 self.is_quoting
196 }
197
198 #[getter]
199 #[pyo3(name = "is_short_sell_restricted")]
200 fn py_is_short_sell_restricted(&self) -> Option<bool> {
201 self.is_short_sell_restricted
202 }
203
204 #[staticmethod]
205 #[pyo3(name = "fully_qualified_name")]
206 fn py_fully_qualified_name() -> String {
207 format!("{}:{}", PY_MODULE_MODEL, stringify!(InstrumentStatus))
208 }
209
210 #[staticmethod]
212 #[pyo3(name = "from_dict")]
213 fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
214 from_dict_pyo3(py, values)
215 }
216
217 #[staticmethod]
219 #[pyo3(name = "get_metadata")]
220 fn py_get_metadata(instrument_id: &InstrumentId) -> HashMap<String, String> {
221 Self::get_metadata(instrument_id)
222 }
223
224 #[pyo3(name = "to_dict")]
226 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
227 to_dict_pyo3(py, self)
228 }
229
230 #[pyo3(name = "to_json_bytes")]
232 fn py_to_json_bytes(&self, py: Python<'_>) -> Py<PyAny> {
233 self.to_json_bytes().unwrap().into_py_any_unwrap(py)
234 }
235
236 #[pyo3(name = "to_msgpack_bytes")]
238 fn py_to_msgpack_bytes(&self, py: Python<'_>) -> Py<PyAny> {
239 self.to_msgpack_bytes().unwrap().into_py_any_unwrap(py)
240 }
241}
242
243#[pymethods]
244impl InstrumentStatus {
245 #[staticmethod]
246 #[pyo3(name = "from_json")]
247 fn py_from_json(data: &[u8]) -> PyResult<Self> {
248 Self::from_json_bytes(data).map_err(to_pyvalue_err)
249 }
250
251 #[staticmethod]
252 #[pyo3(name = "from_msgpack")]
253 fn py_from_msgpack(data: &[u8]) -> PyResult<Self> {
254 Self::from_msgpack_bytes(data).map_err(to_pyvalue_err)
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use nautilus_core::python::IntoPyObjectNautilusExt;
261 use pyo3::Python;
262 use rstest::rstest;
263
264 use crate::data::{status::InstrumentStatus, stubs::stub_instrument_status};
265
266 #[rstest]
267 fn test_to_dict(stub_instrument_status: InstrumentStatus) {
268 Python::initialize();
269 Python::attach(|py| {
270 let dict_string = stub_instrument_status.py_to_dict(py).unwrap().to_string();
271 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}";
272 assert_eq!(dict_string, expected_string);
273 });
274 }
275
276 #[rstest]
277 fn test_from_dict(stub_instrument_status: InstrumentStatus) {
278 Python::initialize();
279 Python::attach(|py| {
280 let dict = stub_instrument_status.py_to_dict(py).unwrap();
281 let parsed = InstrumentStatus::py_from_dict(py, dict).unwrap();
282 assert_eq!(parsed, stub_instrument_status);
283 });
284 }
285
286 #[rstest]
287 fn test_from_pyobject(stub_instrument_status: InstrumentStatus) {
288 Python::initialize();
289 Python::attach(|py| {
290 let status_pyobject = stub_instrument_status.into_py_any_unwrap(py);
291 let parsed_status = InstrumentStatus::from_pyobject(status_pyobject.bind(py)).unwrap();
292 assert_eq!(parsed_status, stub_instrument_status);
293 });
294 }
295}