Files
raptorbt/src/indicators/rolling.rs
T
vatsal 4d0d4cbfa3 feat: update version to 0.2.1 and add rolling min/max indicators (#6)
* feat: add session tracking and multi-leg spread backtesting

Add SessionTracker for trading session management:                             - Market hours detection (pre-open, trading, squareoff, post-close)
 - Session boundary tracking with configurable timezone    - Squareoff time support for intraday strategies                                   - Session high/low/open price tracking

Add SpreadBacktest for multi-leg options strategies:                                  - Support for straddles, strangles, vertical spreads, iron condors                                      - Coordinated entry/exit across all legs                                             - Net premium P&L calculation with max loss/target profit exits                                            - Helper functions for common spread configurations

Extend StreamingMetrics for backtest integration:                                - Add equity and drawdown tracking (update_equity, current_drawdown_pct)           - Add trade recording (record_trade, record_fees)                              - Add finalize() method to produce BacktestMetrics                        - Add with_initial_capital() constructor

Bump version to 0.2.0.

* chore: bump up version to 0.2.0

* feat: update version to 0.2.1 and add rolling min/max indicators

* fix: formatting
2026-02-02 04:24:45 +05:30

107 lines
3.3 KiB
Rust

//! Rolling min/max indicators for LLV/HHV support.
//!
//! Provides rolling minimum and maximum calculations for Lowest Low Value (LLV)
//! and Highest High Value (HHV) expressions.
use crate::core::error::RaptorError;
/// Calculate rolling minimum (Lowest Low Value) over a period.
///
/// Returns NaN for the first (period - 1) values where insufficient data exists.
///
/// # Arguments
/// * `data` - Input data slice
/// * `period` - Lookback period
///
/// # Returns
/// Vec of rolling minimum values
pub fn rolling_min(data: &[f64], period: usize) -> Result<Vec<f64>, RaptorError> {
if period == 0 {
return Err(RaptorError::invalid_parameter("period must be at least 1"));
}
let n = data.len();
let mut result = vec![f64::NAN; n];
for i in (period - 1)..n {
let start = i + 1 - period;
let min_val =
data[start..=i]
.iter()
.fold(f64::INFINITY, |a, &b| if b.is_nan() { a } else { a.min(b) });
result[i] = if min_val == f64::INFINITY { f64::NAN } else { min_val };
}
Ok(result)
}
/// Calculate rolling maximum (Highest High Value) over a period.
///
/// Returns NaN for the first (period - 1) values where insufficient data exists.
///
/// # Arguments
/// * `data` - Input data slice
/// * `period` - Lookback period
///
/// # Returns
/// Vec of rolling maximum values
pub fn rolling_max(data: &[f64], period: usize) -> Result<Vec<f64>, RaptorError> {
if period == 0 {
return Err(RaptorError::invalid_parameter("period must be at least 1"));
}
let n = data.len();
let mut result = vec![f64::NAN; n];
for i in (period - 1)..n {
let start = i + 1 - period;
let max_val =
data[start..=i]
.iter()
.fold(f64::NEG_INFINITY, |a, &b| if b.is_nan() { a } else { a.max(b) });
result[i] = if max_val == f64::NEG_INFINITY { f64::NAN } else { max_val };
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rolling_min() {
let data = vec![5.0, 3.0, 8.0, 2.0, 7.0, 1.0, 9.0];
let result = rolling_min(&data, 3).unwrap();
assert!(result[0].is_nan());
assert!(result[1].is_nan());
assert!((result[2] - 3.0).abs() < f64::EPSILON); // min(5, 3, 8)
assert!((result[3] - 2.0).abs() < f64::EPSILON); // min(3, 8, 2)
assert!((result[4] - 2.0).abs() < f64::EPSILON); // min(8, 2, 7)
assert!((result[5] - 1.0).abs() < f64::EPSILON); // min(2, 7, 1)
assert!((result[6] - 1.0).abs() < f64::EPSILON); // min(7, 1, 9)
}
#[test]
fn test_rolling_max() {
let data = vec![5.0, 3.0, 8.0, 2.0, 7.0, 1.0, 9.0];
let result = rolling_max(&data, 3).unwrap();
assert!(result[0].is_nan());
assert!(result[1].is_nan());
assert!((result[2] - 8.0).abs() < f64::EPSILON); // max(5, 3, 8)
assert!((result[3] - 8.0).abs() < f64::EPSILON); // max(3, 8, 2)
assert!((result[4] - 8.0).abs() < f64::EPSILON); // max(8, 2, 7)
assert!((result[5] - 7.0).abs() < f64::EPSILON); // max(2, 7, 1)
assert!((result[6] - 9.0).abs() < f64::EPSILON); // max(7, 1, 9)
}
#[test]
fn test_invalid_period() {
let data = vec![1.0, 2.0, 3.0];
assert!(rolling_min(&data, 0).is_err());
assert!(rolling_max(&data, 0).is_err());
}
}