nautilus_model/data/
prices.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::fmt::Display;
17
18use nautilus_core::{UnixNanos, serialization::Serializable};
19use serde::{Deserialize, Serialize};
20
21use super::GetTsInit;
22use crate::{identifiers::InstrumentId, types::Price};
23
24// TODO: Development notes:
25// TODO: - We avoid adding these to the `Data` enum for now, as we won't be using FFI or pycapsule.
26// TODO: - Only defined in Rust with a view to minimizing Cython which will soon be removed.
27
28/// Represents a mark price update.
29#[repr(C)]
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
31#[serde(tag = "type")]
32#[cfg_attr(
33    feature = "python",
34    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
35)]
36pub struct MarkPriceUpdate {
37    /// The instrument ID for the mark price.
38    pub instrument_id: InstrumentId,
39    /// The mark price.
40    pub value: Price,
41    /// UNIX timestamp (nanoseconds) when the price event occurred.
42    pub ts_event: UnixNanos,
43    /// UNIX timestamp (nanoseconds) when the struct was initialized.
44    pub ts_init: UnixNanos,
45}
46
47impl MarkPriceUpdate {
48    /// Creates a new [`MarkPriceUpdate`] instance.
49    #[must_use]
50    pub fn new(
51        instrument_id: InstrumentId,
52        value: Price,
53        ts_event: UnixNanos,
54        ts_init: UnixNanos,
55    ) -> Self {
56        Self {
57            instrument_id,
58            value,
59            ts_event,
60            ts_init,
61        }
62    }
63}
64
65impl Display for MarkPriceUpdate {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(
68            f,
69            "{},{},{},{}",
70            self.instrument_id, self.value, self.ts_event, self.ts_init
71        )
72    }
73}
74
75impl Serializable for MarkPriceUpdate {}
76
77impl GetTsInit for MarkPriceUpdate {
78    fn ts_init(&self) -> UnixNanos {
79        self.ts_init
80    }
81}
82
83/// Represents an index price update.
84#[repr(C)]
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
86#[serde(tag = "type")]
87#[cfg_attr(
88    feature = "python",
89    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
90)]
91pub struct IndexPriceUpdate {
92    /// The instrument ID for the index price.
93    pub instrument_id: InstrumentId,
94    /// The index price.
95    pub value: Price,
96    /// UNIX timestamp (nanoseconds) when the price event occurred.
97    pub ts_event: UnixNanos,
98    /// UNIX timestamp (nanoseconds) when the struct was initialized.
99    pub ts_init: UnixNanos,
100}
101
102impl IndexPriceUpdate {
103    /// Creates a new [`IndexPriceUpdate`] instance.
104    #[must_use]
105    pub fn new(
106        instrument_id: InstrumentId,
107        value: Price,
108        ts_event: UnixNanos,
109        ts_init: UnixNanos,
110    ) -> Self {
111        Self {
112            instrument_id,
113            value,
114            ts_event,
115            ts_init,
116        }
117    }
118}
119
120impl Display for IndexPriceUpdate {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        write!(
123            f,
124            "{},{},{},{}",
125            self.instrument_id, self.value, self.ts_event, self.ts_init
126        )
127    }
128}
129
130impl Serializable for IndexPriceUpdate {}
131
132impl GetTsInit for IndexPriceUpdate {
133    fn ts_init(&self) -> UnixNanos {
134        self.ts_init
135    }
136}
137
138////////////////////////////////////////////////////////////////////////////////
139// Tests
140////////////////////////////////////////////////////////////////////////////////
141#[cfg(test)]
142mod tests {
143    use std::{
144        collections::hash_map::DefaultHasher,
145        hash::{Hash, Hasher},
146    };
147
148    use nautilus_core::serialization::Serializable;
149    use rstest::{fixture, rstest};
150    use serde_json;
151
152    use super::*;
153
154    #[fixture]
155    fn instrument_id() -> InstrumentId {
156        InstrumentId::from("BTC-USDT.OKX")
157    }
158
159    #[fixture]
160    fn price() -> Price {
161        Price::from("150_500.10")
162    }
163
164    #[rstest]
165    fn test_mark_price_update_new(instrument_id: InstrumentId, price: Price) {
166        let ts_event = UnixNanos::from(1);
167        let ts_init = UnixNanos::from(2);
168
169        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
170
171        assert_eq!(mark_price.instrument_id, instrument_id);
172        assert_eq!(mark_price.value, price);
173        assert_eq!(mark_price.ts_event, ts_event);
174        assert_eq!(mark_price.ts_init, ts_init);
175    }
176
177    #[rstest]
178    fn test_mark_price_update_display(instrument_id: InstrumentId, price: Price) {
179        let ts_event = UnixNanos::from(1);
180        let ts_init = UnixNanos::from(2);
181
182        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
183
184        assert_eq!(format!("{mark_price}"), "BTC-USDT.OKX,150500.10,1,2");
185    }
186
187    #[rstest]
188    fn test_mark_price_update_get_ts_init(instrument_id: InstrumentId, price: Price) {
189        let ts_event = UnixNanos::from(1);
190        let ts_init = UnixNanos::from(2);
191
192        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
193
194        assert_eq!(mark_price.ts_init(), ts_init);
195    }
196
197    #[rstest]
198    fn test_mark_price_update_eq_hash(instrument_id: InstrumentId, price: Price) {
199        let ts_event = UnixNanos::from(1);
200        let ts_init = UnixNanos::from(2);
201
202        let mark_price1 = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
203        let mark_price2 = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
204        let mark_price3 =
205            MarkPriceUpdate::new(instrument_id, Price::from("143_500.50"), ts_event, ts_init);
206
207        assert_eq!(mark_price1, mark_price2);
208        assert_ne!(mark_price1, mark_price3);
209
210        // Test Hash implementation
211        use std::{
212            collections::hash_map::DefaultHasher,
213            hash::{Hash, Hasher},
214        };
215
216        let mut hasher1 = DefaultHasher::new();
217        let mut hasher2 = DefaultHasher::new();
218        mark_price1.hash(&mut hasher1);
219        mark_price2.hash(&mut hasher2);
220        assert_eq!(hasher1.finish(), hasher2.finish());
221    }
222
223    #[rstest]
224    fn test_mark_price_update_json_serialization(instrument_id: InstrumentId, price: Price) {
225        let ts_event = UnixNanos::from(1);
226        let ts_init = UnixNanos::from(2);
227
228        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
229
230        let serialized = mark_price.as_json_bytes().unwrap();
231        let deserialized = MarkPriceUpdate::from_json_bytes(&serialized).unwrap();
232
233        assert_eq!(mark_price, deserialized);
234    }
235
236    #[rstest]
237    fn test_mark_price_update_msgpack_serialization(instrument_id: InstrumentId, price: Price) {
238        let ts_event = UnixNanos::from(1);
239        let ts_init = UnixNanos::from(2);
240
241        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
242
243        let serialized = mark_price.as_msgpack_bytes().unwrap();
244        let deserialized = MarkPriceUpdate::from_msgpack_bytes(&serialized).unwrap();
245
246        assert_eq!(mark_price, deserialized);
247    }
248
249    #[rstest]
250    fn test_mark_price_update_clone(instrument_id: InstrumentId, price: Price) {
251        let ts_event = UnixNanos::from(1);
252        let ts_init = UnixNanos::from(2);
253
254        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
255        let cloned = mark_price.clone();
256
257        assert_eq!(mark_price, cloned);
258    }
259
260    #[rstest]
261    fn test_mark_price_update_serde_json(instrument_id: InstrumentId, price: Price) {
262        let ts_event = UnixNanos::from(1);
263        let ts_init = UnixNanos::from(2);
264
265        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
266
267        let json_str = serde_json::to_string(&mark_price).unwrap();
268        let deserialized: MarkPriceUpdate = serde_json::from_str(&json_str).unwrap();
269
270        assert_eq!(mark_price, deserialized);
271    }
272
273    #[rstest]
274    fn test_index_price_update_new(instrument_id: InstrumentId, price: Price) {
275        let ts_event = UnixNanos::from(1);
276        let ts_init = UnixNanos::from(2);
277
278        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
279
280        assert_eq!(index_price.instrument_id, instrument_id);
281        assert_eq!(index_price.value, price);
282        assert_eq!(index_price.ts_event, ts_event);
283        assert_eq!(index_price.ts_init, ts_init);
284    }
285
286    #[rstest]
287    fn test_index_price_update_display(instrument_id: InstrumentId, price: Price) {
288        let ts_event = UnixNanos::from(1);
289        let ts_init = UnixNanos::from(2);
290
291        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
292
293        assert_eq!(format!("{index_price}"), "BTC-USDT.OKX,150500.10,1,2");
294    }
295
296    #[rstest]
297    fn test_index_price_update_get_ts_init(instrument_id: InstrumentId, price: Price) {
298        let ts_event = UnixNanos::from(1);
299        let ts_init = UnixNanos::from(2);
300
301        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
302
303        assert_eq!(index_price.ts_init(), ts_init);
304    }
305
306    #[rstest]
307    fn test_index_price_update_eq_hash(instrument_id: InstrumentId, price: Price) {
308        let ts_event = UnixNanos::from(1);
309        let ts_init = UnixNanos::from(2);
310
311        let index_price1 = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
312        let index_price2 = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
313        let index_price3 = IndexPriceUpdate::new(instrument_id, price, UnixNanos::from(3), ts_init);
314
315        assert_eq!(index_price1, index_price2);
316        assert_ne!(index_price1, index_price3);
317
318        let mut hasher1 = DefaultHasher::new();
319        let mut hasher2 = DefaultHasher::new();
320        index_price1.hash(&mut hasher1);
321        index_price2.hash(&mut hasher2);
322        assert_eq!(hasher1.finish(), hasher2.finish());
323    }
324
325    #[rstest]
326    fn test_index_price_update_json_serialization(instrument_id: InstrumentId, price: Price) {
327        let ts_event = UnixNanos::from(1);
328        let ts_init = UnixNanos::from(2);
329
330        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
331
332        let serialized = index_price.as_json_bytes().unwrap();
333        let deserialized = IndexPriceUpdate::from_json_bytes(&serialized).unwrap();
334
335        assert_eq!(index_price, deserialized);
336    }
337
338    #[rstest]
339    fn test_index_price_update_msgpack_serialization(instrument_id: InstrumentId, price: Price) {
340        let ts_event = UnixNanos::from(1);
341        let ts_init = UnixNanos::from(2);
342
343        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
344
345        let serialized = index_price.as_msgpack_bytes().unwrap();
346        let deserialized = IndexPriceUpdate::from_msgpack_bytes(&serialized).unwrap();
347
348        assert_eq!(index_price, deserialized);
349    }
350
351    #[rstest]
352    fn test_index_price_update_serde_json(instrument_id: InstrumentId, price: Price) {
353        let ts_event = UnixNanos::from(1);
354        let ts_init = UnixNanos::from(2);
355
356        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
357
358        let json_str = serde_json::to_string(&index_price).unwrap();
359        let deserialized: IndexPriceUpdate = serde_json::from_str(&json_str).unwrap();
360
361        assert_eq!(index_price, deserialized);
362    }
363}