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}