kalshi_rust/exchange/mod.rs
1use super::Kalshi;
2use crate::kalshi_error::*;
3use serde::{Deserialize, Serialize};
4
5impl Kalshi {
6 /// Retrieves the current status of the Kalshi exchange and trading engine.
7 ///
8 /// This method checks whether both the exchange platform and the trading engine
9 /// are currently operational and accepting orders.
10 ///
11 /// # Returns
12 ///
13 /// - `Ok(ExchangeStatus)`: The current status of the exchange and trading engine on successful retrieval.
14 /// - `Err(KalshiError)`: An error if there is an issue with the request.
15 ///
16 /// # Example
17 ///
18 /// ```
19 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
20 /// let status = kalshi_instance.get_exchange_status().await.unwrap();
21 /// if status.trading_active && status.exchange_active {
22 /// println!("Exchange is live and accepting trades");
23 /// }
24 /// ```
25 ///
26 pub async fn get_exchange_status(&self) -> Result<ExchangeStatus, KalshiError> {
27 let url = format!("{}/exchange/status", self.base_url);
28 Ok(self.client.get(&url).send().await?.json().await?)
29 }
30
31 /// Retrieves the exchange schedule including trading hours and maintenance windows.
32 ///
33 /// This method fetches the standard trading hours for each day of the week
34 /// and any scheduled maintenance windows when the exchange may be unavailable.
35 ///
36 /// # Returns
37 ///
38 /// - `Ok(ExchangeSchedule)`: The exchange schedule including trading hours and maintenance windows on successful retrieval.
39 /// - `Err(KalshiError)`: An error if there is an issue with the request.
40 ///
41 /// # Example
42 ///
43 /// ```
44 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
45 /// let schedule = kalshi_instance.get_exchange_schedule().await.unwrap();
46 /// println!("Standard hours: {:?}", schedule.standard_hours);
47 /// println!("Maintenance windows: {:?}", schedule.maintenance_windows);
48 /// ```
49 ///
50 pub async fn get_exchange_schedule(&self) -> Result<ExchangeSchedule, KalshiError> {
51 let url = format!("{}/exchange/schedule", self.base_url);
52 let res: ExchangeScheduleResponse = self.client.get(&url).send().await?.json().await?;
53 Ok(res.schedule)
54 }
55
56 /// Retrieves exchange announcements including active and historical messages.
57 ///
58 /// This method fetches all exchange-wide announcements, both active and expired,
59 /// allowing users to stay informed about important updates, maintenance schedules,
60 /// and other exchange-related information.
61 ///
62 /// # Arguments
63 ///
64 /// * `limit` - An optional integer to limit the number of announcements returned.
65 /// * `cursor` - An optional string for pagination cursor.
66 ///
67 /// # Returns
68 ///
69 /// - `Ok((Option<String>, Vec<ExchangeAnnouncement>))`: A tuple containing an optional pagination cursor
70 /// and a vector of `ExchangeAnnouncement` objects on successful retrieval.
71 /// - `Err(KalshiError)`: An error if there is an issue with the request.
72 ///
73 /// # Example
74 ///
75 /// ```
76 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
77 /// let (cursor, announcements) = kalshi_instance.get_exchange_announcements(
78 /// Some(10), None
79 /// ).await.unwrap();
80 /// for announcement in announcements {
81 /// println!("{}: {}", announcement.ts, announcement.message);
82 /// }
83 /// ```
84 ///
85 pub async fn get_exchange_announcements(
86 &self,
87 limit: Option<i64>,
88 cursor: Option<String>,
89 ) -> Result<(Option<String>, Vec<ExchangeAnnouncement>), KalshiError> {
90 let url = format!("{}/exchange/announcements", self.base_url);
91
92 let mut params = vec![];
93 add_param!(params, "limit", limit);
94 add_param!(params, "cursor", cursor);
95
96 let final_url = reqwest::Url::parse_with_params(&url, ¶ms)?;
97 let res: ExchangeAnnouncementsResponse =
98 self.client.get(final_url).send().await?.json().await?;
99 Ok((res.cursor, res.announcements))
100 }
101
102 /// Retrieves the timestamp indicating when portfolio data was last refreshed.
103 ///
104 /// This method provides the timestamp of the last update to portfolio-related
105 /// endpoints, allowing users to determine the freshness of their account data.
106 ///
107 /// # Returns
108 ///
109 /// - `Ok(UserDataTimestamp)`: The timestamp of the last portfolio data refresh on successful retrieval.
110 /// - `Err(KalshiError)`: An error if there is an issue with the request.
111 ///
112 /// # Example
113 ///
114 /// ```
115 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
116 /// let timestamp = kalshi_instance.get_user_data_timestamp().await.unwrap();
117 /// println!("Portfolio data last updated: {}", timestamp.last_validated_ts);
118 /// ```
119 ///
120 pub async fn get_user_data_timestamp(&self) -> Result<UserDataTimestamp, KalshiError> {
121 let url = format!("{}/exchange/user_data_timestamp", self.base_url);
122 Ok(self.client.get(&url).send().await?.json().await?)
123 }
124
125 /// Checks if the exchange is active with exponential backoff retry logic.
126 ///
127 /// This method attempts to verify that the exchange is operational and trading is active.
128 /// If the exchange is not active, it will retry with exponential backoff delays.
129 /// After all attempts are exhausted, the program will exit with a non-zero status code.
130 ///
131 /// # Arguments
132 ///
133 /// * `max_attempts` - Maximum number of attempts to check exchange status (default: 5)
134 /// * `base_delay_secs` - Base delay in seconds for exponential backoff (default: 30.0)
135 /// * `max_delay_secs` - Maximum delay in seconds to cap exponential growth (default: 300.0)
136 ///
137 /// # Returns
138 ///
139 /// - `Ok(())`: If the exchange becomes active within the retry attempts
140 /// - `Err(KalshiError)`: If there's an unrecoverable error during the process
141 ///
142 /// # Example
143 ///
144 /// ```
145 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
146 /// // This will exit the program if exchange doesn't become active after retries
147 /// kalshi_instance.check_exchange_active_with_backoff().await.unwrap();
148 ///
149 /// // Custom configuration
150 /// kalshi_instance.check_exchange_active_with_backoff(3, 60.0, 600.0).await.unwrap();
151 /// ```
152 ///
153 pub async fn check_exchange_active_with_backoff(
154 &self,
155 max_attempts: u32,
156 base_delay_secs: f64,
157 max_delay_secs: f64,
158 ) -> Result<(), KalshiError> {
159 use std::process;
160 use tokio::time::{sleep, Duration};
161
162 for attempt in 1..=max_attempts {
163 match self.get_exchange_status().await {
164 Ok(status) => {
165 if status.trading_active && status.exchange_active {
166 println!("Exchange is active (attempt {}/{}", attempt, max_attempts);
167 return Ok(());
168 }
169
170 if attempt < max_attempts {
171 let delay_secs = (base_delay_secs * (2.0_f64.powi((attempt - 1) as i32)))
172 .min(max_delay_secs);
173 println!(
174 "Exchange not active (attempt {}/{}). Waiting {:.1} seconds before retry...",
175 attempt, max_attempts, delay_secs
176 );
177 sleep(Duration::from_secs_f64(delay_secs)).await;
178 } else {
179 println!("Exchange not active after {} attempts", max_attempts);
180 }
181 }
182 Err(e) => {
183 if attempt < max_attempts {
184 let delay_secs = (base_delay_secs * (2.0_f64.powi((attempt - 1) as i32)))
185 .min(max_delay_secs);
186 println!(
187 "Error checking exchange status (attempt {}/{}): {}. Waiting {:.1} seconds before retry...",
188 attempt, max_attempts, e, delay_secs
189 );
190 sleep(Duration::from_secs_f64(delay_secs)).await;
191 } else {
192 println!(
193 "Failed to check exchange status after {} attempts: {}",
194 max_attempts, e
195 );
196 }
197 }
198 }
199 }
200
201 println!("Exiting as exchange is not active after all retry attempts");
202 process::exit(1);
203 }
204
205 /// Convenience method to check exchange status with default backoff settings.
206 ///
207 /// This method calls `check_exchange_active_with_backoff` with default parameters:
208 /// - max_attempts: 5
209 /// - base_delay_secs: 30.0
210 /// - max_delay_secs: 300.0
211 ///
212 /// # Returns
213 ///
214 /// - `Ok(())`: If the exchange becomes active within the retry attempts
215 /// - `Err(KalshiError)`: If there's an unrecoverable error during the process
216 ///
217 /// # Example
218 ///
219 /// ```
220 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
221 /// kalshi_instance.check_exchange_active().await.unwrap();
222 /// ```
223 ///
224 pub async fn check_exchange_active(&self) -> Result<(), KalshiError> {
225 self.check_exchange_active_with_backoff(5, 30.0, 300.0)
226 .await
227 }
228
229 /// Retrieves series fee changes from the exchange.
230 ///
231 /// This method fetches information about fee changes for specific series,
232 /// including historical and upcoming fee adjustments.
233 ///
234 /// # Arguments
235 ///
236 /// * `series_ticker` - Optional series ticker to filter fee changes.
237 ///
238 /// # Returns
239 ///
240 /// - `Ok(Vec<SeriesFeeChange>)`: A vector of fee change information on successful retrieval.
241 /// - `Err(KalshiError)`: An error if there is an issue with the request.
242 ///
243 /// # Example
244 ///
245 /// ```
246 /// // Assuming `kalshi_instance` is an instance of `Kalshi`
247 /// let fee_changes = kalshi_instance.get_series_fee_changes(None).await.unwrap();
248 /// for change in fee_changes {
249 /// println!("Series: {}, New Fee: {}", change.series_ticker, change.new_fee);
250 /// }
251 /// ```
252 ///
253 pub async fn get_series_fee_changes(
254 &self,
255 series_ticker: Option<String>,
256 ) -> Result<Vec<SeriesFeeChange>, KalshiError> {
257 let path = "/series/fee_changes";
258 let mut params = vec![];
259 add_param!(params, "series_ticker", series_ticker);
260
261 let url = format!("{}{}", self.base_url, path);
262 let final_url = reqwest::Url::parse_with_params(&url, ¶ms)?;
263 let res: SeriesFeeChangesResponse = self.client.get(final_url).send().await?.json().await?;
264 Ok(res.fee_changes)
265 }
266}
267
268// -------- public models --------
269
270/// Represents the operational status of the Kalshi exchange.
271///
272/// This struct provides simple boolean flags indicating whether the exchange
273/// platform and trading engine are currently active and operational.
274#[derive(Debug, Deserialize, Serialize)]
275pub struct ExchangeStatus {
276 /// Indicates whether the trading engine is currently active and accepting orders.
277 pub trading_active: bool,
278 /// Indicates whether the exchange platform is currently operational.
279 pub exchange_active: bool,
280}
281
282/// Represents the trading schedule and maintenance windows for the Kalshi exchange.
283///
284/// This struct contains the standard trading hours for each day of the week
285/// and any scheduled maintenance windows when the exchange may be unavailable.
286#[derive(Debug, Deserialize, Serialize)]
287pub struct ExchangeSchedule {
288 /// The standard trading hours for each day of the week.
289 pub standard_hours: Vec<StandardHours>,
290 /// Scheduled maintenance windows when the exchange may be unavailable.
291 pub maintenance_windows: Vec<MaintenanceWindow>,
292}
293
294/// Represents an exchange-wide announcement from Kalshi.
295///
296/// Announcements provide important information about exchange updates,
297/// maintenance schedules, new features, or other relevant information
298/// that users need to be aware of.
299#[derive(Debug, Deserialize, Serialize)]
300pub struct ExchangeAnnouncement {
301 /// The announcement message content.
302 pub message: String,
303 /// The timestamp when the announcement was created (seconds since epoch, ISO string, or RFC3339).
304 pub ts: String,
305 /// The current status of the announcement (e.g., "active" or "expired").
306 pub status: String,
307}
308
309/// Represents the timestamp of the last portfolio data refresh.
310///
311/// This struct provides information about when user portfolio data
312/// was last updated, allowing users to determine data freshness.
313#[derive(Debug, Deserialize, Serialize)]
314pub struct UserDataTimestamp {
315 /// The timestamp of the last portfolio data validation/refresh.
316 pub last_validated_ts: String,
317}
318
319/// Represents a scheduled maintenance window for the Kalshi exchange.
320///
321/// Maintenance windows indicate periods when the exchange may be unavailable
322/// for trading or other operations due to scheduled maintenance.
323#[derive(Debug, Deserialize, Serialize)]
324pub struct MaintenanceWindow {
325 /// The start datetime of the maintenance window.
326 pub start_datetime: String,
327 /// The end datetime of the maintenance window.
328 pub end_datetime: String,
329}
330
331/// Represents the trading schedule for a specific day.
332///
333/// This struct defines the opening and closing times for trading
334/// on a particular day of the week.
335#[derive(Debug, Deserialize, Serialize)]
336pub struct DaySchedule {
337 /// The time when trading opens for this day.
338 pub open_time: String,
339 /// The time when trading closes for this day.
340 pub close_time: String,
341}
342
343/// Represents the standard trading hours for the Kalshi exchange.
344///
345/// This struct defines the trading schedule for each day of the week,
346/// including multiple time slots per day if applicable.
347#[derive(Debug, Deserialize, Serialize)]
348pub struct StandardHours {
349 /// The start time for the trading period.
350 pub start_time: String,
351 /// The end time for the trading period.
352 pub end_time: String,
353 /// Trading schedule for Monday.
354 #[serde(default)]
355 pub monday: Vec<DaySchedule>,
356 /// Trading schedule for Tuesday.
357 #[serde(default)]
358 pub tuesday: Vec<DaySchedule>,
359 /// Trading schedule for Wednesday.
360 #[serde(default)]
361 pub wednesday: Vec<DaySchedule>,
362 /// Trading schedule for Thursday.
363 #[serde(default)]
364 pub thursday: Vec<DaySchedule>,
365 /// Trading schedule for Friday.
366 #[serde(default)]
367 pub friday: Vec<DaySchedule>,
368 /// Trading schedule for Saturday.
369 #[serde(default)]
370 pub saturday: Vec<DaySchedule>,
371 /// Trading schedule for Sunday.
372 #[serde(default)]
373 pub sunday: Vec<DaySchedule>,
374}
375
376// -------- response wrappers --------
377
378#[derive(Debug, Deserialize)]
379struct ExchangeScheduleResponse {
380 schedule: ExchangeSchedule,
381}
382
383#[derive(Debug, Deserialize)]
384struct ExchangeAnnouncementsResponse {
385 cursor: Option<String>,
386 announcements: Vec<ExchangeAnnouncement>,
387}
388
389#[derive(Debug, Deserialize)]
390struct SeriesFeeChangesResponse {
391 fee_changes: Vec<SeriesFeeChange>,
392}
393
394/// Represents a fee change for a series.
395#[derive(Debug, Deserialize, Serialize)]
396pub struct SeriesFeeChange {
397 /// The series ticker.
398 pub series_ticker: String,
399 /// The old fee (in cents or basis points).
400 pub old_fee: Option<f64>,
401 /// The new fee (in cents or basis points).
402 pub new_fee: f64,
403 /// The effective date of the fee change.
404 pub effective_date: String,
405}