Skip to main content

finance_solution/stocks/ta/
ring.rs

1//! Fixed-capacity ring buffer for incremental TA windows (private helper).
2
3/// Ring of `f64` with optional running sum (SMA / RVOL / rolling VWAP).
4#[derive(Clone, Debug)]
5pub(crate) struct RingF64 {
6    buf: Vec<f64>,
7    /// Next write index.
8    head: usize,
9    /// Number of valid elements ≤ capacity.
10    len: usize,
11    sum: f64,
12}
13
14impl RingF64 {
15    pub(crate) fn with_capacity(cap: usize) -> Self {
16        debug_assert!(cap >= 1);
17        Self {
18            buf: vec![0.0; cap],
19            head: 0,
20            len: 0,
21            sum: 0.0,
22        }
23    }
24
25    pub(crate) fn capacity(&self) -> usize {
26        self.buf.len()
27    }
28
29    pub(crate) fn len(&self) -> usize {
30        self.len
31    }
32
33    pub(crate) fn is_full(&self) -> bool {
34        self.len == self.buf.len()
35    }
36
37    pub(crate) fn sum(&self) -> f64 {
38        self.sum
39    }
40
41    pub(crate) fn clear(&mut self) {
42        self.head = 0;
43        self.len = 0;
44        self.sum = 0.0;
45    }
46
47    /// Push value; if full, overwrites oldest and adjusts sum.
48    pub(crate) fn push(&mut self, value: f64) {
49        let cap = self.buf.len();
50        if self.len < cap {
51            self.buf[self.head] = value;
52            self.sum += value;
53            self.head = (self.head + 1) % cap;
54            self.len += 1;
55        } else {
56            let old = self.buf[self.head];
57            self.sum += value - old;
58            self.buf[self.head] = value;
59            self.head = (self.head + 1) % cap;
60        }
61    }
62
63    /// Logical order oldest → newest into `out` (cleared first).
64    pub(crate) fn copy_ordered(&self, out: &mut Vec<f64>) {
65        out.clear();
66        if self.len == 0 {
67            return;
68        }
69        let cap = self.buf.len();
70        let start = if self.len < cap { 0 } else { self.head };
71        for i in 0..self.len {
72            out.push(self.buf[(start + i) % cap]);
73        }
74    }
75
76    pub(crate) fn mean(&self) -> Option<f64> {
77        if self.len == 0 {
78            None
79        } else {
80            Some(self.sum / self.len as f64)
81        }
82    }
83
84    pub(crate) fn max(&self) -> Option<f64> {
85        if self.len == 0 {
86            return None;
87        }
88        let cap = self.buf.len();
89        let start = if self.len < cap { 0 } else { self.head };
90        let mut m = f64::NEG_INFINITY;
91        for i in 0..self.len {
92            m = m.max(self.buf[(start + i) % cap]);
93        }
94        Some(m)
95    }
96
97    pub(crate) fn min(&self) -> Option<f64> {
98        if self.len == 0 {
99            return None;
100        }
101        let cap = self.buf.len();
102        let start = if self.len < cap { 0 } else { self.head };
103        let mut m = f64::INFINITY;
104        for i in 0..self.len {
105            m = m.min(self.buf[(start + i) % cap]);
106        }
107        Some(m)
108    }
109}
110
111/// Ring of (price*volume, volume) pairs for rolling VWAP.
112#[derive(Clone, Debug)]
113pub(crate) struct RingPv {
114    pv: RingF64,
115    vol: RingF64,
116}
117
118impl RingPv {
119    pub(crate) fn with_capacity(cap: usize) -> Self {
120        Self {
121            pv: RingF64::with_capacity(cap),
122            vol: RingF64::with_capacity(cap),
123        }
124    }
125
126    pub(crate) fn clear(&mut self) {
127        self.pv.clear();
128        self.vol.clear();
129    }
130
131    pub(crate) fn is_full(&self) -> bool {
132        self.pv.is_full()
133    }
134
135    pub(crate) fn len(&self) -> usize {
136        self.pv.len()
137    }
138
139    pub(crate) fn push(&mut self, price: f64, volume: f64) {
140        self.pv.push(price * volume);
141        self.vol.push(volume);
142    }
143
144    pub(crate) fn vwap(&self) -> Option<f64> {
145        let v = self.vol.sum();
146        if v > 0.0 {
147            Some(self.pv.sum() / v)
148        } else {
149            None
150        }
151    }
152}