use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Debug, Clone)]
pub struct AccumulativeSwingIndex {
limit_move: f64,
prev_open: f64,
prev_high: f64,
prev_low: f64,
prev_close: f64,
swing_index: f64,
asi: f64,
bars_count: usize,
is_ready: bool,
}
impl Default for AccumulativeSwingIndex {
fn default() -> Self {
Self::new()
}
}
impl AccumulativeSwingIndex {
pub fn new() -> Self {
Self::with_limit_move(0.0) }
pub fn with_limit_move(limit_move: f64) -> Self {
Self {
limit_move: limit_move.max(0.0),
prev_open: 0.0,
prev_high: 0.0,
prev_low: 0.0,
prev_close: 0.0,
swing_index: 0.0,
asi: 0.0,
bars_count: 0,
is_ready: false,
}
}
#[inline]
pub fn reset(&mut self) {
self.prev_open = 0.0;
self.prev_high = 0.0;
self.prev_low = 0.0;
self.prev_close = 0.0;
self.swing_index = 0.0;
self.asi = 0.0;
self.bars_count = 0;
self.is_ready = false;
}
#[inline]
pub fn is_ready(&self) -> bool {
self.is_ready
}
#[inline]
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.asi)
}
pub fn update_bar(&mut self, open: f64, high: f64, low: f64, close: f64, _volume: f64) -> f64 {
self.bars_count += 1;
if self.bars_count < 2 {
self.prev_open = open;
self.prev_high = high;
self.prev_low = low;
self.prev_close = close;
return self.asi;
}
let cy = self.prev_close; let oy = self.prev_open; let c = close; let o = open; let h = high; let l = low;
let k = (h - cy).abs().max((l - cy).abs());
let hl = h - l;
let h_cy = (h - cy).abs();
let l_cy = (l - cy).abs();
let r = if h_cy >= l_cy && h_cy >= hl {
h_cy + 0.5 * l_cy + 0.25 * (cy - oy).abs()
} else if l_cy >= h_cy && l_cy >= hl {
l_cy + 0.5 * h_cy + 0.25 * (cy - oy).abs()
} else {
hl + 0.25 * (cy - oy).abs()
};
let t = if self.limit_move > 0.0 {
self.limit_move
} else {
close * 0.03
}.max(1e-10);
if r > 1e-10 {
let numerator = (cy - c) + 0.5 * (cy - oy) + 0.25 * (c - o);
self.swing_index = 50.0 * numerator / r * k / t;
self.swing_index = self.swing_index.clamp(-100.0, 100.0);
} else {
self.swing_index = 0.0;
}
self.asi += self.swing_index;
self.prev_open = open;
self.prev_high = high;
self.prev_low = low;
self.prev_close = close;
if self.bars_count >= 2 {
self.is_ready = true;
}
self.asi
}
pub fn swing_index(&self) -> f64 {
self.swing_index
}
pub fn limit_move(&self) -> f64 {
self.limit_move
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_asi_creation() {
let asi = AccumulativeSwingIndex::new();
assert!(!asi.is_ready());
assert_eq!(asi.value().main(), 0.0);
}
#[test]
fn test_asi_with_limit_move() {
let asi = AccumulativeSwingIndex::with_limit_move(5.0);
assert_eq!(asi.limit_move(), 5.0);
}
#[test]
fn test_asi_warmup() {
let mut asi = AccumulativeSwingIndex::new();
asi.update_bar(100.0, 102.0, 99.0, 101.0, 1000.0);
assert!(!asi.is_ready());
asi.update_bar(101.0, 103.0, 100.0, 102.0, 1000.0);
assert!(asi.is_ready());
}
#[test]
fn test_asi_uptrend() {
let mut asi = AccumulativeSwingIndex::new();
for i in 0..20 {
let base = 100.0 + i as f64 * 2.0;
let open = base;
let high = base + 2.5;
let low = base - 0.5;
let close = base + 2.0; asi.update_bar(open, high, low, close, 1000.0);
}
assert!(asi.is_ready());
}
#[test]
fn test_asi_downtrend() {
let mut asi = AccumulativeSwingIndex::new();
for i in 0..20 {
let base = 200.0 - i as f64 * 2.0;
let open = base;
let high = base + 0.5;
let low = base - 2.5;
let close = base - 2.0; asi.update_bar(open, high, low, close, 1000.0);
}
assert!(asi.is_ready());
}
#[test]
fn test_asi_values_finite() {
let mut asi = AccumulativeSwingIndex::new();
for i in 0..30 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
let value = asi.update_bar(price, price + 1.0, price - 1.0, price + 0.5, 1000.0);
assert!(value.is_finite(), "ASI value should be finite");
}
}
#[test]
fn test_asi_swing_index() {
let mut asi = AccumulativeSwingIndex::new();
asi.update_bar(100.0, 102.0, 99.0, 101.0, 1000.0);
assert_eq!(asi.swing_index(), 0.0);
asi.update_bar(101.0, 104.0, 100.0, 103.0, 1000.0);
assert!(asi.swing_index().is_finite());
}
#[test]
fn test_asi_reset() {
let mut asi = AccumulativeSwingIndex::new();
for i in 0..10 {
asi.update_bar(100.0 + i as f64, 105.0, 95.0, 101.0, 1000.0);
}
assert!(asi.is_ready());
asi.reset();
assert!(!asi.is_ready());
assert_eq!(asi.value().main(), 0.0);
assert_eq!(asi.swing_index(), 0.0);
}
}