use super::last_value;
use crate::Candle;
use crate::indicators::{
accumulation_distribution, adx, alma, aroon, atr, atr::atr_raw, awesome_oscillator,
balance_of_power, bollinger_bands, bull_bear_power, cci, chaikin_oscillator, choppiness_index,
cmf, cmo, coppock_curve, dema, donchian_channels, elder_ray, ema::ema_raw, hma, ichimoku,
keltner_channels::keltner_with_atr_dense, macd, mcginley_dynamic, mfi, momentum, obv,
parabolic_sar, roc, rsi::rsi_raw, sma::sma_raw, stochastic,
stochastic_rsi::stochastic_rsi_from_rsi_dense, supertrend::supertrend_with_atr_dense, tema,
true_range, vwap, vwma, williams_r, wma::wma_raw,
};
#[inline]
fn last_from_result(result: crate::indicators::Result<Vec<Option<f64>>>) -> Option<f64> {
result.ok().and_then(|v| last_value(&v))
}
pub(crate) fn calculate_indicators(candles: &[Candle]) -> IndicatorsSummary {
if candles.is_empty() {
return IndicatorsSummary::default();
}
let len = candles.len();
let mut closes = Vec::with_capacity(len);
let mut highs = Vec::with_capacity(len);
let mut lows = Vec::with_capacity(len);
let mut opens = Vec::with_capacity(len);
let mut volumes = Vec::with_capacity(len);
for c in candles {
closes.push(c.close);
highs.push(c.high);
lows.push(c.low);
opens.push(c.open);
volumes.push(c.volume as f64);
}
let rsi_14_dense = rsi_raw(&closes, 14).ok();
let atr_10_dense = atr_raw(&highs, &lows, &closes, 10).ok();
IndicatorsSummary {
sma_10: sma_raw(&closes, 10).last().copied(),
sma_20: sma_raw(&closes, 20).last().copied(),
sma_50: sma_raw(&closes, 50).last().copied(),
sma_100: sma_raw(&closes, 100).last().copied(),
sma_200: sma_raw(&closes, 200).last().copied(),
ema_10: ema_raw(&closes, 10).last().copied(),
ema_20: ema_raw(&closes, 20).last().copied(),
ema_50: ema_raw(&closes, 50).last().copied(),
ema_100: ema_raw(&closes, 100).last().copied(),
ema_200: ema_raw(&closes, 200).last().copied(),
wma_10: wma_raw(&closes, 10).last().copied(),
wma_20: wma_raw(&closes, 20).last().copied(),
wma_50: wma_raw(&closes, 50).last().copied(),
wma_100: wma_raw(&closes, 100).last().copied(),
wma_200: wma_raw(&closes, 200).last().copied(),
dema_20: dema(&closes, 20).ok().and_then(|v| last_value(&v)),
tema_20: tema(&closes, 20).ok().and_then(|v| last_value(&v)),
hma_20: hma(&closes, 20).ok().and_then(|v| last_value(&v)),
vwma_20: vwma(&closes, &volumes, 20)
.ok()
.and_then(|v| last_value(&v)),
alma_9: alma(&closes, 9, 0.85, 6.0)
.ok()
.and_then(|v| last_value(&v)),
mcginley_dynamic_20: mcginley_dynamic(&closes, 20)
.ok()
.and_then(|v| last_value(&v)),
rsi_14: rsi_14_dense.as_deref().and_then(|v| v.last().copied()),
stochastic: {
stochastic(&highs, &lows, &closes, 14, 1, 3)
.ok()
.map(|result| StochasticData {
k: last_value(&result.k),
d: last_value(&result.d),
})
},
stochastic_rsi: {
rsi_14_dense.as_deref().and_then(|rsi_dense| {
stochastic_rsi_from_rsi_dense(rsi_dense, len, 14, 14, 3, 3)
.ok()
.map(|result| StochasticData {
k: last_value(&result.k),
d: last_value(&result.d),
})
})
},
cci_20: last_from_result(cci(&highs, &lows, &closes, 20)),
williams_r_14: last_from_result(williams_r(&highs, &lows, &closes, 14)),
roc_12: last_from_result(roc(&closes, 12)),
momentum_10: last_from_result(momentum(&closes, 10)),
cmo_14: last_from_result(cmo(&closes, 14)),
awesome_oscillator: last_from_result(awesome_oscillator(&highs, &lows, 5, 34)),
coppock_curve: last_from_result(coppock_curve(&closes, 14, 11, 10)),
macd: {
macd(&closes, 12, 26, 9).ok().map(|result| MacdData {
macd: last_value(&result.macd_line),
signal: last_value(&result.signal_line),
histogram: last_value(&result.histogram),
})
},
adx_14: last_from_result(adx(&highs, &lows, &closes, 14)),
aroon: {
aroon(&highs, &lows, 25).ok().map(|result| AroonData {
aroon_up: last_value(&result.aroon_up),
aroon_down: last_value(&result.aroon_down),
})
},
supertrend: {
atr_10_dense.as_deref().and_then(|atr_dense| {
supertrend_with_atr_dense(&highs, &lows, &closes, atr_dense, 10, 3.0)
.ok()
.map(|result| SuperTrendData {
value: last_value(&result.value),
trend: result.is_uptrend.last().and_then(|&v| v).map(|v| {
if v {
"up".to_string()
} else {
"down".to_string()
}
}),
})
})
},
ichimoku: {
ichimoku(&highs, &lows, &closes, 9, 26, 26, 26)
.ok()
.map(|result| IchimokuData {
conversion_line: last_value(&result.conversion_line),
base_line: last_value(&result.base_line),
leading_span_a: last_value(&result.leading_span_a),
leading_span_b: last_value(&result.leading_span_b),
lagging_span: last_value(&result.lagging_span),
})
},
parabolic_sar: last_from_result(parabolic_sar(&highs, &lows, &closes, 0.02, 0.2)),
bull_bear_power: {
bull_bear_power(&highs, &lows, &closes, 13)
.ok()
.map(|result| BullBearPowerData {
bull_power: last_value(&result.bull_power),
bear_power: last_value(&result.bear_power),
})
},
elder_ray_index: {
elder_ray(&highs, &lows, &closes, 13)
.ok()
.map(|result| ElderRayData {
bull_power: last_value(&result.bull_power),
bear_power: last_value(&result.bear_power),
})
},
bollinger_bands: {
bollinger_bands(&closes, 20, 2.0)
.ok()
.map(|result| BollingerBandsData {
upper: last_value(&result.upper),
middle: last_value(&result.middle),
lower: last_value(&result.lower),
})
},
keltner_channels: {
atr_10_dense.as_deref().and_then(|atr_dense| {
keltner_with_atr_dense(&closes, 20, atr_dense, 10, 2.0)
.ok()
.map(|result| KeltnerChannelsData {
upper: last_value(&result.upper),
middle: last_value(&result.middle),
lower: last_value(&result.lower),
})
})
},
donchian_channels: {
donchian_channels(&highs, &lows, 20)
.ok()
.map(|result| DonchianChannelsData {
upper: last_value(&result.upper),
middle: last_value(&result.middle),
lower: last_value(&result.lower),
})
},
atr_14: last_from_result(atr(&highs, &lows, &closes, 14)),
true_range: last_from_result(true_range(&highs, &lows, &closes)),
choppiness_index_14: last_from_result(choppiness_index(&highs, &lows, &closes, 14)),
obv: last_from_result(obv(&closes, &volumes)),
mfi_14: last_from_result(mfi(&highs, &lows, &closes, &volumes, 14)),
cmf_20: last_from_result(cmf(&highs, &lows, &closes, &volumes, 20)),
chaikin_oscillator: last_from_result(chaikin_oscillator(&highs, &lows, &closes, &volumes)),
accumulation_distribution: last_from_result(accumulation_distribution(
&highs, &lows, &closes, &volumes,
)),
vwap: last_from_result(vwap(&highs, &lows, &closes, &volumes)),
balance_of_power: last_from_result(balance_of_power(&opens, &highs, &lows, &closes, None)),
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
pub struct IndicatorsSummary {
#[serde(skip_serializing_if = "Option::is_none")]
pub sma_10: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sma_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sma_50: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sma_100: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sma_200: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ema_10: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ema_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ema_50: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ema_100: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ema_200: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wma_10: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wma_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wma_50: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wma_100: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wma_200: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dema_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tema_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hma_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vwma_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alma_9: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcginley_dynamic_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rsi_14: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stochastic: Option<StochasticData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cci_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub williams_r_14: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stochastic_rsi: Option<StochasticData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roc_12: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub momentum_10: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cmo_14: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub awesome_oscillator: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub coppock_curve: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub macd: Option<MacdData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub adx_14: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aroon: Option<AroonData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supertrend: Option<SuperTrendData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ichimoku: Option<IchimokuData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parabolic_sar: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bull_bear_power: Option<BullBearPowerData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub elder_ray_index: Option<ElderRayData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bollinger_bands: Option<BollingerBandsData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub atr_14: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keltner_channels: Option<KeltnerChannelsData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub donchian_channels: Option<DonchianChannelsData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub true_range: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub choppiness_index_14: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub obv: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mfi_14: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cmf_20: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chaikin_oscillator: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub accumulation_distribution: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vwap: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub balance_of_power: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StochasticData {
#[serde(rename = "%K", skip_serializing_if = "Option::is_none")]
pub k: Option<f64>,
#[serde(rename = "%D", skip_serializing_if = "Option::is_none")]
pub d: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MacdData {
#[serde(skip_serializing_if = "Option::is_none")]
pub macd: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signal: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub histogram: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AroonData {
#[serde(skip_serializing_if = "Option::is_none")]
pub aroon_up: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aroon_down: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BollingerBandsData {
#[serde(skip_serializing_if = "Option::is_none")]
pub upper: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub middle: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lower: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SuperTrendData {
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trend: Option<String>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IchimokuData {
#[serde(skip_serializing_if = "Option::is_none")]
pub conversion_line: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_line: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub leading_span_a: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub leading_span_b: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lagging_span: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct KeltnerChannelsData {
#[serde(skip_serializing_if = "Option::is_none")]
pub upper: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub middle: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lower: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DonchianChannelsData {
#[serde(skip_serializing_if = "Option::is_none")]
pub upper: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub middle: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lower: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BullBearPowerData {
#[serde(skip_serializing_if = "Option::is_none")]
pub bull_power: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bear_power: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ElderRayData {
#[serde(skip_serializing_if = "Option::is_none")]
pub bull_power: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bear_power: Option<f64>,
}