1use crate::model::types::Direction;
9use serde::{Deserialize, Serialize};
10use serde_with::skip_serializing_none;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
14#[serde(rename_all = "snake_case")]
15pub enum BlockRfqState {
16 #[default]
18 Open,
19 Filled,
21 Traded,
23 Cancelled,
25 Expired,
27 Closed,
29 Created,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
35#[serde(rename_all = "snake_case")]
36pub enum BlockRfqRole {
37 #[default]
39 Taker,
40 Maker,
42 Any,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
48#[serde(rename_all = "snake_case")]
49pub enum QuoteState {
50 #[default]
52 Open,
53 Filled,
55 Cancelled,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
61#[serde(rename_all = "snake_case")]
62pub enum ExecutionInstruction {
63 AllOrNone,
65 #[default]
67 AnyPartOf,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
72#[serde(rename_all = "snake_case")]
73pub enum BlockRfqTimeInForce {
74 #[default]
76 FillOrKill,
77 GoodTilCancelled,
79}
80
81#[skip_serializing_none]
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct BlockRfqLeg {
85 pub instrument_name: String,
87 pub direction: Direction,
89 #[serde(default)]
91 pub ratio: Option<f64>,
92 #[serde(default)]
94 pub amount: Option<f64>,
95 #[serde(default)]
97 pub price: Option<f64>,
98}
99
100#[skip_serializing_none]
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct BlockRfqHedge {
104 pub instrument_name: String,
106 pub direction: Direction,
108 pub price: f64,
110 pub amount: f64,
112}
113
114#[skip_serializing_none]
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117pub struct BlockRfqBidAsk {
118 #[serde(default)]
120 pub maker: Option<String>,
121 pub price: f64,
123 #[serde(default)]
125 pub last_update_timestamp: Option<i64>,
126 #[serde(default)]
128 pub execution_instruction: Option<ExecutionInstruction>,
129 #[serde(default)]
131 pub amount: Option<f64>,
132}
133
134#[skip_serializing_none]
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct BlockRfqTradeAllocation {
138 #[serde(default)]
140 pub user_id: Option<i64>,
141 #[serde(default)]
143 pub client_info: Option<BlockRfqClientInfo>,
144 pub amount: f64,
146}
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
150pub struct BlockRfqClientInfo {
151 pub client_id: String,
153 #[serde(default)]
155 pub user_id: Option<String>,
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct IndexPrices {
161 #[serde(default)]
163 pub btc_usd: Option<f64>,
164 #[serde(default)]
166 pub btc_usdc: Option<f64>,
167 #[serde(default)]
169 pub eth_usd: Option<f64>,
170 #[serde(default)]
172 pub eth_usdc: Option<f64>,
173}
174
175#[skip_serializing_none]
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct BlockRfq {
179 pub block_rfq_id: i64,
181 pub state: BlockRfqState,
183 pub role: BlockRfqRole,
185 pub amount: f64,
187 #[serde(default)]
189 pub min_trade_amount: Option<f64>,
190 #[serde(default)]
192 pub combo_id: Option<String>,
193 pub legs: Vec<BlockRfqLeg>,
195 #[serde(default)]
197 pub hedge: Option<BlockRfqHedge>,
198 pub creation_timestamp: i64,
200 pub expiration_timestamp: i64,
202 #[serde(default)]
204 pub label: Option<String>,
205 #[serde(default)]
207 pub makers: Option<Vec<String>>,
208 #[serde(default)]
210 pub taker_rating: Option<String>,
211 #[serde(default)]
213 pub bids: Option<Vec<BlockRfqBidAsk>>,
214 #[serde(default)]
216 pub asks: Option<Vec<BlockRfqBidAsk>>,
217 #[serde(default)]
219 pub mark_price: Option<f64>,
220 #[serde(default)]
222 pub trades: Option<Vec<BlockRfqTradeInfo>>,
223}
224
225impl BlockRfq {
226 #[must_use]
228 pub fn is_open(&self) -> bool {
229 self.state == BlockRfqState::Open
230 }
231
232 #[must_use]
234 pub fn is_filled(&self) -> bool {
235 self.state == BlockRfqState::Filled
236 }
237
238 #[must_use]
240 pub fn is_cancelled(&self) -> bool {
241 self.state == BlockRfqState::Cancelled
242 }
243
244 #[must_use]
246 pub fn is_taker(&self) -> bool {
247 self.role == BlockRfqRole::Taker
248 }
249
250 #[must_use]
252 pub fn is_maker(&self) -> bool {
253 self.role == BlockRfqRole::Maker
254 }
255}
256
257#[skip_serializing_none]
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct BlockRfqTradeInfo {
261 pub price: f64,
263 pub amount: f64,
265 pub direction: Direction,
267 #[serde(default)]
269 pub hedge_amount: Option<f64>,
270}
271
272#[skip_serializing_none]
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct BlockRfqQuote {
276 pub block_rfq_quote_id: i64,
278 pub block_rfq_id: i64,
280 pub quote_state: QuoteState,
282 pub price: f64,
284 pub amount: f64,
286 pub direction: Direction,
288 #[serde(default)]
290 pub filled_amount: Option<f64>,
291 pub legs: Vec<BlockRfqLeg>,
293 #[serde(default)]
295 pub hedge: Option<BlockRfqHedge>,
296 #[serde(default)]
298 pub execution_instruction: Option<ExecutionInstruction>,
299 pub creation_timestamp: i64,
301 pub last_update_timestamp: i64,
303 #[serde(default)]
305 pub replaced: Option<bool>,
306 #[serde(default)]
308 pub label: Option<String>,
309 #[serde(default)]
311 pub app_name: Option<String>,
312 #[serde(default)]
314 pub cancel_reason: Option<String>,
315}
316
317impl BlockRfqQuote {
318 #[must_use]
320 pub fn is_open(&self) -> bool {
321 self.quote_state == QuoteState::Open
322 }
323
324 #[must_use]
326 pub fn is_filled(&self) -> bool {
327 self.quote_state == QuoteState::Filled
328 }
329
330 #[must_use]
332 pub fn is_cancelled(&self) -> bool {
333 self.quote_state == QuoteState::Cancelled
334 }
335}
336
337#[skip_serializing_none]
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct BlockRfqPublicTrade {
341 pub id: i64,
343 pub timestamp: i64,
345 #[serde(default)]
347 pub combo_id: Option<String>,
348 pub legs: Vec<BlockRfqLeg>,
350 pub amount: f64,
352 pub direction: Direction,
354 #[serde(default)]
356 pub mark_price: Option<f64>,
357 #[serde(default)]
359 pub trades: Option<Vec<BlockRfqTradeInfo>>,
360 #[serde(default)]
362 pub hedge: Option<BlockRfqHedge>,
363 #[serde(default)]
365 pub index_prices: Option<IndexPrices>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct BlockRfqTradesResponse {
371 #[serde(default)]
373 pub continuation: Option<String>,
374 pub block_rfqs: Vec<BlockRfqPublicTrade>,
376}
377
378impl BlockRfqTradesResponse {
379 #[must_use]
381 pub fn is_empty(&self) -> bool {
382 self.block_rfqs.is_empty()
383 }
384
385 #[must_use]
387 pub fn len(&self) -> usize {
388 self.block_rfqs.len()
389 }
390
391 #[must_use]
393 pub fn has_more(&self) -> bool {
394 self.continuation.is_some()
395 }
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct BlockRfqsResponse {
401 #[serde(default)]
403 pub continuation: Option<String>,
404 pub block_rfqs: Vec<BlockRfq>,
406}
407
408impl BlockRfqsResponse {
409 #[must_use]
411 pub fn is_empty(&self) -> bool {
412 self.block_rfqs.is_empty()
413 }
414
415 #[must_use]
417 pub fn len(&self) -> usize {
418 self.block_rfqs.len()
419 }
420
421 #[must_use]
423 pub fn has_more(&self) -> bool {
424 self.continuation.is_some()
425 }
426}
427
428#[skip_serializing_none]
430#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct BlockRfqAcceptTrade {
432 pub trade_id: String,
434 #[serde(default)]
436 pub trade_seq: Option<i64>,
437 pub instrument_name: String,
439 pub timestamp: i64,
441 pub state: String,
443 #[serde(default)]
445 pub fee: Option<f64>,
446 #[serde(default)]
448 pub fee_currency: Option<String>,
449 pub amount: f64,
451 pub direction: Direction,
453 pub price: f64,
455 #[serde(default)]
457 pub index_price: Option<f64>,
458 #[serde(default)]
460 pub mark_price: Option<f64>,
461 #[serde(default)]
463 pub profit_loss: Option<f64>,
464 #[serde(default)]
466 pub order_id: Option<String>,
467 #[serde(default)]
469 pub order_type: Option<String>,
470 #[serde(default)]
472 pub tick_direction: Option<i32>,
473 #[serde(default)]
475 pub combo_id: Option<String>,
476 #[serde(default)]
478 pub block_rfq_id: Option<i64>,
479 #[serde(default)]
481 pub block_trade_id: Option<String>,
482 #[serde(default)]
484 pub block_trade_leg_count: Option<i32>,
485 #[serde(default)]
487 pub api: Option<bool>,
488 #[serde(default)]
490 pub contracts: Option<f64>,
491 #[serde(default)]
493 pub post_only: Option<bool>,
494 #[serde(default)]
496 pub mmp: Option<bool>,
497 #[serde(default)]
499 pub risk_reducing: Option<bool>,
500 #[serde(default)]
502 pub reduce_only: Option<bool>,
503 #[serde(default)]
505 pub self_trade: Option<bool>,
506 #[serde(default)]
508 pub liquidity: Option<String>,
509 #[serde(default)]
511 pub matching_id: Option<String>,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct BlockRfqAcceptBlockTrade {
517 pub id: String,
519 pub timestamp: i64,
521 pub trades: Vec<BlockRfqAcceptTrade>,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct AcceptBlockRfqResponse {
528 pub block_trades: Vec<BlockRfqAcceptBlockTrade>,
530}
531
532impl AcceptBlockRfqResponse {
533 #[must_use]
535 pub fn total_trades(&self) -> usize {
536 self.block_trades.iter().map(|bt| bt.trades.len()).sum()
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn test_block_rfq_state_deserialization() {
546 let json = r#""open""#;
547 let state: BlockRfqState = serde_json::from_str(json).unwrap();
548 assert_eq!(state, BlockRfqState::Open);
549
550 let json = r#""cancelled""#;
551 let state: BlockRfqState = serde_json::from_str(json).unwrap();
552 assert_eq!(state, BlockRfqState::Cancelled);
553 }
554
555 #[test]
556 fn test_block_rfq_role_deserialization() {
557 let json = r#""taker""#;
558 let role: BlockRfqRole = serde_json::from_str(json).unwrap();
559 assert_eq!(role, BlockRfqRole::Taker);
560
561 let json = r#""maker""#;
562 let role: BlockRfqRole = serde_json::from_str(json).unwrap();
563 assert_eq!(role, BlockRfqRole::Maker);
564 }
565
566 #[test]
567 fn test_block_rfq_leg_deserialization() {
568 let json = r#"{
569 "instrument_name": "BTC-PERPETUAL",
570 "direction": "buy",
571 "ratio": 1,
572 "price": 70000
573 }"#;
574
575 let leg: BlockRfqLeg = serde_json::from_str(json).unwrap();
576 assert_eq!(leg.instrument_name, "BTC-PERPETUAL");
577 assert!(matches!(leg.direction, Direction::Buy));
578 assert_eq!(leg.ratio, Some(1.0));
579 assert_eq!(leg.price, Some(70000.0));
580 }
581
582 #[test]
583 fn test_block_rfq_deserialization() {
584 let json = r#"{
585 "block_rfq_id": 507,
586 "state": "created",
587 "role": "taker",
588 "amount": 20000,
589 "combo_id": "BTC-15NOV24",
590 "legs": [
591 {
592 "direction": "sell",
593 "instrument_name": "BTC-15NOV24",
594 "ratio": 1
595 }
596 ],
597 "creation_timestamp": 1731062187555,
598 "expiration_timestamp": 1731062487555,
599 "bids": [],
600 "asks": [],
601 "makers": ["MAKER1"]
602 }"#;
603
604 let rfq: BlockRfq = serde_json::from_str(json).unwrap();
605 assert_eq!(rfq.block_rfq_id, 507);
606 assert_eq!(rfq.state, BlockRfqState::Created);
607 assert!(rfq.is_taker());
608 assert_eq!(rfq.amount, 20000.0);
609 assert_eq!(rfq.legs.len(), 1);
610 }
611
612 #[test]
613 fn test_block_rfq_quote_deserialization() {
614 let json = r#"{
615 "block_rfq_quote_id": 8,
616 "block_rfq_id": 3,
617 "quote_state": "open",
618 "price": 69600,
619 "amount": 10000,
620 "direction": "buy",
621 "legs": [
622 {
623 "direction": "buy",
624 "price": 69600,
625 "instrument_name": "BTC-15NOV24",
626 "ratio": 1
627 }
628 ],
629 "creation_timestamp": 1731076586371,
630 "last_update_timestamp": 1731076586371,
631 "replaced": false,
632 "filled_amount": 0,
633 "execution_instruction": "all_or_none"
634 }"#;
635
636 let quote: BlockRfqQuote = serde_json::from_str(json).unwrap();
637 assert_eq!(quote.block_rfq_quote_id, 8);
638 assert!(quote.is_open());
639 assert_eq!(
640 quote.execution_instruction,
641 Some(ExecutionInstruction::AllOrNone)
642 );
643 }
644
645 #[test]
646 fn test_block_rfq_trades_response_deserialization() {
647 let json = r#"{
648 "continuation": "1739739009234:6570",
649 "block_rfqs": [
650 {
651 "id": 6611,
652 "timestamp": 1739803305362,
653 "combo_id": "BTC-CS-28FEB25-100000_106000",
654 "legs": [
655 {
656 "price": 0.1,
657 "direction": "buy",
658 "instrument_name": "BTC-28FEB25-100000-C",
659 "ratio": 1
660 }
661 ],
662 "amount": 12.5,
663 "direction": "sell",
664 "mark_price": 0.010356754
665 }
666 ]
667 }"#;
668
669 let response: BlockRfqTradesResponse = serde_json::from_str(json).unwrap();
670 assert!(response.has_more());
671 assert_eq!(response.len(), 1);
672 assert_eq!(response.block_rfqs[0].id, 6611);
673 }
674}