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