use crate::indicator::{Indicator, IndicatorOutput};
use crate::model::{Bar, BarValidationError};
#[derive(Debug, Clone, PartialEq)]
pub struct TimestampedOutput {
pub timestamp: i64,
pub output: Option<IndicatorOutput>,
}
pub fn run_batch<I: Indicator + ?Sized>(indicator: &mut I, bars: &[Bar]) -> Vec<TimestampedOutput> {
indicator.reset();
bars.iter()
.map(|bar| TimestampedOutput {
timestamp: bar.timestamp,
output: indicator.on_bar(bar),
})
.collect()
}
pub fn run_batch_checked<I: Indicator + ?Sized>(
indicator: &mut I,
bars: &[Bar],
) -> Result<Vec<TimestampedOutput>, (Vec<TimestampedOutput>, BarValidationError)> {
indicator.reset();
let mut series = Vec::with_capacity(bars.len());
for bar in bars {
match indicator.on_checked_bar(bar) {
Ok(output) => series.push(TimestampedOutput {
timestamp: bar.timestamp,
output,
}),
Err(err) => return Err((series, err)),
}
}
Ok(series)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicator::moving_averages::SmaEngine;
fn sample_bars(closes: &[f64]) -> Vec<Bar> {
closes
.iter()
.enumerate()
.map(|(i, &c)| {
let c = c + 100.0;
Bar::new((i as i64) * 60, c, c + 1.0, c - 1.0, c, 100.0)
})
.collect()
}
#[test]
fn test_run_batch_is_timestamp_aligned_and_warmup_aware() {
let bars = sample_bars(&[1.0, 2.0, 3.0, 4.0, 5.0]);
let mut sma = SmaEngine::new(3);
let series = run_batch(&mut sma, &bars);
assert_eq!(series.len(), bars.len());
for (entry, bar) in series.iter().zip(&bars) {
assert_eq!(entry.timestamp, bar.timestamp);
}
assert!(series[0].output.is_none());
assert!(series[1].output.is_none());
assert!(series[2].output.is_some());
assert_eq!(series[2].output.as_ref().unwrap().value, 102.0);
assert_eq!(series[4].output.as_ref().unwrap().value, 104.0);
}
#[test]
fn test_run_batch_is_deterministic_and_resets_prior_state() {
let bars = sample_bars(&[1.0, 2.0, 3.0, 4.0, 5.0]);
let mut sma = SmaEngine::new(3);
let first = run_batch(&mut sma, &bars);
let second = run_batch(&mut sma, &bars);
assert_eq!(first, second);
}
#[test]
fn test_run_batch_checked_stops_at_invalid_bar() {
let mut bars = sample_bars(&[10.0, 20.0]);
bars.push(Bar::new(120, f64::NAN, 1.0, -1.0, 1.0, 100.0));
bars.push(Bar::new(180, 3.0, 4.0, 2.0, 3.0, 100.0));
let mut sma = SmaEngine::new(2);
let result = run_batch_checked(&mut sma, &bars);
let (partial, err) = result.unwrap_err();
assert_eq!(partial.len(), 2);
assert_eq!(err, BarValidationError::NonFiniteValue);
}
}