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