use crate::aes::Aesthetic;
use crate::data::{DataFrame, Value};
use crate::scale::ScaleSet;
use super::Stat;
#[derive(Clone, Copy, Debug, Default)]
pub struct StatHeikinAshi;
pub fn heikin_ashi(ohlc: &[(f64, f64, f64, f64)]) -> Vec<(f64, f64, f64, f64)> {
let mut out = Vec::with_capacity(ohlc.len());
let mut prev: Option<(f64, f64)> = None;
for &(o, h, l, c) in ohlc {
let close = (o + h + l + c) / 4.0;
let open = match prev {
Some((po, pc)) => (po + pc) / 2.0,
None => (o + c) / 2.0,
};
out.push((open, h.max(open).max(close), l.min(open).min(close), close));
prev = Some((open, close));
}
out
}
impl Stat for StatHeikinAshi {
fn compute_group(&self, data: &DataFrame, _scales: &ScaleSet) -> DataFrame {
let cols: Option<[&[Value]; 5]> = (|| {
Some([
data.column("x")?,
data.column("open")?,
data.column("high")?,
data.column("low")?,
data.column("close")?,
])
})();
let Some([x, o, h, l, c]) = cols else {
return DataFrame::new();
};
let key = |v: &Value| v.as_f64().unwrap_or(f64::NAN);
let mut order: Vec<usize> = (0..data.nrows())
.filter(|&i| {
[&o[i], &h[i], &l[i], &c[i]]
.iter()
.all(|v| v.as_f64().is_some())
})
.collect();
order.sort_by(|&a, &b| key(&x[a]).total_cmp(&key(&x[b])));
let ha = heikin_ashi(
&order
.iter()
.map(|&i| (key(&o[i]), key(&h[i]), key(&l[i]), key(&c[i])))
.collect::<Vec<_>>(),
);
let mut out = DataFrame::new();
for name in data.column_names() {
let vals: Vec<Value> = match name {
"open" => ha.iter().map(|v| Value::Float(v.0)).collect(),
"high" => ha.iter().map(|v| Value::Float(v.1)).collect(),
"low" => ha.iter().map(|v| Value::Float(v.2)).collect(),
"close" => ha.iter().map(|v| Value::Float(v.3)).collect(),
_ => {
let col = data.column(name).unwrap_or_default();
order.iter().map(|&i| col[i].clone()).collect()
}
};
out.add_column(name.to_string(), vals);
}
out
}
fn required_aes(&self) -> Vec<Aesthetic> {
vec![
Aesthetic::X,
Aesthetic::Open,
Aesthetic::High,
Aesthetic::Low,
Aesthetic::Close,
]
}
fn panelwise(&self) -> bool {
true
}
fn name(&self) -> &str {
"heikin_ashi"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_the_reference_formulas() {
let ha = heikin_ashi(&[(10.0, 12.0, 9.0, 11.0), (11.0, 13.0, 10.0, 12.0)]);
assert_eq!(ha[0], (10.5, 12.0, 9.0, 10.5));
assert_eq!(ha[1], (10.5, 13.0, 10.0, 11.5));
}
}