1use crate::Candle;
10use crate::indicators::{
11 accumulation_distribution, adx, alma, aroon, atr, awesome_oscillator, balance_of_power,
12 bollinger_bands, bull_bear_power, cci, chaikin_oscillator, choppiness_index, cmf, cmo,
13 coppock_curve, dema, donchian_channels, elder_ray, ema, hma, ichimoku, keltner_channels, macd,
14 mcginley_dynamic, mfi, momentum, obv, parabolic_sar, roc, rsi, sma, stochastic, stochastic_rsi,
15 supertrend, tema, true_range, vwap, vwma, williams_r, wma,
16};
17
18#[inline]
22fn last_value(series: &[Option<f64>]) -> Option<f64> {
23 series.iter().rev().find_map(|&v| v)
24}
25
26#[inline]
28fn last_from_result(result: crate::indicators::Result<Vec<Option<f64>>>) -> Option<f64> {
29 result.ok().and_then(|v| last_value(&v))
30}
31
32pub(crate) fn calculate_indicators(candles: &[Candle]) -> IndicatorsSummary {
37 if candles.is_empty() {
38 return IndicatorsSummary::default();
39 }
40
41 let closes: Vec<f64> = candles.iter().map(|c| c.close).collect();
43 let highs: Vec<f64> = candles.iter().map(|c| c.high).collect();
44 let lows: Vec<f64> = candles.iter().map(|c| c.low).collect();
45 let opens: Vec<f64> = candles.iter().map(|c| c.open).collect();
46 let volumes: Vec<f64> = candles.iter().map(|c| c.volume as f64).collect();
47
48 IndicatorsSummary {
49 sma_10: last_value(&sma(&closes, 10)),
52 sma_20: last_value(&sma(&closes, 20)),
53 sma_50: last_value(&sma(&closes, 50)),
54 sma_100: last_value(&sma(&closes, 100)),
55 sma_200: last_value(&sma(&closes, 200)),
56
57 ema_10: last_value(&ema(&closes, 10)),
59 ema_20: last_value(&ema(&closes, 20)),
60 ema_50: last_value(&ema(&closes, 50)),
61 ema_100: last_value(&ema(&closes, 100)),
62 ema_200: last_value(&ema(&closes, 200)),
63
64 wma_10: wma(&closes, 10).ok().and_then(|v| last_value(&v)),
66 wma_20: wma(&closes, 20).ok().and_then(|v| last_value(&v)),
67 wma_50: wma(&closes, 50).ok().and_then(|v| last_value(&v)),
68 wma_100: wma(&closes, 100).ok().and_then(|v| last_value(&v)),
69 wma_200: wma(&closes, 200).ok().and_then(|v| last_value(&v)),
70
71 dema_20: dema(&closes, 20).ok().and_then(|v| last_value(&v)),
73 tema_20: tema(&closes, 20).ok().and_then(|v| last_value(&v)),
74 hma_20: hma(&closes, 20).ok().and_then(|v| last_value(&v)),
75 vwma_20: vwma(&closes, &volumes, 20)
76 .ok()
77 .and_then(|v| last_value(&v)),
78 alma_9: alma(&closes, 9, 0.85, 6.0)
79 .ok()
80 .and_then(|v| last_value(&v)),
81 mcginley_dynamic_20: mcginley_dynamic(&closes, 20)
82 .ok()
83 .and_then(|v| last_value(&v)),
84
85 rsi_14: last_from_result(rsi(&closes, 14)),
87 stochastic: {
88 stochastic(&highs, &lows, &closes, 14, 3)
89 .ok()
90 .map(|result| StochasticData {
91 k: last_value(&result.k),
92 d: last_value(&result.d),
93 })
94 },
95 stochastic_rsi: {
96 stochastic_rsi(&closes, 14, 14).ok().and_then(|result| {
97 last_value(&result).map(|k| StochasticData {
98 k: Some(k),
99 d: None,
100 })
101 })
102 },
103 cci_20: last_from_result(cci(&highs, &lows, &closes, 20)),
104 williams_r_14: last_from_result(williams_r(&highs, &lows, &closes, 14)),
105 roc_12: last_from_result(roc(&closes, 12)),
106 momentum_10: last_from_result(momentum(&closes, 10)),
107 cmo_14: last_from_result(cmo(&closes, 14)),
108 awesome_oscillator: last_from_result(awesome_oscillator(&highs, &lows)),
109 coppock_curve: last_from_result(coppock_curve(&closes)),
110
111 macd: {
113 macd(&closes, 12, 26, 9).ok().map(|result| MacdData {
114 macd: last_value(&result.macd_line),
115 signal: last_value(&result.signal_line),
116 histogram: last_value(&result.histogram),
117 })
118 },
119 adx_14: last_from_result(adx(&highs, &lows, &closes, 14)),
120 aroon: {
121 aroon(&highs, &lows, 25).ok().map(|result| AroonData {
122 aroon_up: last_value(&result.aroon_up),
123 aroon_down: last_value(&result.aroon_down),
124 })
125 },
126 supertrend: {
127 supertrend(&highs, &lows, &closes, 10, 3.0)
128 .ok()
129 .map(|result| SuperTrendData {
130 value: last_value(&result.value),
131 trend: result.is_uptrend.last().and_then(|&v| v).map(|v| {
132 if v {
133 "up".to_string()
134 } else {
135 "down".to_string()
136 }
137 }),
138 })
139 },
140 ichimoku: {
141 ichimoku(&highs, &lows, &closes)
142 .ok()
143 .map(|result| IchimokuData {
144 conversion_line: last_value(&result.conversion_line),
145 base_line: last_value(&result.base_line),
146 leading_span_a: last_value(&result.leading_span_a),
147 leading_span_b: last_value(&result.leading_span_b),
148 lagging_span: last_value(&result.lagging_span),
149 })
150 },
151 parabolic_sar: last_from_result(parabolic_sar(&highs, &lows, &closes, 0.02, 0.2)),
152 bull_bear_power: {
153 bull_bear_power(&highs, &lows, &closes)
154 .ok()
155 .map(|result| BullBearPowerData {
156 bull_power: last_value(&result.bull_power),
157 bear_power: last_value(&result.bear_power),
158 })
159 },
160 elder_ray_index: {
161 elder_ray(&highs, &lows, &closes)
162 .ok()
163 .map(|result| ElderRayData {
164 bull_power: last_value(&result.bull_power),
165 bear_power: last_value(&result.bear_power),
166 })
167 },
168
169 bollinger_bands: {
171 bollinger_bands(&closes, 20, 2.0)
172 .ok()
173 .map(|result| BollingerBandsData {
174 upper: last_value(&result.upper),
175 middle: last_value(&result.middle),
176 lower: last_value(&result.lower),
177 })
178 },
179 keltner_channels: {
180 keltner_channels(&highs, &lows, &closes, 20, 10, 2.0)
181 .ok()
182 .map(|result| KeltnerChannelsData {
183 upper: last_value(&result.upper),
184 middle: last_value(&result.middle),
185 lower: last_value(&result.lower),
186 })
187 },
188 donchian_channels: {
189 donchian_channels(&highs, &lows, 20)
190 .ok()
191 .map(|result| DonchianChannelsData {
192 upper: last_value(&result.upper),
193 middle: last_value(&result.middle),
194 lower: last_value(&result.lower),
195 })
196 },
197 atr_14: last_from_result(atr(&highs, &lows, &closes, 14)),
198 true_range: last_from_result(true_range(&highs, &lows, &closes)),
199 choppiness_index_14: last_from_result(choppiness_index(&highs, &lows, &closes, 14)),
200
201 obv: last_from_result(obv(&closes, &volumes)),
203 mfi_14: last_from_result(mfi(&highs, &lows, &closes, &volumes, 14)),
204 cmf_20: last_from_result(cmf(&highs, &lows, &closes, &volumes, 20)),
205 chaikin_oscillator: last_from_result(chaikin_oscillator(&highs, &lows, &closes, &volumes)),
206 accumulation_distribution: last_from_result(accumulation_distribution(
207 &highs, &lows, &closes, &volumes,
208 )),
209 vwap: last_from_result(vwap(&highs, &lows, &closes, &volumes)),
210 balance_of_power: last_from_result(balance_of_power(&opens, &highs, &lows, &closes, None)),
211 }
212}
213
214#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
216#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
217#[serde(rename_all = "camelCase")]
218pub struct IndicatorsSummary {
219 #[serde(skip_serializing_if = "Option::is_none")]
223 pub sma_10: Option<f64>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub sma_20: Option<f64>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub sma_50: Option<f64>,
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub sma_100: Option<f64>,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub sma_200: Option<f64>,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
240 pub ema_10: Option<f64>,
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub ema_20: Option<f64>,
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub ema_50: Option<f64>,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub ema_100: Option<f64>,
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub ema_200: Option<f64>,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
257 pub wma_10: Option<f64>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub wma_20: Option<f64>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub wma_50: Option<f64>,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub wma_100: Option<f64>,
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub wma_200: Option<f64>,
270
271 #[serde(skip_serializing_if = "Option::is_none")]
274 pub dema_20: Option<f64>,
275 #[serde(skip_serializing_if = "Option::is_none")]
277 pub tema_20: Option<f64>,
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub hma_20: Option<f64>,
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub vwma_20: Option<f64>,
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub alma_9: Option<f64>,
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub mcginley_dynamic_20: Option<f64>,
290
291 #[serde(skip_serializing_if = "Option::is_none")]
294 pub rsi_14: Option<f64>,
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub stochastic: Option<StochasticData>,
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub cci_20: Option<f64>,
301 #[serde(skip_serializing_if = "Option::is_none")]
303 pub williams_r_14: Option<f64>,
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub stochastic_rsi: Option<StochasticData>,
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub roc_12: Option<f64>,
310 #[serde(skip_serializing_if = "Option::is_none")]
312 pub momentum_10: Option<f64>,
313 #[serde(skip_serializing_if = "Option::is_none")]
315 pub cmo_14: Option<f64>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub awesome_oscillator: Option<f64>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub coppock_curve: Option<f64>,
322
323 #[serde(skip_serializing_if = "Option::is_none")]
326 pub macd: Option<MacdData>,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub adx_14: Option<f64>,
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub aroon: Option<AroonData>,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 pub supertrend: Option<SuperTrendData>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub ichimoku: Option<IchimokuData>,
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub parabolic_sar: Option<f64>,
342 #[serde(skip_serializing_if = "Option::is_none")]
344 pub bull_bear_power: Option<BullBearPowerData>,
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub elder_ray_index: Option<ElderRayData>,
348
349 #[serde(skip_serializing_if = "Option::is_none")]
352 pub bollinger_bands: Option<BollingerBandsData>,
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub atr_14: Option<f64>,
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub keltner_channels: Option<KeltnerChannelsData>,
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub donchian_channels: Option<DonchianChannelsData>,
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub true_range: Option<f64>,
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub choppiness_index_14: Option<f64>,
368
369 #[serde(skip_serializing_if = "Option::is_none")]
372 pub obv: Option<f64>,
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub mfi_14: Option<f64>,
376 #[serde(skip_serializing_if = "Option::is_none")]
378 pub cmf_20: Option<f64>,
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub chaikin_oscillator: Option<f64>,
382 #[serde(skip_serializing_if = "Option::is_none")]
384 pub accumulation_distribution: Option<f64>,
385 #[serde(skip_serializing_if = "Option::is_none")]
387 pub vwap: Option<f64>,
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub balance_of_power: Option<f64>,
391}
392
393#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct StochasticData {
397 #[serde(rename = "%K", skip_serializing_if = "Option::is_none")]
399 pub k: Option<f64>,
400 #[serde(rename = "%D", skip_serializing_if = "Option::is_none")]
402 pub d: Option<f64>,
403}
404
405#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
407#[serde(rename_all = "camelCase")]
408pub struct MacdData {
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub macd: Option<f64>,
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub signal: Option<f64>,
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub histogram: Option<f64>,
418}
419
420#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
422#[serde(rename_all = "camelCase")]
423pub struct AroonData {
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub aroon_up: Option<f64>,
427 #[serde(skip_serializing_if = "Option::is_none")]
429 pub aroon_down: Option<f64>,
430}
431
432#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct BollingerBandsData {
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub upper: Option<f64>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 pub middle: Option<f64>,
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub lower: Option<f64>,
445}
446
447#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct SuperTrendData {
451 #[serde(skip_serializing_if = "Option::is_none")]
453 pub value: Option<f64>,
454 #[serde(skip_serializing_if = "Option::is_none")]
456 pub trend: Option<String>,
457}
458
459#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct IchimokuData {
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub conversion_line: Option<f64>,
466 #[serde(skip_serializing_if = "Option::is_none")]
468 pub base_line: Option<f64>,
469 #[serde(skip_serializing_if = "Option::is_none")]
471 pub leading_span_a: Option<f64>,
472 #[serde(skip_serializing_if = "Option::is_none")]
474 pub leading_span_b: Option<f64>,
475 #[serde(skip_serializing_if = "Option::is_none")]
477 pub lagging_span: Option<f64>,
478}
479
480#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
482#[serde(rename_all = "camelCase")]
483pub struct KeltnerChannelsData {
484 #[serde(skip_serializing_if = "Option::is_none")]
486 pub upper: Option<f64>,
487 #[serde(skip_serializing_if = "Option::is_none")]
489 pub middle: Option<f64>,
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub lower: Option<f64>,
493}
494
495#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
497#[serde(rename_all = "camelCase")]
498pub struct DonchianChannelsData {
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub upper: Option<f64>,
502 #[serde(skip_serializing_if = "Option::is_none")]
504 pub middle: Option<f64>,
505 #[serde(skip_serializing_if = "Option::is_none")]
507 pub lower: Option<f64>,
508}
509
510#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
512#[serde(rename_all = "camelCase")]
513pub struct BullBearPowerData {
514 #[serde(skip_serializing_if = "Option::is_none")]
516 pub bull_power: Option<f64>,
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub bear_power: Option<f64>,
520}
521
522#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
524#[serde(rename_all = "camelCase")]
525pub struct ElderRayData {
526 #[serde(skip_serializing_if = "Option::is_none")]
528 pub bull_power: Option<f64>,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub bear_power: Option<f64>,
532}