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}