1mod momentum;
7mod moving_avg;
8mod trend;
9mod volatility;
10mod volume;
11
12use crate::models::chart::Candle;
13
14pub(crate) fn calculate_indicators(candles: &[Candle]) -> IndicatorsSummary {
18 if candles.is_empty() {
19 return IndicatorsSummary::default();
20 }
21
22 let (closes, highs, lows, opens, volumes) = prepare_data(candles);
23
24 IndicatorsSummary {
25 sma_10: moving_avg::sma(&closes, 10),
28 sma_20: moving_avg::sma(&closes, 20),
29 sma_50: moving_avg::sma(&closes, 50),
30 sma_100: moving_avg::sma(&closes, 100),
31 sma_200: moving_avg::sma(&closes, 200),
32 ema_10: moving_avg::ema(&closes, 10),
34 ema_20: moving_avg::ema(&closes, 20),
35 ema_50: moving_avg::ema(&closes, 50),
36 ema_100: moving_avg::ema(&closes, 100),
37 ema_200: moving_avg::ema(&closes, 200),
38 wma_10: moving_avg::wma(&closes, 10),
40 wma_20: moving_avg::wma(&closes, 20),
41 wma_50: moving_avg::wma(&closes, 50),
42 wma_100: moving_avg::wma(&closes, 100),
43 wma_200: moving_avg::wma(&closes, 200),
44 dema_20: moving_avg::dema(&closes, 20),
46 tema_20: moving_avg::tema(&closes, 20),
47 hma_20: moving_avg::hma(&closes, 20),
48 vwma_20: moving_avg::vwma(&closes, &volumes, 20),
49 alma_9: moving_avg::alma(&closes, 9, 0.85, 6.0),
50 mcginley_dynamic_20: moving_avg::mcginley_dynamic(&closes, 20),
51
52 rsi_14: momentum::rsi(&closes, 14),
54 stochastic: {
55 let (k, d) = momentum::stochastic(&highs, &lows, &closes, 14, 3);
56 Some(StochasticData { k, d })
57 },
58 stochastic_rsi: momentum::stochastic_rsi(&closes, 14, 14).map(|k| StochasticData {
59 k: Some(k),
60 d: None,
61 }),
62 cci_20: momentum::cci(&highs, &lows, &closes, 20),
63 williams_r_14: momentum::williams_r(&highs, &lows, &closes, 14),
64 roc_12: momentum::roc(&closes, 12),
65 momentum_10: momentum::momentum(&closes, 10),
66 cmo_14: momentum::cmo(&closes, 14),
67 awesome_oscillator: momentum::awesome_oscillator(&highs, &lows),
68 coppock_curve: momentum::coppock_curve(&closes),
69
70 macd: Some(trend::macd(&closes, 12, 26, 9)),
72 adx_14: trend::adx(&highs, &lows, &closes, 14),
73 aroon: Some(trend::aroon(&highs, &lows, 25)),
74 supertrend: Some(trend::supertrend(&highs, &lows, &closes, 10, 3.0)),
75 ichimoku: Some(trend::ichimoku(&highs, &lows, &closes)),
76 parabolic_sar: trend::parabolic_sar(&highs, &lows, &closes, 0.02, 0.2),
77 bull_bear_power: Some(trend::bull_bear_power(&highs, &lows, &closes)),
78 elder_ray_index: Some(trend::elder_ray(&highs, &lows, &closes)),
79
80 bollinger_bands: Some(volatility::bollinger_bands(&closes, 20, 2.0)),
82 keltner_channels: Some(volatility::keltner_channels(
83 &highs, &lows, &closes, 20, 10, 2.0,
84 )),
85 donchian_channels: Some(volatility::donchian_channels(&highs, &lows, 20)),
86 atr_14: volatility::atr(&highs, &lows, &closes, 14),
87 true_range: volatility::true_range(&highs, &lows, &closes),
88 choppiness_index_14: volatility::choppiness_index(&highs, &lows, &closes, 14),
89
90 obv: volume::obv(&closes, &volumes),
92 mfi_14: volume::mfi(&highs, &lows, &closes, &volumes, 14),
93 cmf_20: volume::cmf(&highs, &lows, &closes, &volumes, 20),
94 chaikin_oscillator: volume::chaikin_oscillator(&highs, &lows, &closes, &volumes),
95 accumulation_distribution: volume::accumulation_distribution(
96 &highs, &lows, &closes, &volumes,
97 ),
98 vwap: volume::vwap(&highs, &lows, &closes, &volumes),
99 balance_of_power: volume::balance_of_power(&opens, &highs, &lows, &closes),
100 }
101}
102
103type PriceData = (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>);
105
106fn prepare_data(candles: &[Candle]) -> PriceData {
108 let closes: Vec<f64> = candles.iter().map(|c| c.close).collect();
109 let highs: Vec<f64> = candles.iter().map(|c| c.high).collect();
110 let lows: Vec<f64> = candles.iter().map(|c| c.low).collect();
111 let opens: Vec<f64> = candles.iter().map(|c| c.open).collect();
112 let volumes: Vec<f64> = candles.iter().map(|c| c.volume as f64).collect();
113 (closes, highs, lows, opens, volumes)
114}
115
116#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
118#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
119#[serde(rename_all = "camelCase")]
120pub struct IndicatorsSummary {
121 #[serde(skip_serializing_if = "Option::is_none")]
125 pub sma_10: Option<f64>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub sma_20: Option<f64>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub sma_50: Option<f64>,
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub sma_100: Option<f64>,
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub sma_200: Option<f64>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
142 pub ema_10: Option<f64>,
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub ema_20: Option<f64>,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub ema_50: Option<f64>,
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub ema_100: Option<f64>,
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub ema_200: Option<f64>,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
159 pub wma_10: Option<f64>,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub wma_20: Option<f64>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub wma_50: Option<f64>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub wma_100: Option<f64>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub wma_200: Option<f64>,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
176 pub dema_20: Option<f64>,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub tema_20: Option<f64>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub hma_20: Option<f64>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub vwma_20: Option<f64>,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub alma_9: Option<f64>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub mcginley_dynamic_20: Option<f64>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
196 pub rsi_14: Option<f64>,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub stochastic: Option<StochasticData>,
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub cci_20: Option<f64>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub williams_r_14: Option<f64>,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub stochastic_rsi: Option<StochasticData>,
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub roc_12: Option<f64>,
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub momentum_10: Option<f64>,
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub cmo_14: Option<f64>,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub awesome_oscillator: Option<f64>,
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub coppock_curve: Option<f64>,
224
225 #[serde(skip_serializing_if = "Option::is_none")]
228 pub macd: Option<MacdData>,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub adx_14: Option<f64>,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub aroon: Option<AroonData>,
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub supertrend: Option<SuperTrendData>,
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub ichimoku: Option<IchimokuData>,
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub parabolic_sar: Option<f64>,
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub bull_bear_power: Option<BullBearPowerData>,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub elder_ray_index: Option<ElderRayData>,
250
251 #[serde(skip_serializing_if = "Option::is_none")]
254 pub bollinger_bands: Option<BollingerBandsData>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub atr_14: Option<f64>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub keltner_channels: Option<KeltnerChannelsData>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub donchian_channels: Option<DonchianChannelsData>,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub true_range: Option<f64>,
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub choppiness_index_14: Option<f64>,
270
271 #[serde(skip_serializing_if = "Option::is_none")]
274 pub obv: Option<f64>,
275 #[serde(skip_serializing_if = "Option::is_none")]
277 pub mfi_14: Option<f64>,
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub cmf_20: Option<f64>,
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub chaikin_oscillator: Option<f64>,
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub accumulation_distribution: Option<f64>,
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub vwap: Option<f64>,
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub balance_of_power: Option<f64>,
293}
294
295#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
297#[serde(rename_all = "camelCase")]
298pub struct StochasticData {
299 #[serde(rename = "%K", skip_serializing_if = "Option::is_none")]
301 pub k: Option<f64>,
302 #[serde(rename = "%D", skip_serializing_if = "Option::is_none")]
304 pub d: Option<f64>,
305}
306
307#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
309#[serde(rename_all = "camelCase")]
310pub struct MacdData {
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub macd: Option<f64>,
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub signal: Option<f64>,
317 #[serde(skip_serializing_if = "Option::is_none")]
319 pub histogram: Option<f64>,
320}
321
322#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
324#[serde(rename_all = "camelCase")]
325pub struct AroonData {
326 #[serde(skip_serializing_if = "Option::is_none")]
328 pub aroon_up: Option<f64>,
329 #[serde(skip_serializing_if = "Option::is_none")]
331 pub aroon_down: Option<f64>,
332}
333
334#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct BollingerBandsData {
338 #[serde(skip_serializing_if = "Option::is_none")]
340 pub upper: Option<f64>,
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub middle: Option<f64>,
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub lower: Option<f64>,
347}
348
349#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
351#[serde(rename_all = "camelCase")]
352pub struct SuperTrendData {
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub value: Option<f64>,
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub trend: Option<String>,
359}
360
361#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
363#[serde(rename_all = "camelCase")]
364pub struct IchimokuData {
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub conversion_line: Option<f64>,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub base_line: Option<f64>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub leading_span_a: Option<f64>,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub leading_span_b: Option<f64>,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub lagging_span: Option<f64>,
380}
381
382#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct KeltnerChannelsData {
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub upper: Option<f64>,
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub middle: Option<f64>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub lower: Option<f64>,
395}
396
397#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
399#[serde(rename_all = "camelCase")]
400pub struct DonchianChannelsData {
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub upper: Option<f64>,
404 #[serde(skip_serializing_if = "Option::is_none")]
406 pub middle: Option<f64>,
407 #[serde(skip_serializing_if = "Option::is_none")]
409 pub lower: Option<f64>,
410}
411
412#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
414#[serde(rename_all = "camelCase")]
415pub struct BullBearPowerData {
416 #[serde(skip_serializing_if = "Option::is_none")]
418 pub bull_power: Option<f64>,
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub bear_power: Option<f64>,
422}
423
424#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
426#[serde(rename_all = "camelCase")]
427pub struct ElderRayData {
428 #[serde(skip_serializing_if = "Option::is_none")]
430 pub bull_power: Option<f64>,
431 #[serde(skip_serializing_if = "Option::is_none")]
433 pub bear_power: Option<f64>,
434}
435
436#[inline]
440pub(crate) fn round2(value: f64) -> f64 {
441 (value * 100.0).round() / 100.0
442}
443
444#[inline]
446#[allow(dead_code)]
447pub(crate) fn last(values: &[f64]) -> Option<f64> {
448 values
449 .last()
450 .and_then(|&v| if v.is_finite() { Some(round2(v)) } else { None })
451}