nautilus_serialization/arrow/
funding.rs1use 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}