1use super::last_value;
10use crate::Candle;
11use crate::indicators::{
12 FibonacciLevels, PivotPoints, ZigZagPoint, accumulation_distribution, adx, alma, aroon, atr,
13 atr::atr_raw, awesome_oscillator, balance_of_power, bollinger_bands, bull_bear_power, cci,
14 chaikin_oscillator, choppiness_index, cmf, cmo, coppock_curve, dema, donchian_channels,
15 elder_ray, ema::ema_raw, fibonacci_pivot_points, fibonacci_retracement, heikin_ashi_raw, hma,
16 ichimoku, keltner_channels::keltner_with_atr_dense, macd, mcginley_dynamic, mfi, momentum, obv,
17 parabolic_sar, pivot_points, roc, rsi::rsi_raw, sma::sma_raw, stochastic,
18 stochastic_rsi::stochastic_rsi_from_rsi_dense, supertrend::supertrend_with_atr_dense, tema,
19 true_range, vwap, vwma, williams_r, wma::wma_raw, zigzag,
20};
21
22#[inline]
24fn last_from_result(result: crate::indicators::Result<Vec<Option<f64>>>) -> Option<f64> {
25 result.ok().and_then(|v| last_value(&v))
26}
27
28pub(crate) fn calculate_indicators(candles: &[Candle]) -> IndicatorsSummary {
33 if candles.is_empty() {
34 return IndicatorsSummary::default();
35 }
36
37 let len = candles.len();
39 let mut closes = Vec::with_capacity(len);
40 let mut highs = Vec::with_capacity(len);
41 let mut lows = Vec::with_capacity(len);
42 let mut opens = Vec::with_capacity(len);
43 let mut volumes = Vec::with_capacity(len);
44 for c in candles {
45 closes.push(c.close);
46 highs.push(c.high);
47 lows.push(c.low);
48 opens.push(c.open);
49 volumes.push(c.volume as f64);
50 }
51
52 let rsi_14_dense = rsi_raw(&closes, 14).ok();
54 let atr_10_dense = atr_raw(&highs, &lows, &closes, 10).ok();
55
56 IndicatorsSummary {
57 sma_10: sma_raw(&closes, 10).last().copied(),
59 sma_20: sma_raw(&closes, 20).last().copied(),
60 sma_50: sma_raw(&closes, 50).last().copied(),
61 sma_100: sma_raw(&closes, 100).last().copied(),
62 sma_200: sma_raw(&closes, 200).last().copied(),
63
64 ema_10: ema_raw(&closes, 10).last().copied(),
65 ema_20: ema_raw(&closes, 20).last().copied(),
66 ema_50: ema_raw(&closes, 50).last().copied(),
67 ema_100: ema_raw(&closes, 100).last().copied(),
68 ema_200: ema_raw(&closes, 200).last().copied(),
69
70 wma_10: wma_raw(&closes, 10).last().copied(),
71 wma_20: wma_raw(&closes, 20).last().copied(),
72 wma_50: wma_raw(&closes, 50).last().copied(),
73 wma_100: wma_raw(&closes, 100).last().copied(),
74 wma_200: wma_raw(&closes, 200).last().copied(),
75
76 dema_20: dema(&closes, 20).ok().and_then(|v| last_value(&v)),
78 tema_20: tema(&closes, 20).ok().and_then(|v| last_value(&v)),
79 hma_20: hma(&closes, 20).ok().and_then(|v| last_value(&v)),
80 vwma_20: vwma(&closes, &volumes, 20)
81 .ok()
82 .and_then(|v| last_value(&v)),
83 alma_9: alma(&closes, 9, 0.85, 6.0)
84 .ok()
85 .and_then(|v| last_value(&v)),
86 mcginley_dynamic_20: mcginley_dynamic(&closes, 20)
87 .ok()
88 .and_then(|v| last_value(&v)),
89
90 rsi_14: rsi_14_dense.as_deref().and_then(|v| v.last().copied()),
92 stochastic: {
93 stochastic(&highs, &lows, &closes, 14, 1, 3)
94 .ok()
95 .map(|result| StochasticData {
96 k: last_value(&result.k),
97 d: last_value(&result.d),
98 })
99 },
100 stochastic_rsi: {
101 rsi_14_dense.as_deref().and_then(|rsi_dense| {
102 stochastic_rsi_from_rsi_dense(rsi_dense, len, 14, 14, 3, 3)
103 .ok()
104 .map(|result| StochasticData {
105 k: last_value(&result.k),
106 d: last_value(&result.d),
107 })
108 })
109 },
110 cci_20: last_from_result(cci(&highs, &lows, &closes, 20)),
111 williams_r_14: last_from_result(williams_r(&highs, &lows, &closes, 14)),
112 roc_12: last_from_result(roc(&closes, 12)),
113 momentum_10: last_from_result(momentum(&closes, 10)),
114 cmo_14: last_from_result(cmo(&closes, 14)),
115 awesome_oscillator: last_from_result(awesome_oscillator(&highs, &lows, 5, 34)),
116 coppock_curve: last_from_result(coppock_curve(&closes, 14, 11, 10)),
117
118 macd: {
120 macd(&closes, 12, 26, 9).ok().map(|result| MacdData {
121 macd: last_value(&result.macd_line),
122 signal: last_value(&result.signal_line),
123 histogram: last_value(&result.histogram),
124 })
125 },
126 adx_14: last_from_result(adx(&highs, &lows, &closes, 14)),
127 aroon: {
128 aroon(&highs, &lows, 25).ok().map(|result| AroonData {
129 aroon_up: last_value(&result.aroon_up),
130 aroon_down: last_value(&result.aroon_down),
131 })
132 },
133 supertrend: {
134 atr_10_dense.as_deref().and_then(|atr_dense| {
135 supertrend_with_atr_dense(&highs, &lows, &closes, atr_dense, 10, 3.0)
136 .ok()
137 .map(|result| SuperTrendData {
138 value: last_value(&result.value),
139 trend: result.is_uptrend.last().and_then(|&v| v).map(|v| {
140 if v {
141 "up".to_string()
142 } else {
143 "down".to_string()
144 }
145 }),
146 })
147 })
148 },
149 ichimoku: {
150 ichimoku(&highs, &lows, &closes, 9, 26, 26, 26)
151 .ok()
152 .map(|result| IchimokuData {
153 conversion_line: last_value(&result.conversion_line),
154 base_line: last_value(&result.base_line),
155 leading_span_a: last_value(&result.leading_span_a),
156 leading_span_b: last_value(&result.leading_span_b),
157 lagging_span: last_value(&result.lagging_span),
158 })
159 },
160 parabolic_sar: last_from_result(parabolic_sar(&highs, &lows, &closes, 0.02, 0.2)),
161 bull_bear_power: {
162 bull_bear_power(&highs, &lows, &closes, 13)
163 .ok()
164 .map(|result| BullBearPowerData {
165 bull_power: last_value(&result.bull_power),
166 bear_power: last_value(&result.bear_power),
167 })
168 },
169 elder_ray_index: {
170 elder_ray(&highs, &lows, &closes, 13)
171 .ok()
172 .map(|result| ElderRayData {
173 bull_power: last_value(&result.bull_power),
174 bear_power: last_value(&result.bear_power),
175 })
176 },
177
178 bollinger_bands: {
180 bollinger_bands(&closes, 20, 2.0)
181 .ok()
182 .map(|result| BollingerBandsData {
183 upper: last_value(&result.upper),
184 middle: last_value(&result.middle),
185 lower: last_value(&result.lower),
186 })
187 },
188 keltner_channels: {
189 atr_10_dense.as_deref().and_then(|atr_dense| {
190 keltner_with_atr_dense(&closes, 20, atr_dense, 10, 2.0)
191 .ok()
192 .map(|result| KeltnerChannelsData {
193 upper: last_value(&result.upper),
194 middle: last_value(&result.middle),
195 lower: last_value(&result.lower),
196 })
197 })
198 },
199 donchian_channels: {
200 donchian_channels(&highs, &lows, 20)
201 .ok()
202 .map(|result| DonchianChannelsData {
203 upper: last_value(&result.upper),
204 middle: last_value(&result.middle),
205 lower: last_value(&result.lower),
206 })
207 },
208 atr_14: last_from_result(atr(&highs, &lows, &closes, 14)),
209 true_range: last_from_result(true_range(&highs, &lows, &closes)),
210 choppiness_index_14: last_from_result(choppiness_index(&highs, &lows, &closes, 14)),
211
212 obv: last_from_result(obv(&closes, &volumes)),
214 mfi_14: last_from_result(mfi(&highs, &lows, &closes, &volumes, 14)),
215 cmf_20: last_from_result(cmf(&highs, &lows, &closes, &volumes, 20)),
216 chaikin_oscillator: last_from_result(chaikin_oscillator(&highs, &lows, &closes, &volumes)),
217 accumulation_distribution: last_from_result(accumulation_distribution(
218 &highs, &lows, &closes, &volumes,
219 )),
220 vwap: last_from_result(vwap(&highs, &lows, &closes, &volumes)),
221 balance_of_power: last_from_result(balance_of_power(&opens, &highs, &lows, &closes, None)),
222
223 pivot_points: pivot_points(&highs, &lows, &closes)
225 .ok()
226 .and_then(|v| v.into_iter().rev().find_map(|p| p)),
227 fibonacci_pivot_points: fibonacci_pivot_points(&highs, &lows, &closes)
228 .ok()
229 .and_then(|v| v.into_iter().rev().find_map(|p| p)),
230 heikin_ashi: heikin_ashi_raw(&opens, &highs, &lows, &closes)
231 .ok()
232 .and_then(|series| {
233 let last = candles.last()?;
234 let i = series.close.len().checked_sub(1)?;
235 Some(Candle {
236 timestamp: last.timestamp,
237 open: series.open[i],
238 high: series.high[i],
239 low: series.low[i],
240 close: series.close[i],
241 volume: last.volume,
242 adj_close: last.adj_close,
243 provider_id: last.provider_id,
244 })
245 }),
246 zigzag_last_pivot: zigzag(&highs, &lows, 5.0)
247 .ok()
248 .and_then(|v| v.last().copied()),
249 fibonacci_retracement_50: fibonacci_retracement(&highs, &lows, 50)
250 .ok()
251 .and_then(|v| v.into_iter().rev().find_map(|p| p)),
252 }
253}
254
255#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
257#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
258#[serde(rename_all = "camelCase")]
259pub struct IndicatorsSummary {
260 #[serde(skip_serializing_if = "Option::is_none")]
264 pub sma_10: Option<f64>,
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub sma_20: Option<f64>,
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub sma_50: Option<f64>,
271 #[serde(skip_serializing_if = "Option::is_none")]
273 pub sma_100: Option<f64>,
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub sma_200: Option<f64>,
277
278 #[serde(skip_serializing_if = "Option::is_none")]
281 pub ema_10: Option<f64>,
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub ema_20: Option<f64>,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub ema_50: Option<f64>,
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub ema_100: Option<f64>,
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub ema_200: Option<f64>,
294
295 #[serde(skip_serializing_if = "Option::is_none")]
298 pub wma_10: Option<f64>,
299 #[serde(skip_serializing_if = "Option::is_none")]
301 pub wma_20: Option<f64>,
302 #[serde(skip_serializing_if = "Option::is_none")]
304 pub wma_50: Option<f64>,
305 #[serde(skip_serializing_if = "Option::is_none")]
307 pub wma_100: Option<f64>,
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub wma_200: Option<f64>,
311
312 #[serde(skip_serializing_if = "Option::is_none")]
315 pub dema_20: Option<f64>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub tema_20: Option<f64>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub hma_20: Option<f64>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub vwma_20: Option<f64>,
325 #[serde(skip_serializing_if = "Option::is_none")]
327 pub alma_9: Option<f64>,
328 #[serde(skip_serializing_if = "Option::is_none")]
330 pub mcginley_dynamic_20: Option<f64>,
331
332 #[serde(skip_serializing_if = "Option::is_none")]
335 pub rsi_14: Option<f64>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub stochastic: Option<StochasticData>,
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub cci_20: Option<f64>,
342 #[serde(skip_serializing_if = "Option::is_none")]
344 pub williams_r_14: Option<f64>,
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub stochastic_rsi: Option<StochasticData>,
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub roc_12: Option<f64>,
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub momentum_10: Option<f64>,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub cmo_14: Option<f64>,
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub awesome_oscillator: Option<f64>,
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub coppock_curve: Option<f64>,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
367 pub macd: Option<MacdData>,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub adx_14: Option<f64>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub aroon: Option<AroonData>,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub supertrend: Option<SuperTrendData>,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub ichimoku: Option<IchimokuData>,
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub parabolic_sar: Option<f64>,
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub bull_bear_power: Option<BullBearPowerData>,
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub elder_ray_index: Option<ElderRayData>,
389
390 #[serde(skip_serializing_if = "Option::is_none")]
393 pub bollinger_bands: Option<BollingerBandsData>,
394 #[serde(skip_serializing_if = "Option::is_none")]
396 pub atr_14: Option<f64>,
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub keltner_channels: Option<KeltnerChannelsData>,
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub donchian_channels: Option<DonchianChannelsData>,
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub true_range: Option<f64>,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub choppiness_index_14: Option<f64>,
409
410 #[serde(skip_serializing_if = "Option::is_none")]
413 pub obv: Option<f64>,
414 #[serde(skip_serializing_if = "Option::is_none")]
416 pub mfi_14: Option<f64>,
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub cmf_20: Option<f64>,
420 #[serde(skip_serializing_if = "Option::is_none")]
422 pub chaikin_oscillator: Option<f64>,
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub accumulation_distribution: Option<f64>,
426 #[serde(skip_serializing_if = "Option::is_none")]
428 pub vwap: Option<f64>,
429 #[serde(skip_serializing_if = "Option::is_none")]
431 pub balance_of_power: Option<f64>,
432
433 #[serde(skip_serializing_if = "Option::is_none")]
436 pub pivot_points: Option<PivotPoints>,
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub fibonacci_pivot_points: Option<PivotPoints>,
440 #[serde(skip_serializing_if = "Option::is_none")]
442 pub heikin_ashi: Option<Candle>,
443 #[serde(skip_serializing_if = "Option::is_none")]
445 pub zigzag_last_pivot: Option<ZigZagPoint>,
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub fibonacci_retracement_50: Option<FibonacciLevels>,
449}
450
451#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
453#[serde(rename_all = "camelCase")]
454pub struct StochasticData {
455 #[serde(rename = "%K", skip_serializing_if = "Option::is_none")]
457 pub k: Option<f64>,
458 #[serde(rename = "%D", skip_serializing_if = "Option::is_none")]
460 pub d: Option<f64>,
461}
462
463#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
465#[serde(rename_all = "camelCase")]
466pub struct MacdData {
467 #[serde(skip_serializing_if = "Option::is_none")]
469 pub macd: Option<f64>,
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub signal: Option<f64>,
473 #[serde(skip_serializing_if = "Option::is_none")]
475 pub histogram: Option<f64>,
476}
477
478#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub struct AroonData {
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub aroon_up: Option<f64>,
485 #[serde(skip_serializing_if = "Option::is_none")]
487 pub aroon_down: Option<f64>,
488}
489
490#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct BollingerBandsData {
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 SuperTrendData {
509 #[serde(skip_serializing_if = "Option::is_none")]
511 pub value: Option<f64>,
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub trend: Option<String>,
515}
516
517#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
519#[serde(rename_all = "camelCase")]
520pub struct IchimokuData {
521 #[serde(skip_serializing_if = "Option::is_none")]
523 pub conversion_line: Option<f64>,
524 #[serde(skip_serializing_if = "Option::is_none")]
526 pub base_line: Option<f64>,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub leading_span_a: Option<f64>,
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub leading_span_b: Option<f64>,
533 #[serde(skip_serializing_if = "Option::is_none")]
535 pub lagging_span: Option<f64>,
536}
537
538#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
540#[serde(rename_all = "camelCase")]
541pub struct KeltnerChannelsData {
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub upper: Option<f64>,
545 #[serde(skip_serializing_if = "Option::is_none")]
547 pub middle: Option<f64>,
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub lower: Option<f64>,
551}
552
553#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
555#[serde(rename_all = "camelCase")]
556pub struct DonchianChannelsData {
557 #[serde(skip_serializing_if = "Option::is_none")]
559 pub upper: Option<f64>,
560 #[serde(skip_serializing_if = "Option::is_none")]
562 pub middle: Option<f64>,
563 #[serde(skip_serializing_if = "Option::is_none")]
565 pub lower: Option<f64>,
566}
567
568#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct BullBearPowerData {
572 #[serde(skip_serializing_if = "Option::is_none")]
574 pub bull_power: Option<f64>,
575 #[serde(skip_serializing_if = "Option::is_none")]
577 pub bear_power: Option<f64>,
578}
579
580#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct ElderRayData {
584 #[serde(skip_serializing_if = "Option::is_none")]
586 pub bull_power: Option<f64>,
587 #[serde(skip_serializing_if = "Option::is_none")]
589 pub bear_power: Option<f64>,
590}