use std::collections::VecDeque;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Series<T> {
capacity: usize,
buffer: VecDeque<T>,
}
impl<T> Series<T> {
pub fn new(capacity: usize) -> Self {
Self {
capacity: capacity.max(1),
buffer: VecDeque::with_capacity(capacity.max(1)),
}
}
pub fn push(&mut self, value: T) {
if self.buffer.len() >= self.capacity {
self.buffer.pop_back();
}
self.buffer.push_front(value);
}
pub fn get(&self, offset: usize) -> Option<&T> {
self.buffer.get(offset)
}
pub fn latest(&self) -> Option<&T> {
self.buffer.front()
}
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn reset(&mut self) {
self.buffer.clear();
}
pub fn barssince<F>(&self, predicate: F) -> Option<usize>
where
F: Fn(&T) -> bool,
{
for (i, val) in self.buffer.iter().enumerate() {
if predicate(val) {
return Some(i);
}
}
None
}
pub fn valuewhen<F>(&self, predicate: F, occurrence: usize) -> Option<&T>
where
F: Fn(&T) -> bool,
{
let mut count = 0;
for val in self.buffer.iter() {
if predicate(val) {
if count == occurrence {
return Some(val);
}
count += 1;
}
}
None
}
}
impl Series<f64> {
pub fn highest(&self, n: usize) -> Option<f64> {
if n == 0 || self.buffer.is_empty() {
return None;
}
self.buffer
.iter()
.take(n)
.copied()
.filter(|v| v.is_finite())
.fold(None, |max, val| match max {
None => Some(val),
Some(m) => Some(m.max(val)),
})
}
pub fn lowest(&self, n: usize) -> Option<f64> {
if n == 0 || self.buffer.is_empty() {
return None;
}
self.buffer
.iter()
.take(n)
.copied()
.filter(|v| v.is_finite())
.fold(None, |min, val| match min {
None => Some(val),
Some(m) => Some(m.min(val)),
})
}
pub fn highestbars(&self, n: usize) -> Option<usize> {
if n == 0 || self.buffer.is_empty() {
return None;
}
let mut max_val = f64::NEG_INFINITY;
let mut max_idx = None;
for (i, &val) in self.buffer.iter().take(n).enumerate() {
if val.is_finite() && val > max_val {
max_val = val;
max_idx = Some(i);
}
}
max_idx
}
pub fn lowestbars(&self, n: usize) -> Option<usize> {
if n == 0 || self.buffer.is_empty() {
return None;
}
let mut min_val = f64::INFINITY;
let mut min_idx = None;
for (i, &val) in self.buffer.iter().take(n).enumerate() {
if val.is_finite() && val < min_val {
min_val = val;
min_idx = Some(i);
}
}
min_idx
}
}
pub struct SeriesEvents;
impl SeriesEvents {
pub fn change(series: &Series<f64>) -> Option<f64> {
if let (Some(&curr), Some(&prev)) = (series.get(0), series.get(1)) {
Some(curr - prev)
} else {
None
}
}
pub fn rising(series: &Series<f64>, length: usize) -> bool {
if length == 0 || series.len() <= length {
return false;
}
for i in 0..length {
if let (Some(&curr), Some(&prev)) = (series.get(i), series.get(i + 1)) {
if curr <= prev {
return false;
}
} else {
return false;
}
}
true
}
pub fn falling(series: &Series<f64>, length: usize) -> bool {
if length == 0 || series.len() <= length {
return false;
}
for i in 0..length {
if let (Some(&curr), Some(&prev)) = (series.get(i), series.get(i + 1)) {
if curr >= prev {
return false;
}
} else {
return false;
}
}
true
}
pub fn cross(a: &Series<f64>, b: &Series<f64>) -> bool {
Self::crossover(a, b) || Self::crossunder(a, b)
}
pub fn crossover(a: &Series<f64>, b: &Series<f64>) -> bool {
if let (Some(&a0), Some(&a1), Some(&b0), Some(&b1)) =
(a.get(0), a.get(1), b.get(0), b.get(1))
{
a1 <= b1 && a0 > b0
} else {
false
}
}
pub fn crossunder(a: &Series<f64>, b: &Series<f64>) -> bool {
if let (Some(&a0), Some(&a1), Some(&b0), Some(&b1)) =
(a.get(0), a.get(1), b.get(0), b.get(1))
{
a1 >= b1 && a0 < b0
} else {
false
}
}
pub fn cum(series: &Series<f64>) -> f64 {
(0..series.len())
.filter_map(|i| series.get(i).copied())
.filter(|v| v.is_finite())
.sum()
}
pub fn value_when<'a, T>(
condition: &Series<bool>,
source: &'a Series<T>,
occurrence: usize,
) -> Option<&'a T> {
let mut seen = 0;
for offset in 0..condition.len().min(source.len()) {
if condition.get(offset) == Some(&true) {
if seen == occurrence {
return source.get(offset);
}
seen += 1;
}
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct CumulativeSum {
value: f64,
}
impl CumulativeSum {
pub fn update(&mut self, value: f64) -> f64 {
if value.is_finite() {
self.value += value;
}
self.value
}
pub fn value(&self) -> f64 {
self.value
}
pub fn reset(&mut self) {
self.value = 0.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_series_basic_lookback() {
let mut s = Series::new(5);
s.push(10.0);
s.push(20.0);
s.push(30.0);
assert_eq!(s.get(0), Some(&30.0));
assert_eq!(s.get(1), Some(&20.0));
assert_eq!(s.get(2), Some(&10.0));
assert_eq!(s.get(3), None);
assert_eq!(s.highest(3), Some(30.0));
assert_eq!(s.lowest(3), Some(10.0));
assert_eq!(s.highestbars(3), Some(0));
assert_eq!(s.lowestbars(3), Some(2));
}
#[test]
fn test_series_events_crossover() {
let mut a = Series::new(5);
let mut b = Series::new(5);
a.push(10.0);
b.push(15.0);
a.push(20.0);
b.push(15.0);
assert!(SeriesEvents::crossover(&a, &b));
assert!(!SeriesEvents::crossunder(&a, &b));
}
#[test]
fn value_when_uses_a_separate_condition_series() {
let mut condition = Series::new(4);
let mut source = Series::new(4);
for (matches, value) in [(true, 10), (false, 20), (true, 30)] {
condition.push(matches);
source.push(value);
}
assert_eq!(SeriesEvents::value_when(&condition, &source, 0), Some(&30));
assert_eq!(SeriesEvents::value_when(&condition, &source, 1), Some(&10));
}
#[test]
fn cumulative_sum_survives_a_small_lookback_capacity() {
let mut cumulative = CumulativeSum::default();
assert_eq!(cumulative.update(1.0), 1.0);
assert_eq!(cumulative.update(2.0), 3.0);
assert_eq!(cumulative.update(3.0), 6.0);
}
}