nautilus_model/data/
prices.rs

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