Skip to main content

finance_query/indicators/
summary.rs

1//! Indicators summary module.
2//!
3//! Provides the `IndicatorsSummary` type which calculates and returns the latest
4//! values for all 56+ technical indicators at once.
5//!
6//! This module reuses the main indicator implementations and extracts the last value,
7//! ensuring consistency and eliminating code duplication.
8
9use 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/// Helper to extract last value from Result-returning indicators
23#[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
28/// Calculate all technical indicators from candle data.
29///
30/// Returns the latest values for all implemented indicators.
31/// Reuses the main indicator implementations for consistency.
32pub(crate) fn calculate_indicators(candles: &[Candle]) -> IndicatorsSummary {
33    if candles.is_empty() {
34        return IndicatorsSummary::default();
35    }
36
37    // Extract price data from candles in a single pass (avoids 5 separate iterations)
38    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    // Pre-compute shared intermediates (avoids redundant passes)
53    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        // === MOVING AVERAGES ===
58        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        // Advanced Moving Averages (Result types)
77        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        // === MOMENTUM OSCILLATORS ===
91        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        // === TREND INDICATORS ===
119        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        // === VOLATILITY INDICATORS ===
179        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        // === VOLUME INDICATORS ===
213        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, HEIKIN-ASHI, ZIGZAG, FIBONACCI ===
224        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/// Summary of all calculated technical indicators
256#[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    // === MOVING AVERAGES ===
261    // Simple Moving Averages
262    /// Simple Moving Average (10-period)
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub sma_10: Option<f64>,
265    /// Simple Moving Average (20-period)
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub sma_20: Option<f64>,
268    /// Simple Moving Average (50-period)
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub sma_50: Option<f64>,
271    /// Simple Moving Average (100-period)
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub sma_100: Option<f64>,
274    /// Simple Moving Average (200-period)
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub sma_200: Option<f64>,
277
278    // Exponential Moving Averages
279    /// Exponential Moving Average (10-period)
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub ema_10: Option<f64>,
282    /// Exponential Moving Average (20-period)
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub ema_20: Option<f64>,
285    /// Exponential Moving Average (50-period)
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub ema_50: Option<f64>,
288    /// Exponential Moving Average (100-period)
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub ema_100: Option<f64>,
291    /// Exponential Moving Average (200-period)
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub ema_200: Option<f64>,
294
295    // Weighted Moving Averages
296    /// Weighted Moving Average (10-period)
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub wma_10: Option<f64>,
299    /// Weighted Moving Average (20-period)
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub wma_20: Option<f64>,
302    /// Weighted Moving Average (50-period)
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub wma_50: Option<f64>,
305    /// Weighted Moving Average (100-period)
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub wma_100: Option<f64>,
308    /// Weighted Moving Average (200-period)
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub wma_200: Option<f64>,
311
312    // Advanced Moving Averages
313    /// Double Exponential Moving Average (20-period)
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub dema_20: Option<f64>,
316    /// Triple Exponential Moving Average (20-period)
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub tema_20: Option<f64>,
319    /// Hull Moving Average (20-period)
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub hma_20: Option<f64>,
322    /// Volume Weighted Moving Average (20-period)
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub vwma_20: Option<f64>,
325    /// Arnaud Legoux Moving Average (9-period)
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub alma_9: Option<f64>,
328    /// McGinley Dynamic (20-period)
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub mcginley_dynamic_20: Option<f64>,
331
332    // === MOMENTUM OSCILLATORS ===
333    /// Relative Strength Index (14-period)
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub rsi_14: Option<f64>,
336    /// Stochastic Oscillator (14, 3, 3)
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub stochastic: Option<StochasticData>,
339    /// Commodity Channel Index (20-period)
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub cci_20: Option<f64>,
342    /// Williams %R (14-period)
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub williams_r_14: Option<f64>,
345    /// Stochastic RSI (14, 14)
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub stochastic_rsi: Option<StochasticData>,
348    /// Rate of Change (12-period)
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub roc_12: Option<f64>,
351    /// Momentum (10-period)
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub momentum_10: Option<f64>,
354    /// Chande Momentum Oscillator (14-period)
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub cmo_14: Option<f64>,
357    /// Awesome Oscillator (5, 34)
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub awesome_oscillator: Option<f64>,
360    /// Coppock Curve (10, 11, 14)
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub coppock_curve: Option<f64>,
363
364    // === TREND INDICATORS ===
365    /// Moving Average Convergence Divergence (12, 26, 9)
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub macd: Option<MacdData>,
368    /// Average Directional Index (14-period)
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub adx_14: Option<f64>,
371    /// Aroon Indicator (25-period)
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub aroon: Option<AroonData>,
374    /// SuperTrend Indicator (10, 3.0)
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub supertrend: Option<SuperTrendData>,
377    /// Ichimoku Cloud (9, 26, 52, 26)
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub ichimoku: Option<IchimokuData>,
380    /// Parabolic SAR (0.02, 0.2)
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub parabolic_sar: Option<f64>,
383    /// Bull Bear Power (13-period EMA based)
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub bull_bear_power: Option<BullBearPowerData>,
386    /// Elder Ray Index (13-period EMA based)
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub elder_ray_index: Option<ElderRayData>,
389
390    // === VOLATILITY INDICATORS ===
391    /// Bollinger Bands (20, 2.0)
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub bollinger_bands: Option<BollingerBandsData>,
394    /// Average True Range (14-period)
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub atr_14: Option<f64>,
397    /// Keltner Channels (20, 10, 2.0)
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub keltner_channels: Option<KeltnerChannelsData>,
400    /// Donchian Channels (20-period)
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub donchian_channels: Option<DonchianChannelsData>,
403    /// True Range (current period)
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub true_range: Option<f64>,
406    /// Choppiness Index (14-period)
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub choppiness_index_14: Option<f64>,
409
410    // === VOLUME INDICATORS ===
411    /// On-Balance Volume
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub obv: Option<f64>,
414    /// Money Flow Index (14-period)
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub mfi_14: Option<f64>,
417    /// Chaikin Money Flow (20-period)
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub cmf_20: Option<f64>,
420    /// Chaikin Oscillator (3, 10)
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub chaikin_oscillator: Option<f64>,
423    /// Accumulation/Distribution Line
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub accumulation_distribution: Option<f64>,
426    /// Volume Weighted Average Price
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub vwap: Option<f64>,
429    /// Balance of Power
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub balance_of_power: Option<f64>,
432
433    // === PIVOT POINTS, HEIKIN-ASHI, ZIGZAG, FIBONACCI ===
434    /// Standard (classic) Pivot Points, derived from the previous bar's high/low/close
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub pivot_points: Option<PivotPoints>,
437    /// Fibonacci Pivot Points, derived from the previous bar's high/low/close
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub fibonacci_pivot_points: Option<PivotPoints>,
440    /// Latest Heikin-Ashi ("average bar") candle
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub heikin_ashi: Option<Candle>,
443    /// Most recent confirmed ZigZag swing point (5% reversal threshold)
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub zigzag_last_pivot: Option<ZigZagPoint>,
446    /// Fibonacci Retracement levels (50-period rolling window)
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub fibonacci_retracement_50: Option<FibonacciLevels>,
449}
450
451/// Stochastic Oscillator data
452#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
453#[serde(rename_all = "camelCase")]
454pub struct StochasticData {
455    /// %K line value
456    #[serde(rename = "%K", skip_serializing_if = "Option::is_none")]
457    pub k: Option<f64>,
458    /// %D line value
459    #[serde(rename = "%D", skip_serializing_if = "Option::is_none")]
460    pub d: Option<f64>,
461}
462
463/// MACD indicator data
464#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
465#[serde(rename_all = "camelCase")]
466pub struct MacdData {
467    /// MACD line value
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub macd: Option<f64>,
470    /// Signal line value
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub signal: Option<f64>,
473    /// Histogram value
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub histogram: Option<f64>,
476}
477
478/// Aroon indicator data
479#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub struct AroonData {
482    /// Aroon Up value
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub aroon_up: Option<f64>,
485    /// Aroon Down value
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub aroon_down: Option<f64>,
488}
489
490/// Bollinger Bands data
491#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct BollingerBandsData {
494    /// Upper band value
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub upper: Option<f64>,
497    /// Middle band value
498    #[serde(skip_serializing_if = "Option::is_none")]
499    pub middle: Option<f64>,
500    /// Lower band value
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub lower: Option<f64>,
503}
504
505/// SuperTrend indicator data
506#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
507#[serde(rename_all = "camelCase")]
508pub struct SuperTrendData {
509    /// SuperTrend value
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub value: Option<f64>,
512    /// Trend direction
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub trend: Option<String>,
515}
516
517/// Ichimoku Cloud data
518#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
519#[serde(rename_all = "camelCase")]
520pub struct IchimokuData {
521    /// Conversion line (Tenkan-sen)
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub conversion_line: Option<f64>,
524    /// Base line (Kijun-sen)
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub base_line: Option<f64>,
527    /// Leading Span A (Senkou Span A)
528    #[serde(skip_serializing_if = "Option::is_none")]
529    pub leading_span_a: Option<f64>,
530    /// Leading Span B (Senkou Span B)
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub leading_span_b: Option<f64>,
533    /// Lagging Span (Chikou Span)
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub lagging_span: Option<f64>,
536}
537
538/// Keltner Channels data
539#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
540#[serde(rename_all = "camelCase")]
541pub struct KeltnerChannelsData {
542    /// Upper channel value
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub upper: Option<f64>,
545    /// Middle channel value
546    #[serde(skip_serializing_if = "Option::is_none")]
547    pub middle: Option<f64>,
548    /// Lower channel value
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub lower: Option<f64>,
551}
552
553/// Donchian Channels data
554#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
555#[serde(rename_all = "camelCase")]
556pub struct DonchianChannelsData {
557    /// Upper channel value
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub upper: Option<f64>,
560    /// Middle channel value
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub middle: Option<f64>,
563    /// Lower channel value
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub lower: Option<f64>,
566}
567
568/// Bull Bear Power indicator data
569#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct BullBearPowerData {
572    /// Bull power value
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub bull_power: Option<f64>,
575    /// Bear power value
576    #[serde(skip_serializing_if = "Option::is_none")]
577    pub bear_power: Option<f64>,
578}
579
580/// Elder Ray Index data
581#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct ElderRayData {
584    /// Bull power value
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub bull_power: Option<f64>,
587    /// Bear power value
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub bear_power: Option<f64>,
590}