use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IndicatorValue {
Single(f64),
Signal(i8),
Flag(bool),
Double(f64, f64),
Triple(f64, f64, f64),
Channel3 {
upper: f64,
middle: f64,
lower: f64,
},
Macd {
line: f64,
signal: f64,
histogram: f64,
},
Ichimoku {
tenkan: f64,
kijun: f64,
senkou_a: f64,
senkou_b: f64,
chikou: f64,
},
Candle {
open: f64,
high: f64,
low: f64,
close: f64,
},
ChannelExtended {
upper: f64,
middle: f64,
lower: f64,
bandwidth: f64,
percent_b: f64,
},
Adaptive {
value: f64,
period: f64,
alpha: f64,
},
StatTest {
statistic: f64,
p_value: f64,
is_significant: bool,
},
Volatility {
total: f64,
close_close: f64,
high_low: f64,
},
ValueFlag(f64, bool),
DoubleFlag(bool, bool),
FuzzyCandle {
direction: i8,
size: i8,
body_size: i8,
upper_wick: i8,
lower_wick: i8,
},
CandleAnatomy {
body: f64,
upper_wick: f64,
lower_wick: f64,
long_upper: bool,
long_lower: bool,
},
Hilbert {
amplitude: f64,
phase: f64,
frequency: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IndicatorValueKind {
Single,
Signal,
Flag,
Double,
Triple,
Channel3,
Macd,
Ichimoku,
Candle,
ChannelExtended,
Adaptive,
StatTest,
Volatility,
ValueFlag,
DoubleFlag,
FuzzyCandle,
CandleAnatomy,
Hilbert,
}
impl IndicatorValueKind {
pub fn output_arity(self) -> usize {
match self {
Self::Single | Self::Signal | Self::Flag => 1,
Self::ValueFlag | Self::DoubleFlag | Self::Double => 2,
Self::Triple | Self::Channel3 | Self::Macd | Self::Volatility
| Self::Adaptive | Self::Hilbert | Self::StatTest => 3,
Self::Candle | Self::FuzzyCandle => 4,
Self::Ichimoku | Self::CandleAnatomy | Self::ChannelExtended => 5,
}
}
}
impl IndicatorValue {
pub fn kind(&self) -> IndicatorValueKind {
match self {
Self::Single(_) => IndicatorValueKind::Single,
Self::Signal(_) => IndicatorValueKind::Signal,
Self::Flag(_) => IndicatorValueKind::Flag,
Self::Double(..) => IndicatorValueKind::Double,
Self::Triple(..) => IndicatorValueKind::Triple,
Self::Channel3 { .. } => IndicatorValueKind::Channel3,
Self::Macd { .. } => IndicatorValueKind::Macd,
Self::Ichimoku { .. } => IndicatorValueKind::Ichimoku,
Self::Candle { .. } => IndicatorValueKind::Candle,
Self::ChannelExtended { .. } => IndicatorValueKind::ChannelExtended,
Self::Adaptive { .. } => IndicatorValueKind::Adaptive,
Self::StatTest { .. } => IndicatorValueKind::StatTest,
Self::Volatility { .. } => IndicatorValueKind::Volatility,
Self::ValueFlag(..) => IndicatorValueKind::ValueFlag,
Self::DoubleFlag(..) => IndicatorValueKind::DoubleFlag,
Self::FuzzyCandle { .. } => IndicatorValueKind::FuzzyCandle,
Self::CandleAnatomy { .. } => IndicatorValueKind::CandleAnatomy,
Self::Hilbert { .. } => IndicatorValueKind::Hilbert,
}
}
}
impl IndicatorValue {
pub fn main(&self) -> f64 {
match self {
Self::Single(v) => *v,
Self::Signal(s) => *s as f64,
Self::Flag(b) => if *b { 1.0 } else { 0.0 },
Self::Double(a, _) => *a,
Self::Triple(a, _, _) => *a,
Self::Channel3 { middle, .. } => *middle,
Self::Macd { line, .. } => *line,
Self::Ichimoku { kijun, .. } => *kijun,
Self::Candle { close, .. } => *close,
Self::ChannelExtended { middle, .. } => *middle,
Self::Adaptive { value, .. } => *value,
Self::StatTest { statistic, .. } => *statistic,
Self::Volatility { total, .. } => *total,
Self::ValueFlag(v, _) => *v,
Self::DoubleFlag(a, _) => if *a { 1.0 } else { 0.0 },
Self::FuzzyCandle { direction, .. } => *direction as f64,
Self::CandleAnatomy { body, .. } => *body,
Self::Hilbert { amplitude, .. } => *amplitude,
}
}
pub fn is_signal(&self) -> bool {
matches!(self, Self::Signal(_))
}
pub fn as_signal(&self) -> Option<i8> {
match self {
Self::Signal(s) => Some(*s),
_ => None,
}
}
pub fn is_channel(&self) -> bool {
matches!(self, Self::Channel3 { .. } | Self::ChannelExtended { .. })
}
pub fn as_channel(&self) -> Option<(f64, f64, f64)> {
match self {
Self::Channel3 { upper, middle, lower } => Some((*upper, *middle, *lower)),
Self::ChannelExtended { upper, middle, lower, .. } => Some((*upper, *middle, *lower)),
_ => None,
}
}
pub fn as_macd(&self) -> Option<(f64, f64, f64)> {
match self {
Self::Macd { line, signal, histogram } => Some((*line, *signal, *histogram)),
_ => None,
}
}
pub fn upper(&self) -> Option<f64> {
match self {
Self::Channel3 { upper, .. } => Some(*upper),
Self::ChannelExtended { upper, .. } => Some(*upper),
Self::Double(a, _) => Some(*a),
Self::Triple(a, _, _) => Some(*a),
_ => None,
}
}
pub fn lower(&self) -> Option<f64> {
match self {
Self::Channel3 { lower, .. } => Some(*lower),
Self::ChannelExtended { lower, .. } => Some(*lower),
Self::Double(_, b) => Some(*b),
Self::Triple(_, _, c) => Some(*c),
_ => None,
}
}
pub fn middle(&self) -> Option<f64> {
match self {
Self::Channel3 { middle, .. } => Some(*middle),
Self::ChannelExtended { middle, .. } => Some(*middle),
Self::Triple(_, b, _) => Some(*b),
_ => None,
}
}
pub fn macd_line(&self) -> Option<f64> {
match self {
Self::Macd { line, .. } => Some(*line),
_ => None,
}
}
pub fn macd_signal(&self) -> Option<f64> {
match self {
Self::Macd { signal, .. } => Some(*signal),
_ => None,
}
}
pub fn macd_histogram(&self) -> Option<f64> {
match self {
Self::Macd { histogram, .. } => Some(*histogram),
_ => None,
}
}
pub fn is_finite(&self) -> bool {
self.main().is_finite()
}
pub fn as_vec(&self) -> Vec<f64> {
match self {
Self::Single(v) => vec![*v],
Self::Signal(s) => vec![*s as f64],
Self::Flag(b) => vec![if *b { 1.0 } else { 0.0 }],
Self::Double(a, b) => vec![*a, *b],
Self::Triple(a, b, c) => vec![*a, *b, *c],
Self::Channel3 { upper, middle, lower } => vec![*upper, *middle, *lower],
Self::Macd { line, signal, histogram } => vec![*line, *signal, *histogram],
Self::Ichimoku { tenkan, kijun, senkou_a, senkou_b, chikou } =>
vec![*tenkan, *kijun, *senkou_a, *senkou_b, *chikou],
Self::Candle { open, high, low, close } => vec![*open, *high, *low, *close],
Self::ChannelExtended { upper, middle, lower, bandwidth, percent_b } =>
vec![*upper, *middle, *lower, *bandwidth, *percent_b],
Self::Adaptive { value, period, alpha } => vec![*value, *period, *alpha],
Self::StatTest { statistic, p_value, is_significant } =>
vec![*statistic, *p_value, if *is_significant { 1.0 } else { 0.0 }],
Self::Volatility { total, close_close, high_low } =>
vec![*total, *close_close, *high_low],
Self::ValueFlag(v, f) => vec![*v, if *f { 1.0 } else { 0.0 }],
Self::DoubleFlag(a, b) => vec![
if *a { 1.0 } else { 0.0 },
if *b { 1.0 } else { 0.0 }
],
Self::FuzzyCandle { direction, size, body_size, upper_wick, lower_wick } =>
vec![*direction as f64, *size as f64, *body_size as f64, *upper_wick as f64, *lower_wick as f64],
Self::CandleAnatomy { body, upper_wick, lower_wick, long_upper, long_lower } =>
vec![*body, *upper_wick, *lower_wick, if *long_upper { 1.0 } else { 0.0 }, if *long_lower { 1.0 } else { 0.0 }],
Self::Hilbert { amplitude, phase, frequency } =>
vec![*amplitude, *phase, *frequency],
}
}
pub fn as_hashmap(&self) -> HashMap<String, f64> {
let mut map = HashMap::new();
match self {
Self::Single(v) => {
map.insert("value".to_string(), *v);
}
Self::Signal(s) => {
map.insert("signal".to_string(), *s as f64);
}
Self::Flag(b) => {
map.insert("flag".to_string(), if *b { 1.0 } else { 0.0 });
}
Self::Double(a, b) => {
map.insert("first".to_string(), *a);
map.insert("second".to_string(), *b);
}
Self::Triple(a, b, c) => {
map.insert("first".to_string(), *a);
map.insert("second".to_string(), *b);
map.insert("third".to_string(), *c);
}
Self::Channel3 { upper, middle, lower } => {
map.insert("upper".to_string(), *upper);
map.insert("middle".to_string(), *middle);
map.insert("lower".to_string(), *lower);
}
Self::Macd { line, signal, histogram } => {
map.insert("line".to_string(), *line);
map.insert("signal".to_string(), *signal);
map.insert("histogram".to_string(), *histogram);
}
Self::Ichimoku { tenkan, kijun, senkou_a, senkou_b, chikou } => {
map.insert("tenkan".to_string(), *tenkan);
map.insert("kijun".to_string(), *kijun);
map.insert("senkou_a".to_string(), *senkou_a);
map.insert("senkou_b".to_string(), *senkou_b);
map.insert("chikou".to_string(), *chikou);
}
Self::Candle { open, high, low, close } => {
map.insert("open".to_string(), *open);
map.insert("high".to_string(), *high);
map.insert("low".to_string(), *low);
map.insert("close".to_string(), *close);
}
Self::ChannelExtended { upper, middle, lower, bandwidth, percent_b } => {
map.insert("upper".to_string(), *upper);
map.insert("middle".to_string(), *middle);
map.insert("lower".to_string(), *lower);
map.insert("bandwidth".to_string(), *bandwidth);
map.insert("percent_b".to_string(), *percent_b);
}
Self::Adaptive { value, period, alpha } => {
map.insert("value".to_string(), *value);
map.insert("period".to_string(), *period);
map.insert("alpha".to_string(), *alpha);
}
Self::StatTest { statistic, p_value, is_significant } => {
map.insert("statistic".to_string(), *statistic);
map.insert("p_value".to_string(), *p_value);
map.insert("is_significant".to_string(), if *is_significant { 1.0 } else { 0.0 });
}
Self::Volatility { total, close_close, high_low } => {
map.insert("total".to_string(), *total);
map.insert("close_close".to_string(), *close_close);
map.insert("high_low".to_string(), *high_low);
}
Self::ValueFlag(v, f) => {
map.insert("value".to_string(), *v);
map.insert("flag".to_string(), if *f { 1.0 } else { 0.0 });
}
Self::DoubleFlag(a, b) => {
map.insert("first".to_string(), if *a { 1.0 } else { 0.0 });
map.insert("second".to_string(), if *b { 1.0 } else { 0.0 });
}
Self::FuzzyCandle { direction, size, body_size, upper_wick, lower_wick } => {
map.insert("direction".to_string(), *direction as f64);
map.insert("size".to_string(), *size as f64);
map.insert("body_size".to_string(), *body_size as f64);
map.insert("upper_wick".to_string(), *upper_wick as f64);
map.insert("lower_wick".to_string(), *lower_wick as f64);
}
Self::CandleAnatomy { body, upper_wick, lower_wick, long_upper, long_lower } => {
map.insert("body".to_string(), *body);
map.insert("upper_wick".to_string(), *upper_wick);
map.insert("lower_wick".to_string(), *lower_wick);
map.insert("long_upper".to_string(), if *long_upper { 1.0 } else { 0.0 });
map.insert("long_lower".to_string(), if *long_lower { 1.0 } else { 0.0 });
}
Self::Hilbert { amplitude, phase, frequency } => {
map.insert("amplitude".to_string(), *amplitude);
map.insert("phase".to_string(), *phase);
map.insert("frequency".to_string(), *frequency);
}
}
map
}
}
impl std::fmt::Display for IndicatorValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Single(v) => write!(f, "{:.6}", v),
Self::Signal(s) => write!(f, "{}", s),
Self::Flag(b) => write!(f, "{}", b),
Self::Double(a, b) => write!(f, "({:.6}, {:.6})", a, b),
Self::Triple(a, b, c) => write!(f, "({:.6}, {:.6}, {:.6})", a, b, c),
Self::Channel3 { upper, middle, lower } =>
write!(f, "Channel(upper={:.6}, middle={:.6}, lower={:.6})", upper, middle, lower),
Self::Macd { line, signal, histogram } =>
write!(f, "MACD(line={:.6}, signal={:.6}, histogram={:.6})", line, signal, histogram),
Self::Ichimoku { tenkan, kijun, senkou_a, senkou_b, chikou } =>
write!(f, "Ichimoku(tenkan={:.6}, kijun={:.6}, senkou_a={:.6}, senkou_b={:.6}, chikou={:.6})",
tenkan, kijun, senkou_a, senkou_b, chikou),
Self::Candle { open, high, low, close } =>
write!(f, "Candle(O={:.6}, H={:.6}, L={:.6}, C={:.6})", open, high, low, close),
Self::ChannelExtended { upper, middle, lower, bandwidth, percent_b } =>
write!(f, "ChannelExt(upper={:.6}, middle={:.6}, lower={:.6}, bw={:.6}, %B={:.6})",
upper, middle, lower, bandwidth, percent_b),
Self::Adaptive { value, period, alpha } =>
write!(f, "Adaptive(value={:.6}, period={:.6}, alpha={:.6})", value, period, alpha),
Self::StatTest { statistic, p_value, is_significant } =>
write!(f, "StatTest(stat={:.6}, p={:.6}, sig={})", statistic, p_value, is_significant),
Self::Volatility { total, close_close, high_low } =>
write!(f, "Vol(total={:.6}, cc={:.6}, hl={:.6})", total, close_close, high_low),
Self::ValueFlag(v, flag) => write!(f, "({:.6}, {})", v, flag),
Self::DoubleFlag(a, b) => write!(f, "({}, {})", a, b),
Self::FuzzyCandle { direction, size, body_size, upper_wick, lower_wick } =>
write!(f, "FuzzyCandle(dir={}, size={}, body={}, uw={}, lw={})",
direction, size, body_size, upper_wick, lower_wick),
Self::CandleAnatomy { body, upper_wick, lower_wick, long_upper, long_lower } =>
write!(f, "Anatomy(body={:.6}, uw={:.6}, lw={:.6}, lu={}, ll={})",
body, upper_wick, lower_wick, long_upper, long_lower),
Self::Hilbert { amplitude, phase, frequency } =>
write!(f, "Hilbert(amp={:.6}, phase={:.6}, freq={:.6})", amplitude, phase, frequency),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_value() {
let val = IndicatorValue::Single(42.5);
assert_eq!(val.main(), 42.5);
assert_eq!(val.as_vec(), vec![42.5]);
}
#[test]
fn test_signal_value() {
let val = IndicatorValue::Signal(1);
assert_eq!(val.main(), 1.0);
assert_eq!(val.as_signal(), Some(1));
assert!(val.is_signal());
}
#[test]
fn test_channel_value() {
let val = IndicatorValue::Channel3 {
upper: 110.0,
middle: 100.0,
lower: 90.0,
};
assert_eq!(val.main(), 100.0);
assert_eq!(val.as_channel(), Some((110.0, 100.0, 90.0)));
assert!(val.is_channel());
}
#[test]
fn test_macd_value() {
let val = IndicatorValue::Macd {
line: 2.5,
signal: 2.0,
histogram: 0.5,
};
assert_eq!(val.main(), 2.5);
assert_eq!(val.as_macd(), Some((2.5, 2.0, 0.5)));
}
#[test]
fn test_as_vec() {
let val = IndicatorValue::Triple(1.0, 2.0, 3.0);
assert_eq!(val.as_vec(), vec![1.0, 2.0, 3.0]);
}
}