mod info;
mod isin;
mod model;
mod options;
mod quote;
pub use model::{Info, OptionChain, OptionContract};
pub use paft::aggregates::FastInfo;
use crate::core::{Action, Candle, HistoryMeta, Interval, Quote, Range};
use crate::fundamentals::{Calendar, ShareCount};
use crate::holders::{
InsiderRosterHolder, InsiderTransaction, InstitutionalHolder, MajorHolder,
NetSharePurchaseActivity,
};
use crate::news::NewsArticle;
use crate::{
EsgBuilder,
core::client::RetryConfig,
core::conversions::{datetime_to_i64, money_to_currency_str, money_to_f64},
core::{CacheMode, YfClient, YfError},
holders::HoldersBuilder,
news::NewsBuilder,
};
use crate::{
analysis::AnalysisBuilder, fundamentals::FundamentalsBuilder, history::HistoryBuilder,
};
use paft::fundamentals::analysis::{
Earnings, EarningsTrendRow, PriceTarget, RecommendationRow, RecommendationSummary,
UpgradeDowngradeRow,
};
use paft::fundamentals::statements::{BalanceSheetRow, CashflowRow, IncomeStatementRow};
use paft::money::Currency;
pub struct Ticker {
#[doc(hidden)]
pub(crate) client: YfClient,
#[doc(hidden)]
pub(crate) symbol: String,
#[doc(hidden)]
cache_mode: CacheMode,
retry_override: Option<RetryConfig>,
}
impl Ticker {
pub fn new(client: &YfClient, symbol: impl Into<String>) -> Self {
Self {
client: client.clone(),
symbol: symbol.into(),
cache_mode: CacheMode::Use,
retry_override: None,
}
}
#[must_use]
pub const fn cache_mode(mut self, mode: CacheMode) -> Self {
self.cache_mode = mode;
self
}
#[must_use]
pub fn retry_policy(mut self, cfg: Option<RetryConfig>) -> Self {
self.retry_override = cfg;
self
}
pub async fn info(&self) -> Result<Info, YfError> {
Box::pin(info::fetch_info(
&self.client,
&self.symbol,
self.cache_mode,
self.retry_override.as_ref(),
))
.await
}
pub async fn quote(&self) -> Result<Quote, YfError> {
quote::fetch_quote(
&self.client,
&self.symbol,
self.cache_mode,
self.retry_override.as_ref(),
)
.await
}
pub async fn fast_info(&self) -> Result<FastInfo, YfError> {
let q = self.quote().await?;
Ok(FastInfo {
symbol: q.symbol,
name: q.shortname.clone(),
exchange: q.exchange,
market_state: q.market_state,
currency: q
.price
.as_ref()
.and_then(money_to_currency_str)
.or_else(|| q.previous_close.as_ref().and_then(money_to_currency_str))
.and_then(|code| code.parse().ok()),
last: q.price,
previous_close: q.previous_close,
})
}
#[must_use]
pub fn news_builder(&self) -> NewsBuilder {
NewsBuilder::new(&self.client, &self.symbol)
.cache_mode(self.cache_mode)
.retry_policy(self.retry_override.clone())
}
pub async fn news(&self) -> Result<Vec<NewsArticle>, YfError> {
self.news_builder().fetch().await
}
#[must_use]
pub fn history_builder(&self) -> HistoryBuilder {
HistoryBuilder::new(&self.client, &self.symbol)
}
pub async fn history(
&self,
range: Option<Range>,
interval: Option<Interval>,
prepost: bool,
) -> Result<Vec<Candle>, crate::core::YfError> {
let mut hb = self.history_builder();
if let Some(r) = range {
hb = hb.range(r);
}
if let Some(i) = interval {
hb = hb.interval(i);
}
hb = hb
.auto_adjust(true)
.prepost(prepost)
.actions(true)
.cache_mode(self.cache_mode)
.retry_policy(self.retry_override.clone());
hb.fetch().await
}
pub async fn actions(&self, range: Option<Range>) -> Result<Vec<Action>, YfError> {
let mut hb = self.history_builder();
hb = hb.range(range.unwrap_or(Range::Max));
let resp = hb.auto_adjust(true).actions(true).fetch_full().await?;
let mut actions = resp.actions;
actions.sort_by_key(|a| match *a {
Action::Dividend { ts, .. }
| Action::Split { ts, .. }
| Action::CapitalGain { ts, .. } => ts,
});
Ok(actions)
}
pub async fn dividends(&self, range: Option<Range>) -> Result<Vec<(i64, f64)>, YfError> {
let acts = self.actions(range).await?;
Ok(acts
.into_iter()
.filter_map(|a| match a {
Action::Dividend { ts, amount } => {
Some((datetime_to_i64(ts), money_to_f64(&amount)))
}
_ => None,
})
.collect())
}
pub async fn splits(&self, range: Option<Range>) -> Result<Vec<(i64, u32, u32)>, YfError> {
let acts = self.actions(range).await?;
Ok(acts
.into_iter()
.filter_map(|a| match a {
Action::Split {
ts,
numerator,
denominator,
} => Some((datetime_to_i64(ts), numerator, denominator)),
_ => None,
})
.collect())
}
pub async fn get_history_metadata(
&self,
range: Option<Range>,
) -> Result<Option<HistoryMeta>, crate::core::YfError> {
let mut hb = self
.history_builder()
.cache_mode(self.cache_mode)
.retry_policy(self.retry_override.clone());
if let Some(r) = range {
hb = hb.range(r);
}
let resp = hb.fetch_full().await?;
Ok(resp.meta)
}
pub async fn isin(&self) -> Result<Option<String>, YfError> {
if self.symbol.contains('^') {
return Ok(None);
}
isin::fetch_isin(&self.client, &self.symbol, self.retry_override.as_ref()).await
}
pub async fn capital_gains(&self, range: Option<Range>) -> Result<Vec<(i64, f64)>, YfError> {
let acts = self.actions(range).await?;
Ok(acts
.into_iter()
.filter_map(|a| match a {
Action::CapitalGain { ts, gain } => {
Some((datetime_to_i64(ts), money_to_f64(&gain)))
}
_ => None,
})
.collect())
}
pub async fn options(&self) -> Result<Vec<i64>, YfError> {
options::expiration_dates(
&self.client,
&self.symbol,
self.cache_mode,
self.retry_override.as_ref(),
)
.await
}
pub async fn option_chain(&self, date: Option<i64>) -> Result<OptionChain, YfError> {
options::option_chain(
&self.client,
&self.symbol,
date,
self.cache_mode,
self.retry_override.as_ref(),
)
.await
}
fn holders_builder(&self) -> HoldersBuilder {
HoldersBuilder::new(&self.client, &self.symbol)
.cache_mode(self.cache_mode)
.retry_policy(self.retry_override.clone())
}
pub async fn major_holders(&self) -> Result<Vec<MajorHolder>, YfError> {
self.holders_builder().major_holders().await
}
pub async fn institutional_holders(&self) -> Result<Vec<InstitutionalHolder>, YfError> {
self.holders_builder().institutional_holders().await
}
pub async fn mutual_fund_holders(&self) -> Result<Vec<InstitutionalHolder>, YfError> {
self.holders_builder().mutual_fund_holders().await
}
pub async fn insider_transactions(&self) -> Result<Vec<InsiderTransaction>, YfError> {
self.holders_builder().insider_transactions().await
}
pub async fn insider_roster_holders(&self) -> Result<Vec<InsiderRosterHolder>, YfError> {
self.holders_builder().insider_roster_holders().await
}
pub async fn net_share_purchase_activity(
&self,
) -> Result<Option<NetSharePurchaseActivity>, YfError> {
self.holders_builder().net_share_purchase_activity().await
}
fn analysis_builder(&self) -> AnalysisBuilder {
AnalysisBuilder::new(&self.client, &self.symbol)
.cache_mode(self.cache_mode)
.retry_policy(self.retry_override.clone())
}
pub async fn recommendations(&self) -> Result<Vec<RecommendationRow>, YfError> {
self.analysis_builder().recommendations().await
}
pub async fn recommendations_summary(&self) -> Result<RecommendationSummary, YfError> {
self.analysis_builder().recommendations_summary().await
}
pub async fn upgrades_downgrades(&self) -> Result<Vec<UpgradeDowngradeRow>, YfError> {
self.analysis_builder().upgrades_downgrades().await
}
pub async fn analyst_price_target(
&self,
override_currency: Option<Currency>,
) -> Result<PriceTarget, YfError> {
self.analysis_builder()
.analyst_price_target(override_currency)
.await
}
pub async fn earnings_trend(
&self,
override_currency: Option<Currency>,
) -> Result<Vec<EarningsTrendRow>, YfError> {
self.analysis_builder()
.earnings_trend(override_currency)
.await
}
fn esg_builder(&self) -> EsgBuilder {
EsgBuilder::new(&self.client, &self.symbol)
.cache_mode(self.cache_mode)
.retry_policy(self.retry_override.clone())
}
pub async fn sustainability(&self) -> Result<paft::fundamentals::esg::EsgSummary, YfError> {
self.esg_builder().fetch().await
}
fn fundamentals_builder(&self) -> FundamentalsBuilder {
FundamentalsBuilder::new(&self.client, &self.symbol)
.cache_mode(self.cache_mode)
.retry_policy(self.retry_override.clone())
}
pub async fn income_stmt(
&self,
override_currency: Option<Currency>,
) -> Result<Vec<IncomeStatementRow>, YfError> {
self.fundamentals_builder()
.income_statement(false, override_currency)
.await
}
pub async fn quarterly_income_stmt(
&self,
override_currency: Option<Currency>,
) -> Result<Vec<IncomeStatementRow>, YfError> {
self.fundamentals_builder()
.income_statement(true, override_currency)
.await
}
pub async fn balance_sheet(
&self,
override_currency: Option<Currency>,
) -> Result<Vec<BalanceSheetRow>, YfError> {
self.fundamentals_builder()
.balance_sheet(false, override_currency)
.await
}
pub async fn quarterly_balance_sheet(
&self,
override_currency: Option<Currency>,
) -> Result<Vec<BalanceSheetRow>, YfError> {
self.fundamentals_builder()
.balance_sheet(true, override_currency)
.await
}
pub async fn cashflow(
&self,
override_currency: Option<Currency>,
) -> Result<Vec<CashflowRow>, YfError> {
self.fundamentals_builder()
.cashflow(false, override_currency)
.await
}
pub async fn quarterly_cashflow(
&self,
override_currency: Option<Currency>,
) -> Result<Vec<CashflowRow>, YfError> {
self.fundamentals_builder()
.cashflow(true, override_currency)
.await
}
pub async fn earnings(&self, override_currency: Option<Currency>) -> Result<Earnings, YfError> {
self.fundamentals_builder()
.earnings(override_currency)
.await
}
pub async fn calendar(&self) -> Result<Calendar, YfError> {
self.fundamentals_builder().calendar().await
}
pub async fn shares(&self) -> Result<Vec<ShareCount>, YfError> {
self.fundamentals_builder().shares(false).await
}
pub async fn quarterly_shares(&self) -> Result<Vec<ShareCount>, YfError> {
self.fundamentals_builder().shares(true).await
}
}