use std::str::FromStr;
use rust_decimal::Decimal;
use crate::models::ClosingPrice;
use crate::util::{shamsi_month_key, shamsi_week_key};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Period {
#[default]
Daily,
Weekly,
Monthly,
}
fn dec(s: &str) -> Decimal {
Decimal::from_str(s).unwrap_or(Decimal::ZERO)
}
fn sum_field<'a, I>(values: I) -> String
where
I: IntoIterator<Item = &'a str>,
{
let total: Decimal = values.into_iter().map(dec).sum();
total.normalize().to_string()
}
fn aggregate(rows: &[ClosingPrice]) -> ClosingPrice {
let first = &rows[0];
let last = &rows[rows.len() - 1];
let mut high = dec(&first.price_max);
let mut low = dec(&first.price_min);
for r in &rows[1..] {
let h = dec(&r.price_max);
if h > high {
high = h;
}
let l = dec(&r.price_min);
if l < low {
low = l;
}
}
ClosingPrice {
ins_code: first.ins_code.clone(),
deven: last.deven.clone(),
pclosing: last.pclosing.clone(),
pdr_cot_val: last.pdr_cot_val.clone(),
ztot_tran: sum_field(rows.iter().map(|r| r.ztot_tran.as_str())),
qtot_tran5j: sum_field(rows.iter().map(|r| r.qtot_tran5j.as_str())),
qtot_cap: sum_field(rows.iter().map(|r| r.qtot_cap.as_str())),
price_min: low.normalize().to_string(),
price_max: high.normalize().to_string(),
price_yesterday: first.price_yesterday.clone(),
price_first: first.price_first.clone(),
}
}
pub fn group(prices: &[ClosingPrice], period: Period) -> Vec<ClosingPrice> {
if period == Period::Daily || prices.is_empty() {
return prices.to_vec();
}
let key_of = |p: &ClosingPrice| -> Option<String> {
match period {
Period::Weekly => shamsi_week_key(&p.deven),
Period::Monthly => shamsi_month_key(&p.deven),
Period::Daily => unreachable!(),
}
};
let mut out: Vec<ClosingPrice> = Vec::new();
let mut current_key: Option<String> = None;
let mut bucket: Vec<ClosingPrice> = Vec::new();
for p in prices {
let Some(key) = key_of(p) else {
continue;
};
match ¤t_key {
Some(k) if *k == key => bucket.push(p.clone()),
Some(_) => {
out.push(aggregate(&bucket));
bucket.clear();
bucket.push(p.clone());
current_key = Some(key);
}
None => {
current_key = Some(key);
bucket.push(p.clone());
}
}
}
if !bucket.is_empty() {
out.push(aggregate(&bucket));
}
out
}