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")
35)]
36pub struct FundingRateUpdate {
37 pub instrument_id: InstrumentId,
39 pub rate: Decimal,
41 pub next_funding_ns: Option<UnixNanos>,
43 pub ts_event: UnixNanos,
45 pub ts_init: UnixNanos,
47}
48
49impl PartialEq for FundingRateUpdate {
50 fn eq(&self, other: &Self) -> bool {
51 self.instrument_id == other.instrument_id
52 && self.rate == other.rate
53 && self.next_funding_ns == other.next_funding_ns
54 }
55}
56
57impl Hash for FundingRateUpdate {
58 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
59 self.instrument_id.hash(state);
61 self.rate.hash(state);
62 self.next_funding_ns.hash(state);
63 }
64}
65
66impl FundingRateUpdate {
67 #[must_use]
69 pub fn new(
70 instrument_id: InstrumentId,
71 rate: Decimal,
72 next_funding_ns: Option<UnixNanos>,
73 ts_event: UnixNanos,
74 ts_init: UnixNanos,
75 ) -> Self {
76 Self {
77 instrument_id,
78 rate,
79 next_funding_ns,
80 ts_event,
81 ts_init,
82 }
83 }
84
85 #[must_use]
87 pub fn get_metadata(instrument_id: &InstrumentId) -> HashMap<String, String> {
88 let mut metadata = HashMap::new();
89 metadata.insert("instrument_id".to_string(), instrument_id.to_string());
90 metadata
91 }
92
93 #[must_use]
95 pub fn get_fields() -> IndexMap<String, String> {
96 let mut metadata = IndexMap::new();
97 metadata.insert("rate".to_string(), "Decimal128".to_string());
98 metadata.insert("next_funding_ns".to_string(), "UInt64".to_string());
99 metadata.insert("ts_event".to_string(), "UInt64".to_string());
100 metadata.insert("ts_init".to_string(), "UInt64".to_string());
101 metadata
102 }
103}
104
105impl Display for FundingRateUpdate {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 write!(
108 f,
109 "{},{},{:?},{},{}",
110 self.instrument_id,
111 self.rate,
112 self.next_funding_ns.map(|ts| ts.as_u64()),
113 self.ts_event,
114 self.ts_init
115 )
116 }
117}
118
119impl Serializable for FundingRateUpdate {}
120
121impl HasTsInit for FundingRateUpdate {
122 fn ts_init(&self) -> UnixNanos {
123 self.ts_init
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use std::{
130 collections::hash_map::DefaultHasher,
131 hash::{Hash, Hasher},
132 str::FromStr,
133 };
134
135 use nautilus_core::serialization::{
136 Serializable,
137 msgpack::{FromMsgPack, ToMsgPack},
138 };
139 use rstest::{fixture, rstest};
140 use serde_json;
141
142 use super::*;
143
144 #[fixture]
145 fn instrument_id() -> InstrumentId {
146 InstrumentId::from("BTCUSDT-PERP.BINANCE")
147 }
148
149 #[rstest]
150 fn test_funding_rate_update_new(instrument_id: InstrumentId) {
151 let rate = Decimal::from_str("0.0001").unwrap();
152 let ts_event = UnixNanos::from(1);
153 let ts_init = UnixNanos::from(2);
154
155 let funding_rate = FundingRateUpdate::new(instrument_id, rate, None, ts_event, ts_init);
156
157 assert_eq!(funding_rate.instrument_id, instrument_id);
158 assert_eq!(funding_rate.rate, rate);
159 assert_eq!(funding_rate.next_funding_ns, None);
160 assert_eq!(funding_rate.ts_event, ts_event);
161 assert_eq!(funding_rate.ts_init, ts_init);
162 }
163
164 #[rstest]
165 fn test_funding_rate_update_new_with_optional_fields(instrument_id: InstrumentId) {
166 let rate = Decimal::from_str("0.0001").unwrap();
167 let next_funding_ns = Some(UnixNanos::from(1000));
168 let ts_event = UnixNanos::from(1);
169 let ts_init = UnixNanos::from(2);
170
171 let funding_rate =
172 FundingRateUpdate::new(instrument_id, rate, next_funding_ns, ts_event, ts_init);
173
174 assert_eq!(funding_rate.instrument_id, instrument_id);
175 assert_eq!(funding_rate.rate, rate);
176 assert_eq!(funding_rate.next_funding_ns, next_funding_ns);
177 assert_eq!(funding_rate.ts_event, ts_event);
178 assert_eq!(funding_rate.ts_init, ts_init);
179 }
180
181 #[rstest]
182 fn test_funding_rate_update_display(instrument_id: InstrumentId) {
183 let rate = Decimal::from_str("0.0001").unwrap();
184 let next_funding_ns = Some(UnixNanos::from(1000));
185 let ts_event = UnixNanos::from(1);
186 let ts_init = UnixNanos::from(2);
187
188 let funding_rate =
189 FundingRateUpdate::new(instrument_id, rate, next_funding_ns, ts_event, ts_init);
190
191 assert_eq!(
192 format!("{funding_rate}"),
193 "BTCUSDT-PERP.BINANCE,0.0001,Some(1000),1,2"
194 );
195 }
196
197 #[rstest]
198 fn test_funding_rate_update_get_ts_init(instrument_id: InstrumentId) {
199 let rate = Decimal::from_str("0.0001").unwrap();
200 let ts_event = UnixNanos::from(1);
201 let ts_init = UnixNanos::from(2);
202
203 let funding_rate = FundingRateUpdate::new(instrument_id, rate, None, ts_event, ts_init);
204
205 assert_eq!(funding_rate.ts_init(), ts_init);
206 }
207
208 #[rstest]
209 fn test_funding_rate_update_eq_hash(instrument_id: InstrumentId) {
210 let rate = Decimal::from_str("0.0001").unwrap();
211 let ts_event = UnixNanos::from(1);
212 let ts_init = UnixNanos::from(2);
213
214 let funding_rate1 = FundingRateUpdate::new(instrument_id, rate, None, ts_event, ts_init);
215 let funding_rate2 = FundingRateUpdate::new(instrument_id, rate, None, ts_event, ts_init);
216 let funding_rate3 = FundingRateUpdate::new(
217 instrument_id,
218 Decimal::from_str("0.0002").unwrap(),
219 None,
220 ts_event,
221 ts_init,
222 );
223
224 assert_eq!(funding_rate1, funding_rate2);
225 assert_ne!(funding_rate1, funding_rate3);
226
227 let mut hasher1 = DefaultHasher::new();
229 let mut hasher2 = DefaultHasher::new();
230 funding_rate1.hash(&mut hasher1);
231 funding_rate2.hash(&mut hasher2);
232 assert_eq!(hasher1.finish(), hasher2.finish());
233 }
234
235 #[rstest]
236 fn test_funding_rate_update_json_serialization(instrument_id: InstrumentId) {
237 let rate = Decimal::from_str("0.0001").unwrap();
238 let next_funding_ns = Some(UnixNanos::from(1000));
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, next_funding_ns, ts_event, ts_init);
244
245 let serialized = funding_rate.to_json_bytes().unwrap();
246 let deserialized = FundingRateUpdate::from_json_bytes(&serialized).unwrap();
247
248 assert_eq!(funding_rate, deserialized);
249 }
250
251 #[rstest]
252 fn test_funding_rate_update_msgpack_serialization(instrument_id: InstrumentId) {
253 let rate = Decimal::from_str("0.0001").unwrap();
254 let next_funding_ns = Some(UnixNanos::from(1000));
255 let ts_event = UnixNanos::from(1);
256 let ts_init = UnixNanos::from(2);
257
258 let funding_rate =
259 FundingRateUpdate::new(instrument_id, rate, next_funding_ns, ts_event, ts_init);
260
261 let serialized = funding_rate.to_msgpack_bytes().unwrap();
262 let deserialized = FundingRateUpdate::from_msgpack_bytes(&serialized).unwrap();
263
264 assert_eq!(funding_rate, deserialized);
265 }
266
267 #[rstest]
268 fn test_funding_rate_update_serde_json(instrument_id: InstrumentId) {
269 let rate = Decimal::from_str("0.0001").unwrap();
270 let next_funding_ns = Some(UnixNanos::from(1000));
271 let ts_event = UnixNanos::from(1);
272 let ts_init = UnixNanos::from(2);
273
274 let funding_rate =
275 FundingRateUpdate::new(instrument_id, rate, next_funding_ns, ts_event, ts_init);
276
277 let json_str = serde_json::to_string(&funding_rate).unwrap();
278 let deserialized: FundingRateUpdate = serde_json::from_str(&json_str).unwrap();
279
280 assert_eq!(funding_rate, deserialized);
281 }
282}