use crate::catalog::{
IndicatorSignature, IndicatorCategory, ParamConstraint, ParamType, ParamValue,
IndicatorRoleKind,
};
use crate::bar_indicators::average::moving_average::MovingAverageType;
use crate::bar_indicators::indicator_value::IndicatorValueKind;
use super::super::bar_indicator_id::BarIndicatorId;
use once_cell::sync::Lazy;
use std::collections::HashMap;
pub const CATEGORY: IndicatorCategory = IndicatorCategory::Trend;
pub fn signature_adx_slope() -> IndicatorSignature {
IndicatorSignature::builder("ADX_SLOPE", CATEGORY)
.name("ADX Slope")
.description("Rate of change of ADX trend strength")
.add_constraint(ParamConstraint::period(2, 200, 14))
.metadata("based_on", "ADX")
.machine_id(BarIndicatorId::AdxSlope)
.role_kind(IndicatorRoleKind::Regime)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("AdxSlope")
.alias("adx_slope")
.alias("ADXSLOPE")
.alias("ADXSlope")
.alias("adxslope")
.alias("Adx_Slope")
.build()
}
pub fn signature_didi_index() -> IndicatorSignature {
IndicatorSignature::builder("DIDI", CATEGORY)
.name("Didi Index")
.description("Triple EMA relationship indicator")
.add_constraint(
ParamConstraint::new("short_period", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(50))
.with_default(ParamValue::USize(3))
.required()
)
.add_constraint(
ParamConstraint::new("mid_period", ParamType::USize)
.with_min(ParamValue::USize(2))
.with_max(ParamValue::USize(100))
.with_default(ParamValue::USize(8))
.required()
)
.add_constraint(
ParamConstraint::new("long_period", ParamType::USize)
.with_min(ParamValue::USize(3))
.with_max(ParamValue::USize(200))
.with_default(ParamValue::USize(20))
.required()
)
.add_constraint(ParamConstraint::ma_type(MovingAverageType::EMA))
.metadata("author", "Odir Aguiar")
.metadata("ma_support", "Supports all 11 MA types for smoothing")
.metadata("ma_note", "Original default: EMA")
.machine_id(BarIndicatorId::Didi)
.role_kind(IndicatorRoleKind::Regime)
.output_kind(IndicatorValueKind::Double)
.validated()
.alias("Didi")
.alias("didi")
.alias("DIDIINDEX")
.alias("DidiIndex")
.alias("didiindex")
.alias("didi_index")
.alias("DIDI_INDEX")
.alias("Didi_Index")
.build()
}
pub fn signature_efficiency_ratio() -> IndicatorSignature {
IndicatorSignature::builder("TR_ER", CATEGORY)
.name("Efficiency Ratio")
.description("Direction vs volatility measure (0-1)")
.add_constraint(ParamConstraint::period(2, 200, 10))
.metadata("author", "Perry Kaufman")
.metadata("range", "0-1")
.machine_id(BarIndicatorId::TrEr)
.role_kind(IndicatorRoleKind::Regime)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("TrEr")
.alias("tr_er")
.alias("EFFICIENCYRATIO")
.alias("EfficiencyRatio")
.alias("efficiencyratio")
.alias("efficiency_ratio")
.alias("EFFICIENCY_RATIO")
.alias("Efficiency_Ratio")
.build()
}
pub fn signature_ehlers_instantaneous_trendline() -> IndicatorSignature {
IndicatorSignature::builder("EIT", CATEGORY)
.name("Ehlers Instantaneous Trendline")
.description("Zero-lag trendline using Hilbert Transform")
.add_constraint(ParamConstraint::period(2, 200, 20))
.metadata("author", "John Ehlers")
.machine_id(BarIndicatorId::Eit)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Eit")
.alias("eit")
.alias("EHLERSINSTANTANEOUSTRENDLINE")
.alias("EhlersInstantaneousTrendline")
.alias("ehlersinstantaneoustrendline")
.alias("ehlers_instantaneous_trendline")
.alias("EHLERS_INSTANTANEOUS_TRENDLINE")
.alias("Ehlers_Instantaneous_Trendline")
.build()
}
pub fn signature_gann_hilo_activator() -> IndicatorSignature {
IndicatorSignature::builder("GANN_HILO", CATEGORY)
.name("Gann HiLo Activator")
.description("Dynamic support/resistance trend follower")
.add_constraint(ParamConstraint::period(2, 200, 10))
.metadata("author", "W.D. Gann")
.machine_id(BarIndicatorId::GannHilo)
.role_kind(IndicatorRoleKind::TrendStop)
.output_kind(IndicatorValueKind::ValueFlag)
.validated()
.alias("GannHilo")
.alias("gann_hilo")
.alias("GANNHILOACTIVATOR")
.alias("GannHiLoActivator")
.alias("gannhiloactivator")
.alias("gann_hilo_activator")
.alias("GANN_HILO_ACTIVATOR")
.alias("Gann_Hilo_Activator")
.build()
}
pub fn signature_gmma_compression() -> IndicatorSignature {
IndicatorSignature::builder("GMMA", CATEGORY)
.name("GMMA Compression")
.description("EMA cluster compression/expansion score")
.add_constraint(ParamConstraint::ma_type(MovingAverageType::EMA))
.metadata("ma_support", "Supports all 11 MA types for smoothing")
.metadata("fast_periods", "3,5,8,10,12,15")
.metadata("slow_periods", "30,35,40,45,50,60")
.metadata("author", "Daryl Guppy")
.machine_id(BarIndicatorId::Gmma)
.role_kind(IndicatorRoleKind::Regime)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Gmma")
.alias("gmma")
.alias("GMMACOMPRESSION")
.alias("GMMACompression")
.alias("gmmacompression")
.alias("gmma_compression")
.alias("GMMA_COMPRESSION")
.alias("Gmma_Compression")
.build()
}
pub fn signature_heikin_ashi_trend() -> IndicatorSignature {
IndicatorSignature::builder("HA_TREND", CATEGORY)
.name("Heikin Ashi Trend")
.description("Trend detection using smoothed candlesticks")
.add_constraint(ParamConstraint::period(2, 100, 5))
.machine_id(BarIndicatorId::HaTrend)
.role_kind(IndicatorRoleKind::Regime)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("HaTrend")
.alias("ha_trend")
.alias("HEIKINASHITREND")
.alias("HeikinAshiTrend")
.alias("heikinashitrend")
.alias("heikin_ashi_trend")
.alias("HEIKIN_ASHI_TREND")
.alias("Heikin_Ashi_Trend")
.build()
}
pub fn signature_kama_slope() -> IndicatorSignature {
IndicatorSignature::builder("KAMA_SLOPE", CATEGORY)
.name("KAMA Slope")
.description("Slope of Kaufman Adaptive Moving Average")
.add_constraint(ParamConstraint::period(2, 200, 10))
.add_constraint(
ParamConstraint::new("fast_period", ParamType::USize)
.with_min(ParamValue::USize(2))
.with_max(ParamValue::USize(50))
.with_default(ParamValue::USize(2))
.required()
)
.add_constraint(
ParamConstraint::new("slow_period", ParamType::USize)
.with_min(ParamValue::USize(2))
.with_max(ParamValue::USize(100))
.with_default(ParamValue::USize(30))
.required()
)
.metadata("based_on", "KAMA")
.machine_id(BarIndicatorId::KamaSlope)
.role_kind(IndicatorRoleKind::OscillatorUnbounded)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("KamaSlope")
.alias("kama_slope")
.alias("KAMASLOPE")
.alias("KAMASlope")
.alias("kamaslope")
.alias("Kama_Slope")
.build()
}
pub fn signature_lr_slope() -> IndicatorSignature {
IndicatorSignature::builder("LR_SLOPE", CATEGORY)
.name("Linear Regression Slope")
.description("Slope of linear regression line")
.add_constraint(ParamConstraint::period(2, 200, 20))
.machine_id(BarIndicatorId::LrSlope)
.role_kind(IndicatorRoleKind::OscillatorUnbounded)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("LrSlope")
.alias("lr_slope")
.alias("LINEARREGRESSIONSLOPE")
.alias("LinearRegressionSlope")
.alias("linearregressionslope")
.alias("linear_regression_slope")
.alias("LINEAR_REGRESSION_SLOPE")
.alias("Linear_Regression_Slope")
.build()
}
pub fn signature_ravi() -> IndicatorSignature {
IndicatorSignature::builder("RAVI", CATEGORY)
.name("Range Action Verification Index")
.description("Percentage difference between fast and slow EMA")
.add_constraint(
ParamConstraint::new("fast_period", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(100))
.with_default(ParamValue::USize(7))
.required()
)
.add_constraint(
ParamConstraint::new("slow_period", ParamType::USize)
.with_min(ParamValue::USize(2))
.with_max(ParamValue::USize(200))
.with_default(ParamValue::USize(65))
.required()
)
.add_constraint(ParamConstraint::ma_type(MovingAverageType::EMA))
.metadata("formula", "|EMA(fast)-EMA(slow)|/EMA(slow)*100")
.metadata("ma_support", "Supports all 11 MA types for smoothing")
.metadata("ma_note", "Original default: EMA")
.machine_id(BarIndicatorId::Ravi)
.role_kind(IndicatorRoleKind::OscillatorUnbounded)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Ravi")
.alias("ravi")
.alias("RANGEACTIONVERIFICATIONINDEX")
.alias("RangeActionVerificationIndex")
.alias("rangeactionverificationindex")
.alias("range_action_verification_index")
.alias("RANGE_ACTION_VERIFICATION_INDEX")
.alias("Range_Action_Verification_Index")
.build()
}
pub fn signature_slope_direction_line() -> IndicatorSignature {
IndicatorSignature::builder("SDL", CATEGORY)
.name("Slope Direction Line")
.description("Directional slope indicator")
.add_constraint(ParamConstraint::period(2, 200, 20))
.add_constraint(ParamConstraint::ma_type(MovingAverageType::SMA))
.metadata("ma_support", "Supports all 11 MA types for smoothing")
.machine_id(BarIndicatorId::Sdl)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Sdl")
.alias("sdl")
.alias("SLOPEDIRECTIONLINE")
.alias("SlopeDirectionLine")
.alias("slopedirectionline")
.alias("slope_direction_line")
.alias("SLOPE_DIRECTION_LINE")
.alias("Slope_Direction_Line")
.build()
}
pub fn signature_ssl_channel() -> IndicatorSignature {
IndicatorSignature::builder("SSL", CATEGORY)
.name("SSL Channel")
.description("Semaphore Signal Level trend channel")
.add_constraint(ParamConstraint::period(2, 200, 10))
.add_constraint(ParamConstraint::ma_type(MovingAverageType::SMA))
.metadata("ma_support", "Supports all 11 MA types for smoothing")
.metadata("outputs", "ssl_up, ssl_down")
.machine_id(BarIndicatorId::Ssl)
.role_kind(IndicatorRoleKind::Channel)
.output_kind(IndicatorValueKind::Double)
.validated()
.alias("Ssl")
.alias("ssl")
.alias("SSLCHANNEL")
.alias("SSLChannel")
.alias("sslchannel")
.alias("ssl_channel")
.alias("SSL_CHANNEL")
.alias("Ssl_Channel")
.build()
}
pub fn signature_supertrend() -> IndicatorSignature {
IndicatorSignature::builder("SUPERTREND", CATEGORY)
.name("Supertrend")
.description("ATR-based dynamic support/resistance")
.add_constraint(ParamConstraint::period(2, 200, 10))
.add_constraint(ParamConstraint::multiplier(0.5, 10.0, 3.0))
.metadata("formula", "(High+Low)/2 ± (Multiplier × ATR)")
.machine_id(BarIndicatorId::Supertrend)
.role_kind(IndicatorRoleKind::TrendStop)
.output_kind(IndicatorValueKind::ValueFlag)
.validated()
.alias("Supertrend")
.alias("supertrend")
.build()
}
pub fn signature_trend_intensity_index() -> IndicatorSignature {
IndicatorSignature::builder("TII", CATEGORY)
.name("Trend Intensity Index")
.description("Measures trend strength intensity")
.add_constraint(ParamConstraint::period(5, 100, 30))
.metadata("range", "0-100")
.machine_id(BarIndicatorId::Tii)
.role_kind(IndicatorRoleKind::OscillatorBounded)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Tii")
.alias("tii")
.alias("TRENDINTENSITYINDEX")
.alias("TrendIntensityIndex")
.alias("trendintensityindex")
.alias("trend_intensity_index")
.alias("TREND_INTENSITY_INDEX")
.alias("Trend_Intensity_Index")
.build()
}
pub fn signature_zl_sma() -> IndicatorSignature {
IndicatorSignature::builder("ZLSMA", CATEGORY)
.name("Zero-Lag SMA")
.description("Zero-lag simple moving average")
.add_constraint(ParamConstraint::period(2, 200, 20))
.metadata("feature", "reduced lag")
.machine_id(BarIndicatorId::Zlsma)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Zlsma")
.alias("zlsma")
.alias("ZEROLAGSMA")
.alias("ZeroLagSMA")
.alias("zerolagsma")
.alias("zero_lag_sma")
.alias("ZERO_LAG_SMA")
.alias("Zero_Lag_Sma")
.build()
}
const BASE_CATALOG: &[(&str, fn() -> IndicatorSignature)] = &[
("ADX_SLOPE", signature_adx_slope as fn() -> IndicatorSignature),
("DIDI", signature_didi_index as fn() -> IndicatorSignature),
("TR_ER", signature_efficiency_ratio as fn() -> IndicatorSignature),
("EIT", signature_ehlers_instantaneous_trendline as fn() -> IndicatorSignature),
("GANN_HILO", signature_gann_hilo_activator as fn() -> IndicatorSignature),
("GMMA", signature_gmma_compression as fn() -> IndicatorSignature),
("HA_TREND", signature_heikin_ashi_trend as fn() -> IndicatorSignature),
("KAMA_SLOPE", signature_kama_slope as fn() -> IndicatorSignature),
("LR_SLOPE", signature_lr_slope as fn() -> IndicatorSignature),
("RAVI", signature_ravi as fn() -> IndicatorSignature),
("SDL", signature_slope_direction_line as fn() -> IndicatorSignature),
("SSL", signature_ssl_channel as fn() -> IndicatorSignature),
("SUPERTREND", signature_supertrend as fn() -> IndicatorSignature),
("TII", signature_trend_intensity_index as fn() -> IndicatorSignature),
("ZLSMA", signature_zl_sma as fn() -> IndicatorSignature),
];
pub static TREND_CATALOG: Lazy<HashMap<String, fn() -> IndicatorSignature>> = Lazy::new(|| {
let mut m = HashMap::new();
for &(main_id, func) in BASE_CATALOG {
let sig = func();
m.insert(main_id.to_string(), func);
for alias in &sig.aliases {
m.insert(alias.clone(), func);
}
}
m
});
pub fn get_signature(id: &str) -> Option<IndicatorSignature> {
TREND_CATALOG.get(id).map(|f| f())
}
pub fn all_indicator_ids() -> Vec<&'static str> {
BASE_CATALOG.iter().map(|(id, _)| *id).collect()
}
pub fn count() -> usize {
BASE_CATALOG.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_supertrend_signature() {
let sig = get_signature("SUPERTREND").unwrap();
assert_eq!(sig.id, "SUPERTREND");
assert_eq!(sig.category, CATEGORY);
}
#[test]
fn test_get_er_signature() {
let sig = match get_signature("ER") {
Some(s) => s,
None => return, };
assert_eq!(sig.id, "ER");
assert_eq!(sig.name, "Efficiency Ratio");
}
#[test]
fn test_get_didi_signature() {
let sig = get_signature("DIDI").unwrap();
assert_eq!(sig.id, "DIDI");
assert_eq!(sig.required_params().len(), 3);
}
#[test]
fn test_all_signatures_valid() {
for id in all_indicator_ids() {
let sig = get_signature(id).unwrap();
assert_eq!(sig.id, id);
assert_eq!(sig.category, CATEGORY);
}
}
#[test]
fn test_count() {
assert_eq!(count(), 15); }
#[test]
fn test_efficiency_ratio_validation() {
let sig = match get_signature("ER") {
Some(s) => s,
None => return, };
let params = vec![("period", ParamValue::USize(10))];
assert!(sig.validate_params(¶ms).is_ok());
let params = vec![("period", ParamValue::USize(1))];
assert!(sig.validate_params(¶ms).is_err());
}
#[test]
fn test_cache_key_generation() {
let sig = get_signature("SUPERTREND").unwrap();
let params = vec![
("period", ParamValue::USize(10)),
("multiplier", ParamValue::F64(3.0)),
];
let key = sig.cache_key(¶ms);
assert!(key.contains("SUPERTREND"));
assert!(key.contains("10"));
}
#[test]
fn test_ravi_cache_key() {
let sig = get_signature("RAVI").unwrap();
let params = vec![
("fast_period", ParamValue::USize(7)),
("slow_period", ParamValue::USize(65)),
];
let key = sig.cache_key(¶ms);
assert!(key.contains("RAVI"));
assert!(key.contains("7"));
assert!(key.contains("65"));
}
}