From 7083139b40862e5958cbbd7eff5d2c17a5196c54 Mon Sep 17 00:00:00 2001
From: gord chung <gord@live.ca>
Date: Tue, 14 May 2024 11:46:08 -0400
Subject: [PATCH] add swing index and acc swing index

---
 src/indicator.rs        | 53 +++++++++++++++++++++++++
 src/main.rs             |  8 +++-
 tests/indicator_test.rs | 88 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 148 insertions(+), 1 deletion(-)

diff --git a/src/indicator.rs b/src/indicator.rs
index 67b6eea..6411844 100644
--- a/src/indicator.rs
+++ b/src/indicator.rs
@@ -466,3 +466,56 @@ pub fn pgo(high: &[f64], low: &[f64], close: &[f64], window: u8) -> Vec<f64> {
     .map(|(c, c_ma, tr_ma)| (c - c_ma) / tr_ma)
     .collect::<Vec<f64>>()
 }
+
+fn _swing<'a>(
+    open: &'a [f64],
+    high: &'a [f64],
+    low: &'a [f64],
+    close: &'a [f64],
+    limit: f64,
+) -> impl Iterator<Item = f64> + 'a {
+    izip!(
+        &open[..open.len() - 1],
+        &open[1..],
+        &high[1..],
+        &low[1..],
+        &close[..close.len() - 1],
+        &close[1..],
+    )
+    .map(move |(prevo, o, h, l, prevc, c)| {
+        let r1 = (h - prevc).abs();
+        let r2 = (l - prevc).abs();
+        let r3 = h - l;
+        let r4 = (prevc - prevo).abs() / 4.0;
+        let max_r = r1.max(r2).max(r3);
+        let r = if r1 == max_r {
+            r1 - r2 / 2.0 + r4
+        } else if r2 == max_r {
+            r2 - r1 / 2.0 + r4
+        } else {
+            r3 + r4
+        };
+        // does not use formula described in investopedia as it appears to be wrong?
+        // it seems to overweight previous period's values
+        ((c - prevc + (c - o) / 2.0 + (prevc - prevo) / 4.0) / r) * 50.0 * r1.max(r2) / limit
+    })
+}
+
+/// Swing Index
+/// https://www.investopedia.com/terms/a/asi.asp
+/// https://quantstrategy.io/blog/accumulative-swing-index-how-to-trade/
+pub fn si(open: &[f64], high: &[f64], low: &[f64], close: &[f64], limit: f64) -> Vec<f64> {
+    _swing(open, high, low, close, limit).collect::<Vec<f64>>()
+}
+
+/// Accumulative Swing Index
+/// https://www.investopedia.com/terms/a/asi.asp
+/// https://quantstrategy.io/blog/accumulative-swing-index-how-to-trade/
+pub fn asi(open: &[f64], high: &[f64], low: &[f64], close: &[f64], limit: f64) -> Vec<f64> {
+    _swing(open, high, low, close, limit)
+        .scan(0.0, |acc, x| {
+            *acc += x;
+            Some(*acc)
+        })
+        .collect::<Vec<f64>>()
+}
diff --git a/src/main.rs b/src/main.rs
index cd291e6..8308d2d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,5 +16,11 @@ fn main() {
     let data = fs::read_to_string("./tests/rddt.input").expect("Unable to read file");
     let stats: SecStats = serde_json::from_str(&data).expect("JSON does not have correct format.");
 
-    dbg!(indicator::pgo(&stats.high, &stats.low, &stats.close, 16,));
+    dbg!(indicator::si(
+        &stats.open,
+        &stats.high,
+        &stats.low,
+        &stats.close,
+        0.5
+    ));
 }
diff --git a/tests/indicator_test.rs b/tests/indicator_test.rs
index ccbb54e..94581cf 100644
--- a/tests/indicator_test.rs
+++ b/tests/indicator_test.rs
@@ -755,3 +755,91 @@ fn test_pgo() {
         result
     );
 }
+
+#[test]
+fn test_si() {
+    let stats = common::test_data();
+    let result = indicator::si(&stats.open, &stats.high, &stats.low, &stats.close, 0.5);
+    assert_eq!(
+        vec![
+            1863.9824746176116,
+            654.8878036623194,
+            -1104.4095193965052,
+            -1214.2944568105527,
+            -487.3407354098372,
+            427.5088169169998,
+            -223.860381128408,
+            -110.7121065452446,
+            162.75807618131765,
+            -98.12145222473158,
+            -101.13846512002326,
+            -403.1845902647818,
+            283.1891569876117,
+            -199.95462669308907,
+            -326.86640146468073,
+            63.131576090683794,
+            -253.67664708736052,
+            215.41843387212208,
+            2.1629182406584904,
+            143.69919207899204,
+            115.4626169352983,
+            51.354553395932655,
+            -18.304465830729768,
+            429.0203486740835,
+            141.26847935246593,
+            -183.79992761728568,
+            160.03660663696868,
+            235.8667734660081,
+            -90.16470772181825,
+            162.16835332629032,
+            144.83405393167723,
+            59.009650555611834,
+            -321.26018448483165,
+        ],
+        result
+    );
+}
+
+#[test]
+fn test_asi() {
+    let stats = common::test_data();
+    let result = indicator::asi(&stats.open, &stats.high, &stats.low, &stats.close, 0.5);
+    assert_eq!(
+        vec![
+            1863.9824746176116,
+            2518.870278279931,
+            1414.4607588834258,
+            200.16630207287312,
+            -287.17443333696406,
+            140.33438358003576,
+            -83.52599754837223,
+            -194.2381040936168,
+            -31.480027912299164,
+            -129.60148013703076,
+            -230.73994525705402,
+            -633.9245355218358,
+            -350.7353785342241,
+            -550.6900052273131,
+            -877.5564066919939,
+            -814.4248306013101,
+            -1068.1014776886707,
+            -852.6830438165487,
+            -850.5201255758901,
+            -706.8209334968981,
+            -591.3583165615997,
+            -540.0037631656671,
+            -558.3082289963969,
+            -129.2878803223134,
+            11.980599030152518,
+            -171.81932858713316,
+            -11.782721950164472,
+            224.08405151584364,
+            133.9193437940254,
+            296.08769712031574,
+            440.92175105199294,
+            499.93140160760476,
+            178.6712171227731,
+        ],
+        result
+    );
+}