rhood_core/endpoints/
markets.rs1use crate::api::paths;
2use crate::client::RobinhoodClient;
3use crate::models::market::*;
4use crate::models::watchlist::WatchlistItem;
5use crate::pagination::PaginatedResponse;
6use crate::{Result, RhoodError};
7
8impl RobinhoodClient {
9 pub async fn get_markets(&self) -> Result<Vec<Market>> {
16 self.get_paginated(&self.api_url(paths::MARKETS), &[]).await
17 }
18
19 pub async fn get_market_hours(&self, mic: &str, date: &str) -> Result<MarketHours> {
29 let url = format!("{}{mic}/hours/{date}/", self.api_url(paths::MARKETS));
30 self.get(&url).await
31 }
32
33 pub async fn get_market_today_hours(&self, mic: &str) -> Result<MarketHours> {
44 let markets = self.get_markets().await?;
45 let market = markets
46 .iter()
47 .find(|market| market.mic.as_deref() == Some(mic))
48 .ok_or_else(|| RhoodError::InvalidParameter(format!("Unknown market: {mic}")))?;
49 let hours_url = market
50 .todays_hours
51 .as_deref()
52 .ok_or_else(|| RhoodError::InvalidParameter("No today's hours URL".into()))?;
53 self.get(hours_url).await
54 }
55
56 pub async fn get_daily_movers(&self) -> Result<Vec<WatchlistItem>> {
66 let resp: PaginatedResponse<WatchlistItem> = self
67 .get_with_params(
68 &self.api_url(paths::WATCHLIST_ITEMS),
69 &[("list_id", paths::DAILY_MOVERS_LIST_ID)],
70 )
71 .await?;
72 Ok(resp.results)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use crate::models::market::{Market, MarketHours};
79
80 #[test]
81 fn market_deserializes() {
82 let json = r#"{
83 "mic": "XNYS",
84 "acronym": "NYSE",
85 "name": "New York Stock Exchange",
86 "city": "New York",
87 "country": "US",
88 "timezone": "US/Eastern",
89 "todays_hours": "https://api.robinhood.com/markets/XNYS/hours/2026-03-31/"
90 }"#;
91 let market: Market = serde_json::from_str(json).unwrap();
92 assert_eq!(market.mic.as_deref(), Some("XNYS"));
93 assert_eq!(market.acronym.as_deref(), Some("NYSE"));
94 assert!(market.todays_hours.is_some());
95 }
96
97 #[test]
98 fn market_hours_deserializes() {
99 let json = r#"{
100 "date": "2026-03-31",
101 "is_open": true,
102 "opens_at": "2026-03-31T13:30:00Z",
103 "closes_at": "2026-03-31T20:00:00Z",
104 "extended_opens_at": "2026-03-31T09:00:00Z",
105 "extended_closes_at": "2026-03-31T22:00:00Z"
106 }"#;
107 let hours: MarketHours = serde_json::from_str(json).unwrap();
108 assert_eq!(hours.date.as_deref(), Some("2026-03-31"));
109 assert_eq!(hours.is_open, Some(true));
110 assert!(hours.opens_at.is_some());
111 assert!(hours.extended_opens_at.is_some());
112 }
113
114 #[test]
115 fn market_hours_closed_day() {
116 let json = r#"{ "date": "2026-04-05", "is_open": false }"#;
117 let hours: MarketHours = serde_json::from_str(json).unwrap();
118 assert_eq!(hours.is_open, Some(false));
119 assert!(hours.opens_at.is_none());
120 }
121}