1use std::{collections::HashMap, fmt::Display, hash::Hash};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, serialization::Serializable};
22use rust_decimal::Decimal;
23use serde::{Deserialize, Serialize};
24
25use super::{ARROW_TIMESTAMP_NANOSECOND, HasTsInit};
26use crate::identifiers::InstrumentId;
27
28#[repr(C)]
30#[derive(Clone, Copy, Debug, Eq, Serialize, Deserialize)]
31#[serde(tag = "type")]
32#[cfg_attr(
33 feature = "python",
34 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
35)]
36#[cfg_attr(
37 feature = "python",
38 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
39)]
40pub struct FundingRateUpdate {
41 pub instrument_id: InstrumentId,
43 pub rate: Decimal,
45 pub interval: Option<u16>,
47 pub next_funding_ns: Option<UnixNanos>,
49 pub ts_event: UnixNanos,
51 pub ts_init: UnixNanos,
53}
54
55impl PartialEq for FundingRateUpdate {
56 fn eq(&self, other: &Self) -> bool {
57 self.instrument_id == other.instrument_id
58 && self.rate == other.rate
59 && self.interval == other.interval
60 && self.next_funding_ns == other.next_funding_ns
61 }
62}
63
64impl Hash for FundingRateUpdate {
65 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
66 self.instrument_id.hash(state);
68 self.rate.hash(state);
69 self.interval.hash(state);
70 self.next_funding_ns.hash(state);
71 }
72}
73
74impl FundingRateUpdate {
75 #[must_use]
77 pub fn new(
78 instrument_id: InstrumentId,
79 rate: Decimal,
80 interval: Option<u16>,
81 next_funding_ns: Option<UnixNanos>,
82 ts_event: UnixNanos,
83 ts_init: UnixNanos,
84 ) -> Self {
85 Self {
86 instrument_id,
87 rate,
88 interval,
89 next_funding_ns,
90 ts_event,
91 ts_init,
92 }
93 }
94
95 #[must_use]
97 pub fn get_metadata(instrument_id: &InstrumentId) -> HashMap<String, String> {
98 let mut metadata = HashMap::new();
99 metadata.insert("instrument_id".to_string(), instrument_id.to_string());
100 metadata
101 }
102
103 #[must_use]
105 pub fn get_fields() -> IndexMap<String, String> {
106 let mut metadata = IndexMap::new();
107 metadata.insert("instrument_id".to_string(), "Utf8".to_string());
108 metadata.insert("rate".to_string(), "Utf8".to_string());
109 metadata.insert("interval".to_string(), "UInt64".to_string());
110 metadata.insert(
111 "next_funding_ns".to_string(),
112 ARROW_TIMESTAMP_NANOSECOND.to_string(),
113 );
114 metadata.insert(
115 "ts_event".to_string(),
116 ARROW_TIMESTAMP_NANOSECOND.to_string(),
117 );
118 metadata.insert(
119 "ts_init".to_string(),
120 ARROW_TIMESTAMP_NANOSECOND.to_string(),
121 );
122 metadata
123 }
124}
125
126impl Display for FundingRateUpdate {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 write!(
129 f,
130 "{},{},{:?},{:?},{},{}",
131 self.instrument_id,
132 self.rate,
133 self.interval,
134 self.next_funding_ns.map(|ts| ts.as_u64()),
135 self.ts_event,
136 self.ts_init
137 )
138 }
139}
140
141impl Serializable for FundingRateUpdate {}
142
143impl HasTsInit for FundingRateUpdate {
144 fn ts_init(&self) -> UnixNanos {
145 self.ts_init
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use std::{
152 collections::hash_map::DefaultHasher,
153 hash::{Hash, Hasher},
154 str::FromStr,
155 };
156
157 use nautilus_core::serialization::{
158 Serializable,
159 msgpack::{FromMsgPack, ToMsgPack},
160 };
161 use rstest::{fixture, rstest};
162 use serde_json;
163
164 use super::*;
165
166 #[fixture]
167 fn instrument_id() -> InstrumentId {
168 InstrumentId::from("BTCUSDT-PERP.BINANCE")
169 }
170
171 #[rstest]
172 fn test_funding_rate_update_new(instrument_id: InstrumentId) {
173 let rate = Decimal::from_str("0.0001").unwrap();
174 let ts_event = UnixNanos::from(1);
175 let ts_init = UnixNanos::from(2);
176
177 let funding_rate =
178 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
179
180 assert_eq!(funding_rate.instrument_id, instrument_id);
181 assert_eq!(funding_rate.rate, rate);
182 assert_eq!(funding_rate.interval, None);
183 assert_eq!(funding_rate.next_funding_ns, None);
184 assert_eq!(funding_rate.ts_event, ts_event);
185 assert_eq!(funding_rate.ts_init, ts_init);
186 }
187
188 #[rstest]
189 fn test_funding_rate_update_new_with_optional_fields(instrument_id: InstrumentId) {
190 let rate = Decimal::from_str("0.0001").unwrap();
191 let interval = Some(60);
192 let next_funding_ns = Some(UnixNanos::from(1000));
193 let ts_event = UnixNanos::from(1);
194 let ts_init = UnixNanos::from(2);
195
196 let funding_rate = FundingRateUpdate::new(
197 instrument_id,
198 rate,
199 interval,
200 next_funding_ns,
201 ts_event,
202 ts_init,
203 );
204
205 assert_eq!(funding_rate.instrument_id, instrument_id);
206 assert_eq!(funding_rate.rate, rate);
207 assert_eq!(funding_rate.interval, interval);
208 assert_eq!(funding_rate.next_funding_ns, next_funding_ns);
209 assert_eq!(funding_rate.ts_event, ts_event);
210 assert_eq!(funding_rate.ts_init, ts_init);
211 }
212
213 #[rstest]
214 fn test_funding_rate_update_display(instrument_id: InstrumentId) {
215 let rate = Decimal::from_str("0.0001").unwrap();
216 let interval = Some(60);
217 let next_funding_ns = Some(UnixNanos::from(1000));
218 let ts_event = UnixNanos::from(1);
219 let ts_init = UnixNanos::from(2);
220
221 let funding_rate = FundingRateUpdate::new(
222 instrument_id,
223 rate,
224 interval,
225 next_funding_ns,
226 ts_event,
227 ts_init,
228 );
229
230 assert_eq!(
231 format!("{funding_rate}"),
232 "BTCUSDT-PERP.BINANCE,0.0001,Some(60),Some(1000),1,2"
233 );
234 }
235
236 #[rstest]
237 fn test_funding_rate_update_get_ts_init(instrument_id: InstrumentId) {
238 let rate = Decimal::from_str("0.0001").unwrap();
239 let ts_event = UnixNanos::from(1);
240 let ts_init = UnixNanos::from(2);
241
242 let funding_rate =
243 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
244
245 assert_eq!(funding_rate.ts_init(), ts_init);
246 }
247
248 #[rstest]
249 fn test_funding_rate_update_eq_hash(instrument_id: InstrumentId) {
250 let rate = Decimal::from_str("0.0001").unwrap();
251 let ts_event = UnixNanos::from(1);
252 let ts_init = UnixNanos::from(2);
253
254 let funding_rate1 =
255 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
256 let funding_rate2 =
257 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
258 let funding_rate3 = FundingRateUpdate::new(
259 instrument_id,
260 Decimal::from_str("0.0002").unwrap(),
261 None,
262 None,
263 ts_event,
264 ts_init,
265 );
266
267 assert_eq!(funding_rate1, funding_rate2);
268 assert_ne!(funding_rate1, funding_rate3);
269
270 let mut hasher1 = DefaultHasher::new();
272 let mut hasher2 = DefaultHasher::new();
273 funding_rate1.hash(&mut hasher1);
274 funding_rate2.hash(&mut hasher2);
275 assert_eq!(hasher1.finish(), hasher2.finish());
276 }
277
278 #[rstest]
279 fn test_funding_rate_update_json_serialization(instrument_id: InstrumentId) {
280 let rate = Decimal::from_str("0.0001").unwrap();
281 let interval = Some(60);
282 let next_funding_ns = Some(UnixNanos::from(1000));
283 let ts_event = UnixNanos::from(1);
284 let ts_init = UnixNanos::from(2);
285
286 let funding_rate = FundingRateUpdate::new(
287 instrument_id,
288 rate,
289 interval,
290 next_funding_ns,
291 ts_event,
292 ts_init,
293 );
294
295 let serialized = funding_rate.to_json_bytes().unwrap();
296 let deserialized = FundingRateUpdate::from_json_bytes(&serialized).unwrap();
297
298 assert_funding_rate_fields(&funding_rate, &deserialized);
299 }
300
301 #[rstest]
302 fn test_funding_rate_update_msgpack_serialization(instrument_id: InstrumentId) {
303 let rate = Decimal::from_str("0.0001").unwrap();
304 let interval = Some(60);
305 let next_funding_ns = Some(UnixNanos::from(1000));
306 let ts_event = UnixNanos::from(1);
307 let ts_init = UnixNanos::from(2);
308
309 let funding_rate = FundingRateUpdate::new(
310 instrument_id,
311 rate,
312 interval,
313 next_funding_ns,
314 ts_event,
315 ts_init,
316 );
317
318 let serialized = funding_rate.to_msgpack_bytes().unwrap();
319 let deserialized = FundingRateUpdate::from_msgpack_bytes(&serialized).unwrap();
320
321 assert_funding_rate_fields(&funding_rate, &deserialized);
322 }
323
324 #[rstest]
325 fn test_funding_rate_update_serde_json(instrument_id: InstrumentId) {
326 let rate = Decimal::from_str("0.0001").unwrap();
327 let interval = Some(60);
328 let next_funding_ns = Some(UnixNanos::from(1000));
329 let ts_event = UnixNanos::from(1);
330 let ts_init = UnixNanos::from(2);
331
332 let funding_rate = FundingRateUpdate::new(
333 instrument_id,
334 rate,
335 interval,
336 next_funding_ns,
337 ts_event,
338 ts_init,
339 );
340
341 let json_str = serde_json::to_string(&funding_rate).unwrap();
342 let deserialized: FundingRateUpdate = serde_json::from_str(&json_str).unwrap();
343
344 assert_funding_rate_fields(&funding_rate, &deserialized);
345 }
346
347 fn assert_funding_rate_fields(expected: &FundingRateUpdate, actual: &FundingRateUpdate) {
348 assert_eq!(expected.instrument_id, actual.instrument_id);
349 assert_eq!(expected.rate, actual.rate);
350 assert_eq!(expected.interval, actual.interval);
351 assert_eq!(expected.next_funding_ns, actual.next_funding_ns);
352 assert_eq!(expected.ts_event, actual.ts_event);
353 assert_eq!(expected.ts_init, actual.ts_init);
354 }
355}