pub(crate) mod response;
use super::chart::ChartMeta;
use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Spark {
pub symbol: String,
pub meta: ChartMeta,
pub timestamps: Vec<i64>,
pub closes: Vec<f64>,
pub interval: Option<String>,
pub range: Option<String>,
}
impl Spark {
pub(crate) fn from_response(
result: &response::SparkSymbolResult,
interval: Option<String>,
range: Option<String>,
) -> Option<Self> {
let data = result.response.first()?;
let timestamps = data.timestamp.clone().unwrap_or_default();
let closes: Vec<f64> = data
.indicators
.quote
.first()
.and_then(|q| q.close.as_ref())
.map(|prices| prices.iter().filter_map(|&p| p).collect())
.unwrap_or_default();
Some(Self {
symbol: result.symbol.clone(),
meta: data.meta.clone(),
timestamps,
closes,
interval,
range,
})
}
pub fn len(&self) -> usize {
self.closes.len()
}
pub fn is_empty(&self) -> bool {
self.closes.is_empty()
}
pub fn price_change(&self) -> Option<f64> {
if self.closes.len() < 2 {
return None;
}
let first = self.closes.first()?;
let last = self.closes.last()?;
Some(last - first)
}
pub fn percent_change(&self) -> Option<f64> {
if self.closes.len() < 2 {
return None;
}
let first = self.closes.first()?;
let last = self.closes.last()?;
if *first == 0.0 {
return None;
}
Some(((last - first) / first) * 100.0)
}
pub fn min_close(&self) -> Option<f64> {
self.closes.iter().copied().reduce(f64::min)
}
pub fn max_close(&self) -> Option<f64> {
self.closes.iter().copied().reduce(f64::max)
}
}