kalshi_rust/communications/mod.rs
1use super::Kalshi;
2use crate::kalshi_error::*;
3use crate::Side;
4use serde::{Deserialize, Serialize};
5
6impl Kalshi {
7 // ========== Task 2.3: get_communications_id() ==========
8
9 /// Retrieves the user's public communications ID.
10 ///
11 /// This ID is used to identify the user in communications with other traders.
12 ///
13 /// # Returns
14 ///
15 /// - `Ok(String)`: The user's communications ID on successful retrieval.
16 /// - `Err(KalshiError)`: An error if there is an issue with the request.
17 ///
18 /// # Example
19 ///
20 /// ```
21 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
22 /// let comm_id = kalshi_instance.get_communications_id().await.unwrap();
23 /// println!("Communications ID: {}", comm_id);
24 /// ```
25 ///
26 pub async fn get_communications_id(&self) -> Result<String, KalshiError> {
27 let path = "/communications/id";
28 let res: CommunicationsIdResponse = self.signed_get(path).await?;
29 Ok(res.communications_id)
30 }
31
32 /// Retrieves a communication by ID.
33 ///
34 /// This method fetches a specific communication message or thread.
35 ///
36 /// # Arguments
37 ///
38 /// * `comm_id` - The communication ID to retrieve.
39 ///
40 /// # Returns
41 ///
42 /// - `Ok(Communication)`: The communication details on successful retrieval.
43 /// - `Err(KalshiError)`: An error if there is an issue with the request.
44 ///
45 /// # Example
46 ///
47 /// ```
48 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
49 /// let comm = kalshi_instance.get_communication("comm-123").await.unwrap();
50 /// ```
51 ///
52 pub async fn get_communication(&self, comm_id: &str) -> Result<Communication, KalshiError> {
53 let path = format!("/communications/{}", comm_id);
54 self.signed_get(&path).await
55 }
56
57 // ========== Task 3.3: get_rfqs() with pagination ==========
58
59 /// Retrieves RFQs (Requests for Quote) with optional filtering and pagination.
60 ///
61 /// This method lists RFQs that the user has created or received, with support
62 /// for filtering by various parameters and pagination.
63 ///
64 /// # Arguments
65 ///
66 /// * `cursor` - Pagination cursor from previous request.
67 /// * `event_ticker` - Filter by event ticker.
68 /// * `market_ticker` - Filter by market ticker.
69 /// * `limit` - Number of results per page (default 100, max 100).
70 /// * `status` - Filter by RFQ status.
71 /// * `creator_user_id` - Filter by creator user ID.
72 ///
73 /// # Returns
74 ///
75 /// - `Ok((Option<String>, Vec<Rfq>))`: A tuple containing the next cursor (if any) and a vector of RFQs.
76 /// - `Err(KalshiError)`: An error if there is an issue with the request.
77 ///
78 /// # Example
79 ///
80 /// ```
81 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
82 /// let (cursor, rfqs) = kalshi_instance.get_rfqs(
83 /// None, // cursor
84 /// None, // event_ticker
85 /// None, // market_ticker
86 /// Some(10), // limit
87 /// None, // status
88 /// None, // creator_user_id
89 /// ).await.unwrap();
90 /// ```
91 ///
92 pub async fn get_rfqs(
93 &self,
94 cursor: Option<String>,
95 event_ticker: Option<String>,
96 market_ticker: Option<String>,
97 limit: Option<i32>,
98 status: Option<String>,
99 creator_user_id: Option<String>,
100 ) -> Result<(Option<String>, Vec<Rfq>), KalshiError> {
101 let mut params: Vec<(&str, String)> = Vec::with_capacity(6);
102 add_param!(params, "cursor", cursor);
103 add_param!(params, "event_ticker", event_ticker);
104 add_param!(params, "market_ticker", market_ticker);
105 add_param!(params, "limit", limit);
106 add_param!(params, "status", status);
107 add_param!(params, "creator_user_id", creator_user_id);
108
109 let path = if params.is_empty() {
110 "/communications/rfqs".to_string()
111 } else {
112 let qs = params
113 .iter()
114 .map(|(k, v)| format!("{}={}", k, v))
115 .collect::<Vec<_>>()
116 .join("&");
117 format!("/communications/rfqs?{}", qs)
118 };
119
120 let res: RfqsResponse = self.signed_get(&path).await?;
121 Ok((res.cursor, res.rfqs))
122 }
123
124 // ========== Task 3.1: create_rfq() ==========
125
126 /// Creates a new RFQ (Request for Quote).
127 ///
128 /// This method submits a new request for quote to market makers or other traders.
129 ///
130 /// # Arguments
131 ///
132 /// * `market_ticker` - The market ticker to request a quote for.
133 /// * `rest_remainder` - Whether to rest the remainder after execution.
134 /// * `contracts` - Number of contracts for the RFQ (optional).
135 /// * `target_cost_centi_cents` - Target cost in centi-cents (optional).
136 /// * `replace_existing` - Whether to delete existing RFQs (default: false).
137 /// * `subtrader_id` - Subtrader ID (FCM members only).
138 ///
139 /// # Returns
140 ///
141 /// - `Ok(CreateRfqResponse)`: The created RFQ response containing the new RFQ ID.
142 /// - `Err(KalshiError)`: An error if there is an issue with the request.
143 ///
144 /// # Example
145 ///
146 /// ```
147 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
148 /// let response = kalshi_instance.create_rfq(
149 /// "MARKET-TICKER",
150 /// false,
151 /// Some(100),
152 /// None,
153 /// None,
154 /// None,
155 /// ).await.unwrap();
156 /// println!("Created RFQ with ID: {}", response.id);
157 /// ```
158 ///
159 pub async fn create_rfq(
160 &self,
161 market_ticker: &str,
162 rest_remainder: bool,
163 contracts: Option<i32>,
164 target_cost_centi_cents: Option<i64>,
165 replace_existing: Option<bool>,
166 subtrader_id: Option<String>,
167 ) -> Result<CreateRfqResponse, KalshiError> {
168 let path = "/communications/rfqs";
169 let body = CreateRfqRequest {
170 market_ticker: market_ticker.to_string(),
171 rest_remainder,
172 contracts,
173 target_cost_centi_cents,
174 replace_existing,
175 subtrader_id,
176 };
177 self.signed_post(path, &body).await
178 }
179
180 /// Retrieves a specific RFQ by ID.
181 ///
182 /// This method fetches detailed information about a specific RFQ.
183 ///
184 /// # Arguments
185 ///
186 /// * `rfq_id` - The RFQ ID to retrieve.
187 ///
188 /// # Returns
189 ///
190 /// - `Ok(Rfq)`: The RFQ details on successful retrieval.
191 /// - `Err(KalshiError)`: An error if there is an issue with the request.
192 ///
193 /// # Example
194 ///
195 /// ```
196 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
197 /// let rfq = kalshi_instance.get_rfq("rfq-123").await.unwrap();
198 /// ```
199 ///
200 pub async fn get_rfq(&self, rfq_id: &str) -> Result<Rfq, KalshiError> {
201 let path = format!("/communications/rfqs/{}", rfq_id);
202 let res: RfqResponse = self.signed_get(&path).await?;
203 Ok(res.rfq)
204 }
205
206 /// Deletes an RFQ.
207 ///
208 /// This method cancels and removes an RFQ. Only the creator can delete an RFQ.
209 ///
210 /// # Arguments
211 ///
212 /// * `rfq_id` - The RFQ ID to delete.
213 ///
214 /// # Returns
215 ///
216 /// - `Ok(())`: Success confirmation.
217 /// - `Err(KalshiError)`: An error if there is an issue with the request.
218 ///
219 /// # Example
220 ///
221 /// ```
222 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
223 /// kalshi_instance.delete_rfq("rfq-123").await.unwrap();
224 /// ```
225 ///
226 pub async fn delete_rfq(&self, rfq_id: &str) -> Result<(), KalshiError> {
227 let path = format!("/communications/rfqs/{}", rfq_id);
228 let _res: DeleteRfqResponse = self.signed_delete(&path).await?;
229 Ok(())
230 }
231
232 // ========== Task 3.3: get_quotes() with pagination ==========
233
234 /// Retrieves quotes with optional filtering and pagination.
235 ///
236 /// This method lists quotes that the user has created or received, with support
237 /// for filtering by various parameters and pagination.
238 ///
239 /// # Arguments
240 ///
241 /// * `cursor` - Pagination cursor from previous request.
242 /// * `event_ticker` - Filter by event ticker.
243 /// * `market_ticker` - Filter by market ticker.
244 /// * `limit` - Number of results per page (default 500, max 500).
245 /// * `status` - Filter by quote status.
246 /// * `quote_creator_user_id` - Filter by quote creator user ID.
247 /// * `rfq_creator_user_id` - Filter by RFQ creator user ID.
248 /// * `rfq_id` - Filter by RFQ ID.
249 ///
250 /// # Returns
251 ///
252 /// - `Ok((Option<String>, Vec<Quote>))`: A tuple containing the next cursor (if any) and a vector of quotes.
253 /// - `Err(KalshiError)`: An error if there is an issue with the request.
254 ///
255 /// # Example
256 ///
257 /// ```
258 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
259 /// let (cursor, quotes) = kalshi_instance.get_quotes(
260 /// None, // cursor
261 /// None, // event_ticker
262 /// None, // market_ticker
263 /// Some(10), // limit
264 /// None, // status
265 /// None, // quote_creator_user_id
266 /// None, // rfq_creator_user_id
267 /// None, // rfq_id
268 /// ).await.unwrap();
269 /// ```
270 ///
271 #[allow(clippy::too_many_arguments)]
272 pub async fn get_quotes(
273 &self,
274 cursor: Option<String>,
275 event_ticker: Option<String>,
276 market_ticker: Option<String>,
277 limit: Option<i32>,
278 status: Option<String>,
279 quote_creator_user_id: Option<String>,
280 rfq_creator_user_id: Option<String>,
281 rfq_id: Option<String>,
282 ) -> Result<(Option<String>, Vec<Quote>), KalshiError> {
283 let mut params: Vec<(&str, String)> = Vec::with_capacity(8);
284 add_param!(params, "cursor", cursor);
285 add_param!(params, "event_ticker", event_ticker);
286 add_param!(params, "market_ticker", market_ticker);
287 add_param!(params, "limit", limit);
288 add_param!(params, "status", status);
289 add_param!(params, "quote_creator_user_id", quote_creator_user_id);
290 add_param!(params, "rfq_creator_user_id", rfq_creator_user_id);
291 add_param!(params, "rfq_id", rfq_id);
292
293 let path = if params.is_empty() {
294 "/communications/quotes".to_string()
295 } else {
296 let qs = params
297 .iter()
298 .map(|(k, v)| format!("{}={}", k, v))
299 .collect::<Vec<_>>()
300 .join("&");
301 format!("/communications/quotes?{}", qs)
302 };
303
304 let res: QuotesResponse = self.signed_get(&path).await?;
305 Ok((res.cursor, res.quotes))
306 }
307
308 // ========== Task 3.2: create_quote() ==========
309
310 /// Creates a new quote in response to an RFQ.
311 ///
312 /// This method submits a quote offer to an RFQ requestor.
313 ///
314 /// # Arguments
315 ///
316 /// * `rfq_id` - The RFQ ID this quote responds to.
317 /// * `yes_bid` - Bid price for YES contracts in dollars ("0.5600" format).
318 /// * `no_bid` - Bid price for NO contracts in dollars ("0.5600" format).
319 /// * `rest_remainder` - Whether to rest the remainder after execution.
320 ///
321 /// # Returns
322 ///
323 /// - `Ok(CreateQuoteResponse)`: The created quote response containing the new quote ID.
324 /// - `Err(KalshiError)`: An error if there is an issue with the request.
325 ///
326 /// # Example
327 ///
328 /// ```
329 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
330 /// let response = kalshi_instance.create_quote(
331 /// "rfq-123",
332 /// "0.5000",
333 /// "0.5000",
334 /// false,
335 /// ).await.unwrap();
336 /// println!("Created quote with ID: {}", response.id);
337 /// ```
338 ///
339 pub async fn create_quote(
340 &self,
341 rfq_id: &str,
342 yes_bid: &str,
343 no_bid: &str,
344 rest_remainder: bool,
345 ) -> Result<CreateQuoteResponse, KalshiError> {
346 let path = "/communications/quotes";
347 let body = CreateQuoteRequest {
348 rfq_id: rfq_id.to_string(),
349 yes_bid: yes_bid.to_string(),
350 no_bid: no_bid.to_string(),
351 rest_remainder,
352 };
353 self.signed_post(path, &body).await
354 }
355
356 /// Retrieves a specific quote by ID.
357 ///
358 /// This method fetches detailed information about a specific quote.
359 ///
360 /// # Arguments
361 ///
362 /// * `quote_id` - The quote ID to retrieve.
363 ///
364 /// # Returns
365 ///
366 /// - `Ok(Quote)`: The quote details on successful retrieval.
367 /// - `Err(KalshiError)`: An error if there is an issue with the request.
368 ///
369 /// # Example
370 ///
371 /// ```
372 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
373 /// let quote = kalshi_instance.get_quote("quote-123").await.unwrap();
374 /// ```
375 ///
376 pub async fn get_quote(&self, quote_id: &str) -> Result<Quote, KalshiError> {
377 let path = format!("/communications/quotes/{}", quote_id);
378 let res: QuoteResponse = self.signed_get(&path).await?;
379 Ok(res.quote)
380 }
381
382 /// Deletes a quote.
383 ///
384 /// This method cancels and removes a quote. Only the creator can delete a quote.
385 ///
386 /// # Arguments
387 ///
388 /// * `quote_id` - The quote ID to delete.
389 ///
390 /// # Returns
391 ///
392 /// - `Ok(())`: Success confirmation.
393 /// - `Err(KalshiError)`: An error if there is an issue with the request.
394 ///
395 /// # Example
396 ///
397 /// ```
398 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
399 /// kalshi_instance.delete_quote("quote-123").await.unwrap();
400 /// ```
401 ///
402 pub async fn delete_quote(&self, quote_id: &str) -> Result<(), KalshiError> {
403 let path = format!("/communications/quotes/{}", quote_id);
404 let _res: DeleteQuoteResponse = self.signed_delete(&path).await?;
405 Ok(())
406 }
407
408 // ========== Task 3.4: accept_quote() with accepted_side ==========
409
410 /// Accepts a quote.
411 ///
412 /// This method accepts a quote offer, which will execute the trade.
413 ///
414 /// # Arguments
415 ///
416 /// * `quote_id` - The quote ID to accept.
417 /// * `accepted_side` - Which side to accept (Yes or No).
418 ///
419 /// # Returns
420 ///
421 /// - `Ok(())`: Success confirmation (API returns 204 No Content).
422 /// - `Err(KalshiError)`: An error if there is an issue with the request.
423 ///
424 /// # Example
425 ///
426 /// ```
427 /// use kalshi::Side;
428 ///
429 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
430 /// kalshi_instance.accept_quote("quote-123", Side::Yes).await.unwrap();
431 /// ```
432 ///
433 pub async fn accept_quote(
434 &self,
435 quote_id: &str,
436 accepted_side: Side,
437 ) -> Result<(), KalshiError> {
438 let path = format!("/communications/quotes/{}/accept", quote_id);
439 let body = AcceptQuoteRequest { accepted_side };
440 let _: serde_json::Value = self.signed_put(&path, Some(&body)).await?;
441 Ok(())
442 }
443
444 /// Confirms a quote.
445 ///
446 /// This method confirms a quote after acceptance, finalizing the transaction.
447 ///
448 /// # Arguments
449 ///
450 /// * `quote_id` - The quote ID to confirm.
451 ///
452 /// # Returns
453 ///
454 /// - `Ok(QuoteConfirmed)`: The confirmed quote details on successful confirmation.
455 /// - `Err(KalshiError)`: An error if there is an issue with the request.
456 ///
457 /// # Example
458 ///
459 /// ```
460 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
461 /// let result = kalshi_instance.confirm_quote("quote-123").await.unwrap();
462 /// ```
463 ///
464 pub async fn confirm_quote(&self, quote_id: &str) -> Result<QuoteConfirmed, KalshiError> {
465 let path = format!("/communications/quotes/{}/confirm", quote_id);
466 self.signed_put(&path, None::<&()>).await
467 }
468}
469
470// -------- Request bodies --------
471
472#[derive(Debug, Deserialize)]
473struct CommunicationsIdResponse {
474 communications_id: String,
475}
476
477#[derive(Debug, Serialize)]
478struct CreateRfqRequest {
479 market_ticker: String,
480 rest_remainder: bool,
481 #[serde(skip_serializing_if = "Option::is_none")]
482 contracts: Option<i32>,
483 #[serde(skip_serializing_if = "Option::is_none")]
484 target_cost_centi_cents: Option<i64>,
485 #[serde(skip_serializing_if = "Option::is_none")]
486 replace_existing: Option<bool>,
487 #[serde(skip_serializing_if = "Option::is_none")]
488 subtrader_id: Option<String>,
489}
490
491#[derive(Debug, Serialize)]
492struct CreateQuoteRequest {
493 rfq_id: String,
494 yes_bid: String,
495 no_bid: String,
496 rest_remainder: bool,
497}
498
499#[derive(Debug, Serialize)]
500struct AcceptQuoteRequest {
501 accepted_side: Side,
502}
503
504// -------- Response wrappers --------
505
506#[derive(Debug, Deserialize)]
507struct RfqsResponse {
508 rfqs: Vec<Rfq>,
509 cursor: Option<String>,
510}
511
512#[derive(Debug, Deserialize)]
513struct RfqResponse {
514 rfq: Rfq,
515}
516
517#[derive(Debug, Deserialize)]
518struct DeleteRfqResponse {}
519
520#[derive(Debug, Deserialize)]
521struct QuotesResponse {
522 quotes: Vec<Quote>,
523 cursor: Option<String>,
524}
525
526#[derive(Debug, Deserialize)]
527struct QuoteResponse {
528 quote: Quote,
529}
530
531#[derive(Debug, Deserialize)]
532struct DeleteQuoteResponse {}
533
534// -------- Public models --------
535
536/// Response from creating a new RFQ.
537#[derive(Debug, Deserialize, Serialize)]
538pub struct CreateRfqResponse {
539 /// The ID of the newly created RFQ.
540 pub id: String,
541}
542
543/// Response from creating a new quote.
544#[derive(Debug, Deserialize, Serialize)]
545pub struct CreateQuoteResponse {
546 /// The ID of the newly created quote.
547 pub id: String,
548}
549
550/// Represents a communication message or thread.
551#[derive(Debug, Deserialize, Serialize)]
552pub struct Communication {
553 /// The communication ID.
554 pub id: String,
555 /// The communication type.
556 #[serde(rename = "type")]
557 pub comm_type: String,
558 /// The message content.
559 pub message: Option<String>,
560 /// Timestamp when created.
561 pub created_time: String,
562 /// Additional fields.
563 #[serde(flatten)]
564 pub details: std::collections::HashMap<String, serde_json::Value>,
565}
566
567/// Represents an RFQ (Request for Quote).
568#[derive(Debug, Deserialize, Serialize)]
569pub struct Rfq {
570 /// The RFQ ID.
571 pub id: String,
572 /// The market ticker requested.
573 #[serde(alias = "ticker")]
574 pub market_ticker: Option<String>,
575 /// The desired quantity.
576 #[serde(default)]
577 pub contracts: Option<i32>,
578 /// The side of the trade ("yes" or "no").
579 pub side: Option<String>,
580 /// Optional message with the RFQ.
581 pub message: Option<String>,
582 /// The status of the RFQ.
583 pub status: Option<String>,
584 /// Timestamp when created.
585 pub created_time: Option<String>,
586 /// Timestamp when expires.
587 pub expires_time: Option<String>,
588 /// Whether to rest the remainder.
589 pub rest_remainder: Option<bool>,
590 /// Target cost in centi-cents.
591 pub target_cost_centi_cents: Option<i64>,
592 /// Creator user ID.
593 pub creator_user_id: Option<String>,
594 /// Additional fields that may be returned by the API.
595 #[serde(flatten)]
596 pub extra: std::collections::HashMap<String, serde_json::Value>,
597}
598
599/// Represents a quote offer.
600#[derive(Debug, Deserialize, Serialize)]
601pub struct Quote {
602 /// The quote ID.
603 pub id: String,
604 /// The RFQ this quote responds to.
605 pub rfq_id: Option<String>,
606 /// The quoted yes bid price in dollars.
607 pub yes_bid: Option<String>,
608 /// The quoted no bid price in dollars.
609 pub no_bid: Option<String>,
610 /// The quoted price in cents (legacy).
611 pub price: Option<i32>,
612 /// The quoted quantity (legacy).
613 pub quantity: Option<i32>,
614 /// The status of the quote.
615 pub status: Option<String>,
616 /// Timestamp when created.
617 pub created_time: Option<String>,
618 /// Timestamp when expires.
619 pub expires_time: Option<String>,
620 /// Whether to rest the remainder.
621 pub rest_remainder: Option<bool>,
622 /// Quote creator user ID.
623 pub quote_creator_user_id: Option<String>,
624 /// RFQ creator user ID.
625 pub rfq_creator_user_id: Option<String>,
626 /// Additional fields that may be returned by the API.
627 #[serde(flatten)]
628 pub extra: std::collections::HashMap<String, serde_json::Value>,
629}
630
631/// Represents a confirmed quote.
632#[derive(Debug, Deserialize, Serialize)]
633pub struct QuoteConfirmed {
634 /// The quote ID.
635 pub quote_id: String,
636 /// The status after confirmation.
637 pub status: String,
638 /// The fill ID if trade was completed.
639 pub fill_id: Option<String>,
640}