deribit_fix/message/quotes/
quote_request.rs1use crate::error::Result as DeribitFixResult;
10use crate::message::builder::MessageBuilder;
11use crate::message::orders::{OrderSide, TimeInForce};
12use crate::model::types::MsgType;
13use chrono::{DateTime, Utc};
14use deribit_base::{impl_json_debug_pretty, impl_json_display};
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum QuoteType {
20 Indicative,
22 Tradeable,
24 RestrictedTradeable,
26 Counter,
28}
29
30impl From<QuoteType> for i32 {
31 fn from(quote_type: QuoteType) -> Self {
32 match quote_type {
33 QuoteType::Indicative => 0,
34 QuoteType::Tradeable => 1,
35 QuoteType::RestrictedTradeable => 2,
36 QuoteType::Counter => 3,
37 }
38 }
39}
40
41impl TryFrom<i32> for QuoteType {
42 type Error = String;
43
44 fn try_from(value: i32) -> Result<Self, Self::Error> {
45 match value {
46 0 => Ok(QuoteType::Indicative),
47 1 => Ok(QuoteType::Tradeable),
48 2 => Ok(QuoteType::RestrictedTradeable),
49 3 => Ok(QuoteType::Counter),
50 _ => Err(format!("Invalid QuoteType: {}", value)),
51 }
52 }
53}
54
55#[derive(Clone, PartialEq, Serialize, Deserialize)]
57pub struct QuoteRequest {
58 pub quote_req_id: String,
60 pub symbol: String,
62 pub quote_type: QuoteType,
64 pub side: OrderSide,
66 pub order_qty: f64,
68 pub valid_until_time: Option<DateTime<Utc>>,
70 pub quote_request_type: Option<i32>,
72 pub time_in_force: Option<TimeInForce>,
74 pub min_qty: Option<f64>,
76 pub settlement_type: Option<char>,
78 pub deribit_label: Option<String>,
80 pub market_segment_id: Option<String>,
82}
83
84impl QuoteRequest {
85 pub fn new(
87 quote_req_id: String,
88 symbol: String,
89 quote_type: QuoteType,
90 side: OrderSide,
91 order_qty: f64,
92 ) -> Self {
93 Self {
94 quote_req_id,
95 symbol,
96 quote_type,
97 side,
98 order_qty,
99 valid_until_time: None,
100 quote_request_type: None,
101 time_in_force: None,
102 min_qty: None,
103 settlement_type: None,
104 deribit_label: None,
105 market_segment_id: None,
106 }
107 }
108
109 pub fn tradeable(
111 quote_req_id: String,
112 symbol: String,
113 side: OrderSide,
114 order_qty: f64,
115 ) -> Self {
116 Self::new(quote_req_id, symbol, QuoteType::Tradeable, side, order_qty)
117 }
118
119 pub fn indicative(
121 quote_req_id: String,
122 symbol: String,
123 side: OrderSide,
124 order_qty: f64,
125 ) -> Self {
126 Self::new(quote_req_id, symbol, QuoteType::Indicative, side, order_qty)
127 }
128
129 pub fn with_valid_until(mut self, valid_until: DateTime<Utc>) -> Self {
131 self.valid_until_time = Some(valid_until);
132 self
133 }
134
135 pub fn with_quote_request_type(mut self, request_type: i32) -> Self {
137 self.quote_request_type = Some(request_type);
138 self
139 }
140
141 pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
143 self.time_in_force = Some(tif);
144 self
145 }
146
147 pub fn with_min_qty(mut self, min_qty: f64) -> Self {
149 self.min_qty = Some(min_qty);
150 self
151 }
152
153 pub fn with_settlement_type(mut self, settlement_type: char) -> Self {
155 self.settlement_type = Some(settlement_type);
156 self
157 }
158
159 pub fn with_label(mut self, label: String) -> Self {
161 self.deribit_label = Some(label);
162 self
163 }
164
165 pub fn with_market_segment_id(mut self, segment_id: String) -> Self {
167 self.market_segment_id = Some(segment_id);
168 self
169 }
170
171 pub fn to_fix_message(
173 &self,
174 sender_comp_id: &str,
175 target_comp_id: &str,
176 msg_seq_num: u32,
177 ) -> DeribitFixResult<String> {
178 let mut builder = MessageBuilder::new()
179 .msg_type(MsgType::QuoteRequest)
180 .sender_comp_id(sender_comp_id.to_string())
181 .target_comp_id(target_comp_id.to_string())
182 .msg_seq_num(msg_seq_num)
183 .sending_time(Utc::now());
184
185 builder = builder
187 .field(131, self.quote_req_id.clone()) .field(55, self.symbol.clone()) .field(537, i32::from(self.quote_type).to_string()) .field(54, char::from(self.side).to_string()) .field(38, self.order_qty.to_string()); if let Some(valid_until_time) = &self.valid_until_time {
195 builder = builder.field(
196 62,
197 valid_until_time.format("%Y%m%d-%H:%M:%S%.3f").to_string(),
198 );
199 }
200
201 if let Some(quote_request_type) = &self.quote_request_type {
202 builder = builder.field(303, quote_request_type.to_string());
203 }
204
205 if let Some(time_in_force) = &self.time_in_force {
206 builder = builder.field(59, char::from(*time_in_force).to_string());
207 }
208
209 if let Some(min_qty) = &self.min_qty {
210 builder = builder.field(110, min_qty.to_string());
211 }
212
213 if let Some(settlement_type) = &self.settlement_type {
214 builder = builder.field(63, settlement_type.to_string());
215 }
216
217 if let Some(deribit_label) = &self.deribit_label {
218 builder = builder.field(100010, deribit_label.clone());
219 }
220
221 if let Some(market_segment_id) = &self.market_segment_id {
222 builder = builder.field(1300, market_segment_id.clone());
223 }
224
225 Ok(builder.build()?.to_string())
226 }
227}
228
229impl_json_display!(QuoteRequest);
230impl_json_debug_pretty!(QuoteRequest);
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_quote_request_creation() {
238 let request = QuoteRequest::new(
239 "QR123".to_string(),
240 "BTC-PERPETUAL".to_string(),
241 QuoteType::Tradeable,
242 OrderSide::Buy,
243 10.0,
244 );
245
246 assert_eq!(request.quote_req_id, "QR123");
247 assert_eq!(request.symbol, "BTC-PERPETUAL");
248 assert_eq!(request.quote_type, QuoteType::Tradeable);
249 assert_eq!(request.side, OrderSide::Buy);
250 assert_eq!(request.order_qty, 10.0);
251 }
252
253 #[test]
254 fn test_quote_request_tradeable() {
255 let request = QuoteRequest::tradeable(
256 "QR456".to_string(),
257 "ETH-PERPETUAL".to_string(),
258 OrderSide::Sell,
259 5.0,
260 );
261
262 assert_eq!(request.quote_type, QuoteType::Tradeable);
263 assert_eq!(request.side, OrderSide::Sell);
264 assert_eq!(request.order_qty, 5.0);
265 }
266
267 #[test]
268 fn test_quote_request_indicative() {
269 let request = QuoteRequest::indicative(
270 "QR789".to_string(),
271 "BTC-PERPETUAL".to_string(),
272 OrderSide::Buy,
273 15.0,
274 );
275
276 assert_eq!(request.quote_type, QuoteType::Indicative);
277 assert_eq!(request.order_qty, 15.0);
278 }
279
280 #[test]
281 fn test_quote_request_with_options() {
282 let valid_until = Utc::now() + chrono::Duration::hours(1);
283 let request = QuoteRequest::new(
284 "QR999".to_string(),
285 "ETH-PERPETUAL".to_string(),
286 QuoteType::RestrictedTradeable,
287 OrderSide::Buy,
288 20.0,
289 )
290 .with_valid_until(valid_until)
291 .with_quote_request_type(1)
292 .with_time_in_force(TimeInForce::GoodTillCancelled)
293 .with_min_qty(5.0)
294 .with_settlement_type('0')
295 .with_label("test-quote".to_string())
296 .with_market_segment_id("DERIBIT".to_string());
297
298 assert_eq!(request.valid_until_time, Some(valid_until));
299 assert_eq!(request.quote_request_type, Some(1));
300 assert_eq!(request.time_in_force, Some(TimeInForce::GoodTillCancelled));
301 assert_eq!(request.min_qty, Some(5.0));
302 assert_eq!(request.settlement_type, Some('0'));
303 assert_eq!(request.deribit_label, Some("test-quote".to_string()));
304 assert_eq!(request.market_segment_id, Some("DERIBIT".to_string()));
305 }
306
307 #[test]
308 fn test_quote_request_to_fix_message() {
309 let request = QuoteRequest::tradeable(
310 "QR123".to_string(),
311 "BTC-PERPETUAL".to_string(),
312 OrderSide::Buy,
313 10.0,
314 )
315 .with_label("test-label".to_string());
316
317 let fix_message = request.to_fix_message("SENDER", "TARGET", 1).unwrap();
318
319 assert!(fix_message.contains("35=R")); assert!(fix_message.contains("131=QR123")); assert!(fix_message.contains("55=BTC-PERPETUAL")); assert!(fix_message.contains("537=1")); assert!(fix_message.contains("54=1")); assert!(fix_message.contains("38=10")); assert!(fix_message.contains("100010=test-label")); }
328
329 #[test]
330 fn test_quote_type_conversions() {
331 assert_eq!(i32::from(QuoteType::Indicative), 0);
332 assert_eq!(i32::from(QuoteType::Tradeable), 1);
333 assert_eq!(i32::from(QuoteType::RestrictedTradeable), 2);
334 assert_eq!(i32::from(QuoteType::Counter), 3);
335
336 assert_eq!(QuoteType::try_from(0).unwrap(), QuoteType::Indicative);
337 assert_eq!(QuoteType::try_from(1).unwrap(), QuoteType::Tradeable);
338 assert_eq!(
339 QuoteType::try_from(2).unwrap(),
340 QuoteType::RestrictedTradeable
341 );
342 assert_eq!(QuoteType::try_from(3).unwrap(), QuoteType::Counter);
343
344 assert!(QuoteType::try_from(99).is_err());
345 }
346}