1use super::last_value;
10use crate::Candle;
11use crate::indicators::{
12 accumulation_distribution, adx, alma, aroon, atr, atr::atr_raw, awesome_oscillator,
13 balance_of_power, bollinger_bands, bull_bear_power, cci, chaikin_oscillator, choppiness_index,
14 cmf, cmo, coppock_curve, dema, donchian_channels, elder_ray, ema::ema_raw, hma, ichimoku,
15 keltner_channels::keltner_with_atr_dense, macd, mcginley_dynamic, mfi, momentum, obv,
16 parabolic_sar, roc, rsi::rsi_raw, sma::sma_raw, stochastic,
17 stochastic_rsi::stochastic_rsi_from_rsi_dense, supertrend::supertrend_with_atr_dense, tema,
18 true_range, vwap, vwma, williams_r, wma::wma_raw,
19};
20
21#[inline]
23fn last_from_result(result: crate::indicators::Result<Vec<Option<f64>>>) -> Option<f64> {
24 result.ok().and_then(|v| last_value(&v))
25}
26
27pub(crate) fn calculate_indicators(candles: &[Candle]) -> IndicatorsSummary {
32 if candles.is_empty() {
33 return IndicatorsSummary::default();
34 }
35
36 let len = candles.len();
38 let mut closes = Vec::with_capacity(len);
39 let mut highs = Vec::with_capacity(len);
40 let mut lows = Vec::with_capacity(len);
41 let mut opens = Vec::with_capacity(len);
42 let mut volumes = Vec::with_capacity(len);
43 for c in candles {
44 closes.push(c.close);
45 highs.push(c.high);
46 lows.push(c.low);
47 opens.push(c.open);
48 volumes.push(c.volume as f64);
49 }
50
51 let rsi_14_dense = rsi_raw(&closes, 14).ok();
53 let atr_10_dense = atr_raw(&highs, &lows, &closes, 10).ok();
54
55 IndicatorsSummary {
56 sma_10: sma_raw(&closes, 10).last().copied(),
58 sma_20: sma_raw(&closes, 20).last().copied(),
59 sma_50: sma_raw(&closes, 50).last().copied(),
60 sma_100: sma_raw(&closes, 100).last().copied(),
61 sma_200: sma_raw(&closes, 200).last().copied(),
62
63 ema_10: ema_raw(&closes, 10).last().copied(),
64 ema_20: ema_raw(&closes, 20).last().copied(),
65 ema_50: ema_raw(&closes, 50).last().copied(),
66 ema_100: ema_raw(&closes, 100).last().copied(),
67 ema_200: ema_raw(&closes, 200).last().copied(),
68
69 wma_10: wma_raw(&closes, 10).last().copied(),
70 wma_20: wma_raw(&closes, 20).last().copied(),
71 wma_50: wma_raw(&closes, 50).last().copied(),
72 wma_100: wma_raw(&closes, 100).last().copied(),
73 wma_200: wma_raw(&closes, 200).last().copied(),
74
75 dema_20: dema(&closes, 20).ok().and_then(|v| last_value(&v)),
77 tema_20: tema(&closes, 20).ok().and_then(|v| last_value(&v)),
78 hma_20: hma(&closes, 20).ok().and_then(|v| last_value(&v)),
79 vwma_20: vwma(&closes, &volumes, 20)
80 .ok()
81 .and_then(|v| last_value(&v)),
82 alma_9: alma(&closes, 9, 0.85, 6.0)
83 .ok()
84 .and_then(|v| last_value(&v)),
85 mcginley_dynamic_20: mcginley_dynamic(&closes, 20)
86 .ok()
87 .and_then(|v| last_value(&v)),
88
89 rsi_14: rsi_14_dense.as_deref().and_then(|v| v.last().copied()),
91 stochastic: {
92 stochastic(&highs, &lows, &closes, 14, 1, 3)
93 .ok()
94 .map(|result| StochasticData {
95 k: last_value(&result.k),
96 d: last_value(&result.d),
97 })
98 },
99 stochastic_rsi: {
100 rsi_14_dense.as_deref().and_then(|rsi_dense| {
101 stochastic_rsi_from_rsi_dense(rsi_dense, len, 14, 14, 3, 3)
102 .ok()
103 .map(|result| StochasticData {
104 k: last_value(&result.k),
105 d: last_value(&result.d),
106 })
107 })
108 },
109 cci_20: last_from_result(cci(&highs, &lows, &closes, 20)),
110 williams_r_14: last_from_result(williams_r(&highs, &lows, &closes, 14)),
111 roc_12: last_from_result(roc(&closes, 12)),
112 momentum_10: last_from_result(momentum(&closes, 10)),
113 cmo_14: last_from_result(cmo(&closes, 14)),
114 awesome_oscillator: last_from_result(awesome_oscillator(&highs, &lows, 5, 34)),
115 coppock_curve: last_from_result(coppock_curve(&closes, 14, 11, 10)),
116
117 macd: {
119 macd(&closes, 12, 26, 9).ok().map(|result| MacdData {
120 macd: last_value(&result.macd_line),
121 signal: last_value(&result.signal_line),
122 histogram: last_value(&result.histogram),
123 })
124 },
125 adx_14: last_from_result(adx(&highs, &lows, &closes, 14)),
126 aroon: {
127 aroon(&highs, &lows, 25).ok().map(|result| AroonData {
128 aroon_up: last_value(&result.aroon_up),
129 aroon_down: last_value(&result.aroon_down),
130 })
131 },
132 supertrend: {
133 atr_10_dense.as_deref().and_then(|atr_dense| {
134 supertrend_with_atr_dense(&highs, &lows, &closes, atr_dense, 10, 3.0)
135 .ok()
136 .map(|result| SuperTrendData {
137 value: last_value(&result.value),
138 trend: result.is_uptrend.last().and_then(|&v| v).map(|v| {
139 if v {
140 "up".to_string()
141 } else {
142 "down".to_string()
143 }
144 }),
145 })
146 })
147 },
148 ichimoku: {
149 ichimoku(&highs, &lows, &closes, 9, 26, 26, 26)
150 .ok()
151 .map(|result| IchimokuData {
152 conversion_line: last_value(&result.conversion_line),
153 base_line: last_value(&result.base_line),
154 leading_span_a: last_value(&result.leading_span_a),
155 leading_span_b: last_value(&result.leading_span_b),
156 lagging_span: last_value(&result.lagging_span),
157 })
158 },
159 parabolic_sar: last_from_result(parabolic_sar(&highs, &lows, &closes, 0.02, 0.2)),
160 bull_bear_power: {
161 bull_bear_power(&highs, &lows, &closes, 13)
162 .ok()
163 .map(|result| BullBearPowerData {
164 bull_power: last_value(&result.bull_power),
165 bear_power: last_value(&result.bear_power),
166 })
167 },
168 elder_ray_index: {
169 elder_ray(&highs, &lows, &closes, 13)
170 .ok()
171 .map(|result| ElderRayData {
172 bull_power: last_value(&result.bull_power),
173 bear_power: last_value(&result.bear_power),
174 })
175 },
176
177 bollinger_bands: {
179 bollinger_bands(&closes, 20, 2.0)
180 .ok()
181 .map(|result| BollingerBandsData {
182 upper: last_value(&result.upper),
183 middle: last_value(&result.middle),
184 lower: last_value(&result.lower),
185 })
186 },
187 keltner_channels: {
188 atr_10_dense.as_deref().and_then(|atr_dense| {
189 keltner_with_atr_dense(&closes, 20, atr_dense, 10, 2.0)
190 .ok()
191 .map(|result| KeltnerChannelsData {
192 upper: last_value(&result.upper),
193 middle: last_value(&result.middle),
194 lower: last_value(&result.lower),
195 })
196 })
197 },
198 donchian_channels: {
199 donchian_channels(&highs, &lows, 20)
200 .ok()
201 .map(|result| DonchianChannelsData {
202 upper: last_value(&result.upper),
203 middle: last_value(&result.middle),
204 lower: last_value(&result.lower),
205 })
206 },
207 atr_14: last_from_result(atr(&highs, &lows, &closes, 14)),
208 true_range: last_from_result(true_range(&highs, &lows, &closes)),
209 choppiness_index_14: last_from_result(choppiness_index(&highs, &lows, &closes, 14)),
210
211 obv: last_from_result(obv(&closes, &volumes)),
213 mfi_14: last_from_result(mfi(&highs, &lows, &closes, &volumes, 14)),
214 cmf_20: last_from_result(cmf(&highs, &lows, &closes, &volumes, 20)),
215 chaikin_oscillator: last_from_result(chaikin_oscillator(&highs, &lows, &closes, &volumes)),
216 accumulation_distribution: last_from_result(accumulation_distribution(
217 &highs, &lows, &closes, &volumes,
218 )),
219 vwap: last_from_result(vwap(&highs, &lows, &closes, &volumes)),
220 balance_of_power: last_from_result(balance_of_power(&opens, &highs, &lows, &closes, None)),
221 }
222}
223
224#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
226#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
227#[serde(rename_all = "camelCase")]
228pub struct IndicatorsSummary {
229 #[serde(skip_serializing_if = "Option::is_none")]
233 pub sma_10: Option<f64>,
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub sma_20: Option<f64>,
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub sma_50: Option<f64>,
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub sma_100: Option<f64>,
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub sma_200: Option<f64>,
246
247 #[serde(skip_serializing_if = "Option::is_none")]
250 pub ema_10: Option<f64>,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub ema_20: Option<f64>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub ema_50: Option<f64>,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub ema_100: Option<f64>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub ema_200: Option<f64>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
267 pub wma_10: Option<f64>,
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub wma_20: Option<f64>,
271 #[serde(skip_serializing_if = "Option::is_none")]
273 pub wma_50: Option<f64>,
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub wma_100: Option<f64>,
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub wma_200: Option<f64>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
284 pub dema_20: Option<f64>,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub tema_20: Option<f64>,
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub hma_20: Option<f64>,
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub vwma_20: Option<f64>,
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub alma_9: Option<f64>,
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub mcginley_dynamic_20: Option<f64>,
300
301 #[serde(skip_serializing_if = "Option::is_none")]
304 pub rsi_14: Option<f64>,
305 #[serde(skip_serializing_if = "Option::is_none")]
307 pub stochastic: Option<StochasticData>,
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub cci_20: Option<f64>,
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub williams_r_14: Option<f64>,
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub stochastic_rsi: Option<StochasticData>,
317 #[serde(skip_serializing_if = "Option::is_none")]
319 pub roc_12: Option<f64>,
320 #[serde(skip_serializing_if = "Option::is_none")]
322 pub momentum_10: Option<f64>,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub cmo_14: Option<f64>,
326 #[serde(skip_serializing_if = "Option::is_none")]
328 pub awesome_oscillator: Option<f64>,
329 #[serde(skip_serializing_if = "Option::is_none")]
331 pub coppock_curve: Option<f64>,
332
333 #[serde(skip_serializing_if = "Option::is_none")]
336 pub macd: Option<MacdData>,
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub adx_14: Option<f64>,
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub aroon: Option<AroonData>,
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub supertrend: Option<SuperTrendData>,
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub ichimoku: Option<IchimokuData>,
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub parabolic_sar: Option<f64>,
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub bull_bear_power: Option<BullBearPowerData>,
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub elder_ray_index: Option<ElderRayData>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
362 pub bollinger_bands: Option<BollingerBandsData>,
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub atr_14: Option<f64>,
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub keltner_channels: Option<KeltnerChannelsData>,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub donchian_channels: Option<DonchianChannelsData>,
372 #[serde(skip_serializing_if = "Option::is_none")]
374 pub true_range: Option<f64>,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub choppiness_index_14: Option<f64>,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
382 pub obv: Option<f64>,
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub mfi_14: Option<f64>,
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub cmf_20: Option<f64>,
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub chaikin_oscillator: Option<f64>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub accumulation_distribution: Option<f64>,
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub vwap: Option<f64>,
398 #[serde(skip_serializing_if = "Option::is_none")]
400 pub balance_of_power: Option<f64>,
401}
402
403#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
405#[serde(rename_all = "camelCase")]
406pub struct StochasticData {
407 #[serde(rename = "%K", skip_serializing_if = "Option::is_none")]
409 pub k: Option<f64>,
410 #[serde(rename = "%D", skip_serializing_if = "Option::is_none")]
412 pub d: Option<f64>,
413}
414
415#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
417#[serde(rename_all = "camelCase")]
418pub struct MacdData {
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub macd: Option<f64>,
422 #[serde(skip_serializing_if = "Option::is_none")]
424 pub signal: Option<f64>,
425 #[serde(skip_serializing_if = "Option::is_none")]
427 pub histogram: Option<f64>,
428}
429
430#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
432#[serde(rename_all = "camelCase")]
433pub struct AroonData {
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub aroon_up: Option<f64>,
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub aroon_down: Option<f64>,
440}
441
442#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
444#[serde(rename_all = "camelCase")]
445pub struct BollingerBandsData {
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub upper: Option<f64>,
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub middle: Option<f64>,
452 #[serde(skip_serializing_if = "Option::is_none")]
454 pub lower: Option<f64>,
455}
456
457#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
459#[serde(rename_all = "camelCase")]
460pub struct SuperTrendData {
461 #[serde(skip_serializing_if = "Option::is_none")]
463 pub value: Option<f64>,
464 #[serde(skip_serializing_if = "Option::is_none")]
466 pub trend: Option<String>,
467}
468
469#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
471#[serde(rename_all = "camelCase")]
472pub struct IchimokuData {
473 #[serde(skip_serializing_if = "Option::is_none")]
475 pub conversion_line: Option<f64>,
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub base_line: Option<f64>,
479 #[serde(skip_serializing_if = "Option::is_none")]
481 pub leading_span_a: Option<f64>,
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub leading_span_b: Option<f64>,
485 #[serde(skip_serializing_if = "Option::is_none")]
487 pub lagging_span: Option<f64>,
488}
489
490#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct KeltnerChannelsData {
494 #[serde(skip_serializing_if = "Option::is_none")]
496 pub upper: Option<f64>,
497 #[serde(skip_serializing_if = "Option::is_none")]
499 pub middle: Option<f64>,
500 #[serde(skip_serializing_if = "Option::is_none")]
502 pub lower: Option<f64>,
503}
504
505#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
507#[serde(rename_all = "camelCase")]
508pub struct DonchianChannelsData {
509 #[serde(skip_serializing_if = "Option::is_none")]
511 pub upper: Option<f64>,
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub middle: Option<f64>,
515 #[serde(skip_serializing_if = "Option::is_none")]
517 pub lower: Option<f64>,
518}
519
520#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct BullBearPowerData {
524 #[serde(skip_serializing_if = "Option::is_none")]
526 pub bull_power: Option<f64>,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub bear_power: Option<f64>,
530}
531
532#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
534#[serde(rename_all = "camelCase")]
535pub struct ElderRayData {
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub bull_power: Option<f64>,
539 #[serde(skip_serializing_if = "Option::is_none")]
541 pub bear_power: Option<f64>,
542}