mod actions;
mod adjust;
mod assemble;
mod fetch;
use crate::core::client::{CacheMode, RetryConfig};
use crate::core::conversions::*;
use crate::core::{YfClient, YfError};
use crate::history::wire::MetaNode;
use chrono_tz::Tz;
use paft::prelude::*;
use actions::extract_actions;
use adjust::cumulative_split_after;
use assemble::assemble_candles;
use fetch::fetch_chart;
#[derive(Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct HistoryBuilder {
#[doc(hidden)]
pub(crate) client: YfClient,
#[doc(hidden)]
pub(crate) symbol: String,
#[doc(hidden)]
pub(crate) range: Option<Range>,
#[doc(hidden)]
pub(crate) period: Option<(i64, i64)>,
#[doc(hidden)]
pub(crate) interval: Interval,
#[doc(hidden)]
pub(crate) auto_adjust: bool,
#[doc(hidden)]
pub(crate) include_prepost: bool,
#[doc(hidden)]
pub(crate) include_actions: bool,
#[doc(hidden)]
pub(crate) keepna: bool,
#[doc(hidden)]
pub(crate) cache_mode: CacheMode,
#[doc(hidden)]
pub(crate) retry_override: Option<RetryConfig>,
}
impl HistoryBuilder {
pub fn new(client: &YfClient, symbol: impl Into<String>) -> Self {
Self {
client: client.clone(),
symbol: symbol.into(),
range: Some(Range::M6),
period: None,
interval: Interval::D1,
auto_adjust: true,
include_prepost: false,
include_actions: true,
keepna: false,
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
}
#[must_use]
pub const fn range(mut self, range: Range) -> Self {
self.period = None;
self.range = Some(range);
self
}
#[must_use]
pub const fn between(
mut self,
start: chrono::DateTime<chrono::Utc>,
end: chrono::DateTime<chrono::Utc>,
) -> Self {
self.range = None;
self.period = Some((start.timestamp(), end.timestamp()));
self
}
#[must_use]
pub const fn interval(mut self, interval: Interval) -> Self {
self.interval = interval;
self
}
#[must_use]
pub const fn auto_adjust(mut self, yes: bool) -> Self {
self.auto_adjust = yes;
self
}
#[must_use]
pub const fn prepost(mut self, yes: bool) -> Self {
self.include_prepost = yes;
self
}
#[must_use]
pub const fn actions(mut self, yes: bool) -> Self {
self.include_actions = yes;
self
}
#[must_use]
pub const fn keepna(mut self, yes: bool) -> Self {
self.keepna = yes;
self
}
pub async fn fetch(self) -> Result<Vec<Candle>, YfError> {
let resp = self.fetch_full().await?;
Ok(resp.candles)
}
pub async fn fetch_full(self) -> Result<HistoryResponse, YfError> {
let fetched = fetch_chart(
&self.client,
&self.symbol,
self.range,
self.period,
self.interval,
self.include_actions,
self.include_prepost,
self.cache_mode,
self.retry_override.as_ref(),
)
.await?;
let (mut actions_out, split_events) = extract_actions(fetched.events.as_ref());
let cum_split_after = cumulative_split_after(&fetched.ts, &split_events);
let currency = fetched.meta.as_ref().and_then(|m| m.currency.as_deref());
let (candles, raw_close) = assemble_candles(
&fetched.ts,
&fetched.quote,
&fetched.adjclose,
self.auto_adjust,
self.keepna,
&cum_split_after,
currency,
);
actions_out.sort_by_key(|a| match a {
Action::Dividend { ts, .. }
| Action::Split { ts, .. }
| Action::CapitalGain { ts, .. } => ts.timestamp(),
});
let meta_out = map_meta(fetched.meta.as_ref());
Ok(HistoryResponse {
candles,
actions: actions_out,
adjusted: self.auto_adjust,
meta: meta_out,
unadjusted_close: Some(
raw_close
.into_iter()
.map(|price| f64_to_money_with_currency_str(price, currency))
.collect(),
),
})
}
}
fn map_meta(m: Option<&MetaNode>) -> Option<HistoryMeta> {
m.as_ref().map(|mm| HistoryMeta {
timezone: mm
.timezone
.as_ref()
.and_then(|tz_str| tz_str.parse::<Tz>().ok()),
utc_offset_seconds: mm.gmtoffset,
})
}