use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SymbolMatch {
pub symbol: String,
pub id: Option<String>,
pub name: Option<String>,
pub exchange: Option<String>,
pub asset_type: Option<String>,
pub currency: Option<String>,
pub active: Option<bool>,
pub market_cap_rank: Option<u32>,
pub thumbnail: Option<String>,
pub image: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SymbolDetails {
pub symbol: String,
pub name: Option<String>,
pub description: Option<String>,
pub exchange: Option<String>,
pub asset_type: Option<String>,
pub cik: Option<String>,
pub sic_code: Option<String>,
pub sic_description: Option<String>,
pub homepage_url: Option<String>,
pub employees: Option<u64>,
pub market_cap: Option<f64>,
pub list_date: Option<String>,
pub shares_outstanding: Option<f64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ExchangeInfo {
pub id: Option<i64>,
pub name: Option<String>,
pub mic: Option<String>,
pub operating_mic: Option<String>,
pub asset_class: Option<String>,
pub locale: Option<String>,
pub exchange_type: Option<String>,
pub url: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ScreenerMatch {
pub symbol: String,
pub name: Option<String>,
pub price: Option<f64>,
pub market_cap: Option<f64>,
pub sector: Option<String>,
pub industry: Option<String>,
pub beta: Option<f64>,
pub volume: Option<f64>,
pub exchange: Option<String>,
pub country: Option<String>,
pub is_etf: Option<bool>,
pub is_actively_trading: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[non_exhaustive]
pub struct ScreenerFilters {
pub market_cap_min: Option<f64>,
pub market_cap_max: Option<f64>,
pub price_min: Option<f64>,
pub price_max: Option<f64>,
pub volume_min: Option<f64>,
pub beta_min: Option<f64>,
pub beta_max: Option<f64>,
pub sector: Option<String>,
pub industry: Option<String>,
pub exchange: Option<String>,
pub country: Option<String>,
pub actively_trading: Option<bool>,
pub limit: Option<u32>,
}
impl ScreenerFilters {
pub fn new() -> Self {
Self::default()
}
pub fn market_cap(mut self, min: Option<f64>, max: Option<f64>) -> Self {
self.market_cap_min = min;
self.market_cap_max = max;
self
}
pub fn price(mut self, min: Option<f64>, max: Option<f64>) -> Self {
self.price_min = min;
self.price_max = max;
self
}
pub fn beta(mut self, min: Option<f64>, max: Option<f64>) -> Self {
self.beta_min = min;
self.beta_max = max;
self
}
pub fn volume_min(mut self, min: f64) -> Self {
self.volume_min = Some(min);
self
}
pub fn sector(mut self, sector: impl Into<String>) -> Self {
self.sector = Some(sector.into());
self
}
pub fn industry(mut self, industry: impl Into<String>) -> Self {
self.industry = Some(industry.into());
self
}
pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
self.exchange = Some(exchange.into());
self
}
pub fn country(mut self, country: impl Into<String>) -> Self {
self.country = Some(country.into());
self
}
pub fn actively_trading(mut self, actively_trading: bool) -> Self {
self.actively_trading = Some(actively_trading);
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
#[cfg(feature = "fmp")]
pub(crate) fn to_query(&self) -> Vec<(&'static str, String)> {
let mut q: Vec<(&'static str, String)> = Vec::new();
let mut num = |k: &'static str, v: Option<f64>| {
if let Some(v) = v {
q.push((k, v.to_string()));
}
};
num("marketCapMoreThan", self.market_cap_min);
num("marketCapLowerThan", self.market_cap_max);
num("priceMoreThan", self.price_min);
num("priceLowerThan", self.price_max);
num("volumeMoreThan", self.volume_min);
num("betaMoreThan", self.beta_min);
num("betaLowerThan", self.beta_max);
for (k, v) in [
("sector", &self.sector),
("industry", &self.industry),
("exchange", &self.exchange),
("country", &self.country),
] {
if let Some(v) = v {
q.push((k, v.clone()));
}
}
if let Some(v) = self.actively_trading {
q.push(("isActivelyTrading", v.to_string()));
}
if let Some(v) = self.limit {
q.push(("limit", v.to_string()));
}
q
}
}
#[cfg(all(test, feature = "fmp"))]
mod tests {
use super::*;
#[test]
fn empty_filters_render_no_query_params() {
assert!(ScreenerFilters::new().to_query().is_empty());
}
#[test]
fn set_filters_render_with_provider_parameter_names() {
let q = ScreenerFilters::new()
.market_cap(Some(1e9), None)
.price(None, Some(50.0))
.sector("Technology")
.actively_trading(true)
.limit(25)
.to_query();
assert_eq!(
q,
vec![
("marketCapMoreThan", "1000000000".to_string()),
("priceLowerThan", "50".to_string()),
("sector", "Technology".to_string()),
("isActivelyTrading", "true".to_string()),
("limit", "25".to_string()),
]
);
}
}