1use crate::stocks::ta::bollinger::{BollingerParams, ValidatedBollinger};
103use crate::stocks::ta::common::{require_hlc, true_range, validate_positive_volume, window_stdev};
104use crate::stocks::ta::keltner::{KeltnerParams, ValidatedKeltner};
105use crate::stocks::ta::macd::{MacdParams, ValidatedMacd};
106use crate::stocks::ta::ring::{RingF64, RingPv};
107use crate::stocks::ta::rvol::{RvolParams, ValidatedRvol};
108use crate::stocks::ta::stochastic::{StochasticParams, ValidatedStochastic};
109use crate::stocks::ta::vwap::{ValidatedVwap, VwapMode, VwapParams, VwapPriceSource};
110use crate::util::error::{require_finite, FinanceError, FinanceResult};
111
112pub use crate::stocks::ta::moving_average::{EmaState, SmaState};
115
116#[derive(Clone, Debug)]
140pub struct StochState {
141 params: StochasticParams,
142 high: RingF64,
143 low: RingF64,
144 close: RingF64,
145 raw_k: RingF64,
146 smooth_k: RingF64,
147 last_k: Option<f64>,
148 last_d: Option<f64>,
149 prev_raw_k: Option<f64>,
151}
152
153impl StochState {
154 pub fn new(params: StochasticParams) -> FinanceResult<Self> {
155 let _ = ValidatedStochastic::new(params)?;
156 Ok(Self {
157 params,
158 high: RingF64::with_capacity(params.k_period),
159 low: RingF64::with_capacity(params.k_period),
160 close: RingF64::with_capacity(params.k_period),
161 raw_k: RingF64::with_capacity(params.k_smooth),
162 smooth_k: RingF64::with_capacity(params.d_period),
163 last_k: None,
164 last_d: None,
165 prev_raw_k: None,
166 })
167 }
168
169 pub fn from_history(
170 params: StochasticParams,
171 high: &[f64],
172 low: &[f64],
173 close: &[f64],
174 ) -> FinanceResult<Self> {
175 let mut s = Self::new(params)?;
176 require_hlc(high, low, close)?;
177 for i in 0..close.len() {
178 s.push(high[i], low[i], close[i])?;
179 }
180 Ok(s)
181 }
182
183 pub fn params(&self) -> StochasticParams {
184 self.params
185 }
186
187 pub fn reset(&mut self) {
188 self.high.clear();
189 self.low.clear();
190 self.close.clear();
191 self.raw_k.clear();
192 self.smooth_k.clear();
193 self.last_k = None;
194 self.last_d = None;
195 self.prev_raw_k = None;
196 }
197
198 pub fn push(&mut self, high: f64, low: f64, close: f64) -> FinanceResult<Option<(f64, f64)>> {
203 let d = self.push_detail(high, low, close)?;
204 match (d.k, d.d) {
205 (Some(k), Some(dd)) => Ok(Some((k, dd))),
206 _ => Ok(None),
207 }
208 }
209
210 pub fn push_detail(
212 &mut self,
213 high: f64,
214 low: f64,
215 close: f64,
216 ) -> FinanceResult<StochBarOutput> {
217 require_finite("high", high)?;
218 require_finite("low", low)?;
219 require_finite("close", close)?;
220 if high < low {
221 return Err(FinanceError::InvalidCashflow {
222 message: "high must be >= low for each bar",
223 });
224 }
225 self.high.push(high);
226 self.low.push(low);
227 self.close.push(close);
228
229 let mut k_out = None;
230 let mut d_out = None;
231
232 if self.high.is_full() {
233 let hh = self.high.max().unwrap();
234 let ll = self.low.min().unwrap();
235 let range = hh - ll;
236 let raw = if range == 0.0 {
238 self.prev_raw_k.unwrap_or(50.0)
239 } else {
240 100.0 * (close - ll) / range
241 };
242 self.prev_raw_k = Some(raw);
243 self.raw_k.push(raw);
244 if self.raw_k.is_full() {
245 let sk = self.raw_k.sum() / self.params.k_smooth as f64;
246 self.last_k = Some(sk);
247 k_out = Some(sk);
248 self.smooth_k.push(sk);
249 if self.smooth_k.is_full() {
250 let d = self.smooth_k.sum() / self.params.d_period as f64;
251 self.last_d = Some(d);
252 d_out = Some(d);
253 }
254 }
255 }
256 Ok(StochBarOutput { k: k_out, d: d_out })
257 }
258
259 pub fn last_kd(&self) -> Option<(f64, f64)> {
260 Some((self.last_k?, self.last_d?))
261 }
262
263 pub fn push_bars(
265 &mut self,
266 high: &[f64],
267 low: &[f64],
268 close: &[f64],
269 ) -> FinanceResult<Vec<StochBarOutput>> {
270 require_hlc(high, low, close)?;
271 let mut out = Vec::with_capacity(close.len());
272 for i in 0..close.len() {
273 out.push(self.push_detail(high[i], low[i], close[i])?);
274 }
275 Ok(out)
276 }
277}
278
279#[derive(Clone, Copy, Debug, PartialEq)]
281pub struct StochBarOutput {
282 pub k: Option<f64>,
283 pub d: Option<f64>,
284}
285
286#[derive(Clone, Debug)]
292pub struct MacdState {
293 params: MacdParams,
294 fast: EmaState,
295 slow: EmaState,
296 signal: EmaState,
297 last: Option<(f64, f64, f64)>,
298}
299
300impl MacdState {
301 pub fn new(params: MacdParams) -> FinanceResult<Self> {
302 let _ = ValidatedMacd::new(params)?;
303 Ok(Self {
304 params,
305 fast: EmaState::new(params.fast)?,
306 slow: EmaState::new(params.slow)?,
307 signal: EmaState::new(params.signal)?,
308 last: None,
309 })
310 }
311
312 pub fn from_history(params: MacdParams, closes: &[f64]) -> FinanceResult<Self> {
313 let mut s = Self::new(params)?;
314 for &c in closes {
315 s.push(c)?;
316 }
317 Ok(s)
318 }
319
320 pub fn params(&self) -> MacdParams {
321 self.params
322 }
323
324 pub fn reset(&mut self) {
325 self.fast.reset();
326 self.slow.reset();
327 self.signal.reset();
328 self.last = None;
329 }
330
331 pub fn push(&mut self, close: f64) -> FinanceResult<Option<(f64, f64, f64)>> {
333 let f = self.fast.push(close)?;
334 let s = self.slow.push(close)?;
335 let macd_line = match (f, s) {
336 (Some(a), Some(b)) => a - b,
337 _ => return Ok(None),
338 };
339 let sig = self.signal.push(macd_line)?;
340 match sig {
341 Some(signal) => {
342 let hist = macd_line - signal;
343 self.last = Some((macd_line, signal, hist));
344 Ok(Some((macd_line, signal, hist)))
345 }
346 None => {
347 self.last = None;
348 Ok(None)
349 }
350 }
351 }
352
353 pub fn last(&self) -> Option<(f64, f64, f64)> {
354 self.last
355 }
356
357 pub fn push_bars(&mut self, closes: &[f64]) -> FinanceResult<Vec<Option<(f64, f64, f64)>>> {
359 let mut out = Vec::with_capacity(closes.len());
360 for &c in closes {
361 out.push(self.push(c)?);
362 }
363 Ok(out)
364 }
365}
366
367#[derive(Clone, Debug)]
373pub struct BollingerState {
374 params: BollingerParams,
375 ring: RingF64,
376 scratch: Vec<f64>,
377 last: Option<BollingerBarOutput>,
378}
379
380#[derive(Clone, Copy, Debug, PartialEq)]
382pub struct BollingerBarOutput {
383 pub middle: f64,
384 pub upper: f64,
385 pub lower: f64,
386 pub pct_b: Option<f64>,
387}
388
389impl BollingerState {
390 pub fn new(params: BollingerParams) -> FinanceResult<Self> {
391 let _ = ValidatedBollinger::new(params)?;
392 Ok(Self {
393 params,
394 ring: RingF64::with_capacity(params.period),
395 scratch: Vec::with_capacity(params.period),
396 last: None,
397 })
398 }
399
400 pub fn from_history(params: BollingerParams, closes: &[f64]) -> FinanceResult<Self> {
401 let mut s = Self::new(params)?;
402 for &c in closes {
403 s.push(c)?;
404 }
405 Ok(s)
406 }
407
408 pub fn params(&self) -> BollingerParams {
409 self.params
410 }
411
412 pub fn reset(&mut self) {
413 self.ring.clear();
414 self.last = None;
415 }
416
417 pub fn push(&mut self, close: f64) -> FinanceResult<Option<BollingerBarOutput>> {
418 require_finite("close", close)?;
419 self.ring.push(close);
420 if !self.ring.is_full() {
421 self.last = None;
422 return Ok(None);
423 }
424 self.ring.copy_ordered(&mut self.scratch);
425 let mid = self.ring.sum() / self.params.period as f64;
426 let sd = window_stdev(&self.scratch, self.params.stdev).unwrap_or(0.0);
427 let band = self.params.num_std * sd;
428 let upper = mid + band;
429 let lower = mid - band;
430 let width = upper - lower;
431 let pct_b = if width > 0.0 {
432 Some((close - lower) / width)
433 } else {
434 None
435 };
436 let out = BollingerBarOutput {
437 middle: mid,
438 upper,
439 lower,
440 pct_b,
441 };
442 self.last = Some(out);
443 Ok(Some(out))
444 }
445
446 pub fn last(&self) -> Option<BollingerBarOutput> {
447 self.last
448 }
449
450 pub fn push_bars(&mut self, closes: &[f64]) -> FinanceResult<Vec<Option<BollingerBarOutput>>> {
452 let mut out = Vec::with_capacity(closes.len());
453 for &c in closes {
454 out.push(self.push(c)?);
455 }
456 Ok(out)
457 }
458}
459
460#[derive(Clone, Debug)]
466pub struct KeltnerState {
467 params: KeltnerParams,
468 mid: EmaState,
469 atr_period: usize,
470 tr_seed: RingF64,
471 atr: Option<f64>,
472 prev_close: Option<f64>,
473 last: Option<KeltnerBarOutput>,
474}
475
476#[derive(Clone, Copy, Debug, PartialEq)]
477pub struct KeltnerBarOutput {
478 pub middle: f64,
479 pub upper: f64,
480 pub lower: f64,
481 pub atr: f64,
482}
483
484impl KeltnerState {
485 pub fn new(params: KeltnerParams) -> FinanceResult<Self> {
486 let _ = ValidatedKeltner::new(params)?;
487 Ok(Self {
488 params,
489 mid: EmaState::new(params.ema_period)?,
490 atr_period: params.atr_period,
491 tr_seed: RingF64::with_capacity(params.atr_period),
492 atr: None,
493 prev_close: None,
494 last: None,
495 })
496 }
497
498 pub fn from_history(
499 params: KeltnerParams,
500 high: &[f64],
501 low: &[f64],
502 close: &[f64],
503 ) -> FinanceResult<Self> {
504 let mut s = Self::new(params)?;
505 require_hlc(high, low, close)?;
506 for i in 0..close.len() {
507 s.push(high[i], low[i], close[i])?;
508 }
509 Ok(s)
510 }
511
512 pub fn params(&self) -> KeltnerParams {
513 self.params
514 }
515
516 pub fn reset(&mut self) {
517 self.mid.reset();
518 self.tr_seed.clear();
519 self.atr = None;
520 self.prev_close = None;
521 self.last = None;
522 }
523
524 pub fn push(
525 &mut self,
526 high: f64,
527 low: f64,
528 close: f64,
529 ) -> FinanceResult<Option<KeltnerBarOutput>> {
530 require_finite("high", high)?;
531 require_finite("low", low)?;
532 require_finite("close", close)?;
533 if high < low {
534 return Err(FinanceError::InvalidCashflow {
535 message: "high must be >= low for each bar",
536 });
537 }
538 let tr = true_range(high, low, self.prev_close);
539 self.prev_close = Some(close);
540
541 let atr_val = if let Some(prev_atr) = self.atr {
542 let n = self.atr_period as f64;
543 let next = (prev_atr * (n - 1.0) + tr) / n;
544 self.atr = Some(next);
545 Some(next)
546 } else {
547 self.tr_seed.push(tr);
548 if self.tr_seed.is_full() {
549 let seed = self.tr_seed.sum() / self.atr_period as f64;
550 self.atr = Some(seed);
551 Some(seed)
552 } else {
553 None
554 }
555 };
556
557 let mid = self.mid.push(close)?;
558 match (mid, atr_val) {
559 (Some(m), Some(a)) => {
560 let out = KeltnerBarOutput {
561 middle: m,
562 upper: m + self.params.atr_mult * a,
563 lower: m - self.params.atr_mult * a,
564 atr: a,
565 };
566 self.last = Some(out);
567 Ok(Some(out))
568 }
569 _ => {
570 self.last = None;
571 Ok(None)
572 }
573 }
574 }
575
576 pub fn last(&self) -> Option<KeltnerBarOutput> {
577 self.last
578 }
579
580 pub fn push_bars(
582 &mut self,
583 high: &[f64],
584 low: &[f64],
585 close: &[f64],
586 ) -> FinanceResult<Vec<Option<KeltnerBarOutput>>> {
587 require_hlc(high, low, close)?;
588 let mut out = Vec::with_capacity(close.len());
589 for i in 0..close.len() {
590 out.push(self.push(high[i], low[i], close[i])?);
591 }
592 Ok(out)
593 }
594}
595
596#[derive(Clone, Debug)]
613pub struct VwapState {
614 params: VwapParams,
615 cum_pv: f64,
617 cum_v: f64,
618 rolling: Option<RingPv>,
620 last: Option<f64>,
621}
622
623impl VwapState {
624 pub fn new(params: VwapParams) -> FinanceResult<Self> {
625 let _ = ValidatedVwap::new(params)?;
626 let rolling = match params.mode {
627 VwapMode::Cumulative => None,
628 VwapMode::Rolling { period } => Some(RingPv::with_capacity(period)),
629 };
630 Ok(Self {
631 params,
632 cum_pv: 0.0,
633 cum_v: 0.0,
634 rolling,
635 last: None,
636 })
637 }
638
639 pub fn from_history(
640 params: VwapParams,
641 high: &[f64],
642 low: &[f64],
643 close: &[f64],
644 volume: &[f64],
645 ) -> FinanceResult<Self> {
646 let mut s = Self::new(params)?;
647 require_hlc(high, low, close)?;
648 validate_positive_volume(volume)?;
649 if close.len() != volume.len() {
650 return Err(FinanceError::LengthMismatch {
651 left: close.len(),
652 right: volume.len(),
653 context: "close/volume",
654 });
655 }
656 for i in 0..close.len() {
657 s.push(high[i], low[i], close[i], volume[i])?;
658 }
659 Ok(s)
660 }
661
662 pub fn params(&self) -> VwapParams {
663 self.params
664 }
665
666 pub fn reset(&mut self) {
668 self.cum_pv = 0.0;
669 self.cum_v = 0.0;
670 if let Some(r) = self.rolling.as_mut() {
671 r.clear();
672 }
673 self.last = None;
674 }
675
676 pub fn push(
677 &mut self,
678 high: f64,
679 low: f64,
680 close: f64,
681 volume: f64,
682 ) -> FinanceResult<Option<f64>> {
683 require_finite("high", high)?;
684 require_finite("low", low)?;
685 require_finite("close", close)?;
686 require_finite("volume", volume)?;
687 if high < low {
688 return Err(FinanceError::InvalidCashflow {
689 message: "high must be >= low for each bar",
690 });
691 }
692 if volume < 0.0 {
693 return Err(FinanceError::InvalidCashflow {
694 message: "volume must be non-negative",
695 });
696 }
697 let price = match self.params.price_source {
698 VwapPriceSource::Typical => (high + low + close) / 3.0,
699 VwapPriceSource::Close => close,
700 };
701 let out = match self.params.mode {
702 VwapMode::Cumulative => {
703 self.cum_pv += price * volume;
704 self.cum_v += volume;
705 if self.cum_v > 0.0 {
706 Some(self.cum_pv / self.cum_v)
707 } else {
708 None
709 }
710 }
711 VwapMode::Rolling { period } => {
712 let ring = self.rolling.as_mut().unwrap();
713 ring.push(price, volume);
714 if ring.len() >= period {
715 ring.vwap()
716 } else {
717 None
718 }
719 }
720 };
721 self.last = out;
722 Ok(out)
723 }
724
725 pub fn last(&self) -> Option<f64> {
726 self.last
727 }
728
729 pub fn push_bars(
731 &mut self,
732 high: &[f64],
733 low: &[f64],
734 close: &[f64],
735 volume: &[f64],
736 ) -> FinanceResult<Vec<Option<f64>>> {
737 require_hlc(high, low, close)?;
738 validate_positive_volume(volume)?;
739 if close.len() != volume.len() {
740 return Err(FinanceError::LengthMismatch {
741 left: close.len(),
742 right: volume.len(),
743 context: "close/volume",
744 });
745 }
746 let mut out = Vec::with_capacity(close.len());
747 for i in 0..close.len() {
748 out.push(self.push(high[i], low[i], close[i], volume[i])?);
749 }
750 Ok(out)
751 }
752}
753
754#[derive(Clone, Debug)]
760pub struct RvolState {
761 params: RvolParams,
762 ring: RingF64,
763 last: Option<f64>,
764}
765
766impl RvolState {
767 pub fn new(params: RvolParams) -> FinanceResult<Self> {
768 let _ = ValidatedRvol::new(params)?;
769 Ok(Self {
770 params,
771 ring: RingF64::with_capacity(params.lookback),
772 last: None,
773 })
774 }
775
776 pub fn from_history(params: RvolParams, volume: &[f64]) -> FinanceResult<Self> {
777 let mut s = Self::new(params)?;
778 for &v in volume {
779 s.push(v)?;
780 }
781 Ok(s)
782 }
783
784 pub fn params(&self) -> RvolParams {
785 self.params
786 }
787
788 pub fn reset(&mut self) {
789 self.ring.clear();
790 self.last = None;
791 }
792
793 pub fn push(&mut self, volume: f64) -> FinanceResult<Option<f64>> {
794 require_finite("volume", volume)?;
795 if volume < 0.0 {
796 return Err(FinanceError::InvalidCashflow {
797 message: "volume must be non-negative",
798 });
799 }
800 self.ring.push(volume);
801 if !self.ring.is_full() {
802 self.last = None;
803 return Ok(None);
804 }
805 let mean = self.ring.sum() / self.params.lookback as f64;
806 let out = if mean > 0.0 {
807 Some(volume / mean)
808 } else {
809 None
810 };
811 self.last = out;
812 Ok(out)
813 }
814
815 pub fn last(&self) -> Option<f64> {
816 self.last
817 }
818
819 pub fn push_bars(&mut self, volume: &[f64]) -> FinanceResult<Vec<Option<f64>>> {
821 let mut out = Vec::with_capacity(volume.len());
822 for &v in volume {
823 out.push(self.push(v)?);
824 }
825 Ok(out)
826 }
827}
828
829#[cfg(test)]
834mod tests {
835 use super::*;
836 use crate::stocks::ta::bollinger::bollinger;
837 use crate::stocks::ta::keltner::keltner;
838 use crate::stocks::ta::macd::macd;
839 use crate::stocks::ta::moving_average::{ema, sma};
840 use crate::stocks::ta::rvol::rvol;
841 use crate::stocks::ta::stochastic::stochastics;
842 use crate::stocks::ta::vwap::vwap;
843
844 fn path(n: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
845 let close: Vec<_> = (0..n)
846 .map(|i| 100.0 + i as f64 * 0.13 + ((i % 7) as f64) * 0.04)
847 .collect();
848 let high: Vec<_> = close.iter().map(|c| c + 0.35).collect();
849 let low: Vec<_> = close.iter().map(|c| c - 0.35).collect();
850 let vol: Vec<_> = (0..n).map(|i| 800.0 + i as f64 * 3.0).collect();
851 (high, low, close, vol)
852 }
853
854 fn approx_opt(a: Option<f64>, b: Option<f64>) {
855 match (a, b) {
856 (None, None) => {}
857 (Some(x), Some(y)) => assert!((x - y).abs() < 1e-9, "{x} vs {y}"),
858 _ => panic!("Option mismatch {a:?} vs {b:?}"),
859 }
860 }
861
862 #[test]
863 fn sma_parity() {
864 let (_, _, c, _) = path(40);
865 let batch = sma(&c, 10).unwrap();
866 let mut st = SmaState::new(10).unwrap();
867 for i in 0..c.len() {
868 approx_opt(st.push(c[i]).unwrap(), batch[i]);
869 }
870 }
871
872 #[test]
873 fn ema_parity() {
874 let (_, _, c, _) = path(40);
875 let batch = ema(&c, 10).unwrap();
876 let mut st = EmaState::new(10).unwrap();
877 for i in 0..c.len() {
878 approx_opt(st.push(c[i]).unwrap(), batch[i]);
879 }
880 }
881
882 #[test]
883 fn stoch_parity() {
884 let (h, l, c, _) = path(50);
885 let p = StochasticParams::full(14, 3, 3);
886 let batch = stochastics(&h, &l, &c, p).unwrap();
887 let mut st = StochState::new(p).unwrap();
888 for i in 0..c.len() {
889 let d = st.push_detail(h[i], l[i], c[i]).unwrap();
890 approx_opt(d.k, batch.k[i]);
891 approx_opt(d.d, batch.d[i]);
892 }
893 }
894
895 #[test]
896 fn macd_parity() {
897 let (_, _, c, _) = path(60);
898 let p = MacdParams::standard();
899 let batch = macd(&c, p).unwrap();
900 let mut st = MacdState::new(p).unwrap();
901 for i in 0..c.len() {
902 let o = st.push(c[i]).unwrap();
903 match (o, batch.signal[i], batch.histogram[i], batch.macd[i]) {
904 (Some((m, s, h)), Some(bs), Some(bh), Some(bm)) => {
905 assert!((m - bm).abs() < 1e-8, "macd {i}");
906 assert!((s - bs).abs() < 1e-8, "signal {i}");
907 assert!((h - bh).abs() < 1e-8, "hist {i}");
908 }
909 (None, None, None, _) => {} other => panic!("macd parity at {i}: {other:?}"),
911 }
912 }
913 let bl = batch.last().unwrap();
914 let sl = st.last().unwrap();
915 assert!((bl.0 - sl.0).abs() < 1e-8);
916 assert!((bl.1 - sl.1).abs() < 1e-8);
917 assert!((bl.2 - sl.2).abs() < 1e-8);
918 }
919
920 #[test]
921 fn bollinger_parity() {
922 let (_, _, c, _) = path(40);
923 let p = BollingerParams::standard();
924 let batch = bollinger(&c, p).unwrap();
925 let mut st = BollingerState::new(p).unwrap();
926 for i in 0..c.len() {
927 let o = st.push(c[i]).unwrap();
928 match (o, batch.middle[i]) {
929 (None, None) => {}
930 (Some(bo), Some(m)) => {
931 assert!((bo.middle - m).abs() < 1e-9);
932 assert!((bo.upper - batch.upper[i].unwrap()).abs() < 1e-9);
933 assert!((bo.lower - batch.lower[i].unwrap()).abs() < 1e-9);
934 }
935 other => panic!("{other:?}"),
936 }
937 }
938 }
939
940 #[test]
941 fn keltner_parity() {
942 let (h, l, c, _) = path(45);
943 let p = KeltnerParams::standard();
944 let batch = keltner(&h, &l, &c, p).unwrap();
945 let mut st = KeltnerState::new(p).unwrap();
946 for i in 0..c.len() {
947 let o = st.push(h[i], l[i], c[i]).unwrap();
948 match (o, batch.middle[i], batch.upper[i], batch.atr[i]) {
949 (Some(ko), Some(m), Some(u), Some(a)) => {
950 assert!((ko.middle - m).abs() < 1e-8, "mid {i}");
951 assert!((ko.upper - u).abs() < 1e-8, "upper {i}");
952 assert!((ko.atr - a).abs() < 1e-8, "atr {i}");
953 }
954 (None, _, None, _) | (None, None, _, _) => {} other => panic!("keltner parity {i}: {other:?}"),
956 }
957 }
958 let sl = st.last().unwrap();
959 let bl_m = batch.middle.iter().rev().find_map(|x| *x).unwrap();
960 let bl_a = batch.atr.iter().rev().find_map(|x| *x).unwrap();
961 assert!((bl_m - sl.middle).abs() < 1e-8);
962 assert!((bl_a - sl.atr).abs() < 1e-8);
963 }
964
965 #[test]
966 fn vwap_cum_parity() {
967 let (h, l, c, v) = path(30);
968 let p = VwapParams::cumulative_typical();
969 let batch = vwap(&h, &l, &c, &v, p).unwrap();
970 let mut st = VwapState::new(p).unwrap();
971 for i in 0..c.len() {
972 approx_opt(st.push(h[i], l[i], c[i], v[i]).unwrap(), batch.vwap[i]);
973 }
974 }
975
976 #[test]
977 fn vwap_reset() {
978 let mut st = VwapState::new(VwapParams::cumulative_typical()).unwrap();
979 st.push(10.0, 10.0, 10.0, 100.0).unwrap();
980 st.reset();
981 let x = st.push(20.0, 20.0, 20.0, 50.0).unwrap().unwrap();
982 assert!((x - 20.0).abs() < 1e-12);
983 }
984
985 #[test]
986 fn rvol_parity() {
987 let (_, _, _, v) = path(40);
988 let p = RvolParams::days_20();
989 let batch = rvol(&v, p).unwrap();
990 let mut st = RvolState::new(p).unwrap();
991 for i in 0..v.len() {
992 approx_opt(st.push(v[i]).unwrap(), batch.rvol[i]);
993 }
994 }
995
996 #[test]
997 fn from_history_matches_push() {
998 let (h, l, c, _) = path(25);
999 let p = StochasticParams::fast(9, 3);
1000 let a = StochState::from_history(p, &h, &l, &c).unwrap();
1001 let mut b = StochState::new(p).unwrap();
1002 for i in 0..c.len() {
1003 b.push(h[i], l[i], c[i]).unwrap();
1004 }
1005 assert_eq!(a.last_kd(), b.last_kd());
1006 }
1007}