finance_query/models/discovery/
reference.rs1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12#[non_exhaustive]
13pub struct SymbolMatch {
14 pub symbol: String,
16 pub id: Option<String>,
21 pub name: Option<String>,
23 pub exchange: Option<String>,
25 pub asset_type: Option<String>,
27 pub currency: Option<String>,
29 pub active: Option<bool>,
31 pub market_cap_rank: Option<u32>,
33 pub thumbnail: Option<String>,
35 pub image: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub ipo_date: Option<String>,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub delisted_date: Option<String>,
43}
44
45#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47#[non_exhaustive]
48pub struct SymbolDetails {
49 pub symbol: String,
51 pub name: Option<String>,
53 pub description: Option<String>,
55 pub exchange: Option<String>,
57 pub asset_type: Option<String>,
59 pub cik: Option<String>,
61 pub sic_code: Option<String>,
63 pub sic_description: Option<String>,
65 pub homepage_url: Option<String>,
67 pub employees: Option<u64>,
69 pub market_cap: Option<f64>,
71 pub list_date: Option<String>,
73 pub shares_outstanding: Option<f64>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub composite_figi: Option<String>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub share_class_figi: Option<String>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub active: Option<bool>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub delisted_utc: Option<String>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub provider_id: Option<crate::Provider>,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94#[non_exhaustive]
95pub struct ExchangeInfo {
96 pub id: Option<i64>,
98 pub name: Option<String>,
100 pub mic: Option<String>,
102 pub operating_mic: Option<String>,
104 pub asset_class: Option<String>,
106 pub locale: Option<String>,
108 pub exchange_type: Option<String>,
110 pub url: Option<String>,
112}
113
114#[derive(Debug, Clone, Default, Serialize, Deserialize)]
116#[non_exhaustive]
117pub struct ScreenerMatch {
118 pub symbol: String,
120 pub name: Option<String>,
122 pub price: Option<f64>,
124 pub market_cap: Option<f64>,
126 pub sector: Option<String>,
128 pub industry: Option<String>,
130 pub beta: Option<f64>,
132 pub volume: Option<f64>,
134 pub exchange: Option<String>,
136 pub country: Option<String>,
138 pub is_etf: Option<bool>,
140 pub is_actively_trading: Option<bool>,
142 pub is_fund: Option<bool>,
144}
145
146#[derive(Debug, Clone, Default, PartialEq)]
151#[non_exhaustive]
152pub struct ScreenerFilters {
153 pub market_cap_min: Option<f64>,
155 pub market_cap_max: Option<f64>,
157 pub price_min: Option<f64>,
159 pub price_max: Option<f64>,
161 pub volume_min: Option<f64>,
163 pub beta_min: Option<f64>,
165 pub beta_max: Option<f64>,
167 pub sector: Option<String>,
169 pub industry: Option<String>,
171 pub exchange: Option<String>,
173 pub country: Option<String>,
175 pub actively_trading: Option<bool>,
177 pub limit: Option<u32>,
179 pub is_etf: Option<bool>,
181 pub is_fund: Option<bool>,
183}
184
185impl ScreenerFilters {
186 pub fn new() -> Self {
188 Self::default()
189 }
190
191 pub fn market_cap(mut self, min: Option<f64>, max: Option<f64>) -> Self {
193 self.market_cap_min = min;
194 self.market_cap_max = max;
195 self
196 }
197
198 pub fn price(mut self, min: Option<f64>, max: Option<f64>) -> Self {
200 self.price_min = min;
201 self.price_max = max;
202 self
203 }
204
205 pub fn beta(mut self, min: Option<f64>, max: Option<f64>) -> Self {
207 self.beta_min = min;
208 self.beta_max = max;
209 self
210 }
211
212 pub fn volume_min(mut self, min: f64) -> Self {
214 self.volume_min = Some(min);
215 self
216 }
217
218 pub fn sector(mut self, sector: impl Into<String>) -> Self {
220 self.sector = Some(sector.into());
221 self
222 }
223
224 pub fn industry(mut self, industry: impl Into<String>) -> Self {
226 self.industry = Some(industry.into());
227 self
228 }
229
230 pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
232 self.exchange = Some(exchange.into());
233 self
234 }
235
236 pub fn country(mut self, country: impl Into<String>) -> Self {
238 self.country = Some(country.into());
239 self
240 }
241
242 pub fn actively_trading(mut self, actively_trading: bool) -> Self {
244 self.actively_trading = Some(actively_trading);
245 self
246 }
247
248 pub fn limit(mut self, limit: u32) -> Self {
250 self.limit = Some(limit);
251 self
252 }
253
254 #[cfg(feature = "fmp")]
259 pub(crate) fn to_query(&self) -> Vec<(&'static str, String)> {
260 let mut q: Vec<(&'static str, String)> = Vec::new();
261 let mut num = |k: &'static str, v: Option<f64>| {
262 if let Some(v) = v {
263 q.push((k, v.to_string()));
264 }
265 };
266 num("marketCapMoreThan", self.market_cap_min);
267 num("marketCapLowerThan", self.market_cap_max);
268 num("priceMoreThan", self.price_min);
269 num("priceLowerThan", self.price_max);
270 num("volumeMoreThan", self.volume_min);
271 num("betaMoreThan", self.beta_min);
272 num("betaLowerThan", self.beta_max);
273 for (k, v) in [
274 ("sector", &self.sector),
275 ("industry", &self.industry),
276 ("exchange", &self.exchange),
277 ("country", &self.country),
278 ] {
279 if let Some(v) = v {
280 q.push((k, v.clone()));
281 }
282 }
283 if let Some(v) = self.actively_trading {
284 q.push(("isActivelyTrading", v.to_string()));
285 }
286 if let Some(v) = self.is_etf {
287 q.push(("isEtf", v.to_string()));
288 }
289 if let Some(v) = self.is_fund {
290 q.push(("isFund", v.to_string()));
291 }
292 if let Some(v) = self.limit {
293 q.push(("limit", v.to_string()));
294 }
295 q
296 }
297}
298
299#[cfg(all(test, feature = "fmp"))]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn empty_filters_render_no_query_params() {
306 assert!(ScreenerFilters::new().to_query().is_empty());
307 }
308
309 #[test]
310 fn set_filters_render_with_provider_parameter_names() {
311 let q = ScreenerFilters::new()
312 .market_cap(Some(1e9), None)
313 .price(None, Some(50.0))
314 .sector("Technology")
315 .actively_trading(true)
316 .limit(25)
317 .to_query();
318
319 assert_eq!(
320 q,
321 vec![
322 ("marketCapMoreThan", "1000000000".to_string()),
323 ("priceLowerThan", "50".to_string()),
324 ("sector", "Technology".to_string()),
325 ("isActivelyTrading", "true".to_string()),
326 ("limit", "25".to_string()),
327 ]
328 );
329 }
330}