use std::sync::Arc;
use chrono::{DateTime, Utc};
use crate::config::Config;
use crate::earnings::EarningsDate;
use crate::error::{Result, YfError};
use crate::history::{History, HistoryMeta, HistoryOptions};
use crate::http::YfSession;
use crate::isin::{isin_for_ticker, resolve_isin};
use crate::mic::{is_isin, resolve_symbol};
use crate::news::NewsArticle;
use crate::quote::{Calendar, FundsData, NamedTable, SecFiling, UpgradesDowngrades};
#[derive(Clone)]
pub struct Client {
session: Arc<YfSession>,
}
impl Client {
pub fn new() -> Result<Self> {
Ok(Self {
session: Arc::new(YfSession::new(Config::default())?),
})
}
pub fn with_config(config: Config) -> Result<Self> {
Ok(Self {
session: Arc::new(YfSession::new(config)?),
})
}
pub fn session(&self) -> &YfSession {
&self.session
}
pub async fn history(&self, ticker: &str, opts: &HistoryOptions) -> Result<History> {
self.session.history(ticker, opts).await
}
pub async fn info(&self, ticker: &str) -> Result<crate::quote::Info> {
self.session.info(ticker).await
}
pub async fn fast_info(&self, ticker: &str) -> Result<crate::quote::FastInfo> {
self.session.fast_info(ticker).await
}
pub async fn holders(&self, ticker: &str) -> Result<crate::quote::Holders> {
self.session.holders(ticker).await
}
pub async fn sustainability(&self, ticker: &str) -> Result<crate::quote::Sustainability> {
self.session.sustainability(ticker).await
}
pub async fn analyst_price_targets(
&self,
ticker: &str,
) -> Result<crate::quote::AnalystPriceTargets> {
self.session.analyst_price_targets(ticker).await
}
pub async fn recommendation_trend(
&self,
ticker: &str,
) -> Result<Vec<crate::quote::RecommendationTrend>> {
self.session.recommendation_trend(ticker).await
}
pub async fn financials(
&self,
ticker: &str,
statement: crate::fundamentals::Statement,
freq: crate::fundamentals::Freq,
) -> Result<crate::fundamentals::Financials> {
self.session.financials(ticker, statement, freq).await
}
pub async fn option_chain(&self, ticker: &str) -> Result<crate::options::OptionChain> {
self.session.option_chain(ticker).await
}
pub async fn search(
&self,
query: &str,
quotes_count: usize,
news_count: usize,
) -> Result<crate::search::SearchResult> {
self.session.search(query, quotes_count, news_count).await
}
pub async fn lookup(
&self,
query: &str,
limit: usize,
lookup_type: &str,
) -> Result<crate::lookup::LookupResult> {
self.session.lookup(query, limit, lookup_type).await
}
pub async fn sector(&self, key: &str) -> Result<crate::domain::Sector> {
self.session.sector(key).await
}
pub async fn industry(&self, key: &str) -> Result<crate::domain::Industry> {
self.session.industry(key).await
}
pub async fn market(
&self,
region: crate::domain::MarketRegion,
) -> Result<crate::domain::Market> {
self.session.market(region).await
}
pub async fn earnings_calendar(
&self,
start: &str,
end: &str,
limit: usize,
) -> Result<Vec<crate::calendars::EarningsEvent>> {
self.session.earnings_calendar(start, end, limit).await
}
pub async fn ipo_calendar(
&self,
start: &str,
end: &str,
limit: usize,
) -> Result<Vec<crate::calendars::IpoEvent>> {
self.session.ipo_calendar(start, end, limit).await
}
pub async fn economic_calendar(
&self,
start: &str,
end: &str,
limit: usize,
) -> Result<Vec<crate::calendars::EconomicEvent>> {
self.session.economic_calendar(start, end, limit).await
}
pub async fn splits_calendar(
&self,
start: &str,
end: &str,
limit: usize,
) -> Result<Vec<crate::calendars::SplitEvent>> {
self.session.splits_calendar(start, end, limit).await
}
pub async fn screen(
&self,
query: impl Into<crate::screener::ScreenerQuery>,
opts: &crate::screener::ScreenOptions,
) -> Result<crate::screener::ScreenerResult> {
self.session.screen(query, opts).await
}
pub async fn resolve_isin(&self, isin: &str) -> Result<String> {
resolve_isin(self.session(), isin).await
}
pub async fn news(&self, ticker: &str, count: usize, tab: &str) -> Result<Vec<NewsArticle>> {
self.session.news(ticker, count, tab).await
}
pub async fn isin(&self, ticker: &str) -> Result<String> {
isin_for_ticker(self.session(), ticker).await
}
pub async fn earnings_dates(&self, ticker: &str, limit: usize) -> Result<Vec<EarningsDate>> {
self.session.earnings_dates(ticker, limit).await
}
pub async fn earnings_estimate(&self, ticker: &str) -> Result<NamedTable> {
self.session.earnings_estimate(ticker).await
}
pub async fn revenue_estimate(&self, ticker: &str) -> Result<NamedTable> {
self.session.revenue_estimate(ticker).await
}
pub async fn earnings_history(&self, ticker: &str) -> Result<NamedTable> {
self.session.earnings_history(ticker).await
}
pub async fn eps_trend(&self, ticker: &str) -> Result<NamedTable> {
self.session.eps_trend(ticker).await
}
pub async fn eps_revisions(&self, ticker: &str) -> Result<NamedTable> {
self.session.eps_revisions(ticker).await
}
pub async fn growth_estimates(&self, ticker: &str) -> Result<NamedTable> {
self.session.growth_estimates(ticker).await
}
pub async fn recommendations(
&self,
ticker: &str,
) -> Result<Vec<crate::quote::RecommendationTrend>> {
self.session.recommendations(ticker).await
}
pub async fn upgrades_downgrades(&self, ticker: &str) -> Result<Vec<UpgradesDowngrades>> {
self.session.upgrades_downgrades(ticker).await
}
pub async fn valuation_measures(&self, ticker: &str) -> Result<NamedTable> {
self.session.valuation_measures(ticker).await
}
pub async fn calendar(&self, ticker: &str) -> Result<Calendar> {
self.session.ticker_calendar(ticker).await
}
pub async fn sec_filings(&self, ticker: &str) -> Result<Vec<SecFiling>> {
self.session.sec_filings(ticker).await
}
pub async fn shares(&self, ticker: &str) -> Result<Option<f64>> {
self.session.shares(ticker).await
}
pub async fn shares_full(
&self,
ticker: &str,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
) -> Result<Vec<(DateTime<Utc>, f64)>> {
self.session.shares_full(ticker, start, end).await
}
pub async fn funds_data(&self, ticker: &str) -> Result<FundsData> {
self.session.funds_data(ticker).await
}
pub async fn history_metadata(&self, ticker: &str) -> Result<HistoryMeta> {
self.session.history_metadata(ticker).await
}
pub fn auth(&self) -> crate::auth::Auth {
crate::auth::Auth::new((*self.session).clone())
}
pub fn live(&self) -> crate::live::LiveWebSocket {
crate::live::LiveWebSocket::new()
}
}
impl Default for Client {
fn default() -> Self {
Self::new().expect("default client should build")
}
}
#[derive(Clone)]
pub struct Ticker {
client: Client,
symbol: String,
}
#[derive(Clone)]
pub enum TickerId {
Symbol(String),
Pair(String, String),
Isin(String),
}
impl Ticker {
pub fn new(symbol: impl Into<String>, client: Client) -> Self {
Self {
client,
symbol: symbol.into(),
}
}
pub fn from_mic(symbol: &str, mic: &str, client: Client) -> Result<Self> {
let resolved = resolve_symbol(symbol, mic)?;
Ok(Self::new(resolved, client))
}
pub async fn from_isin(isin: &str, client: Client) -> Result<Self> {
if !is_isin(isin) {
return Err(YfError::msg(format!("Invalid ISIN number: {isin}")));
}
let resolved = resolve_isin(client.session(), isin).await?;
Ok(Self::new(resolved, client))
}
pub async fn from_id(id: TickerId, client: Client) -> Result<Self> {
match id {
TickerId::Symbol(s) => Ok(Self::new(s, client)),
TickerId::Pair(s, mic) => Self::from_mic(&s, &mic, client),
TickerId::Isin(isin) => Self::from_isin(&isin, client).await,
}
}
pub fn symbol(&self) -> &str {
&self.symbol
}
pub fn client(&self) -> &Client {
&self.client
}
pub async fn history(&self, opts: &HistoryOptions) -> Result<History> {
self.client.history(&self.symbol, opts).await
}
pub async fn dividends(&self, opts: &HistoryOptions) -> Result<Vec<crate::history::Dividend>> {
let mut o = opts.clone();
o.actions = true;
let h = self.client.history(&self.symbol, &o).await?;
Ok(h.actions.map(|a| a.dividends).unwrap_or_default())
}
pub async fn splits(&self, opts: &HistoryOptions) -> Result<Vec<crate::history::Split>> {
let mut o = opts.clone();
o.actions = true;
let h = self.client.history(&self.symbol, &o).await?;
Ok(h.actions.map(|a| a.splits).unwrap_or_default())
}
pub async fn actions(&self, opts: &HistoryOptions) -> Result<Option<crate::history::Actions>> {
let mut o = opts.clone();
o.actions = true;
let h = self.client.history(&self.symbol, &o).await?;
Ok(h.actions)
}
pub async fn info(&self) -> Result<crate::quote::Info> {
self.client.info(&self.symbol).await
}
pub async fn fast_info(&self) -> Result<crate::quote::FastInfo> {
self.client.fast_info(&self.symbol).await
}
pub async fn holders(&self) -> Result<crate::quote::Holders> {
self.client.holders(&self.symbol).await
}
pub async fn sustainability(&self) -> Result<crate::quote::Sustainability> {
self.client.sustainability(&self.symbol).await
}
pub async fn analyst_price_targets(&self) -> Result<crate::quote::AnalystPriceTargets> {
self.client.analyst_price_targets(&self.symbol).await
}
pub async fn recommendation_trend(&self) -> Result<Vec<crate::quote::RecommendationTrend>> {
self.client.recommendation_trend(&self.symbol).await
}
pub async fn financials(
&self,
statement: crate::fundamentals::Statement,
freq: crate::fundamentals::Freq,
) -> Result<crate::fundamentals::Financials> {
self.client.financials(&self.symbol, statement, freq).await
}
pub async fn option_chain(&self) -> Result<crate::options::OptionChain> {
self.client.option_chain(&self.symbol).await
}
pub async fn news(&self, count: usize, tab: &str) -> Result<Vec<NewsArticle>> {
self.client.news(&self.symbol, count, tab).await
}
pub async fn isin(&self) -> Result<String> {
self.client.isin(&self.symbol).await
}
pub async fn earnings_dates(&self, limit: usize) -> Result<Vec<EarningsDate>> {
self.client.earnings_dates(&self.symbol, limit).await
}
pub async fn earnings_estimate(&self) -> Result<NamedTable> {
self.client.earnings_estimate(&self.symbol).await
}
pub async fn revenue_estimate(&self) -> Result<NamedTable> {
self.client.revenue_estimate(&self.symbol).await
}
pub async fn earnings_history(&self) -> Result<NamedTable> {
self.client.earnings_history(&self.symbol).await
}
pub async fn eps_trend(&self) -> Result<NamedTable> {
self.client.eps_trend(&self.symbol).await
}
pub async fn eps_revisions(&self) -> Result<NamedTable> {
self.client.eps_revisions(&self.symbol).await
}
pub async fn growth_estimates(&self) -> Result<NamedTable> {
self.client.growth_estimates(&self.symbol).await
}
pub async fn recommendations(&self) -> Result<Vec<crate::quote::RecommendationTrend>> {
self.client.recommendations(&self.symbol).await
}
pub async fn upgrades_downgrades(&self) -> Result<Vec<UpgradesDowngrades>> {
self.client.upgrades_downgrades(&self.symbol).await
}
pub async fn valuation_measures(&self) -> Result<NamedTable> {
self.client.valuation_measures(&self.symbol).await
}
pub async fn calendar(&self) -> Result<Calendar> {
self.client.calendar(&self.symbol).await
}
pub async fn sec_filings(&self) -> Result<Vec<SecFiling>> {
self.client.sec_filings(&self.symbol).await
}
pub async fn shares(&self) -> Result<Option<f64>> {
self.client.shares(&self.symbol).await
}
pub async fn shares_full(
&self,
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
) -> Result<Vec<(DateTime<Utc>, f64)>> {
self.client.shares_full(&self.symbol, start, end).await
}
pub async fn funds_data(&self) -> Result<FundsData> {
self.client.funds_data(&self.symbol).await
}
pub async fn history_metadata(&self) -> Result<HistoryMeta> {
self.client.history_metadata(&self.symbol).await
}
}
#[derive(Debug, Clone)]
pub struct DownloadResult {
pub histories: Vec<History>,
pub errors: Vec<(String, String)>,
}
pub async fn download(
tickers: &[&str],
opts: &HistoryOptions,
client: &Client,
) -> Result<DownloadResult> {
let lenient = client.session().config().lenient;
let futures = tickers.iter().map(|t| async move {
match client.history(t, opts).await {
Ok(h) => Ok((t.to_string(), h)),
Err(e) => Err((t.to_string(), e)),
}
});
let outcomes = futures::future::join_all(futures).await;
let mut histories = Vec::new();
let mut errors = Vec::new();
for r in outcomes {
match r {
Ok((_, h)) => histories.push(h),
Err((sym, e)) => {
if lenient {
errors.push((sym, e.to_string()));
} else {
return Err(YfError::msg(format!("download failed for {sym}: {e}")));
}
}
}
}
Ok(DownloadResult { histories, errors })
}