Files
ferro-ta/crates/ferro_ta_core/src/chunked.rs
T
Pratik Bhadane 436954138f chore: prepare v1.1.0 release
Update version numbers across Rust, Python, and documentation files to 1.1.0. Enhance the .gitignore to include macOS dSYM files and plans directory. Introduce new dependencies in the Rust core library and update the README to reflect recent performance benchmarks and backtesting engine capabilities. Add new artifacts to the benchmarks manifest and improve documentation for the backtesting engine API.
2026-03-30 12:45:52 +05:30

124 lines
3.6 KiB
Rust

//! Chunked / out-of-core execution helpers.
//!
//! - `trim_overlap` — remove the first N elements from a slice
//! - `stitch_chunks` — concatenate trimmed chunk results
//! - `make_chunk_ranges` — compute (start, end) index pairs for chunked processing
//! - `forward_fill_nan` — forward-fill NaN values
/// Remove the first `overlap` elements from a slice.
pub fn trim_overlap(chunk_out: &[f64], overlap: usize) -> Vec<f64> {
if overlap > chunk_out.len() {
return vec![];
}
chunk_out[overlap..].to_vec()
}
/// Concatenate a list of slices into a single Vec.
pub fn stitch_chunks(chunks: &[&[f64]]) -> Vec<f64> {
let mut out = Vec::new();
for &chunk in chunks {
out.extend_from_slice(chunk);
}
out
}
/// Compute (start, end) index pairs for chunked processing.
///
/// Returns a flat Vec of pairs: [start0, end0, start1, end1, ...].
/// `chunk_size` is the desired output bars per chunk, `overlap` is the warm-up prefix.
pub fn make_chunk_ranges(n: usize, chunk_size: usize, overlap: usize) -> Vec<i64> {
if chunk_size == 0 || n == 0 {
return vec![];
}
let mut ranges: Vec<i64> = Vec::new();
let mut start: usize = 0;
loop {
let end = (start + chunk_size + overlap).min(n);
ranges.push(start as i64);
ranges.push(end as i64);
if end >= n {
break;
}
start = end.saturating_sub(overlap);
}
ranges
}
/// Forward-fill NaN values in a 1-D array.
/// Leading NaN values are preserved until the first non-NaN value appears.
pub fn forward_fill_nan(values: &[f64]) -> Vec<f64> {
let mut out = Vec::with_capacity(values.len());
let mut last = f64::NAN;
for &value in values {
if value.is_nan() {
out.push(last);
} else {
last = value;
out.push(value);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trim_overlap() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = trim_overlap(&data, 2);
assert_eq!(result, vec![3.0, 4.0, 5.0]);
}
#[test]
fn test_trim_overlap_zero() {
let data = vec![1.0, 2.0, 3.0];
assert_eq!(trim_overlap(&data, 0), data);
}
#[test]
fn test_trim_overlap_exceeds() {
let data = vec![1.0, 2.0];
assert!(trim_overlap(&data, 5).is_empty());
}
#[test]
fn test_stitch_chunks() {
let a = vec![1.0, 2.0];
let b = vec![3.0, 4.0, 5.0];
let chunks: Vec<&[f64]> = vec![&a, &b];
let result = stitch_chunks(&chunks);
assert_eq!(result, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn test_make_chunk_ranges() {
let ranges = make_chunk_ranges(10, 4, 2);
// Expected: [0,6], [4,10]
assert_eq!(ranges.len() % 2, 0);
assert!(ranges.len() >= 4);
assert_eq!(ranges[0], 0);
}
#[test]
fn test_forward_fill_nan() {
let data = vec![f64::NAN, 1.0, f64::NAN, f64::NAN, 2.0, f64::NAN];
let result = forward_fill_nan(&data);
assert!(result[0].is_nan()); // leading NaN preserved
assert!((result[1] - 1.0).abs() < 1e-10);
assert!((result[2] - 1.0).abs() < 1e-10); // filled
assert!((result[3] - 1.0).abs() < 1e-10); // filled
assert!((result[4] - 2.0).abs() < 1e-10);
assert!((result[5] - 2.0).abs() < 1e-10); // filled
}
#[test]
fn test_empty() {
assert!(trim_overlap(&[], 0).is_empty());
assert!(stitch_chunks(&[]).is_empty());
assert!(make_chunk_ranges(0, 4, 2).is_empty());
assert!(forward_fill_nan(&[]).is_empty());
}
}