velora_ta/trend/
vortex.rs1use chrono::{DateTime, Utc};
6
7use crate::{
8 traits::{Indicator, MultiIndicator},
9 types::{MultiIndicatorValue, OhlcBar},
10 utils::CircularBuffer,
11 IndicatorError, IndicatorResult,
12};
13
14#[derive(Debug, Clone, Copy, Default)]
15struct VortexPoint {
16 high: f64,
17 low: f64,
18 close: f64,
19}
20
21#[derive(Debug, Clone)]
23pub struct Vortex {
24 period: usize,
25 buffer: CircularBuffer<VortexPoint>,
26 name: String,
27}
28
29impl Vortex {
30 pub fn new(period: usize) -> IndicatorResult<Self> {
32 if period == 0 {
33 return Err(IndicatorError::InvalidParameter(
34 "Period must be > 0".to_string(),
35 ));
36 }
37
38 Ok(Vortex {
39 period,
40 buffer: CircularBuffer::new(period + 1),
41 name: format!("Vortex({period})"),
42 })
43 }
44
45 pub fn update_ohlc(
47 &mut self,
48 bar: &OhlcBar,
49 _timestamp: DateTime<Utc>,
50 ) -> IndicatorResult<Option<Vec<f64>>> {
51 self.buffer.push(VortexPoint {
52 high: bar.high,
53 low: bar.low,
54 close: bar.close,
55 });
56
57 if !self.is_ready() {
58 return Ok(None);
59 }
60
61 let mut vm_plus = 0.0;
62 let mut vm_minus = 0.0;
63 let mut tr_sum = 0.0;
64
65 for i in 1..self.buffer.len() {
66 let curr = self.buffer.get(i).unwrap();
67 let prev = self.buffer.get(i - 1).unwrap();
68
69 vm_plus += (curr.high - prev.low).abs();
70 vm_minus += (curr.low - prev.high).abs();
71 tr_sum += (curr.high - curr.low)
72 .max((curr.high - prev.close).abs())
73 .max((curr.low - prev.close).abs());
74 }
75
76 if tr_sum == 0.0 {
77 return Ok(Some(vec![0.0, 0.0]));
78 }
79
80 let vi_plus = vm_plus / tr_sum;
81 let vi_minus = vm_minus / tr_sum;
82
83 Ok(Some(vec![vi_plus, vi_minus]))
84 }
85}
86
87impl Indicator for Vortex {
88 fn name(&self) -> &str {
89 &self.name
90 }
91
92 fn warmup_period(&self) -> usize {
93 self.period + 1
94 }
95
96 fn is_ready(&self) -> bool {
97 self.buffer.is_full()
98 }
99
100 fn reset(&mut self) {
101 self.buffer.clear();
102 }
103}
104
105impl MultiIndicator for Vortex {
106 fn output_count(&self) -> usize {
107 2
108 }
109
110 fn output_names(&self) -> Vec<&str> {
111 vec!["VI+", "VI-"]
112 }
113
114 fn update(
115 &mut self,
116 _price: f64,
117 _timestamp: DateTime<Utc>,
118 ) -> IndicatorResult<Option<Vec<f64>>> {
119 Err(IndicatorError::NotInitialized(
120 "Vortex requires OHLC data".to_string(),
121 ))
122 }
123
124 fn current(&self) -> Option<Vec<f64>> {
125 None
126 }
127
128 fn calculate(&self, _prices: &[f64]) -> IndicatorResult<Vec<Option<MultiIndicatorValue>>> {
129 Err(IndicatorError::NotInitialized(
130 "Vortex requires OHLC data".to_string(),
131 ))
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_vortex_creation() {
141 let vortex = Vortex::new(14).unwrap();
142 assert_eq!(vortex.output_count(), 2);
143 }
144}