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("rate".to_string(), "Utf8".to_string());
108 metadata.insert("interval".to_string(), "UInt64".to_string());
109 metadata.insert(
110 "next_funding_ns".to_string(),
111 ARROW_TIMESTAMP_NANOSECOND.to_string(),
112 );
113 metadata.insert(
114 "ts_event".to_string(),
115 ARROW_TIMESTAMP_NANOSECOND.to_string(),
116 );
117 metadata.insert(
118 "ts_init".to_string(),
119 ARROW_TIMESTAMP_NANOSECOND.to_string(),
120 );
121 metadata
122 }
123}
124
125impl Display for FundingRateUpdate {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(
128 f,
129 "{},{},{:?},{:?},{},{}",
130 self.instrument_id,
131 self.rate,
132 self.interval,
133 self.next_funding_ns.map(|ts| ts.as_u64()),
134 self.ts_event,
135 self.ts_init
136 )
137 }
138}
139
140impl Serializable for FundingRateUpdate {}
141
142impl HasTsInit for FundingRateUpdate {
143 fn ts_init(&self) -> UnixNanos {
144 self.ts_init
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use std::{
151 collections::hash_map::DefaultHasher,
152 hash::{Hash, Hasher},
153 str::FromStr,
154 };
155
156 use nautilus_core::serialization::{
157 Serializable,
158 msgpack::{FromMsgPack, ToMsgPack},
159 };
160 use rstest::{fixture, rstest};
161 use serde_json;
162
163 use super::*;
164
165 #[fixture]
166 fn instrument_id() -> InstrumentId {
167 InstrumentId::from("BTCUSDT-PERP.BINANCE")
168 }
169
170 #[rstest]
171 fn test_funding_rate_update_new(instrument_id: InstrumentId) {
172 let rate = Decimal::from_str("0.0001").unwrap();
173 let ts_event = UnixNanos::from(1);
174 let ts_init = UnixNanos::from(2);
175
176 let funding_rate =
177 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
178
179 assert_eq!(funding_rate.instrument_id, instrument_id);
180 assert_eq!(funding_rate.rate, rate);
181 assert_eq!(funding_rate.interval, None);
182 assert_eq!(funding_rate.next_funding_ns, None);
183 assert_eq!(funding_rate.ts_event, ts_event);
184 assert_eq!(funding_rate.ts_init, ts_init);
185 }
186
187 #[rstest]
188 fn test_funding_rate_update_new_with_optional_fields(instrument_id: InstrumentId) {
189 let rate = Decimal::from_str("0.0001").unwrap();
190 let interval = Some(60);
191 let next_funding_ns = Some(UnixNanos::from(1000));
192 let ts_event = UnixNanos::from(1);
193 let ts_init = UnixNanos::from(2);
194
195 let funding_rate = FundingRateUpdate::new(
196 instrument_id,
197 rate,
198 interval,
199 next_funding_ns,
200 ts_event,
201 ts_init,
202 );
203
204 assert_eq!(funding_rate.instrument_id, instrument_id);
205 assert_eq!(funding_rate.rate, rate);
206 assert_eq!(funding_rate.interval, interval);
207 assert_eq!(funding_rate.next_funding_ns, next_funding_ns);
208 assert_eq!(funding_rate.ts_event, ts_event);
209 assert_eq!(funding_rate.ts_init, ts_init);
210 }
211
212 #[rstest]
213 fn test_funding_rate_update_display(instrument_id: InstrumentId) {
214 let rate = Decimal::from_str("0.0001").unwrap();
215 let interval = Some(60);
216 let next_funding_ns = Some(UnixNanos::from(1000));
217 let ts_event = UnixNanos::from(1);
218 let ts_init = UnixNanos::from(2);
219
220 let funding_rate = FundingRateUpdate::new(
221 instrument_id,
222 rate,
223 interval,
224 next_funding_ns,
225 ts_event,
226 ts_init,
227 );
228
229 assert_eq!(
230 format!("{funding_rate}"),
231 "BTCUSDT-PERP.BINANCE,0.0001,Some(60),Some(1000),1,2"
232 );
233 }
234
235 #[rstest]
236 fn test_funding_rate_update_get_ts_init(instrument_id: InstrumentId) {
237 let rate = Decimal::from_str("0.0001").unwrap();
238 let ts_event = UnixNanos::from(1);
239 let ts_init = UnixNanos::from(2);
240
241 let funding_rate =
242 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
243
244 assert_eq!(funding_rate.ts_init(), ts_init);
245 }
246
247 #[rstest]
248 fn test_funding_rate_update_eq_hash(instrument_id: InstrumentId) {
249 let rate = Decimal::from_str("0.0001").unwrap();
250 let ts_event = UnixNanos::from(1);
251 let ts_init = UnixNanos::from(2);
252
253 let funding_rate1 =
254 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
255 let funding_rate2 =
256 FundingRateUpdate::new(instrument_id, rate, None, None, ts_event, ts_init);
257 let funding_rate3 = FundingRateUpdate::new(
258 instrument_id,
259 Decimal::from_str("0.0002").unwrap(),
260 None,
261 None,
262 ts_event,
263 ts_init,
264 );
265
266 assert_eq!(funding_rate1, funding_rate2);
267 assert_ne!(funding_rate1, funding_rate3);
268
269 let mut hasher1 = DefaultHasher::new();
271 let mut hasher2 = DefaultHasher::new();
272 funding_rate1.hash(&mut hasher1);
273 funding_rate2.hash(&mut hasher2);
274 assert_eq!(hasher1.finish(), hasher2.finish());
275 }
276
277 #[rstest]
278 fn test_funding_rate_update_json_serialization(instrument_id: InstrumentId) {
279 let rate = Decimal::from_str("0.0001").unwrap();
280 let interval = Some(60);
281 let next_funding_ns = Some(UnixNanos::from(1000));
282 let ts_event = UnixNanos::from(1);
283 let ts_init = UnixNanos::from(2);
284
285 let funding_rate = FundingRateUpdate::new(
286 instrument_id,
287 rate,
288 interval,
289 next_funding_ns,
290 ts_event,
291 ts_init,
292 );
293
294 let serialized = funding_rate.to_json_bytes().unwrap();
295 let deserialized = FundingRateUpdate::from_json_bytes(&serialized).unwrap();
296
297 assert_funding_rate_fields(&funding_rate, &deserialized);
298 }
299
300 #[rstest]
301 fn test_funding_rate_update_msgpack_serialization(instrument_id: InstrumentId) {
302 let rate = Decimal::from_str("0.0001").unwrap();
303 let interval = Some(60);
304 let next_funding_ns = Some(UnixNanos::from(1000));
305 let ts_event = UnixNanos::from(1);
306 let ts_init = UnixNanos::from(2);
307
308 let funding_rate = FundingRateUpdate::new(
309 instrument_id,
310 rate,
311 interval,
312 next_funding_ns,
313 ts_event,
314 ts_init,
315 );
316
317 let serialized = funding_rate.to_msgpack_bytes().unwrap();
318 let deserialized = FundingRateUpdate::from_msgpack_bytes(&serialized).unwrap();
319
320 assert_funding_rate_fields(&funding_rate, &deserialized);
321 }
322
323 #[rstest]
324 fn test_funding_rate_update_serde_json(instrument_id: InstrumentId) {
325 let rate = Decimal::from_str("0.0001").unwrap();
326 let interval = Some(60);
327 let next_funding_ns = Some(UnixNanos::from(1000));
328 let ts_event = UnixNanos::from(1);
329 let ts_init = UnixNanos::from(2);
330
331 let funding_rate = FundingRateUpdate::new(
332 instrument_id,
333 rate,
334 interval,
335 next_funding_ns,
336 ts_event,
337 ts_init,
338 );
339
340 let json_str = serde_json::to_string(&funding_rate).unwrap();
341 let deserialized: FundingRateUpdate = serde_json::from_str(&json_str).unwrap();
342
343 assert_funding_rate_fields(&funding_rate, &deserialized);
344 }
345
346 fn assert_funding_rate_fields(expected: &FundingRateUpdate, actual: &FundingRateUpdate) {
347 assert_eq!(expected.instrument_id, actual.instrument_id);
348 assert_eq!(expected.rate, actual.rate);
349 assert_eq!(expected.interval, actual.interval);
350 assert_eq!(expected.next_funding_ns, actual.next_funding_ns);
351 assert_eq!(expected.ts_event, actual.ts_event);
352 assert_eq!(expected.ts_init, actual.ts_init);
353 }
354}