Skip to main content

egui_charts/studies/
indicator_trait.rs

1//! Core indicator trait and value types.
2//!
3//! This module defines the [`Indicator`] trait that every technical indicator
4//! (built-in or custom) must implement, as well as the [`IndicatorValue`]
5//! row format used to construct or export column-wise output.
6
7use crate::model::Bar;
8use crate::studies::IndicatorValues;
9use egui::Color32;
10
11/// The output value of an indicator at a single bar position.
12///
13/// An owned row for constructing or exporting [`IndicatorValues`](super::IndicatorValues).
14/// Studies store columns; this row format is also accepted by custom calculation closures.
15/// The variant depends on the indicator:
16///
17/// - **`Single(f64)`** -- used by single-line indicators (SMA, EMA, RSI, ATR, ...).
18/// - **`Multiple(Vec<f64>)`** -- used by multi-line indicators, one value per
19///   line in the order of [`line_names`](Indicator::line_names). For example,
20///   MACD emits `[macd_line, signal_line, histogram]` and Bollinger Bands
21///   emits `[upper, middle, lower]`. A line without a value on the bar, such
22///   as a signal line during its warmup, is NaN.
23/// - **`None`** -- emitted for bars where no line has a value: within the
24///   indicator's warmup period, or on a bar whose inputs are not finite.
25///
26/// An indicator with more than one line emits `Multiple` with
27/// [`line_count`](Indicator::line_count) values or `None`, never `Single`.
28#[derive(Debug, Clone)]
29pub enum IndicatorValue {
30    /// A single computed value (e.g. SMA output at one bar).
31    Single(f64),
32    /// One value per line for multi-line indicators (e.g. MACD producing
33    /// MACD line, signal line, and histogram), NaN for a line without one.
34    Multiple(Vec<f64>),
35    /// No value on this bar: warmup, or inputs that are not finite.
36    None,
37}
38
39/// Direction of a crossing marker.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum CrossDirection {
42    /// First line crosses above the second.
43    Up,
44    /// First line crosses below the second.
45    Down,
46}
47
48/// A shape at a study value on a source bar.
49#[derive(Clone, Copy, Debug)]
50pub struct IndicatorMarker {
51    /// Index in the study's input bars.
52    pub bar: usize,
53    /// Price at which the crossing is marked.
54    pub value: f64,
55    /// Crossing direction.
56    pub direction: CrossDirection,
57}
58
59/// A vertical fade between an overlay line and the plot's bottom edge.
60#[derive(Clone, Copy, Debug)]
61pub struct IndicatorFill {
62    /// Fill colour; alpha is supplied separately at each end of the gradient.
63    pub color: Color32,
64    /// Alpha just under the line, from 0 to 255.
65    pub top_alpha: u8,
66    /// Alpha at the plot's bottom edge, from 0 to 255.
67    pub bottom_alpha: u8,
68}
69
70/// Width, optional direction colours and optional bottom fill of an overlay line.
71#[derive(Clone, Copy, Debug)]
72pub struct IndicatorLineStyle {
73    /// Stroke width in logical pixels.
74    pub width: f32,
75    /// Colour on rising segments, or the study's line colour when absent.
76    pub rising: Option<Color32>,
77    /// Colour on falling segments, or the study's line colour when absent.
78    pub falling: Option<Color32>,
79    /// Fill from the line to the plot's bottom.
80    pub fill: Option<IndicatorFill>,
81}
82
83impl Default for IndicatorLineStyle {
84    fn default() -> Self {
85        Self {
86            width: 1.0,
87            rising: None,
88            falling: None,
89            fill: None,
90        }
91    }
92}
93
94/// Default layout and reference levels supplied by a study for its separate pane.
95#[derive(Clone, Debug, Default)]
96pub struct StudyPane {
97    /// Preferred initial height in logical pixels; `None` shares the available space.
98    pub height: Option<f32>,
99    /// Fixed initial range, or automatic fitting to visible values.
100    pub range: Option<(f64, f64)>,
101    /// Reference level, colour and caption: the defaults of
102    /// [`StudySettings::levels`](super::StudySettings::levels).
103    pub levels: Vec<(f64, Color32, &'static str)>,
104    /// Fill between the outer levels using [`Indicator::level_fill_color`].
105    pub fill_between_levels: bool,
106}
107
108/// The core trait that every technical indicator must implement.
109///
110/// The trait defines the full indicator lifecycle: naming, calculation,
111/// value access, color management, and display properties. It is
112/// object-safe so indicators can be stored as `Box<dyn Indicator>` in
113/// the [`IndicatorRegistry`](super::IndicatorRegistry).
114///
115/// # Implementors
116///
117/// All built-in indicators implement this trait (e.g. `SMA`, `RSI`, `MACD` with the
118/// `studies` feature), and users can implement it
119/// for their own indicators or use [`CustomIndicator`](super::CustomIndicator)
120/// to define one at runtime via a closure.
121///
122/// # Lifecycle
123///
124/// 1. Construct the indicator with desired parameters. Built-in indicators
125///    raise a length (period, smoothing, level or row count) of 0 to 1, and
126///    clamp a float parameter (multiplier, percentage, divisor, threshold or
127///    tick size) into the range its constructor or setter documents, NaN to
128///    the bottom of that range. No length, parameter or bar value makes
129///    `calculate` panic or allocate without bound.
130/// 2. Call [`calculate`](Indicator::calculate) with bar data.
131/// 3. Read results via [`values`](Indicator::values).
132/// 4. The rendering layer queries [`is_overlay`](Indicator::is_overlay),
133///    [`colors`](Indicator::colors), [`line_names`](Indicator::line_names),
134///    etc. for drawing.
135///
136/// # Non-finite bars
137///
138/// A bar whose inputs, the fields a study reads, are not finite has no value, and the
139/// study computes every other bar as if that bar were not in the series, so running
140/// state carries over it unchanged. A window study may also leave the windows that
141/// contain it without a value. The chart's datafeed drops such bars before they reach a
142/// study, so this matters to direct callers of [`calculate`](Indicator::calculate).
143pub trait Indicator: Send {
144    /// Sparse projected price bands, with fractional endpoints in future whitespace.
145    fn projections(&self) -> &[super::StudyProjection] {
146        &[]
147    }
148
149    /// A labelled table of the latest study statistics, drawn independently of the plots.
150    fn table(&self) -> Option<&super::StudyTable> {
151        None
152    }
153
154    /// Whether this study stores presentation edits itself. Charts wrap other studies once.
155    fn has_settings(&self) -> bool {
156        false
157    }
158
159    /// Colour inside a projected price band.
160    fn projection_fill_color(&self, projection: &super::StudyProjection) -> Color32 {
161        self.plot_color(projection.plot, projection.bars.start as usize)
162            .unwrap_or_else(|| self.plot_style(projection.plot).color)
163            .gamma_multiply(0.2)
164    }
165
166    /// Optional background and text colours for captions on plotted markers.
167    fn plot_label_colors(&self, _line: usize) -> Option<(Color32, Color32)> {
168        None
169    }
170
171    /// Optional presentation of crossing markers; absent uses directional triangles.
172    fn marker_style(&self) -> Option<super::PlotStyle> {
173        None
174    }
175
176    /// Stroke pattern for a pane level at the given value.
177    fn level_style(&self, _value: f64) -> crate::model::LineStyle {
178        crate::model::LineStyle::Dashed
179    }
180
181    /// Colour of the band between the outer pane levels.
182    fn level_fill_color(&self) -> Color32 {
183        let color = self.plot_style(0).color;
184        Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), 16)
185    }
186
187    /// Presentation defaults for a zero-based output line. Any line is accepted: one at or past
188    /// [`line_count`](Self::line_count), which renderers do not draw, still gets a style, so an
189    /// implementation must not panic on it. The default takes the line's colour from
190    /// [`colors`](Self::colors), white for a line without one, and its width from
191    /// [`line_style`](Self::line_style).
192    fn plot_style(&self, line: usize) -> super::PlotStyle {
193        super::PlotStyle {
194            color: self.colors().get(line).copied().unwrap_or(Color32::WHITE),
195            width: self.line_style(line).width,
196            pattern: crate::model::LineStyle::Solid,
197            plot_type: super::PlotType::Line,
198            visible: true,
199        }
200    }
201
202    /// Whether output `line` appears in the legend. Defaults to plot visibility; a study can
203    /// retain a labelled readout for a quantity excluded from its plot and scale by default.
204    fn legend_visible(&self, line: usize) -> bool {
205        self.plot_style(line).visible
206    }
207
208    /// Whether plot `line` shows its last value in a tag on the price scale while the chart
209    /// shows study values. By default every plot of a study with up to four plots does and no
210    /// plot of a study with more, whose values stay in the legend, so that the tags leave the
211    /// scale's tick labels readable. A study on a chart starts from this choice and keeps the
212    /// user's in [`StudySettings::untagged_plots`](super::StudySettings::untagged_plots).
213    fn plot_tagged(&self, line: usize) -> bool {
214        let _ = line;
215        self.line_count() <= 4
216    }
217
218    /// The output whose visibility in the style tab also applies to plot `line`, for a plot past
219    /// [`line_count`](Self::line_count) that draws zones or candles for one of the outputs. The
220    /// default is `line` itself.
221    fn plot_output(&self, line: usize) -> usize {
222        line
223    }
224
225    /// Horizontal displacement in bars. Positive values draw into future whitespace.
226    /// Values remain indexed by their source bar; missing bars still occupy a position.
227    fn plot_offset(&self, line: usize) -> isize {
228        let _ = line;
229        0
230    }
231
232    /// Optional per-bar colour, overriding the plot colour at a source bar.
233    fn plot_color(&self, line: usize, bar: usize) -> Option<Color32> {
234        let _ = (line, bar);
235        None
236    }
237
238    /// The last source bar whose value on a zero-based line is final. The segments after it can
239    /// still move with later bars and are drawn dashed when the line is solid. `None`, the
240    /// default, draws the whole line as styled.
241    fn provisional_after(&self, line: usize) -> Option<usize> {
242        let _ = line;
243        None
244    }
245
246    /// Optional text beside the shape a circle, cross or triangle plot draws at a source bar:
247    /// under an upward triangle and over the other shapes, in the shape's colour.
248    fn plot_text(&self, line: usize, bar: usize) -> Option<String> {
249        let _ = (line, bar);
250        None
251    }
252
253    /// Filled bands, painted beneath this study's plots. Missing endpoints break a band.
254    fn fills(&self) -> Vec<super::StudyFill> {
255        Vec::new()
256    }
257
258    /// Value at a displayed bar, accounting for the plot's displacement.
259    fn plot_value(&self, line: usize, bar: usize) -> Option<f64> {
260        let source = bar.checked_add_signed(self.plot_offset(line).checked_neg()?)?;
261        self.values().value(line, source)
262    }
263
264    /// Current applied presentation, initialized from the study's own defaults.
265    fn settings(&self) -> super::StudySettings {
266        let (levels, fill_between_levels, level_fill_color) =
267            super::settings::declared_levels(self);
268        super::StudySettings {
269            plots: (0..self.line_count())
270                .map(|line| self.plot_style(line))
271                .collect(),
272            precision: self.precision(),
273            hidden_intervals: Vec::new(),
274            interval_range: Default::default(),
275            levels,
276            fill_between_levels,
277            level_fill_color,
278            untagged_plots: (0..self.line_count())
279                .filter(|&line| !self.plot_tagged(line))
280                .collect(),
281        }
282    }
283
284    /// Capture editable inputs and presentation without copying calculated values.
285    /// The chart presentation wrapper also retains visibility before interval filtering.
286    /// Custom presentations can use [`super::StudySettingsSnapshot::capture`] with their
287    /// global visibility toggle to do the same.
288    fn snapshot_settings(&self) -> super::StudySettingsSnapshot {
289        super::StudySettingsSnapshot::capture(self, self.is_visible())
290    }
291
292    /// Restore a snapshot from this study. Recalculate against current data afterwards.
293    /// The default applies changed inputs and presentation through [`Self::apply_settings`].
294    /// Returns an error if the snapshot is no longer accepted.
295    fn restore_settings(
296        &mut self,
297        snapshot: &super::StudySettingsSnapshot,
298    ) -> Result<(), crate::studies::StudyError> {
299        snapshot.restore(self)
300    }
301
302    /// Apply a complete edit atomically. Charts wrap studies in [`ConfiguredStudy`](super::ConfiguredStudy),
303    /// which validates inputs on a clone before replacing the calculation and presentation.
304    fn apply_settings(
305        &mut self,
306        inputs: &[(String, super::InputValue)],
307        settings: super::StudySettings,
308    ) -> Result<(), crate::studies::StudyError> {
309        let _ = (inputs, settings);
310        Err(super::StudyError::NotConfigured)
311    }
312
313    /// Visible source bars, end exclusive, supplied after pan, zoom and resize and before
314    /// autoscaling. Returns whether values need recalculation. Direct callers may supply it too.
315    fn set_visible_range(&mut self, range: std::ops::Range<usize>) -> bool {
316        let _ = range;
317        false
318    }
319
320    /// The chart's current interval, supplied when added and when the interval changes.
321    fn set_timeframe(&mut self, timeframe: crate::model::Timeframe) {
322        let _ = timeframe;
323    }
324
325    /// Source bar calendar, including the chart's selected timezone and session anchor.
326    /// Returns whether cached values must be recalculated. Direct callers default to UTC.
327    fn set_bar_clock(&mut self, clock: &crate::model::BarClock) -> bool {
328        let _ = clock;
329        false
330    }
331
332    /// Units of this study's values. Placement defaults preserve price overlays and numeric panes.
333    fn value_format(&self) -> crate::scales::StudyFormat {
334        if self.is_volume() {
335            crate::scales::StudyFormat::Volume
336        } else if self.is_overlay() {
337            crate::scales::StudyFormat::Price
338        } else {
339            crate::scales::StudyFormat::Fixed(2)
340        }
341    }
342
343    /// Scale used in the pane selected by `is_overlay`.
344    fn scale(&self) -> super::StudyScale {
345        if self.is_overlay() && !self.is_volume() {
346            super::StudyScale::Main
347        } else {
348            super::StudyScale::Independent
349        }
350    }
351
352    /// Price mapping supplied by the chart for a shared scale, independent of pane bounds.
353    /// Custom presentation implementations can retain it to support shared scales.
354    fn scale_mapping(&self) -> Option<crate::scales::PriceMap> {
355        None
356    }
357
358    /// Updates the chart's shared-scale mapping. `None` restores the ordinary pane mapping.
359    fn set_scale_mapping(&mut self, mapping: Option<crate::scales::PriceMap>) {
360        let _ = mapping;
361    }
362
363    /// Replaces value formatting when supported by the presentation. `None` restores the
364    /// declared units and precision. Returns false when this study owns its formatting.
365    fn set_value_formatter(
366        &mut self,
367        formatter: Option<std::sync::Arc<dyn crate::scales::PriceFormatter>>,
368    ) -> bool {
369        let _ = formatter;
370        false
371    }
372
373    /// Format a value using the declared units and current precision override.
374    fn format_value(&self, value: f64) -> String {
375        self.value_format().format(value, self.precision())
376    }
377
378    /// Formats one output, including readouts whose units differ from the study's scale.
379    /// The default uses [`format_value`](Self::format_value).
380    fn format_plot_value(&self, line: usize, value: f64) -> String {
381        let _ = line;
382        self.format_value(value)
383    }
384
385    /// Decimal places for study readouts, or the format's default precision.
386    fn precision(&self) -> Option<usize> {
387        match self.value_format() {
388            crate::scales::StudyFormat::Fixed(decimals) => Some(decimals.min(12)),
389            _ => None,
390        }
391    }
392
393    /// Additional histories this study needs. The chart resolves defaults, requests history
394    /// and live updates from its feed, and supplies them before recalculating the study.
395    fn data_requests(&self) -> Vec<super::StudyDataRequest> {
396        Vec::new()
397    }
398
399    /// The oldest time this study reads from `request`, one of its
400    /// [`data_requests`](Indicator::data_requests). A connected feed stops loading older
401    /// history of that series once it holds a bar at or before the earliest such time of the
402    /// studies reading it, and loads more when that time moves earlier. `None`, the default,
403    /// and a time before the chart's first bar load the series back to that bar. Price-based
404    /// chart types such as Renko do not use the times and always load it back to that bar.
405    fn data_start(
406        &self,
407        request: &super::StudyDataRequest,
408    ) -> Option<chrono::DateTime<chrono::Utc>> {
409        let _ = request;
410        None
411    }
412
413    /// Update with requested histories. Studies using only chart bars keep the default.
414    /// Pass 0 when a requested history changes; nonzero `from` retains the same requested
415    /// histories and follows [`update`](Indicator::update)'s unchanged-prefix contract.
416    fn update_data(&mut self, data: &[Bar], from: usize, sources: &[super::StudyData<'_>]) {
417        let _ = sources;
418        self.update(data, from);
419    }
420
421    /// The single price series this study reads, or `None` for OHLC or multi-source studies.
422    /// Configured studies expose this as a Source input. Study references use chart ids and
423    /// undisplaced output columns; missing or cyclic references produce missing values.
424    fn source(&self) -> Option<super::StudySource> {
425        None
426    }
427
428    /// Update using an aligned source column. Missing source values remain gaps.
429    /// The default substitutes the source for OHLC and retains the bar's other fields.
430    fn update_source(&mut self, data: &[Bar], from: usize, source: &[f64]) {
431        let bars: Vec<_> = data
432            .iter()
433            .enumerate()
434            .map(|(at, bar)| {
435                let value = source.get(at).copied().unwrap_or(f64::NAN);
436                Bar {
437                    open: value,
438                    high: value,
439                    low: value,
440                    close: value,
441                    ..bar.clone()
442                }
443            })
444            .collect();
445        self.update(&bars, from);
446    }
447
448    /// Current editable inputs. A host study can supply the same schema as built-ins.
449    fn inputs(&self) -> Vec<super::StudyInput> {
450        Vec::new()
451    }
452
453    /// Change a calculation input, rejecting unknown ids with [`super::StudyError::UnknownInput`]
454    /// and wrong types and invalid values with [`super::StudyError::InvalidInput`].
455    /// On success cached values are cleared; calculate again or let the chart refresh.
456    fn set_input(
457        &mut self,
458        id: &str,
459        value: super::InputValue,
460    ) -> Result<(), crate::studies::StudyError> {
461        let _ = value;
462        Err(super::StudyError::UnknownInput(id.into()))
463    }
464
465    /// Return the short display name of this indicator (e.g. `"SMA"`, `"RSI"`, `"MACD"`).
466    fn name(&self) -> &str;
467
468    /// Return a human-readable description of what this indicator measures.
469    ///
470    /// Defaults to an empty string. Override to provide a tooltip-friendly
471    /// summary such as `"Relative Strength Index - Momentum oscillator (0-100)"`.
472    fn desc(&self) -> &str {
473        ""
474    }
475
476    /// Compute the indicator's output from the given bar data.
477    ///
478    /// After this call, [`values`](Indicator::values) returns one
479    /// [`IndicatorValue`] per input bar. Implementations should clear
480    /// any previous state so the indicator can be recalculated on new data.
481    fn calculate(&mut self, data: &[Bar]);
482
483    /// Order-flow sources this study reads: [`OrderFlowInput`](crate::data::OrderFlowInput)s of
484    /// fixed aggregations, footprints or books. A chart with a feed connects them before its
485    /// updates are coalesced, gives them its instrument's tick size, applies the trade store's
486    /// retention and the replay cutoff, and updates the study from the first bar that changed.
487    /// Hosts without a feed push events through the handles and recalculate. Several studies may
488    /// return the same source; it receives each event once.
489    fn order_flow_inputs(&self) -> &[crate::data::OrderFlowInput] {
490        &[]
491    }
492
493    /// Whether the study has results only with its
494    /// [`order_flow_inputs`](Self::order_flow_inputs), so that it cannot be calculated on the bars
495    /// of another interval or symbol, whose order flow the chart does not hold. The default is
496    /// whether it names any. A study that builds its results from the bars and refines them with
497    /// order flow where the chart holds it returns `false`, and keeps its Interval input.
498    fn needs_order_flow(&self) -> bool {
499        !self.order_flow_inputs().is_empty()
500    }
501
502    /// Bring [`values`](Indicator::values) up to date after `data[from..]` changed or grew,
503    /// given that `data[..from]` is unchanged since the previous call. The values equal those of
504    /// [`calculate`](Indicator::calculate) on the same data, bit for bit. After bars are
505    /// prepended, or after a setting of the study changed, pass 0; after bars are removed from
506    /// the end pass the new length.
507    ///
508    /// The default recomputes everything.
509    fn update(&mut self, data: &[Bar], from: usize) {
510        let _ = from;
511        self.calculate(data);
512    }
513
514    /// Whether live updates avoid recomputing unaffected history. Causal studies do work
515    /// independent of the series length; studies that redraw earlier legs or periods also
516    /// revisit the bars whose output changes. Earlier corrections may recompute in full.
517    /// Defaults to `false`.
518    fn is_incremental(&self) -> bool {
519        false
520    }
521
522    /// Apply the chart instrument's time zone and trading session. Returns `true`
523    /// if calculation settings changed and the caller must recalculate from bar 0.
524    /// The chart calls this on add and before calculation after its symbol or metadata changes.
525    /// `None` clears chart metadata and restores the study's own settings.
526    /// Studies independent of instrument metadata keep the default.
527    fn set_instrument(&mut self, instrument: Option<&crate::model::Instrument>) -> bool {
528        let _ = instrument;
529        false
530    }
531
532    /// The typed events the chart holds for its symbol, those given to
533    /// `Chart::set_time_scale_events`, in the order supplied. Returns `true` if calculation
534    /// settings changed and the caller must recalculate from bar 0. The chart calls this on add
535    /// and before calculation. Studies that read no events keep the default.
536    fn set_time_scale_events(&mut self, events: &[crate::model::TimeScaleEvent]) -> bool {
537        let _ = events;
538        false
539    }
540
541    /// Return the computed output columns, with NaN for missing values.
542    ///
543    /// The length of the returned output matches the length of the `data`
544    /// slice most recently passed to [`calculate`](Indicator::calculate).
545    fn values(&self) -> &IndicatorValues;
546
547    /// Rendering style of an overlay line. Defaults to a 1 px line in its configured colour.
548    /// Any line is accepted, as for [`plot_style`](Self::plot_style).
549    fn line_style(&self, line: usize) -> IndicatorLineStyle {
550        let _ = line;
551        IndicatorLineStyle::default()
552    }
553
554    /// Candle bodies and wicks, rendered in place of line plots when nonempty.
555    /// Source ranges and OHLCV remain available to hosts for per-bar readouts.
556    fn candles(&self) -> &[super::StudyCandle] {
557        &[]
558    }
559
560    /// Drawing geometry generated by this study, styled as plot 0, empty by default.
561    /// Calculations belong in `calculate`; use tools whose renderer needs only their anchors
562    /// and settings, such as trend lines, parallel channels, Fibonacci levels and pitchforks.
563    #[cfg(feature = "drawings")]
564    fn drawings(&self) -> &[crate::drawings::Drawing] {
565        &[]
566    }
567
568    /// Market Profile settings of a study whose session profiles the chart builds with
569    /// [`to_tpo_profiles`](crate::model::to_tpo_profiles) and draws on the price pane, beside
570    /// each session's bars, as it does for the TPO chart type. `None` by default.
571    fn tpo(&self) -> Option<&crate::model::TPOConfig> {
572        None
573    }
574
575    /// Price bands painted beneath this study's plots, stored per zone rather than per bar.
576    /// Empty by default.
577    fn zones(&self) -> &[super::StudyZone] {
578        &[]
579    }
580
581    /// Whether each of [`zones`](Self::zones) spans its bars from the left edge of the first to
582    /// the right edge of the last, as study candles do, rather than from the centre of the first
583    /// to the centre of the last. Off by default.
584    fn zones_cover_whole_bars(&self) -> bool {
585        false
586    }
587
588    /// Text beside study values, in order of `bar`. The chart draws the labels near the visible
589    /// bars, in the colour of their plot, inside the plot area, so they never run under the
590    /// price axis. Empty by default. Keep the text from the last calculation: it is drawn every
591    /// frame.
592    fn labels(&self) -> &[super::StudyLabel] {
593        &[]
594    }
595
596    /// Draw [`labels`](Self::labels) on the main price chart, even when the study has its
597    /// own pane. Label prices use the main series' scale. Off by default.
598    fn labels_on_price(&self) -> bool {
599        false
600    }
601
602    /// The decimal places the study's presentation settings set for the prices in
603    /// [`labels`](Self::labels), `None` when they set none and the instrument's decimals apply.
604    /// The text is written when the study calculates, so calculate again afterwards, as a chart
605    /// does after any settings edit. Studies without price labels keep the default.
606    fn set_label_precision(&mut self, precision: Option<usize>) {
607        let _ = precision;
608    }
609
610    /// Price-row volume profiles intersecting a half-open visible source-bar range.
611    /// Fixed and calendar profiles include their whole selected span, even offscreen bars.
612    /// Visible-range studies recompute for this range; ordinary studies return no profiles.
613    fn volume_profiles(&self, visible: std::ops::Range<usize>) -> Vec<crate::data::StudyProfile> {
614        let _ = visible;
615        Vec::new()
616    }
617
618    /// Shapes attached to study values, empty by default.
619    fn markers(&self) -> &[IndicatorMarker] {
620        &[]
621    }
622
623    /// Extend the last two values of each overlay line to the plot's right edge.
624    fn extend_right(&self) -> bool {
625        false
626    }
627
628    /// An annotation beside the latest value of a zero-based overlay line.
629    fn plot_label(&self) -> Option<(usize, String)> {
630        None
631    }
632
633    /// Return the plot colours and any extra palette entries used by fills or per-bar colouring.
634    ///
635    /// Extra palette entries are saved with the study and do not add output columns.
636    /// Their layout is specific to the study.
637    fn colors(&self) -> Vec<Color32>;
638
639    /// Replace all line colors at once.
640    ///
641    /// For single-line indicators, pass a `Vec` with one color.
642    /// For multi-line indicators, pass one color per line.
643    fn set_colors(&mut self, colors: Vec<Color32>);
644
645    /// Set the color of a single line by its zero-based index.
646    ///
647    /// This is a convenience wrapper around [`colors`](Indicator::colors)
648    /// and [`set_colors`](Indicator::set_colors). If `line_idx` is out of
649    /// range the call is silently ignored.
650    fn set_color(&mut self, line_idx: usize, color: Color32) {
651        let mut colors = self.colors();
652        if line_idx < colors.len() {
653            colors[line_idx] = color;
654            self.set_colors(colors);
655        }
656    }
657
658    /// Whether this indicator is drawn on the main price chart (overlay)
659    /// or in a separate sub-pane.
660    ///
661    /// Returns `true` for overlays (e.g. SMA, Bollinger Bands, Ichimoku),
662    /// `false` for oscillators and volume indicators that need their own
663    /// Y-axis (e.g. RSI, MACD, OBV). Defaults to `true`.
664    fn is_overlay(&self) -> bool {
665        true
666    }
667
668    /// Default separate-pane layout and reference levels, independent of the study name.
669    /// [`ConfiguredStudy`](super::ConfiguredStudy) returns its levels where its settings place
670    /// them, in their colours, and a hidden level in `Color32::TRANSPARENT`, which still bounds
671    /// the fill.
672    fn pane(&self) -> StudyPane {
673        StudyPane::default()
674    }
675
676    /// Changes placement between the main chart and a separate pane.
677    /// Returns whether this study supports the change. The chart's presentation wrapper does.
678    fn set_overlay(&mut self, _overlay: bool) -> bool {
679        false
680    }
681
682    /// Changes the [`scale`](Self::scale) used in the study's pane.
683    /// Returns whether this study supports the change. The chart's presentation wrapper does.
684    fn set_scale(&mut self, _scale: super::StudyScale) -> bool {
685        false
686    }
687
688    /// Volume plots use an independent zero-based scale: up columns, down columns and MA.
689    /// Renderers size columns to 80% of the bar pitch and format values as volume.
690    fn is_volume(&self) -> bool {
691        false
692    }
693
694    /// The number of output columns this indicator plots. Renderers draw lines
695    /// `0..line_count()`.
696    ///
697    /// For example, SMA returns `1`, MACD returns `3` (MACD line, signal,
698    /// histogram), and Ichimoku returns `5`. Defaults to the number of
699    /// [`line_names`](Indicator::line_names).
700    fn line_count(&self) -> usize {
701        self.line_names().len()
702    }
703
704    /// Return display names for each line, used in chart legends.
705    ///
706    /// The length of the returned `Vec` must match [`line_count`](Indicator::line_count).
707    /// Defaults to a single entry containing [`name`](Indicator::name).
708    fn line_names(&self) -> Vec<String> {
709        vec![self.name().to_string()]
710    }
711
712    /// Whether this indicator should currently be rendered.
713    ///
714    /// Defaults to `true`. Toggle with [`set_visible`](Indicator::set_visible).
715    fn is_visible(&self) -> bool {
716        true
717    }
718
719    /// Show or hide this indicator.
720    fn set_visible(&mut self, visible: bool);
721
722    /// Clone this indicator into a new `Box<dyn Indicator>`.
723    ///
724    /// This enables cloning of heterogeneous indicator collections.
725    fn clone_box(&self) -> Box<dyn Indicator>;
726}
727
728/// Blanket `Clone` implementation for boxed indicators, delegating to
729/// [`Indicator::clone_box`].
730impl Clone for Box<dyn Indicator> {
731    fn clone(&self) -> Self {
732        self.clone_box()
733    }
734}