indexes_rs/v2/cci/
main.rs1use crate::v2::cci::types::{CCIConfig, CCIError, CCIInput, CCIMarketCondition, CCIOutput, CCIState};
2
3pub struct CCI {
22 state: CCIState,
23}
24
25impl CCI {
26 pub fn new() -> Self {
28 Self::with_config(CCIConfig::default())
29 }
30
31 pub fn with_period(period: usize) -> Result<Self, CCIError> {
33 if period == 0 {
34 return Err(CCIError::InvalidPeriod);
35 }
36
37 let config = CCIConfig { period, ..Default::default() };
38 Ok(Self::with_config(config))
39 }
40
41 pub fn with_thresholds(period: usize, overbought: f64, oversold: f64, extreme_overbought: f64, extreme_oversold: f64) -> Result<Self, CCIError> {
43 if period == 0 {
44 return Err(CCIError::InvalidPeriod);
45 }
46
47 if overbought <= oversold || extreme_overbought <= overbought || extreme_oversold >= oversold {
48 return Err(CCIError::InvalidThresholds);
49 }
50
51 let config = CCIConfig {
52 period,
53 overbought,
54 oversold,
55 extreme_overbought,
56 extreme_oversold,
57 };
58 Ok(Self::with_config(config))
59 }
60
61 pub fn with_config(config: CCIConfig) -> Self {
63 Self { state: CCIState::new(config) }
64 }
65
66 pub fn calculate(&mut self, input: CCIInput) -> Result<CCIOutput, CCIError> {
68 self.validate_input(&input)?;
70 self.validate_config()?;
71
72 let typical_price = self.calculate_typical_price(&input);
74
75 self.update_typical_price_history(typical_price);
77
78 let (cci, sma_tp, mean_deviation) = if self.state.has_sufficient_data {
80 self.calculate_cci_value(typical_price)?
81 } else {
82 (0.0, typical_price, 0.0) };
84
85 let market_condition = self.determine_market_condition(cci);
87
88 let distance_from_zero = cci.abs();
90
91 Ok(CCIOutput {
92 cci,
93 typical_price,
94 sma_tp,
95 mean_deviation,
96 market_condition,
97 distance_from_zero,
98 })
99 }
100
101 pub fn calculate_batch(&mut self, inputs: &[CCIInput]) -> Result<Vec<CCIOutput>, CCIError> {
103 inputs.iter().map(|input| self.calculate(*input)).collect()
104 }
105
106 pub fn reset(&mut self) {
108 self.state = CCIState::new(self.state.config);
109 }
110
111 pub fn get_state(&self) -> &CCIState {
113 &self.state
114 }
115
116 pub fn set_state(&mut self, state: CCIState) {
118 self.state = state;
119 }
120
121 pub fn market_condition(&self) -> CCIMarketCondition {
123 if !self.state.has_sufficient_data {
124 CCIMarketCondition::Insufficient
125 } else {
126 CCIMarketCondition::Normal
128 }
129 }
130
131 pub fn is_overbought(&self, cci: f64) -> bool {
133 cci >= self.state.config.overbought
134 }
135
136 pub fn is_oversold(&self, cci: f64) -> bool {
138 cci <= self.state.config.oversold
139 }
140
141 pub fn is_extreme_condition(&self, cci: f64) -> bool {
143 cci >= self.state.config.extreme_overbought || cci <= self.state.config.extreme_oversold
144 }
145
146 fn validate_input(&self, input: &CCIInput) -> Result<(), CCIError> {
149 if !input.high.is_finite() || !input.low.is_finite() || !input.close.is_finite() {
151 return Err(CCIError::InvalidPrice);
152 }
153
154 if input.high < input.low {
156 return Err(CCIError::InvalidHLC);
157 }
158
159 if input.close < input.low || input.close > input.high {
160 return Err(CCIError::InvalidHLC);
161 }
162
163 Ok(())
164 }
165
166 fn validate_config(&self) -> Result<(), CCIError> {
167 if self.state.config.period == 0 {
168 return Err(CCIError::InvalidPeriod);
169 }
170
171 let config = &self.state.config;
172 if config.overbought <= config.oversold || config.extreme_overbought <= config.overbought || config.extreme_oversold >= config.oversold {
173 return Err(CCIError::InvalidThresholds);
174 }
175
176 Ok(())
177 }
178
179 fn calculate_typical_price(&self, input: &CCIInput) -> f64 {
180 (input.high + input.low + input.close) / 3.0
181 }
182
183 fn update_typical_price_history(&mut self, typical_price: f64) {
184 if self.state.typical_prices.len() >= self.state.config.period {
186 if let Some(oldest) = self.state.typical_prices.pop_front() {
187 self.state.tp_sum -= oldest;
188 }
189 }
190
191 self.state.typical_prices.push_back(typical_price);
193 self.state.tp_sum += typical_price;
194
195 self.state.has_sufficient_data = self.state.typical_prices.len() >= self.state.config.period;
197 }
198
199 fn calculate_cci_value(&self, current_tp: f64) -> Result<(f64, f64, f64), CCIError> {
200 if !self.state.has_sufficient_data {
201 return Ok((0.0, current_tp, 0.0));
202 }
203
204 let sma_tp = self.state.tp_sum / self.state.config.period as f64;
206
207 let mean_deviation = self.calculate_mean_deviation(sma_tp);
209
210 if mean_deviation == 0.0 {
212 return Ok((0.0, sma_tp, mean_deviation));
214 }
215
216 let cci = (current_tp - sma_tp) / (0.015 * mean_deviation);
218
219 if !cci.is_finite() {
220 return Err(CCIError::DivisionByZero);
221 }
222
223 Ok((cci, sma_tp, mean_deviation))
224 }
225
226 fn calculate_mean_deviation(&self, sma_tp: f64) -> f64 {
227 let sum_deviations: f64 = self.state.typical_prices.iter().map(|&tp| (tp - sma_tp).abs()).sum();
228
229 sum_deviations / self.state.config.period as f64
230 }
231
232 fn determine_market_condition(&self, cci: f64) -> CCIMarketCondition {
233 if !self.state.has_sufficient_data {
234 CCIMarketCondition::Insufficient
235 } else if cci >= self.state.config.extreme_overbought {
236 CCIMarketCondition::ExtremeOverbought
237 } else if cci >= self.state.config.overbought {
238 CCIMarketCondition::Overbought
239 } else if cci <= self.state.config.extreme_oversold {
240 CCIMarketCondition::ExtremeOversold
241 } else if cci <= self.state.config.oversold {
242 CCIMarketCondition::Oversold
243 } else {
244 CCIMarketCondition::Normal
245 }
246 }
247}
248
249impl Default for CCI {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255pub fn calculate_cci_simple(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> Result<Vec<f64>, CCIError> {
257 let len = highs.len();
258 if len != lows.len() || len != closes.len() {
259 return Err(CCIError::InvalidInput("All price arrays must have same length".to_string()));
260 }
261
262 if len == 0 {
263 return Ok(Vec::new());
264 }
265
266 let mut cci_calculator = CCI::with_period(period)?;
267 let mut results = Vec::with_capacity(len);
268
269 for i in 0..len {
270 let input = CCIInput {
271 high: highs[i],
272 low: lows[i],
273 close: closes[i],
274 };
275 let output = cci_calculator.calculate(input)?;
276 results.push(output.cci);
277 }
278
279 Ok(results)
280}