Skip to main content

kestrel_chartkit/indicator/
mod.rs

1pub mod adx;
2pub mod alligator;
3pub mod anchored_vwap;
4pub mod atr;
5pub mod bbtrend;
6pub mod bollinger;
7pub mod bop;
8pub mod bos_choch;
9pub mod buy_sell_pressure;
10pub mod candle_story;
11pub mod cci;
12pub mod chaikin_osc;
13pub mod chande_kroll;
14pub mod chandelier_exit;
15pub mod chandelier_flip_radar;
16pub mod chart_patterns;
17pub mod choppiness;
18pub mod connors_rsi;
19pub mod coppock;
20pub mod cvd_intrabar;
21pub mod divergence;
22pub mod dpo;
23pub mod efficiency;
24pub mod elliott;
25pub mod envelope;
26pub mod eom;
27pub mod fisher_transform;
28pub mod force_index;
29pub mod harmonics;
30pub mod kst;
31pub mod liquidity_fvg;
32pub mod liquidity_sweeps;
33pub mod lsma;
34pub mod macd;
35pub mod market_structure_breaks;
36pub mod mass_index;
37pub mod mcginley;
38pub mod mfi;
39pub mod midas;
40pub mod momentum_indicators;
41pub mod money_flow_profile;
42pub mod moving_averages;
43pub mod multi_factor;
44pub mod nvi_pvi;
45pub mod order_block;
46pub mod params;
47pub mod pivot_sets;
48pub mod pivots_structure;
49pub mod pmo;
50pub mod price_levels;
51pub mod pvt;
52pub mod rci;
53pub mod registry;
54pub mod relative_strength;
55pub mod relative_volatility;
56pub mod rsi;
57pub mod rvat;
58pub mod rvi;
59pub mod smart_money_structure;
60pub mod smi;
61pub mod smoothing;
62pub mod source_mapped;
63pub mod stoch_rsi;
64pub mod swing_structure;
65pub mod t3;
66pub mod tema;
67pub mod trend_quality;
68pub mod trend_relationship;
69pub mod trend_structural;
70pub mod trix;
71pub mod tsi;
72pub mod twap;
73pub mod ulcer;
74pub mod vidya;
75pub mod vix_fix;
76pub mod volatility_indicators;
77pub mod volatility_regime;
78pub mod volume_flow;
79pub mod volume_flow_hires;
80pub mod volume_indicators;
81pub mod volume_profile;
82pub mod volume_profile_extended;
83pub mod volume_profile_persistent;
84pub mod vortex;
85pub mod vwap;
86pub mod wavetrend;
87pub mod williams_r;
88pub mod wyckoff;
89pub mod zigzag;
90pub mod zigzag_advanced;
91pub mod zscore;
92
93use std::collections::HashMap;
94use std::fmt;
95
96#[cfg(feature = "serde")]
97use serde::{Deserialize, Serialize};
98
99use crate::artifact::Artifact;
100use crate::model::{Bar, BarValidationError, SeriesCapabilities};
101
102/// Output struct returned by an indicator on each processed Bar.
103#[derive(Debug, Clone, PartialEq)]
104#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
105pub struct IndicatorOutput {
106    pub value: f64,
107    pub secondary: Option<f64>,
108    pub signal: Option<f64>,
109    pub extra: HashMap<String, f64>,
110    /// Machine-readable state label (e.g. "trending", "range", "spring"), replacing indicator-
111    /// local string keys stuffed into `extra`.
112    pub state: Option<String>,
113    /// Human-readable explanation for the current `state`/`value`.
114    pub reason: Option<String>,
115    /// Typed result artifacts (pivots, zones, profiles, scenario progress) emitted this bar.
116    pub artifacts: Vec<Artifact>,
117    /// Which series this output was computed on, if the caller attached one via
118    /// [`IndicatorOutput::with_capabilities`]. `None` by default: a result is only meaningful for
119    /// the series it was computed on (session cut, roll/adjustment, provenance).
120    /// [`crate::runner::run_batch_with_applicability`] sets this automatically from
121    /// the `SeriesCapabilities` it already receives; plain [`crate::runner::run_batch`] and direct
122    /// [`Indicator::on_bar`] calls leave it `None` since neither has a capabilities value to
123    /// attach. This is the same field/pattern already used on `elliott::ImpulseValidation`/
124    /// `CorrectionValidation` and `swing_structure::SwingStructureOutput`, generalized here so it
125    /// covers every indicator behind the shared `Indicator`/`IndicatorOutput` trait (including
126    /// `pivots_structure`/`zigzag`/`zigzag_advanced`/`pivot_sets`) instead of just those three
127    /// hand-picked result types.
128    pub series_capabilities: Option<SeriesCapabilities>,
129}
130
131impl IndicatorOutput {
132    pub fn new(value: f64) -> Self {
133        Self {
134            value,
135            secondary: None,
136            signal: None,
137            extra: HashMap::new(),
138            state: None,
139            reason: None,
140            artifacts: Vec::new(),
141            series_capabilities: None,
142        }
143    }
144
145    pub fn with_secondary(mut self, secondary: f64) -> Self {
146        self.secondary = Some(secondary);
147        self
148    }
149
150    pub fn with_signal(mut self, signal: f64) -> Self {
151        self.signal = Some(signal);
152        self
153    }
154
155    pub fn with_extra(value: f64, extra: HashMap<String, f64>) -> Self {
156        Self {
157            value,
158            secondary: None,
159            signal: None,
160            extra,
161            state: None,
162            reason: None,
163            artifacts: Vec::new(),
164            series_capabilities: None,
165        }
166    }
167
168    pub fn with_state(mut self, state: impl Into<String>) -> Self {
169        self.state = Some(state.into());
170        self
171    }
172
173    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
174        self.reason = Some(reason.into());
175        self
176    }
177
178    pub fn with_artifact(mut self, artifact: impl Into<Artifact>) -> Self {
179        self.artifacts.push(artifact.into());
180        self
181    }
182
183    /// Tags this output with the series it was computed on. See
184    /// [`IndicatorOutput::series_capabilities`] for why this matters.
185    pub fn with_capabilities(mut self, capabilities: SeriesCapabilities) -> Self {
186        self.series_capabilities = Some(capabilities);
187        self
188    }
189}
190
191/// Alert emitted by an indicator when a critical threshold or cross occurs.
192#[derive(Debug, Clone, PartialEq)]
193#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
194pub struct IndicatorAlert {
195    pub kind: String,
196    pub note: String,
197    pub strength: f64,
198}
199
200impl IndicatorAlert {
201    pub fn new(kind: impl Into<String>, note: impl Into<String>, strength: f64) -> Self {
202        Self {
203            kind: kind.into(),
204            note: note.into(),
205            strength,
206        }
207    }
208}
209
210impl fmt::Display for IndicatorAlert {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        write!(f, "{} ({})", self.note, self.kind)
213    }
214}
215
216/// Core trait implemented by all technical indicators.
217pub trait Indicator: Send + Sync {
218    fn name(&self) -> &str;
219    fn warmup_period(&self) -> usize {
220        0
221    }
222    fn reset(&mut self);
223    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput>;
224    /// Validates a bar before forwarding it to [`Indicator::on_bar`].
225    fn on_checked_bar(&mut self, bar: &Bar) -> Result<Option<IndicatorOutput>, BarValidationError> {
226        bar.validate()?;
227        Ok(self.on_bar(bar))
228    }
229    fn alerts(&self) -> Vec<IndicatorAlert> {
230        Vec::new()
231    }
232}
233
234/// Lets a boxed indicator (as returned by [`registry::build`]/[`registry::build_typed`]) be used
235/// anywhere a concrete `Indicator` is expected, e.g. as the generic parameter of
236/// [`source_mapped::SourceMapped`] or [`crate::graph::Leaf`].
237impl<T: Indicator + ?Sized> Indicator for Box<T> {
238    fn name(&self) -> &str {
239        (**self).name()
240    }
241    fn warmup_period(&self) -> usize {
242        (**self).warmup_period()
243    }
244    fn reset(&mut self) {
245        (**self).reset()
246    }
247    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
248        (**self).on_bar(bar)
249    }
250    fn on_checked_bar(&mut self, bar: &Bar) -> Result<Option<IndicatorOutput>, BarValidationError> {
251        (**self).on_checked_bar(bar)
252    }
253    fn alerts(&self) -> Vec<IndicatorAlert> {
254        (**self).alerts()
255    }
256}