Skip to main content

nautilus_serialization/arrow/
funding.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
16use std::collections::HashMap;
17
18use arrow::{datatypes::Schema, error::ArrowError, record_batch::RecordBatch};
19use nautilus_model::data::{Data, FundingRateUpdate};
20
21use super::{
22    ArrowSchemaProvider, DecodeDataFromRecordBatch, DecodeTypedFromRecordBatch,
23    EncodeToRecordBatch, EncodingError, KEY_INSTRUMENT_ID,
24    json::{JsonFieldSpec, decode_batch, encode_batch, metadata_for_type, schema_for_type},
25};
26
27const FUNDING_RATE_UPDATE_FIELDS: &[JsonFieldSpec] = &[
28    JsonFieldSpec::utf8("instrument_id", false),
29    JsonFieldSpec::utf8("rate", false),
30    JsonFieldSpec::u64("interval", true),
31    JsonFieldSpec::u64("next_funding_ns", true),
32    JsonFieldSpec::u64("ts_event", false),
33    JsonFieldSpec::u64("ts_init", false),
34];
35
36impl ArrowSchemaProvider for FundingRateUpdate {
37    fn get_schema(metadata: Option<HashMap<String, String>>) -> Schema {
38        schema_for_type("FundingRateUpdate", metadata, FUNDING_RATE_UPDATE_FIELDS)
39    }
40}
41
42impl EncodeToRecordBatch for FundingRateUpdate {
43    fn encode_batch(
44        metadata: &HashMap<String, String>,
45        data: &[Self],
46    ) -> Result<RecordBatch, ArrowError> {
47        encode_batch(
48            "FundingRateUpdate",
49            metadata,
50            data,
51            FUNDING_RATE_UPDATE_FIELDS,
52        )
53    }
54
55    fn metadata(&self) -> HashMap<String, String> {
56        let mut metadata = metadata_for_type("FundingRateUpdate");
57        metadata.insert(
58            KEY_INSTRUMENT_ID.to_string(),
59            self.instrument_id.to_string(),
60        );
61        metadata
62    }
63}
64
65impl DecodeTypedFromRecordBatch for FundingRateUpdate {
66    fn decode_typed_batch(
67        metadata: &HashMap<String, String>,
68        record_batch: RecordBatch,
69    ) -> Result<Vec<Self>, EncodingError> {
70        decode_batch(
71            metadata,
72            &record_batch,
73            FUNDING_RATE_UPDATE_FIELDS,
74            Some("FundingRateUpdate"),
75        )
76    }
77}
78
79impl DecodeDataFromRecordBatch for FundingRateUpdate {
80    fn decode_data_batch(
81        metadata: &HashMap<String, String>,
82        record_batch: RecordBatch,
83    ) -> Result<Vec<Data>, EncodingError> {
84        let updates = Self::decode_typed_batch(metadata, record_batch)?;
85        Ok(updates.into_iter().map(Data::from).collect())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use std::str::FromStr;
92
93    use nautilus_core::UnixNanos;
94    use nautilus_model::identifiers::InstrumentId;
95    use rstest::rstest;
96    use rust_decimal::Decimal;
97
98    use super::*;
99
100    #[rstest]
101    fn test_funding_rate_update_round_trip_preserves_decimal_precision() {
102        let update = FundingRateUpdate::new(
103            InstrumentId::from("BTCUSDT-PERP.BINANCE"),
104            Decimal::from_str("0.000123456789123456789").unwrap(),
105            Some(480),
106            Some(UnixNanos::from(9_000_000_000)),
107            UnixNanos::from(1_000_000_000),
108            UnixNanos::from(2_000_000_000),
109        );
110        let metadata = update.metadata();
111        let batch = FundingRateUpdate::encode_batch(&metadata, &[update]).unwrap();
112        let decoded =
113            FundingRateUpdate::decode_typed_batch(batch.schema().metadata(), batch).unwrap();
114
115        assert_eq!(decoded, vec![update]);
116    }
117
118    #[rstest]
119    fn test_funding_rate_update_round_trip_null_optionals() {
120        let update = FundingRateUpdate::new(
121            InstrumentId::from("BTCUSDT-PERP.BINANCE"),
122            Decimal::from_str("0.0001").unwrap(),
123            None,
124            None,
125            UnixNanos::from(1_000_000_000),
126            UnixNanos::from(2_000_000_000),
127        );
128        let metadata = update.metadata();
129        let batch = FundingRateUpdate::encode_batch(&metadata, &[update]).unwrap();
130        let decoded =
131            FundingRateUpdate::decode_typed_batch(batch.schema().metadata(), batch).unwrap();
132
133        assert_eq!(decoded, vec![update]);
134        assert!(decoded[0].interval.is_none());
135        assert!(decoded[0].next_funding_ns.is_none());
136    }
137}