finance_query/models/spark/
mod.rs1pub(crate) mod response;
7
8use super::chart::ChartMeta;
9use serde::{Deserialize, Serialize};
10
11#[non_exhaustive]
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct Spark {
21 pub symbol: String,
23 pub meta: ChartMeta,
25 pub timestamps: Vec<i64>,
27 pub closes: Vec<f64>,
29 pub interval: Option<String>,
31 pub range: Option<String>,
33}
34
35impl Spark {
36 pub(crate) fn from_response(
38 result: &response::SparkSymbolResult,
39 interval: Option<String>,
40 range: Option<String>,
41 ) -> Option<Self> {
42 let data = result.response.first()?;
43
44 let timestamps = data.timestamp.clone().unwrap_or_default();
45
46 let closes: Vec<f64> = data
48 .indicators
49 .quote
50 .first()
51 .and_then(|q| q.close.as_ref())
52 .map(|prices| prices.iter().filter_map(|&p| p).collect())
53 .unwrap_or_default();
54
55 Some(Self {
56 symbol: result.symbol.clone(),
57 meta: data.meta.clone(),
58 timestamps,
59 closes,
60 interval,
61 range,
62 })
63 }
64
65 pub fn len(&self) -> usize {
67 self.closes.len()
68 }
69
70 pub fn is_empty(&self) -> bool {
72 self.closes.is_empty()
73 }
74
75 pub fn price_change(&self) -> Option<f64> {
77 if self.closes.len() < 2 {
78 return None;
79 }
80 let first = self.closes.first()?;
81 let last = self.closes.last()?;
82 Some(last - first)
83 }
84
85 pub fn percent_change(&self) -> Option<f64> {
87 if self.closes.len() < 2 {
88 return None;
89 }
90 let first = self.closes.first()?;
91 let last = self.closes.last()?;
92 if *first == 0.0 {
93 return None;
94 }
95 Some(((last - first) / first) * 100.0)
96 }
97
98 pub fn min_close(&self) -> Option<f64> {
100 self.closes.iter().copied().reduce(f64::min)
101 }
102
103 pub fn max_close(&self) -> Option<f64> {
105 self.closes.iter().copied().reduce(f64::max)
106 }
107}