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 QuoteAccepted {
191 #[serde(rename = "E")]
192 event_time: i64,
193 #[serde(rename = "R")]
194 rfq_id: u64,
195 #[serde(rename = "u")]
196 quote_id: u64,
197 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
198 client_id: Option<u32>,
199 #[serde(rename = "s")]
200 symbol: String,
201 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
202 price: Option<Decimal>,
203 #[serde(rename = "X")]
204 order_status: OrderStatus,
205 #[serde(rename = "T")]
206 timestamp: i64,
207 },
208 QuoteCancelled {
209 #[serde(rename = "E")]
210 event_time: i64,
211 #[serde(rename = "R")]
212 rfq_id: u64,
213 #[serde(rename = "u")]
214 quote_id: u64,
215 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
216 client_id: Option<u32>,
217 #[serde(rename = "s")]
218 symbol: String,
219 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
220 price: Option<Decimal>,
221 #[serde(rename = "X")]
222 order_status: OrderStatus,
223 #[serde(rename = "T")]
224 timestamp: i64,
225 },
226 RfqCandidate {
227 #[serde(rename = "E")]
228 event_time: i64,
229 #[serde(rename = "R")]
230 rfq_id: u64,
231 #[serde(rename = "u")]
232 quote_id: u64,
233 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
234 client_id: Option<u32>,
235 #[serde(rename = "s")]
236 symbol: String,
237 #[serde(rename = "S", skip_serializing_if = "Option::is_none")]
238 side: Option<Side>,
239 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
240 quantity: Option<Decimal>,
241 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
242 quote_quantity: Option<Decimal>,
243 #[serde(rename = "p")]
244 price: Decimal,
245 #[serde(rename = "X")]
246 order_status: OrderStatus,
247 #[serde(rename = "T")]
248 timestamp: i64,
249 },
250 RfqFilled {
251 #[serde(rename = "E")]
252 event_time: i64,
253 #[serde(rename = "R")]
254 rfq_id: u64,
255 #[serde(rename = "u")]
256 quote_id: u64,
257 #[serde(rename = "C", skip_serializing_if = "Option::is_none")]
258 client_id: Option<u32>,
259 #[serde(rename = "s")]
260 symbol: String,
261 #[serde(rename = "S")]
262 side: Side,
263 #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
264 quantity: Option<Decimal>,
265 #[serde(rename = "Q", skip_serializing_if = "Option::is_none")]
266 quote_quantity: Option<Decimal>,
267 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
268 price: Option<Decimal>,
269 #[serde(rename = "X")]
270 order_status: OrderStatus,
271 #[serde(rename = "T")]
272 timestamp: i64,
273 #[serde(rename = "o", default)]
274 system_order_type: Option<SystemOrderType>,
275 },
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct Quote {
281 pub rfq_id: String,
283
284 pub quote_id: String,
286
287 pub client_id: Option<u32>,
289
290 pub bid_price: Decimal,
292
293 pub ask_price: Decimal,
295
296 pub status: OrderStatus,
298
299 pub created_at: i64,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct RequestForQuote {
306 pub rfq_id: String,
307 pub client_id: Option<u32>,
308 pub symbol: String,
309 pub side: Side,
310 pub price: Option<Decimal>,
311 pub quantity: Option<Decimal>,
312 pub quote_quantity: Option<Decimal>,
313 pub submission_time: i64,
314 pub expiry_time: i64,
315 pub status: OrderStatus,
316 pub execution_mode: RfqExecutionMode,
317 pub created_at: i64,
318 #[serde(default)]
319 pub system_order_type: Option<SystemOrderType>,
320}
321
322impl QuotePayload {
323 pub fn new(rfq_id: String, bid_price: Decimal, ask_price: Decimal) -> Self {
324 Self {
325 rfq_id,
326 bid_price,
327 ask_price,
328 client_id: None,
329 auto_lend: None,
330 auto_lend_redeem: None,
331 auto_borrow: None,
332 auto_borrow_repay: None,
333 }
334 }
335
336 pub fn with_client_id(mut self, client_id: u32) -> Self {
337 self.client_id = Some(client_id);
338 self
339 }
340
341 pub fn with_auto_lend(mut self, auto_lend: bool) -> Self {
342 self.auto_lend = Some(auto_lend);
343 self
344 }
345
346 pub fn with_auto_lend_redeem(mut self, auto_lend_redeem: bool) -> Self {
347 self.auto_lend_redeem = Some(auto_lend_redeem);
348 self
349 }
350
351 pub fn with_auto_borrow(mut self, auto_borrow: bool) -> Self {
352 self.auto_borrow = Some(auto_borrow);
353 self
354 }
355
356 pub fn with_auto_borrow_repay(mut self, auto_borrow_repay: bool) -> Self {
357 self.auto_borrow_repay = Some(auto_borrow_repay);
358 self
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn rfq_active_without_system_order_type() {
368 let data = r#"{"e":"rfqActive","E":1234567890,"R":123,"s":"BTC_USDC","q":"1.5","w":1234567890,"W":1234567899,"X":"New","T":1234567890}"#;
369 let update: RequestForQuoteUpdate = serde_json::from_str(data).unwrap();
370 match update {
371 RequestForQuoteUpdate::RfqActive {
372 system_order_type, ..
373 } => {
374 assert!(system_order_type.is_none());
375 }
376 _ => panic!("Expected RfqActive"),
377 }
378 }
379
380 #[test]
381 fn rfq_active_with_system_order_type() {
382 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"}"#;
383 let update: RequestForQuoteUpdate = serde_json::from_str(data).unwrap();
384 match update {
385 RequestForQuoteUpdate::RfqActive {
386 system_order_type, ..
387 } => {
388 assert_eq!(
389 system_order_type,
390 Some(SystemOrderType::CollateralConversion)
391 );
392 }
393 _ => panic!("Expected RfqActive"),
394 }
395 }
396}