Skip to main content

kestrel_chartkit/indicator/
mod.rs

1pub mod adx;
2pub mod alligator;
3pub mod anchored_vwap;
4pub mod atr;
5pub mod bollinger;
6pub mod bop;
7pub mod bos_choch;
8pub mod buy_sell_pressure;
9pub mod candle_story;
10pub mod cci;
11pub mod chaikin_osc;
12pub mod chandelier_exit;
13pub mod chart_patterns;
14pub mod choppiness;
15pub mod connors_rsi;
16pub mod coppock;
17pub mod divergence;
18pub mod dpo;
19pub mod efficiency;
20pub mod elliott;
21pub mod envelope;
22pub mod eom;
23pub mod fisher_transform;
24pub mod kst;
25pub mod liquidity_fvg;
26pub mod liquidity_sweeps;
27pub mod lsma;
28pub mod macd;
29pub mod market_structure_breaks;
30pub mod mass_index;
31pub mod mcginley;
32pub mod mfi;
33pub mod midas;
34pub mod momentum_indicators;
35pub mod moving_averages;
36pub mod multi_factor;
37pub mod nvi_pvi;
38pub mod order_block;
39pub mod params;
40pub mod pivot_sets;
41pub mod pivots_structure;
42pub mod price_levels;
43pub mod registry;
44pub mod relative_strength;
45pub mod rsi;
46pub mod rvi;
47pub mod smart_money_structure;
48pub mod smoothing;
49pub mod source_mapped;
50pub mod stoch_rsi;
51pub mod swing_structure;
52pub mod tema;
53pub mod trend_quality;
54pub mod trend_relationship;
55pub mod trend_structural;
56pub mod tsi;
57pub mod vix_fix;
58pub mod volatility_indicators;
59pub mod volatility_regime;
60pub mod volume_flow;
61pub mod volume_flow_hires;
62pub mod volume_indicators;
63pub mod volume_profile;
64pub mod volume_profile_extended;
65pub mod volume_profile_persistent;
66pub mod vortex;
67pub mod vwap;
68pub mod wavetrend;
69pub mod williams_r;
70pub mod wyckoff;
71pub mod zigzag;
72pub mod zigzag_advanced;
73pub mod zscore;
74
75use std::collections::HashMap;
76use std::fmt;
77
78#[cfg(feature = "serde")]
79use serde::{Deserialize, Serialize};
80
81use crate::artifact::Artifact;
82use crate::model::{Bar, BarValidationError};
83
84/// Output struct returned by an indicator on each processed Bar.
85#[derive(Debug, Clone, PartialEq)]
86#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87pub struct IndicatorOutput {
88    pub value: f64,
89    pub secondary: Option<f64>,
90    pub signal: Option<f64>,
91    pub extra: HashMap<String, f64>,
92    /// Machine-readable state label (e.g. "trending", "range", "spring"), replacing indicator-
93    /// local string keys stuffed into `extra`.
94    pub state: Option<String>,
95    /// Human-readable explanation for the current `state`/`value`.
96    pub reason: Option<String>,
97    /// Typed result artifacts (pivots, zones, profiles, scenario progress) emitted this bar.
98    pub artifacts: Vec<Artifact>,
99}
100
101impl IndicatorOutput {
102    pub fn new(value: f64) -> Self {
103        Self {
104            value,
105            secondary: None,
106            signal: None,
107            extra: HashMap::new(),
108            state: None,
109            reason: None,
110            artifacts: Vec::new(),
111        }
112    }
113
114    pub fn with_secondary(mut self, secondary: f64) -> Self {
115        self.secondary = Some(secondary);
116        self
117    }
118
119    pub fn with_signal(mut self, signal: f64) -> Self {
120        self.signal = Some(signal);
121        self
122    }
123
124    pub fn with_extra(value: f64, extra: HashMap<String, f64>) -> Self {
125        Self {
126            value,
127            secondary: None,
128            signal: None,
129            extra,
130            state: None,
131            reason: None,
132            artifacts: Vec::new(),
133        }
134    }
135
136    pub fn with_state(mut self, state: impl Into<String>) -> Self {
137        self.state = Some(state.into());
138        self
139    }
140
141    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
142        self.reason = Some(reason.into());
143        self
144    }
145
146    pub fn with_artifact(mut self, artifact: impl Into<Artifact>) -> Self {
147        self.artifacts.push(artifact.into());
148        self
149    }
150}
151
152/// Alert emitted by an indicator when a critical threshold or cross occurs.
153#[derive(Debug, Clone, PartialEq)]
154#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
155pub struct IndicatorAlert {
156    pub kind: String,
157    pub note: String,
158    pub strength: f64,
159}
160
161impl IndicatorAlert {
162    pub fn new(kind: impl Into<String>, note: impl Into<String>, strength: f64) -> Self {
163        Self {
164            kind: kind.into(),
165            note: note.into(),
166            strength,
167        }
168    }
169}
170
171impl fmt::Display for IndicatorAlert {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        write!(f, "{} ({})", self.note, self.kind)
174    }
175}
176
177/// Core trait implemented by all technical indicators.
178pub trait Indicator: Send + Sync {
179    fn name(&self) -> &str;
180    fn warmup_period(&self) -> usize {
181        0
182    }
183    fn reset(&mut self);
184    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput>;
185    /// Validates a bar before forwarding it to [`Indicator::on_bar`].
186    fn on_checked_bar(&mut self, bar: &Bar) -> Result<Option<IndicatorOutput>, BarValidationError> {
187        bar.validate()?;
188        Ok(self.on_bar(bar))
189    }
190    fn alerts(&self) -> Vec<IndicatorAlert> {
191        Vec::new()
192    }
193}
194
195/// Lets a boxed indicator (as returned by [`registry::build`]/[`registry::build_typed`]) be used
196/// anywhere a concrete `Indicator` is expected, e.g. as the generic parameter of
197/// [`source_mapped::SourceMapped`] or [`crate::graph::Leaf`].
198impl<T: Indicator + ?Sized> Indicator for Box<T> {
199    fn name(&self) -> &str {
200        (**self).name()
201    }
202    fn warmup_period(&self) -> usize {
203        (**self).warmup_period()
204    }
205    fn reset(&mut self) {
206        (**self).reset()
207    }
208    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
209        (**self).on_bar(bar)
210    }
211    fn on_checked_bar(&mut self, bar: &Bar) -> Result<Option<IndicatorOutput>, BarValidationError> {
212        (**self).on_checked_bar(bar)
213    }
214    fn alerts(&self) -> Vec<IndicatorAlert> {
215        (**self).alerts()
216    }
217}