Skip to main content

nautilus_model/data/
prices.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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
16//! Domain types representing *price* data (index-price, mark-price, etc.).
17
18use std::{collections::HashMap, fmt::Display};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, serialization::Serializable};
22use serde::{Deserialize, Serialize};
23
24use super::HasTsInit;
25use crate::{
26    identifiers::InstrumentId,
27    types::{Price, fixed::FIXED_SIZE_BINARY},
28};
29
30/// Represents a mark price update.
31#[repr(C)]
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
33#[serde(tag = "type")]
34#[cfg_attr(
35    feature = "python",
36    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
37)]
38#[cfg_attr(
39    feature = "python",
40    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
41)]
42pub struct MarkPriceUpdate {
43    /// The instrument ID for the mark price.
44    pub instrument_id: InstrumentId,
45    /// The mark price.
46    pub value: Price,
47    /// UNIX timestamp (nanoseconds) when the price event occurred.
48    pub ts_event: UnixNanos,
49    /// UNIX timestamp (nanoseconds) when the instance was created.
50    pub ts_init: UnixNanos,
51}
52
53impl MarkPriceUpdate {
54    /// Creates a new [`MarkPriceUpdate`] instance.
55    #[must_use]
56    pub fn new(
57        instrument_id: InstrumentId,
58        value: Price,
59        ts_event: UnixNanos,
60        ts_init: UnixNanos,
61    ) -> Self {
62        Self {
63            instrument_id,
64            value,
65            ts_event,
66            ts_init,
67        }
68    }
69
70    /// Returns the metadata for the type, for use with serialization formats.
71    #[must_use]
72    pub fn get_metadata(
73        instrument_id: &InstrumentId,
74        price_precision: u8,
75    ) -> HashMap<String, String> {
76        let mut metadata = HashMap::new();
77        metadata.insert("instrument_id".to_string(), instrument_id.to_string());
78        metadata.insert("price_precision".to_string(), price_precision.to_string());
79        metadata
80    }
81
82    /// Returns the field map for the type, for use with Arrow schemas.
83    #[must_use]
84    pub fn get_fields() -> IndexMap<String, String> {
85        let mut metadata = IndexMap::new();
86        metadata.insert("value".to_string(), FIXED_SIZE_BINARY.to_string());
87        metadata.insert("ts_event".to_string(), "UInt64".to_string());
88        metadata.insert("ts_init".to_string(), "UInt64".to_string());
89        metadata
90    }
91}
92
93impl Display for MarkPriceUpdate {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(
96            f,
97            "{},{},{},{}",
98            self.instrument_id, self.value, self.ts_event, self.ts_init
99        )
100    }
101}
102
103impl Serializable for MarkPriceUpdate {}
104
105impl HasTsInit for MarkPriceUpdate {
106    fn ts_init(&self) -> UnixNanos {
107        self.ts_init
108    }
109}
110
111/// Represents an index price update.
112#[repr(C)]
113#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
114#[serde(tag = "type")]
115#[cfg_attr(
116    feature = "python",
117    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
118)]
119#[cfg_attr(
120    feature = "python",
121    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
122)]
123pub struct IndexPriceUpdate {
124    /// The instrument ID for the index price.
125    pub instrument_id: InstrumentId,
126    /// The index price.
127    pub value: Price,
128    /// UNIX timestamp (nanoseconds) when the price event occurred.
129    pub ts_event: UnixNanos,
130    /// UNIX timestamp (nanoseconds) when the instance was created.
131    pub ts_init: UnixNanos,
132}
133
134impl IndexPriceUpdate {
135    /// Creates a new [`IndexPriceUpdate`] instance.
136    #[must_use]
137    pub fn new(
138        instrument_id: InstrumentId,
139        value: Price,
140        ts_event: UnixNanos,
141        ts_init: UnixNanos,
142    ) -> Self {
143        Self {
144            instrument_id,
145            value,
146            ts_event,
147            ts_init,
148        }
149    }
150
151    /// Returns the metadata for the type, for use with serialization formats.
152    #[must_use]
153    pub fn get_metadata(
154        instrument_id: &InstrumentId,
155        price_precision: u8,
156    ) -> HashMap<String, String> {
157        let mut metadata = HashMap::new();
158        metadata.insert("instrument_id".to_string(), instrument_id.to_string());
159        metadata.insert("price_precision".to_string(), price_precision.to_string());
160        metadata
161    }
162
163    /// Returns the field map for the type, for use with Arrow schemas.
164    #[must_use]
165    pub fn get_fields() -> IndexMap<String, String> {
166        let mut metadata = IndexMap::new();
167        metadata.insert("value".to_string(), FIXED_SIZE_BINARY.to_string());
168        metadata.insert("ts_event".to_string(), "UInt64".to_string());
169        metadata.insert("ts_init".to_string(), "UInt64".to_string());
170        metadata
171    }
172}
173
174impl Display for IndexPriceUpdate {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        write!(
177            f,
178            "{},{},{},{}",
179            self.instrument_id, self.value, self.ts_event, self.ts_init
180        )
181    }
182}
183
184impl Serializable for IndexPriceUpdate {}
185
186impl HasTsInit for IndexPriceUpdate {
187    fn ts_init(&self) -> UnixNanos {
188        self.ts_init
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use std::{
195        collections::hash_map::DefaultHasher,
196        hash::{Hash, Hasher},
197    };
198
199    use nautilus_core::serialization::{
200        Serializable,
201        msgpack::{FromMsgPack, ToMsgPack},
202    };
203    use rstest::{fixture, rstest};
204    use serde_json;
205
206    use super::*;
207
208    #[fixture]
209    fn instrument_id() -> InstrumentId {
210        InstrumentId::from("BTC-USDT.OKX")
211    }
212
213    #[fixture]
214    fn price() -> Price {
215        Price::from("150_500.10")
216    }
217
218    #[rstest]
219    fn test_mark_price_update_new(instrument_id: InstrumentId, price: Price) {
220        let ts_event = UnixNanos::from(1);
221        let ts_init = UnixNanos::from(2);
222
223        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
224
225        assert_eq!(mark_price.instrument_id, instrument_id);
226        assert_eq!(mark_price.value, price);
227        assert_eq!(mark_price.ts_event, ts_event);
228        assert_eq!(mark_price.ts_init, ts_init);
229    }
230
231    #[rstest]
232    fn test_mark_price_update_display(instrument_id: InstrumentId, price: Price) {
233        let ts_event = UnixNanos::from(1);
234        let ts_init = UnixNanos::from(2);
235
236        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
237
238        assert_eq!(format!("{mark_price}"), "BTC-USDT.OKX,150500.10,1,2");
239    }
240
241    #[rstest]
242    fn test_mark_price_update_get_ts_init(instrument_id: InstrumentId, price: Price) {
243        let ts_event = UnixNanos::from(1);
244        let ts_init = UnixNanos::from(2);
245
246        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
247
248        assert_eq!(mark_price.ts_init(), ts_init);
249    }
250
251    #[rstest]
252    fn test_mark_price_update_eq_hash(instrument_id: InstrumentId, price: Price) {
253        use std::{
254            collections::hash_map::DefaultHasher,
255            hash::{Hash, Hasher},
256        };
257
258        let ts_event = UnixNanos::from(1);
259        let ts_init = UnixNanos::from(2);
260
261        let mark_price1 = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
262        let mark_price2 = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
263        let mark_price3 =
264            MarkPriceUpdate::new(instrument_id, Price::from("143_500.50"), ts_event, ts_init);
265
266        assert_eq!(mark_price1, mark_price2);
267        assert_ne!(mark_price1, mark_price3);
268
269        // Test Hash implementation
270        let mut hasher1 = DefaultHasher::new();
271        let mut hasher2 = DefaultHasher::new();
272        mark_price1.hash(&mut hasher1);
273        mark_price2.hash(&mut hasher2);
274        assert_eq!(hasher1.finish(), hasher2.finish());
275    }
276
277    #[rstest]
278    fn test_mark_price_update_json_serialization(instrument_id: InstrumentId, price: Price) {
279        let ts_event = UnixNanos::from(1);
280        let ts_init = UnixNanos::from(2);
281
282        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
283
284        let serialized = mark_price.to_json_bytes().unwrap();
285        let deserialized = MarkPriceUpdate::from_json_bytes(&serialized).unwrap();
286
287        assert_eq!(mark_price, deserialized);
288    }
289
290    #[rstest]
291    fn test_mark_price_update_msgpack_serialization(instrument_id: InstrumentId, price: Price) {
292        let ts_event = UnixNanos::from(1);
293        let ts_init = UnixNanos::from(2);
294
295        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
296
297        let serialized = mark_price.to_msgpack_bytes().unwrap();
298        let deserialized = MarkPriceUpdate::from_msgpack_bytes(&serialized).unwrap();
299
300        assert_eq!(mark_price, deserialized);
301    }
302
303    #[rstest]
304    fn test_mark_price_update_clone(instrument_id: InstrumentId, price: Price) {
305        let ts_event = UnixNanos::from(1);
306        let ts_init = UnixNanos::from(2);
307
308        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
309        let cloned = mark_price;
310
311        assert_eq!(mark_price, cloned);
312    }
313
314    #[rstest]
315    fn test_mark_price_update_serde_json(instrument_id: InstrumentId, price: Price) {
316        let ts_event = UnixNanos::from(1);
317        let ts_init = UnixNanos::from(2);
318
319        let mark_price = MarkPriceUpdate::new(instrument_id, price, ts_event, ts_init);
320
321        let json_str = serde_json::to_string(&mark_price).unwrap();
322        let deserialized: MarkPriceUpdate = serde_json::from_str(&json_str).unwrap();
323
324        assert_eq!(mark_price, deserialized);
325    }
326
327    #[rstest]
328    fn test_index_price_update_new(instrument_id: InstrumentId, price: Price) {
329        let ts_event = UnixNanos::from(1);
330        let ts_init = UnixNanos::from(2);
331
332        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
333
334        assert_eq!(index_price.instrument_id, instrument_id);
335        assert_eq!(index_price.value, price);
336        assert_eq!(index_price.ts_event, ts_event);
337        assert_eq!(index_price.ts_init, ts_init);
338    }
339
340    #[rstest]
341    fn test_index_price_update_display(instrument_id: InstrumentId, price: Price) {
342        let ts_event = UnixNanos::from(1);
343        let ts_init = UnixNanos::from(2);
344
345        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
346
347        assert_eq!(format!("{index_price}"), "BTC-USDT.OKX,150500.10,1,2");
348    }
349
350    #[rstest]
351    fn test_index_price_update_get_ts_init(instrument_id: InstrumentId, price: Price) {
352        let ts_event = UnixNanos::from(1);
353        let ts_init = UnixNanos::from(2);
354
355        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
356
357        assert_eq!(index_price.ts_init(), ts_init);
358    }
359
360    #[rstest]
361    fn test_index_price_update_eq_hash(instrument_id: InstrumentId, price: Price) {
362        let ts_event = UnixNanos::from(1);
363        let ts_init = UnixNanos::from(2);
364
365        let index_price1 = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
366        let index_price2 = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
367        let index_price3 = IndexPriceUpdate::new(instrument_id, price, UnixNanos::from(3), ts_init);
368
369        assert_eq!(index_price1, index_price2);
370        assert_ne!(index_price1, index_price3);
371
372        let mut hasher1 = DefaultHasher::new();
373        let mut hasher2 = DefaultHasher::new();
374        index_price1.hash(&mut hasher1);
375        index_price2.hash(&mut hasher2);
376        assert_eq!(hasher1.finish(), hasher2.finish());
377    }
378
379    #[rstest]
380    fn test_index_price_update_json_serialization(instrument_id: InstrumentId, price: Price) {
381        let ts_event = UnixNanos::from(1);
382        let ts_init = UnixNanos::from(2);
383
384        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
385
386        let serialized = index_price.to_json_bytes().unwrap();
387        let deserialized = IndexPriceUpdate::from_json_bytes(&serialized).unwrap();
388
389        assert_eq!(index_price, deserialized);
390    }
391
392    #[rstest]
393    fn test_index_price_update_msgpack_serialization(instrument_id: InstrumentId, price: Price) {
394        let ts_event = UnixNanos::from(1);
395        let ts_init = UnixNanos::from(2);
396
397        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
398
399        let serialized = index_price.to_msgpack_bytes().unwrap();
400        let deserialized = IndexPriceUpdate::from_msgpack_bytes(&serialized).unwrap();
401
402        assert_eq!(index_price, deserialized);
403    }
404
405    #[rstest]
406    fn test_index_price_update_serde_json(instrument_id: InstrumentId, price: Price) {
407        let ts_event = UnixNanos::from(1);
408        let ts_init = UnixNanos::from(2);
409
410        let index_price = IndexPriceUpdate::new(instrument_id, price, ts_event, ts_init);
411
412        let json_str = serde_json::to_string(&index_price).unwrap();
413        let deserialized: IndexPriceUpdate = serde_json::from_str(&json_str).unwrap();
414
415        assert_eq!(index_price, deserialized);
416    }
417}