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, DecodeFromRecordBatch, EncodeToRecordBatch,
23 EncodingError, KEY_INSTRUMENT_ID,
24 json::{
25 JsonFieldSpec, decode_batch_with_metadata_fields, encode_batch_with_identifier,
26 metadata_for_type, schema_for_type_with_identifier,
27 },
28};
29
30const FUNDING_RATE_UPDATE_FIELDS: &[JsonFieldSpec] = &[
31 JsonFieldSpec::utf8("rate", false),
32 JsonFieldSpec::u64("interval", true),
33 JsonFieldSpec::timestamp("next_funding_ns", true),
34 JsonFieldSpec::timestamp("ts_event", false),
35 JsonFieldSpec::timestamp("ts_init", false),
36];
37
38impl ArrowSchemaProvider for FundingRateUpdate {
39 fn get_schema(metadata: Option<HashMap<String, String>>) -> Schema {
40 schema_for_type_with_identifier("FundingRateUpdate", metadata, FUNDING_RATE_UPDATE_FIELDS)
41 }
42}
43
44impl EncodeToRecordBatch for FundingRateUpdate {
45 fn encode_batch<T>(
46 metadata: &HashMap<String, String>,
47 data: &[T],
48 ) -> Result<RecordBatch, ArrowError>
49 where
50 T: std::borrow::Borrow<Self>,
51 {
52 encode_batch_with_identifier(
53 "FundingRateUpdate",
54 metadata,
55 data.iter().map(std::borrow::Borrow::borrow),
56 FUNDING_RATE_UPDATE_FIELDS,
57 data.iter()
58 .map(std::borrow::Borrow::borrow)
59 .map(|update| update.instrument_id),
60 )
61 }
62
63 fn metadata(&self) -> HashMap<String, String> {
64 let mut metadata = metadata_for_type("FundingRateUpdate");
65 metadata.insert(
66 KEY_INSTRUMENT_ID.to_string(),
67 self.instrument_id.to_string(),
68 );
69 metadata
70 }
71}
72
73impl DecodeFromRecordBatch for FundingRateUpdate {
74 fn decode_batch(
75 metadata: &HashMap<String, String>,
76 record_batch: RecordBatch,
77 ) -> Result<Vec<Self>, EncodingError> {
78 decode_batch_with_metadata_fields(
79 metadata,
80 &record_batch,
81 FUNDING_RATE_UPDATE_FIELDS,
82 &[KEY_INSTRUMENT_ID],
83 Some("FundingRateUpdate"),
84 )
85 }
86}
87
88impl DecodeDataFromRecordBatch for FundingRateUpdate {
89 fn decode_data_batch(
90 metadata: &HashMap<String, String>,
91 record_batch: RecordBatch,
92 ) -> Result<Vec<Data>, EncodingError> {
93 let updates = Self::decode_batch(metadata, record_batch)?;
94 Ok(updates.into_iter().map(Data::from).collect())
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::str::FromStr;
101
102 use arrow::array::StringArray;
103 use nautilus_core::UnixNanos;
104 use nautilus_model::identifiers::InstrumentId;
105 use rstest::rstest;
106 use rust_decimal::Decimal;
107
108 use super::*;
109
110 #[rstest]
111 fn test_funding_rate_update_round_trip_preserves_decimal_precision() {
112 let update = FundingRateUpdate::new(
113 InstrumentId::from("BTCUSDT-PERP.BINANCE"),
114 Decimal::from_str("0.000123456789123456789").unwrap(),
115 Some(480),
116 Some(UnixNanos::from(9_000_000_000)),
117 UnixNanos::from(1_000_000_000),
118 UnixNanos::from(2_000_000_000),
119 );
120 let metadata = update.metadata();
121 let batch = FundingRateUpdate::encode_batch(&metadata, &[update]).unwrap();
122 let identifiers = batch
123 .column_by_name("identifier")
124 .unwrap()
125 .as_any()
126 .downcast_ref::<StringArray>()
127 .unwrap();
128 assert_eq!(FundingRateUpdate::get_fields()["rate"], "Utf8");
129 assert_eq!(
130 batch.schema().field_with_name("rate").unwrap().data_type(),
131 &arrow::datatypes::DataType::Utf8
132 );
133 assert_eq!(identifiers.value(0), "BTCUSDT-PERP.BINANCE");
134 assert!(batch.schema().field_with_name("instrument_id").is_err());
135 assert_eq!(
136 batch.schema().metadata(),
137 &HashMap::from([
138 ("type_name".to_string(), "FundingRateUpdate".to_string()),
139 (
140 KEY_INSTRUMENT_ID.to_string(),
141 "BTCUSDT-PERP.BINANCE".to_string()
142 ),
143 ])
144 );
145 let decoded = FundingRateUpdate::decode_batch(batch.schema().metadata(), batch).unwrap();
146
147 assert_eq!(decoded, vec![update]);
148 }
149
150 #[rstest]
151 fn test_funding_rate_update_round_trip_null_optionals() {
152 let update = FundingRateUpdate::new(
153 InstrumentId::from("BTCUSDT-PERP.BINANCE"),
154 Decimal::from_str("0.0001").unwrap(),
155 None,
156 None,
157 UnixNanos::from(1_000_000_000),
158 UnixNanos::from(2_000_000_000),
159 );
160 let metadata = update.metadata();
161 let batch = FundingRateUpdate::encode_batch(&metadata, &[update]).unwrap();
162 let decoded = FundingRateUpdate::decode_batch(batch.schema().metadata(), batch).unwrap();
163
164 assert_eq!(decoded, vec![update]);
165 assert!(decoded[0].interval.is_none());
166 assert!(decoded[0].next_funding_ns.is_none());
167 }
168}