type Ohlcv5 = (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>);
pub fn volume_bars(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
volume_threshold: f64,
) -> Ohlcv5 {
assert!(volume_threshold > 0.0, "volume_threshold must be > 0");
let n = open.len();
assert!(n > 0, "input arrays must be non-empty");
assert!(
high.len() == n && low.len() == n && close.len() == n && volume.len() == n,
"all input arrays must have equal length"
);
let mut out_open: Vec<f64> = Vec::new();
let mut out_high: Vec<f64> = Vec::new();
let mut out_low: Vec<f64> = Vec::new();
let mut out_close: Vec<f64> = Vec::new();
let mut out_vol: Vec<f64> = Vec::new();
let mut bar_open = open[0];
let mut bar_high = high[0];
let mut bar_low = low[0];
let mut bar_close = close[0];
let mut bar_vol = volume[0];
for i in 1..n {
bar_high = bar_high.max(high[i]);
bar_low = bar_low.min(low[i]);
bar_close = close[i];
bar_vol += volume[i];
if bar_vol >= volume_threshold {
out_open.push(bar_open);
out_high.push(bar_high);
out_low.push(bar_low);
out_close.push(bar_close);
out_vol.push(bar_vol);
if i + 1 < n {
bar_open = open[i + 1];
bar_high = high[i + 1];
bar_low = low[i + 1];
bar_close = close[i + 1];
bar_vol = volume[i + 1];
}
}
}
if bar_vol > 0.0 && out_vol.last().is_none_or(|&last| last != bar_vol) {
out_open.push(bar_open);
out_high.push(bar_high);
out_low.push(bar_low);
out_close.push(bar_close);
out_vol.push(bar_vol);
}
(out_open, out_high, out_low, out_close, out_vol)
}
pub fn ohlcv_agg(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
labels: &[i64],
) -> Ohlcv5 {
let n = open.len();
assert!(n > 0, "input arrays must be non-empty");
assert!(
high.len() == n
&& low.len() == n
&& close.len() == n
&& volume.len() == n
&& labels.len() == n,
"all input arrays must have equal length"
);
let mut out_open: Vec<f64> = Vec::new();
let mut out_high: Vec<f64> = Vec::new();
let mut out_low: Vec<f64> = Vec::new();
let mut out_close: Vec<f64> = Vec::new();
let mut out_vol: Vec<f64> = Vec::new();
let mut cur_label = labels[0];
let mut bar_open = open[0];
let mut bar_high = high[0];
let mut bar_low = low[0];
let mut bar_close = close[0];
let mut bar_vol = volume[0];
for i in 1..n {
if labels[i] != cur_label {
out_open.push(bar_open);
out_high.push(bar_high);
out_low.push(bar_low);
out_close.push(bar_close);
out_vol.push(bar_vol);
cur_label = labels[i];
bar_open = open[i];
bar_high = high[i];
bar_low = low[i];
bar_close = close[i];
bar_vol = volume[i];
} else {
bar_high = bar_high.max(high[i]);
bar_low = bar_low.min(low[i]);
bar_close = close[i];
bar_vol += volume[i];
}
}
out_open.push(bar_open);
out_high.push(bar_high);
out_low.push(bar_low);
out_close.push(bar_close);
out_vol.push(bar_vol);
(out_open, out_high, out_low, out_close, out_vol)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_volume_bars_basic() {
let o = [100.0, 101.0, 102.0, 103.0, 104.0];
let h = [105.0, 106.0, 107.0, 108.0, 109.0];
let l = [95.0, 96.0, 97.0, 98.0, 99.0];
let c = [101.0, 102.0, 103.0, 104.0, 105.0];
let v = [50.0, 60.0, 40.0, 70.0, 30.0];
let (ro, rh, rl, rc, rv) = volume_bars(&o, &h, &l, &c, &v, 100.0);
assert!(rv.len() >= 2);
assert!((rv[0] - 110.0).abs() < 1e-10);
assert!((ro[0] - 100.0).abs() < 1e-10);
assert!((rh[0] - 106.0).abs() < 1e-10);
assert!((rl[0] - 95.0).abs() < 1e-10);
assert!((rc[0] - 102.0).abs() < 1e-10);
}
#[test]
fn test_volume_bars_single_element() {
let (ro, rh, rl, rc, rv) = volume_bars(&[10.0], &[12.0], &[8.0], &[11.0], &[50.0], 100.0);
assert_eq!(rv.len(), 1);
assert!((rv[0] - 50.0).abs() < 1e-10);
assert!((ro[0] - 10.0).abs() < 1e-10);
}
#[test]
#[should_panic(expected = "volume_threshold must be > 0")]
fn test_volume_bars_zero_threshold() {
volume_bars(&[1.0], &[1.0], &[1.0], &[1.0], &[1.0], 0.0);
}
#[test]
#[should_panic(expected = "input arrays must be non-empty")]
fn test_volume_bars_empty() {
volume_bars(&[], &[], &[], &[], &[], 100.0);
}
#[test]
fn test_ohlcv_agg_basic() {
let o = [100.0, 101.0, 102.0, 103.0];
let h = [105.0, 106.0, 108.0, 109.0];
let l = [95.0, 96.0, 97.0, 98.0];
let c = [101.0, 102.0, 103.0, 104.0];
let v = [10.0, 20.0, 30.0, 40.0];
let labels: [i64; 4] = [0, 0, 1, 1];
let (ro, rh, rl, rc, rv) = ohlcv_agg(&o, &h, &l, &c, &v, &labels);
assert_eq!(ro.len(), 2);
assert!((ro[0] - 100.0).abs() < 1e-10);
assert!((rh[0] - 106.0).abs() < 1e-10);
assert!((rl[0] - 95.0).abs() < 1e-10);
assert!((rc[0] - 102.0).abs() < 1e-10);
assert!((rv[0] - 30.0).abs() < 1e-10);
assert!((ro[1] - 102.0).abs() < 1e-10);
assert!((rh[1] - 109.0).abs() < 1e-10);
assert!((rl[1] - 97.0).abs() < 1e-10);
assert!((rc[1] - 104.0).abs() < 1e-10);
assert!((rv[1] - 70.0).abs() < 1e-10);
}
#[test]
fn test_ohlcv_agg_single_group() {
let o = [100.0, 101.0];
let h = [105.0, 106.0];
let l = [95.0, 96.0];
let c = [101.0, 102.0];
let v = [10.0, 20.0];
let labels: [i64; 2] = [0, 0];
let (ro, rh, rl, rc, rv) = ohlcv_agg(&o, &h, &l, &c, &v, &labels);
assert_eq!(ro.len(), 1);
assert!((rv[0] - 30.0).abs() < 1e-10);
}
#[test]
fn test_ohlcv_agg_each_bar_own_group() {
let o = [100.0, 101.0, 102.0];
let h = [105.0, 106.0, 107.0];
let l = [95.0, 96.0, 97.0];
let c = [101.0, 102.0, 103.0];
let v = [10.0, 20.0, 30.0];
let labels: [i64; 3] = [0, 1, 2];
let (ro, _rh, _rl, _rc, rv) = ohlcv_agg(&o, &h, &l, &c, &v, &labels);
assert_eq!(ro.len(), 3);
assert!((rv[0] - 10.0).abs() < 1e-10);
assert!((rv[1] - 20.0).abs() < 1e-10);
assert!((rv[2] - 30.0).abs() < 1e-10);
}
#[test]
#[should_panic(expected = "input arrays must be non-empty")]
fn test_ohlcv_agg_empty() {
ohlcv_agg(&[], &[], &[], &[], &[], &[]);
}
}