use crate::catalog::{
IndicatorSignature, IndicatorCategory, ParamConstraint,
IndicatorRoleKind,
};
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::Levels;
pub fn signature_pivot_points() -> IndicatorSignature {
IndicatorSignature::builder("PIVOT", CATEGORY)
.name("Classic Pivot Points")
.description("Classic pivot levels: PP, R1-R3, S1-S3 for support/resistance")
.add_constraint(ParamConstraint::period(1, 168, 1))
.metadata("author", "Classic TA")
.metadata("levels", "7 (PP, R1-R3, S1-S3)")
.machine_id(BarIndicatorId::Pivot)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Triple)
.validated()
.alias("Pivot")
.alias("pivot")
.alias("CLASSICPIVOTPOINTS")
.alias("ClassicPivotPoints")
.alias("classicpivotpoints")
.alias("classic_pivot_points")
.alias("CLASSIC_PIVOT_POINTS")
.alias("Classic_Pivot_Points")
.build()
}
pub fn signature_floor_trader_pivots() -> IndicatorSignature {
IndicatorSignature::builder("FLOORPIVOT", CATEGORY)
.name("Floor Trader Pivots")
.description("Floor trader pivot calculation for intraday levels")
.add_constraint(ParamConstraint::period(1, 168, 1))
.metadata("author", "Floor Traders")
.metadata("use_case", "intraday trading")
.machine_id(BarIndicatorId::Floorpivot)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Floorpivot")
.alias("floorpivot")
.alias("FLOORTRADERPIVOTS")
.alias("FloorTraderPivots")
.alias("floortraderpivots")
.alias("floor_trader_pivots")
.alias("FLOOR_TRADER_PIVOTS")
.alias("Floor_Trader_Pivots")
.build()
}
pub fn signature_camarilla_pivots() -> IndicatorSignature {
IndicatorSignature::builder("CAMARILLA", CATEGORY)
.name("Camarilla Pivots")
.description("Camarilla pivot levels with R1-R4 and S1-S4 using 1.1 multiplier")
.add_constraint(ParamConstraint::period(1, 168, 24))
.metadata("author", "Nick Stott")
.metadata("levels", "9 (PP, R1-R4, S1-S4)")
.metadata("multiplier", "1.1")
.machine_id(BarIndicatorId::Camarilla)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Camarilla")
.alias("camarilla")
.alias("CAMARILLAPIVOTS")
.alias("CamarillaPivots")
.alias("camarillapivots")
.alias("camarilla_pivots")
.alias("CAMARILLA_PIVOTS")
.alias("Camarilla_Pivots")
.build()
}
pub fn signature_woodie_pivots() -> IndicatorSignature {
IndicatorSignature::builder("WOODIE", CATEGORY)
.name("Woodie Pivots")
.description("Woodie's pivot calculation emphasizing current session")
.add_constraint(ParamConstraint::period(1, 168, 1))
.metadata("author", "Woodie")
.metadata("emphasis", "current session")
.machine_id(BarIndicatorId::Woodie)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Woodie")
.alias("woodie")
.alias("WOODIEPIVOTS")
.alias("WoodiePivots")
.alias("woodiepivots")
.alias("woodie_pivots")
.alias("WOODIE_PIVOTS")
.alias("Woodie_Pivots")
.build()
}
pub fn signature_demark_pivots() -> IndicatorSignature {
IndicatorSignature::builder("DEMARK", CATEGORY)
.name("DeMark Pivots")
.description("Tom DeMark's pivot calculation based on open/close relationship")
.add_constraint(ParamConstraint::period(1, 168, 1))
.metadata("author", "Tom DeMark")
.metadata("feature", "open/close relationship")
.machine_id(BarIndicatorId::Demark)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Demark")
.alias("demark")
.alias("DEMARKPIVOTS")
.alias("DeMarkPivots")
.alias("demarkpivots")
.alias("demark_pivots")
.alias("DEMARK_PIVOTS")
.alias("Demark_Pivots")
.build()
}
pub fn signature_anchored_vwap() -> IndicatorSignature {
IndicatorSignature::builder("AVWAP", CATEGORY)
.name("Anchored VWAP")
.description("Volume-weighted average price anchored to monthly periods")
.metadata("uses_volume", "true")
.metadata("anchor", "monthly")
.metadata("reset", "calendar-based")
.machine_id(BarIndicatorId::Avwap)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Avwap")
.alias("avwap")
.alias("ANCHOREDVWAP")
.alias("AnchoredVWAP")
.alias("anchoredvwap")
.alias("anchored_vwap")
.alias("ANCHORED_VWAP")
.alias("Anchored_Vwap")
.build()
}
pub fn signature_avwap_multi_anchor_reversion() -> IndicatorSignature {
IndicatorSignature::builder("AVWAPREV", CATEGORY)
.name("AVWAP Multi-Anchor Reversion")
.description("Reversion probability to multiple AVWAP anchor points")
.metadata("uses_volume", "true")
.metadata("multi_anchor", "true")
.machine_id(BarIndicatorId::Avwaprev)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Avwaprev")
.alias("avwaprev")
.alias("AVWAPMULTIANCHORREVERSION")
.alias("AVWAPMultiAnchorReversion")
.alias("avwapmultianchorreversion")
.alias("avwap_multi_anchor_reversion")
.alias("AVWAP_MULTI_ANCHOR_REVERSION")
.alias("Avwap_Multi_Anchor_Reversion")
.build()
}
pub fn signature_avwap_touch_probability() -> IndicatorSignature {
IndicatorSignature::builder("AVWAPTOUCH", CATEGORY)
.name("AVWAP Touch Probability")
.description("Statistical probability of price touching AVWAP level")
.metadata("uses_volume", "true")
.metadata("statistical", "true")
.machine_id(BarIndicatorId::Avwaptouch)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Avwaptouch")
.alias("avwaptouch")
.alias("AVWAPTOUCHPROBABILITY")
.alias("AVWAPTouchProbability")
.alias("avwaptouchprobability")
.alias("avwap_touch_probability")
.alias("AVWAP_TOUCH_PROBABILITY")
.alias("Avwap_Touch_Probability")
.build()
}
pub fn signature_break_of_structure() -> IndicatorSignature {
IndicatorSignature::builder("BOS", CATEGORY)
.name("Break of Structure")
.description("Detects Break of Structure and Change of Character patterns")
.add_constraint(ParamConstraint::period(2, 50, 10))
.metadata("pattern", "BOS/CHOCH")
.metadata("use_case", "structure breaks")
.machine_id(BarIndicatorId::Bos)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::DoubleFlag)
.validated()
.alias("Bos")
.alias("bos")
.alias("BREAKOFSTRUCTURE")
.alias("BreakofStructure")
.alias("breakofstructure")
.alias("break_of_structure")
.alias("BREAK_OF_STRUCTURE")
.alias("Break_Of_Structure")
.build()
}
pub fn signature_fvg_detector() -> IndicatorSignature {
IndicatorSignature::builder("FVG", CATEGORY)
.name("Fair Value Gap Detector")
.description("Detects Fair Value Gaps in 3-bar patterns")
.metadata("pattern", "3-bar FVG")
.metadata("type", "bull/bear gaps")
.machine_id(BarIndicatorId::Fvg)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::DoubleFlag)
.validated()
.alias("Fvg")
.alias("fvg")
.alias("FAIRVALUEGAPDETECTOR")
.alias("FairValueGapDetector")
.alias("fairvaluegapdetector")
.alias("fair_value_gap_detector")
.alias("FAIR_VALUE_GAP_DETECTOR")
.alias("Fair_Value_Gap_Detector")
.build()
}
pub fn signature_hl_value_area() -> IndicatorSignature {
IndicatorSignature::builder("HLVA", CATEGORY)
.name("High-Low Value Area")
.description("Value area calculation based on high/low ranges")
.metadata("basis", "high/low ranges")
.machine_id(BarIndicatorId::Hlva)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Hlva")
.alias("hlva")
.alias("HIGHLOWVALUEAREA")
.alias("HighLowValueArea")
.alias("highlowvaluearea")
.alias("high_low_value_area")
.alias("HIGH_LOW_VALUE_AREA")
.alias("High_Low_Value_Area")
.build()
}
pub fn signature_pivot_anchored_vwap() -> IndicatorSignature {
IndicatorSignature::builder("PIVAVWAP", CATEGORY)
.name("Pivot Anchored VWAP")
.description("VWAP anchored to pivot point levels")
.metadata("uses_volume", "true")
.metadata("anchor", "pivot points")
.machine_id(BarIndicatorId::Pivavwap)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Pivavwap")
.alias("pivavwap")
.alias("PIVOTANCHOREDVWAP")
.alias("PivotAnchoredVWAP")
.alias("pivotanchoredvwap")
.alias("pivot_anchored_vwap")
.alias("PIVOT_ANCHORED_VWAP")
.alias("Pivot_Anchored_Vwap")
.build()
}
pub fn signature_rolling_midline() -> IndicatorSignature {
IndicatorSignature::builder("RMID", CATEGORY)
.name("Rolling Midline")
.description("Rolling average of (High + Low) / 2")
.add_constraint(ParamConstraint::period(1, 200, 20))
.metadata("formula", "(H+L)/2 average")
.machine_id(BarIndicatorId::Rmid)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Single)
.validated()
.alias("Rmid")
.alias("rmid")
.alias("ROLLINGMIDLINE")
.alias("RollingMidline")
.alias("rollingmidline")
.alias("rolling_midline")
.alias("ROLLING_MIDLINE")
.alias("Rolling_Midline")
.build()
}
pub fn signature_rolling_quartiles() -> IndicatorSignature {
IndicatorSignature::builder("RQUART", CATEGORY)
.name("Rolling Quartiles")
.description("Rolling Q1, Q2 (median), Q3 quartiles of close prices")
.add_constraint(ParamConstraint::period(1, 200, 20))
.metadata("outputs", "Q1, Q2, Q3")
.metadata("complexity", "O(N log N) per update")
.machine_id(BarIndicatorId::Rquart)
.role_kind(IndicatorRoleKind::Level)
.output_kind(IndicatorValueKind::Triple)
.validated()
.alias("Rquart")
.alias("rquart")
.alias("ROLLINGQUARTILES")
.alias("RollingQuartiles")
.alias("rollingquartiles")
.alias("rolling_quartiles")
.alias("ROLLING_QUARTILES")
.alias("Rolling_Quartiles")
.build()
}
const BASE_CATALOG: &[(&str, fn() -> IndicatorSignature)] = &[
("PIVOT", signature_pivot_points as fn() -> IndicatorSignature),
("FLOORPIVOT", signature_floor_trader_pivots as fn() -> IndicatorSignature),
("CAMARILLA", signature_camarilla_pivots as fn() -> IndicatorSignature),
("WOODIE", signature_woodie_pivots as fn() -> IndicatorSignature),
("DEMARK", signature_demark_pivots as fn() -> IndicatorSignature),
("AVWAP", signature_anchored_vwap as fn() -> IndicatorSignature),
("AVWAPREV", signature_avwap_multi_anchor_reversion as fn() -> IndicatorSignature),
("AVWAPTOUCH", signature_avwap_touch_probability as fn() -> IndicatorSignature),
("BOS", signature_break_of_structure as fn() -> IndicatorSignature),
("FVG", signature_fvg_detector as fn() -> IndicatorSignature),
("HLVA", signature_hl_value_area as fn() -> IndicatorSignature),
("PIVAVWAP", signature_pivot_anchored_vwap as fn() -> IndicatorSignature),
("RMID", signature_rolling_midline as fn() -> IndicatorSignature),
("RQUART", signature_rolling_quartiles as fn() -> IndicatorSignature),
];
pub static LEVELS_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> {
LEVELS_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::*;
use crate::catalog::ParamValue;
#[test]
fn test_get_pivot_signature() {
let sig = get_signature("PIVOT").unwrap();
assert_eq!(sig.id, "PIVOT");
assert_eq!(sig.category, CATEGORY);
assert_eq!(sig.required_params().len(), 1);
}
#[test]
fn test_get_camarilla_signature() {
let sig = get_signature("CAMARILLA").unwrap();
assert_eq!(sig.id, "CAMARILLA");
assert_eq!(sig.name, "Camarilla Pivots");
}
#[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(), 14); }
#[test]
fn test_pivot_validation() {
let sig = get_signature("PIVOT").unwrap();
let params = vec![("period", ParamValue::USize(1))];
assert!(sig.validate_params(¶ms).is_ok());
let params = vec![("period", ParamValue::USize(200))];
assert!(sig.validate_params(¶ms).is_err());
}
#[test]
fn test_cache_key_generation() {
let sig = get_signature("PIVOT").unwrap();
let params = vec![("period", ParamValue::USize(1))];
let key = sig.cache_key(¶ms);
assert_eq!(key, "PIVOT_1");
}
#[test]
fn test_bos_signature() {
let sig = get_signature("BOS").unwrap();
assert_eq!(sig.id, "BOS");
assert_eq!(sig.name, "Break of Structure");
assert_eq!(sig.required_params().len(), 1);
}
}