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}
38
39#[derive(Debug, Clone, Default, Serialize, Deserialize)]
41#[non_exhaustive]
42pub struct SymbolDetails {
43 pub symbol: String,
45 pub name: Option<String>,
47 pub description: Option<String>,
49 pub exchange: Option<String>,
51 pub asset_type: Option<String>,
53 pub cik: Option<String>,
55 pub sic_code: Option<String>,
57 pub sic_description: Option<String>,
59 pub homepage_url: Option<String>,
61 pub employees: Option<u64>,
63 pub market_cap: Option<f64>,
65 pub list_date: Option<String>,
67 pub shares_outstanding: Option<f64>,
69}
70
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct ExchangeInfo {
75 pub id: Option<i64>,
77 pub name: Option<String>,
79 pub mic: Option<String>,
81 pub operating_mic: Option<String>,
83 pub asset_class: Option<String>,
85 pub locale: Option<String>,
87 pub exchange_type: Option<String>,
89 pub url: Option<String>,
91}
92
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95#[non_exhaustive]
96pub struct ScreenerMatch {
97 pub symbol: String,
99 pub name: Option<String>,
101 pub price: Option<f64>,
103 pub market_cap: Option<f64>,
105 pub sector: Option<String>,
107 pub industry: Option<String>,
109 pub beta: Option<f64>,
111 pub volume: Option<f64>,
113 pub exchange: Option<String>,
115 pub country: Option<String>,
117 pub is_etf: Option<bool>,
119 pub is_actively_trading: Option<bool>,
121}
122
123#[derive(Debug, Clone, Default, PartialEq)]
128#[non_exhaustive]
129pub struct ScreenerFilters {
130 pub market_cap_min: Option<f64>,
132 pub market_cap_max: Option<f64>,
134 pub price_min: Option<f64>,
136 pub price_max: Option<f64>,
138 pub volume_min: Option<f64>,
140 pub beta_min: Option<f64>,
142 pub beta_max: Option<f64>,
144 pub sector: Option<String>,
146 pub industry: Option<String>,
148 pub exchange: Option<String>,
150 pub country: Option<String>,
152 pub actively_trading: Option<bool>,
154 pub limit: Option<u32>,
156}
157
158impl ScreenerFilters {
159 pub fn new() -> Self {
161 Self::default()
162 }
163
164 pub fn market_cap(mut self, min: Option<f64>, max: Option<f64>) -> Self {
166 self.market_cap_min = min;
167 self.market_cap_max = max;
168 self
169 }
170
171 pub fn price(mut self, min: Option<f64>, max: Option<f64>) -> Self {
173 self.price_min = min;
174 self.price_max = max;
175 self
176 }
177
178 pub fn beta(mut self, min: Option<f64>, max: Option<f64>) -> Self {
180 self.beta_min = min;
181 self.beta_max = max;
182 self
183 }
184
185 pub fn volume_min(mut self, min: f64) -> Self {
187 self.volume_min = Some(min);
188 self
189 }
190
191 pub fn sector(mut self, sector: impl Into<String>) -> Self {
193 self.sector = Some(sector.into());
194 self
195 }
196
197 pub fn industry(mut self, industry: impl Into<String>) -> Self {
199 self.industry = Some(industry.into());
200 self
201 }
202
203 pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
205 self.exchange = Some(exchange.into());
206 self
207 }
208
209 pub fn country(mut self, country: impl Into<String>) -> Self {
211 self.country = Some(country.into());
212 self
213 }
214
215 pub fn actively_trading(mut self, actively_trading: bool) -> Self {
217 self.actively_trading = Some(actively_trading);
218 self
219 }
220
221 pub fn limit(mut self, limit: u32) -> Self {
223 self.limit = Some(limit);
224 self
225 }
226
227 #[cfg(feature = "fmp")]
232 pub(crate) fn to_query(&self) -> Vec<(&'static str, String)> {
233 let mut q: Vec<(&'static str, String)> = Vec::new();
234 let mut num = |k: &'static str, v: Option<f64>| {
235 if let Some(v) = v {
236 q.push((k, v.to_string()));
237 }
238 };
239 num("marketCapMoreThan", self.market_cap_min);
240 num("marketCapLowerThan", self.market_cap_max);
241 num("priceMoreThan", self.price_min);
242 num("priceLowerThan", self.price_max);
243 num("volumeMoreThan", self.volume_min);
244 num("betaMoreThan", self.beta_min);
245 num("betaLowerThan", self.beta_max);
246 for (k, v) in [
247 ("sector", &self.sector),
248 ("industry", &self.industry),
249 ("exchange", &self.exchange),
250 ("country", &self.country),
251 ] {
252 if let Some(v) = v {
253 q.push((k, v.clone()));
254 }
255 }
256 if let Some(v) = self.actively_trading {
257 q.push(("isActivelyTrading", v.to_string()));
258 }
259 if let Some(v) = self.limit {
260 q.push(("limit", v.to_string()));
261 }
262 q
263 }
264}
265
266#[cfg(all(test, feature = "fmp"))]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn empty_filters_render_no_query_params() {
273 assert!(ScreenerFilters::new().to_query().is_empty());
274 }
275
276 #[test]
277 fn set_filters_render_with_provider_parameter_names() {
278 let q = ScreenerFilters::new()
279 .market_cap(Some(1e9), None)
280 .price(None, Some(50.0))
281 .sector("Technology")
282 .actively_trading(true)
283 .limit(25)
284 .to_query();
285
286 assert_eq!(
287 q,
288 vec![
289 ("marketCapMoreThan", "1000000000".to_string()),
290 ("priceLowerThan", "50".to_string()),
291 ("sector", "Technology".to_string()),
292 ("isActivelyTrading", "true".to_string()),
293 ("limit", "25".to_string()),
294 ]
295 );
296 }
297}