1use rust_decimal::Decimal;
2use serde::{Deserialize, Serialize};
3use strum::{Display, EnumString};
4
5use crate::order::{OrderStatus, Side, SystemOrderType};
6
7#[derive(
8 Debug, Display, Clone, Copy, Serialize, Deserialize, Default, EnumString, PartialEq, Eq, Hash,
9)]
10#[strum(serialize_all = "PascalCase")]
11#[serde(rename_all = "PascalCase")]
12pub enum RfqExecutionMode {
13 #[default]
14 AwaitAccept,
15 Immediate,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct RequestForQuotePayload {
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub client_id: Option<u32>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub quantity: Option<Decimal>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub quote_quantity: Option<Decimal>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub price: Option<Decimal>,
29 pub symbol: String,
30 pub side: Side,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub execution_mode: Option<RfqExecutionMode>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct RequestForQuoteCancelPayload {
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub rfq_id: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub client_id: Option<u32>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct RequestForQuoteRefreshPayload {
47 pub rfq_id: String,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct QuoteAcceptPayload {
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub rfq_id: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub client_id: Option<u32>,
57 pub quote_id: String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct QuotePayload {
63 pub rfq_id: String,
64 pub bid_price: Decimal,
65 pub ask_price: Decimal,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub client_id: Option<u32>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub auto_lend: Option<bool>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub auto_lend_redeem: Option<bool>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub auto_borrow: Option<bool>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub auto_borrow_repay: Option<bool>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct RequestForQuoteStream {
80 pub stream: String,
81 pub data: RequestForQuoteUpdate,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(tag = "e", rename_all = "camelCase")] pub enum RequestForQuoteUpdate {
88 RfqActive {
89 #[serde(rename = "E")]
90 event_time: i64,
91 #[serde(rename = "R")]
92 rfq_id: u64,
93 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
94 client_id: Option<u32>,
95 #[serde(rename = "s")]
96 symbol: String,
97 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
98 quantity: Option<Decimal>,
99 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
100 quote_quantity: Option<Decimal>,
101 #[serde(rename = "w")]
102 submission_time: i64,
103 #[serde(rename = "W")]
104 expiry_time: i64,
105 #[serde(rename = "X")]
106 order_status: OrderStatus,
107 #[serde(rename = "T")]
108 timestamp: i64,
109 #[serde(rename = "o", default)]
110 system_order_type: Option<SystemOrderType>,
111 },
112 RfqRefreshed {
113 #[serde(rename = "E")]
114 event_time: i64,
115 #[serde(rename = "R")]
116 rfq_id: u64,
117 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
118 client_id: Option<u32>,
119 #[serde(rename = "s")]
120 symbol: String,
121 #[serde(rename = "S")]
122 side: Side,
123 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
124 quantity: Option<Decimal>,
125 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
126 quote_quantity: Option<Decimal>,
127 #[serde(rename = "w")]
128 submission_time: i64,
129 #[serde(rename = "W")]
130 expiry_time: i64,
131 #[serde(rename = "X")]
132 order_status: OrderStatus,
133 #[serde(rename = "T")]
134 timestamp: i64,
135 #[serde(rename = "o", default)]
136 system_order_type: Option<SystemOrderType>,
137 },
138 RfqAccepted {
139 #[serde(rename = "E")]
140 event_time: i64,
141 #[serde(rename = "R")]
142 rfq_id: u64,
143 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
144 client_id: Option<u32>,
145 #[serde(rename = "s")]
146 symbol: String,
147 #[serde(rename = "S")]
148 side: Side,
149 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
150 quantity: Option<Decimal>,
151 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
152 quote_quantity: Option<Decimal>,
153 #[serde(rename = "w")]
154 submission_time: i64,
155 #[serde(rename = "W")]
156 expiry_time: i64,
157 #[serde(rename = "X")]
158 order_status: OrderStatus,
159 #[serde(rename = "T")]
160 timestamp: i64,
161 #[serde(rename = "o", default)]
162 system_order_type: Option<SystemOrderType>,
163 },
164 RfqCancelled {
165 #[serde(rename = "E")]
166 event_time: i64,
167 #[serde(rename = "R")]
168 rfq_id: u64,
169 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
170 client_id: Option<u32>,
171 #[serde(rename = "s")]
172 symbol: String,
173 #[serde(rename = "S")]
174 side: Side,
175 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
176 quantity: Option<Decimal>,
177 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
178 quote_quantity: Option<Decimal>,
179 #[serde(rename = "w")]
180 submission_time: i64,
181 #[serde(rename = "W")]
182 expiry_time: i64,
183 #[serde(rename = "X")]
184 order_status: OrderStatus,
185 #[serde(rename = "T")]
186 timestamp: i64,
187 #[serde(rename = "o", default)]
188 system_order_type: Option<SystemOrderType>,
189 },
190 RfqAcceptedBinding {
192 #[serde(rename = "E")]
193 event_time: i64,
194 #[serde(rename = "R")]
195 rfq_id: u64,
196 #[serde(rename = "u")]
197 quote_id: u64,
198 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
199 client_id: Option<u32>,
200 #[serde(rename = "s")]
201 symbol: String,
202 #[serde(rename = "S", skip_serializing_if = "Option::is_none")]
204 side: Option<Side>,
205 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
206 quantity: Option<Decimal>,
207 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
208 quote_quantity: Option<Decimal>,
209 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
210 price: Option<Decimal>,
211 #[serde(rename = "X")]
212 order_status: OrderStatus,
213 #[serde(rename = "T")]
214 timestamp: i64,
215 },
216 QuoteAccepted {
217 #[serde(rename = "E")]
218 event_time: i64,
219 #[serde(rename = "R")]
220 rfq_id: u64,
221 #[serde(rename = "u")]
222 quote_id: u64,
223 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
224 client_id: Option<u32>,
225 #[serde(rename = "s")]
226 symbol: String,
227 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
228 price: Option<Decimal>,
229 #[serde(rename = "X")]
230 order_status: OrderStatus,
231 #[serde(rename = "T")]
232 timestamp: i64,
233 },
234 QuoteCancelled {
235 #[serde(rename = "E")]
236 event_time: i64,
237 #[serde(rename = "R")]
238 rfq_id: u64,
239 #[serde(rename = "u")]
240 quote_id: u64,
241 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
242 client_id: Option<u32>,
243 #[serde(rename = "s")]
244 symbol: String,
245 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
246 price: Option<Decimal>,
247 #[serde(rename = "X")]
248 order_status: OrderStatus,
249 #[serde(rename = "T")]
250 timestamp: i64,
251 },
252 RfqCandidate {
253 #[serde(rename = "E")]
254 event_time: i64,
255 #[serde(rename = "R")]
256 rfq_id: u64,
257 #[serde(rename = "u")]
258 quote_id: u64,
259 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
260 client_id: Option<u32>,
261 #[serde(rename = "s")]
262 symbol: String,
263 #[serde(rename = "S", skip_serializing_if = "Option::is_none")]
264 side: Option<Side>,
265 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
266 quantity: Option<Decimal>,
267 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
268 quote_quantity: Option<Decimal>,
269 #[serde(rename = "p")]
270 price: Decimal,
271 #[serde(rename = "X")]
272 order_status: OrderStatus,
273 #[serde(rename = "T")]
274 timestamp: i64,
275 },
276 RfqFilled {
277 #[serde(rename = "E")]
278 event_time: i64,
279 #[serde(rename = "R")]
280 rfq_id: u64,
281 #[serde(rename = "u")]
282 quote_id: u64,
283 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
284 client_id: Option<u32>,
285 #[serde(rename = "s")]
286 symbol: String,
287 #[serde(rename = "S")]
288 side: Side,
289 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
290 quantity: Option<Decimal>,
291 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
292 quote_quantity: Option<Decimal>,
293 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
294 price: Option<Decimal>,
295 #[serde(rename = "X")]
296 order_status: OrderStatus,
297 #[serde(rename = "T")]
298 timestamp: i64,
299 #[serde(rename = "o", default)]
300 system_order_type: Option<SystemOrderType>,
301 },
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct Quote {
307 pub rfq_id: String,
309
310 pub quote_id: String,
312
313 pub client_id: Option<u32>,
315
316 pub bid_price: Decimal,
318
319 pub ask_price: Decimal,
321
322 pub status: OrderStatus,
324
325 pub created_at: i64,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct RequestForQuote {
332 pub rfq_id: String,
333 pub client_id: Option<u32>,
334 pub symbol: String,
335 pub side: Side,
336 pub price: Option<Decimal>,
337 pub quantity: Option<Decimal>,
338 pub quote_quantity: Option<Decimal>,
339 pub submission_time: i64,
340 pub expiry_time: i64,
341 pub status: OrderStatus,
342 pub execution_mode: RfqExecutionMode,
343 pub created_at: i64,
344 #[serde(default)]
345 pub system_order_type: Option<SystemOrderType>,
346}
347
348impl QuotePayload {
349 pub fn new(rfq_id: String, bid_price: Decimal, ask_price: Decimal) -> Self {
350 Self {
351 rfq_id,
352 bid_price,
353 ask_price,
354 client_id: None,
355 auto_lend: None,
356 auto_lend_redeem: None,
357 auto_borrow: None,
358 auto_borrow_repay: None,
359 }
360 }
361
362 pub fn with_client_id(mut self, client_id: u32) -> Self {
363 self.client_id = Some(client_id);
364 self
365 }
366
367 pub fn with_auto_lend(mut self, auto_lend: bool) -> Self {
368 self.auto_lend = Some(auto_lend);
369 self
370 }
371
372 pub fn with_auto_lend_redeem(mut self, auto_lend_redeem: bool) -> Self {
373 self.auto_lend_redeem = Some(auto_lend_redeem);
374 self
375 }
376
377 pub fn with_auto_borrow(mut self, auto_borrow: bool) -> Self {
378 self.auto_borrow = Some(auto_borrow);
379 self
380 }
381
382 pub fn with_auto_borrow_repay(mut self, auto_borrow_repay: bool) -> Self {
383 self.auto_borrow_repay = Some(auto_borrow_repay);
384 self
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn rfq_active_without_system_order_type() {
394 let data = r#"{"e":"rfqActive","E":1234567890,"R":123,"s":"BTC_USDC","q":"1.5","w":1234567890,"W":1234567899,"X":"New","T":1234567890}"#;
395 let update: RequestForQuoteUpdate = serde_json::from_str(data).unwrap();
396 match update {
397 RequestForQuoteUpdate::RfqActive {
398 system_order_type, ..
399 } => {
400 assert!(system_order_type.is_none());
401 }
402 _ => panic!("Expected RfqActive"),
403 }
404 }
405
406 #[test]
407 fn rfq_active_with_system_order_type() {
408 let data = r#"{"e":"rfqActive","E":1234567890,"R":123,"s":"BTC_USDC","q":"1.5","w":1234567890,"W":1234567899,"X":"New","T":1234567890,"o":"CollateralConversion"}"#;
409 let update: RequestForQuoteUpdate = serde_json::from_str(data).unwrap();
410 match update {
411 RequestForQuoteUpdate::RfqActive {
412 system_order_type, ..
413 } => {
414 assert_eq!(
415 system_order_type,
416 Some(SystemOrderType::CollateralConversion)
417 );
418 }
419 _ => panic!("Expected RfqActive"),
420 }
421 }
422}