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