# Indicator API

#[indicator], IndicatorOutput builders, all 7 PlotTypes, per-point color builders, ShapeSpec, fills, hlines.

<a id="indicator"></a>
## #[indicator]

```rust
#[indicator](name, description?, overlay?, data_mode, timeframe?, cross_view?, version?, columns?)
```

Attribute macro. Marks a struct as a Rust indicator.

The struct must `#[derive(Default)]` and implement the `Indicator` trait. `overlay = true` draws on the price chart; `overlay = false` puts the indicator in its own pane.

**`data_mode`** is required. `"tick"` feeds the indicator raw TBBO ticks; `"ohlc"` feeds OHLC bars at the declared `timeframe` (the source can be either a native OHLC parquet OR a TBBO file aggregated at runtime). Authors must choose explicitly - there is no default.

**`timeframe`** is required when `data_mode = "ohlc"`. Format `"1m"`, `"5m"`, `"15m"`, etc.

**`cross_view`** (default `false`) opts a tick-mode indicator into rendering on OHLC chart views (canonical case: Volume Profile - tick-fidelity compute, render on any view). Symmetric for the rare ohlc + cross_view case (render bar values on a tick chart). Without it a tick-mode indicator is hidden in OHLC view and an ohlc-mode indicator is hidden in tick view.

**Decision tree:**

- Per-bar overlay computed from bars (EMA, BB, regime line) -> `data_mode = "ohlc", timeframe = "1m"` (or your TF)
- Tick-fidelity indicator that should render on candlestick charts (VP, VWAP) -> `data_mode = "tick", cross_view = true`
- Per-tick output that only makes sense in tick view (raw spread, raw imbalance) -> `data_mode = "tick", cross_view = false`

### Parameters

- `name` (`str`, default `required`): Display name in the indicator panel
- `description` (`str`, default `""`): Optional tooltip description
- `overlay` (`bool`, default `false`): Draw on price chart vs separate pane
- `data_mode` (`str`, default `required`): "tick" (raw TBBO) or "ohlc" (bars at `timeframe`). Required.
- `timeframe` (`str`, default `required when ohlc`): "1m" / "5m" / etc. Required when data_mode = "ohlc".
- `cross_view` (`bool`, default `false`): Render across both tick and OHLC chart views (Volume Profile pattern).
- `version` (`str`, default `"1.0.0"`): Author-managed semver-ish version surfaced to the UI for cache invalidation. Bump on API-breaking edits.
- `columns` (`[&str]`, default `[]`): Extra column names this indicator reads (UI hint)

### Example

```rust
// Tick-fidelity Volume Profile that also renders on OHLC charts
#[indicator(
    name = "VP",
    description = "Per-tick volume distribution",
    overlay = true,
    data_mode = "tick",
    cross_view = true,
)]
#[derive(Default)]
pub struct Vp { /* params */ }

// OHLC overlay; renders only on OHLC chart views
#[indicator(
    name = "EMA",
    description = "Bar-close EMA",
    overlay = true,
    data_mode = "ohlc",
    timeframe = "1m",
)]
#[derive(Default)]
pub struct Ema { /* params */ }
```

<a id="indicator-trait"></a>
## Indicator trait

```rust
Indicator traitimpl Indicator for MyIndicator
```

One required method: calculate(). Optional prepare() for hoisted per-day work.

### Example

```rust
impl Indicator for MyIndicator {
    fn prepare(data: &TickData) -> DayPrep {
        let mut prep = DayPrep::empty();
        prep.insert_f64("baseline", rolling_mean(&data.spread, 200));
        prep
    }

    fn calculate(&self, data: &TickData, prep: &DayPrep) -> IndicatorOutput {
        let baseline = prep.f64("baseline").unwrap_or(&[]);
        let smoothed = rolling_mean(&data.spread, self.smoothing);
        IndicatorOutput::new()
            .plot_line("Spread", smoothed, "#7aa2f7")
            .plot_line("Baseline", baseline.to_vec(), "#a1a1aa")
    }
}
```

<a id="indicatoroutput"></a>
## IndicatorOutput

Builder for plots, fills, hlines, shapes, regions, and tags.

Default-constructed via `IndicatorOutput::new()`. Builder methods chain to add visual elements. Final value is returned from `calculate()`.

### Example

```rust
IndicatorOutput::new()
    .with_overlay(false)
    .plot_line("MA", ma_values, "#7aa2f7")
    .plot_histogram("Volume", vols, "#3a3a45")
    .hline("Zero", 0.0, "#63636e")
    .fill("Upper", "Lower", "#7aa2f7", 15)
    .with_tag("breakout", breakout_mask)
    .with_tag_config("breakout", "Breakout Bar", "#26A69A")
```

<a id="plottype-rust"></a>
## PlotType (Rust)

Seven plot types: Line, Histogram, Area, Columns, Cross, Circles, StepLine.

### Parameters

- `PlotType::Line` (`enum`): Continuous line
- `PlotType::Histogram` (`enum`): Vertical bars from zero. Per-bar `point_colors` supported.
- `PlotType::Area` (`enum`): Filled area under the line
- `PlotType::Columns` (`enum`): Vertical columns. Per-bar `point_colors` supported.
- `PlotType::Cross` (`enum`): Per-point cross marker. Per-point `point_colors` supported.
- `PlotType::Circles` (`enum`): Per-point circle marker. Per-point `point_colors` supported.
- `PlotType::StepLine` (`enum`): Step / staircase line

### Notes

- Per-point colors are valid only for Histogram, Columns, Cross, Circles. Lines/Areas/StepLines stay single-color.

<a id="plot_line-plot_histogram"></a>
## plot_line / plot_histogram

```rust
plot_line / plot_histogram.plot_line(name, data, color) / .plot_histogram(name, data, color)
```

Convenience builder methods for the two most common plot types.

### Example

```rust
IndicatorOutput::new()
    .plot_line("EMA(20)", ta::ema(&data.mid, 20), "#7aa2f7")
    .plot_histogram("Delta", data.col_or("delta", &[]).to_vec(), "#73daca")
```

<a id="plot_histogram_colored-rust"></a>
## plot_histogram_colored (Rust)

```rust
plot_histogram_colored (Rust).plot_histogram_colored(name, values, base_color, colors)
```

Histogram with per-bar color overrides.

Per-bar coloring for Histogram plots. The `colors` Vec must equal `values.len()`. Each entry is `Option<String>`: `Some("#hex")` overrides the bar, `None` falls back to `base_color`.

Validation is loud: `assert_eq!(colors.len(), values.len())` panics during indicator dev if you mismatch lengths.

### Parameters

- `name` (`&str`, default `required`): Series name (legend label)
- `values` (`Vec<f64>`, default `required`): Histogram values, one per tick
- `base_color` (`&str`, default `required`): Hex fallback when colors[i] is None
- `colors` (`Vec<Option<String>>`, default `required`): Per-bar color overrides

### Example

```rust
// "block trade = yellow" pattern
let mut colors: Vec<Option<String>> = vec![None; data.len()];
for i in 0..data.len() {
    if let Some(v) = data.col("volume").and_then(|c| c.get(i)) {
        if *v > big_threshold {
            colors[i] = Some("#e0af68".to_string()); // yellow
        }
    }
}

IndicatorOutput::new()
    .plot_histogram_colored("Volume", volumes, "#3a3a45", colors)
```

<a id="plot_columns_colored-plot_cross_colored-plot_circles_colored"></a>
## plot_columns_colored / plot_cross_colored / plot_circles_colored

Same per-point coloring shape as plot_histogram_colored, applied to Columns, Cross, Circles.

Same signature as [`plot_histogram_colored`](https://quantchartsllc.com/docs/python/py-plotting.md#plot_histogram_colored). Use Columns for vertical column bars, Cross/Circles for per-point markers (e.g. divergence flags, signal markers, microstructure events).

### Example

```rust
// Cross markers, red on losing trade entries, green on winning
let mut marker_colors: Vec<Option<String>> = vec![None; n];
for i in 0..n {
    if entry_signal[i] {
        marker_colors[i] = Some(if was_winner[i] { "#22c55e" } else { "#ef4444" }.to_string());
    }
}

IndicatorOutput::new()
    .plot_cross_colored("Entries", entry_prices, "#a1a1aa", marker_colors)
```

<a id="plotspec-general-plot"></a>
## PlotSpec (general plot)

Construct a PlotSpec directly when you need full control (line width, opacity, visibility).

For anything beyond the convenience builders, build a `PlotSpec` and pass it to `.plot(spec)`.

### Example

```rust
let spec = PlotSpec {
    name: "Smoothed Delta".to_string(),
    plot_type: PlotType::Area,
    color: "#73daca".to_string(),
    line_width: 2,
    opacity: 30,
    visible: true,
    data: smoothed,
};
IndicatorOutput::new().plot(spec)
```

<a id="hline-fill"></a>
## hline / fill

```rust
hline / fill.hline(name, value, color) / .fill(plot_a, plot_b, color, opacity)
```

Horizontal reference line; fill between two named plots.

### Example

```rust
IndicatorOutput::new()
    .plot_line("Upper", upper, "#7aa2f7")
    .plot_line("Lower", lower, "#7aa2f7")
    .fill("Upper", "Lower", "#7aa2f7", 15)
    .hline("Zero", 0.0, "#63636e")
```

<a id="shapespec"></a>
## ShapeSpec

Per-tick shape markers (arrow_up, circle, cross, etc.) at specific indices.

For sparse markers (a few hundred points across millions of ticks), use `ShapeSpec` instead of `plot_*_colored`. Push the specific tick indices and a single shape/color/size.

### Example

```rust
output.shapes.push(ShapeSpec {
    indices: signal_indices,
    shape: "arrow_up".to_string(),
    location: "below".to_string(),
    color: "#22c55e".to_string(),
    size: "normal".to_string(),
});
```

<a id="with_align-with_width_px"></a>
## with_align / with_width_px

```rust
with_align / with_width_px.with_align(HistogramAlign) / .with_width_px(u32)
```

Anchor a histogram/columns/area plot to a chart edge or visible-range edge.

Reshapes geometry instead of laying bars along the time axis. Pair with `with_width_px(...)` for the strip width (`PinnedLeft`/`PinnedRight`) or thickness (`PinnedTop`/`PinnedBottom`). Apply via `PlotSpec` or chain after `plot_histogram(...)`.

Use cases: side-anchored cumulative delta (`PinnedLeft`/`PinnedRight`), volume-pane layout (`PinnedBottom` - bars time-align to candles, height normalized to visible-range max), session-anchored volume profile (`RightOfRange`), full-range overlay (`OverRange`).

### Parameters

- `HistogramAlign::PinnedTop` (`enum`): Horizontal strip glued to the top edge; bars hang downward, time-aligned to candles.
- `HistogramAlign::PinnedBottom` (`enum`): Horizontal strip glued to the bottom edge (volume-pane layout); bars rise upward, time-aligned to candles.
- `HistogramAlign::PinnedLeft` (`enum`): Vertical strip glued to the chart left edge; bars extend right, anchored to price axis.
- `HistogramAlign::PinnedRight` (`enum`): Vertical strip glued to the chart right edge; bars extend left.
- `HistogramAlign::LeftOfRange` (`enum`): Anchor at the first visible bar of the series time range.
- `HistogramAlign::RightOfRange` (`enum`): Anchor at the last visible bar.
- `HistogramAlign::OverRange` (`enum`): Stretch across the visible range.

### Example

```rust
IndicatorOutput::new()
    .plot_histogram("Cum Delta", cum_delta, "#7aa2f7")
    .with_align(HistogramAlign::PinnedLeft)
    .with_width_px(60)
```

### Notes

- `with_align` only affects the LAST plot pushed to the output - chain it directly after the plot builder.
- Per-bar `point_colors` still apply to pinned histograms; bars stack vertically by row index.

<a id="with_border_color"></a>
## with_border_color

```rust
with_border_color.with_border_color(Vec<Option<String>>)
```

Per-bar candle border color (independent from `with_bar_color`).

Setting just the border (without changing the body) gives the TradingView "hollow candle" look. `Some("#hex")` overrides; `None` falls back to the body color.

Mirrors Python [`border_color()`](https://quantchartsllc.com/docs/python/py-styling.md#border_color). Accepts 6-char `#rrggbb` or 8-char `#rrggbbaa` hex.

### Parameters

- `colors` (`Vec<Option<String>>`, default `required`): Same length as bars. None entries inherit the body color.

### Example

```rust
let borders: Vec<Option<String>> = (0..n).map(|i| {
    if hit_tp[i] { Some("#22c55e".into()) }
    else if hit_sl[i] { Some("#ef4444".into()) }
    else { None }
}).collect();

IndicatorOutput::new()
    .plot_line("EMA", ema, "#7aa2f7")
    .with_border_color(borders)
```

<a id="with_custom_layer"></a>
## with_custom_layer

```rust
with_custom_layer.with_custom_layer(CustomDrawSpec)
```

Attach a [`CanvasLayer`](https://quantchartsllc.com/docs/rust/rust-custom-draw.md#canvaslayer) for arbitrary 2D draws.

Build a layer via `CanvasLayer::new(key)`, chain style and path ops, finalize with `.into_spec()`, then attach. Multiple layers can be attached; each gets its own zOrder bucket.

See the Rust Custom Canvas section for full builder API.

### Parameters

- `spec` (`CustomDrawSpec`, default `required`): Built via `CanvasLayer::new(...).into_spec()`.

### Example

```rust
use qc_strategy_api::prelude::*;

let layer = CanvasLayer::new("tf-boundaries")
    .z(ZOrder::Top)
    .style(StyleDelta::default()
        .stroke("#ffffff44")
        .line_width(1.0)
        .line_dash(vec![4.0, 3.0]));

let mut layer = layer;
for ms in boundaries {
    layer = layer
        .begin()
        .move_to_chart(ms, f64::NAN /* Y_MIN sentinel */)
        .line_to_chart(ms, f64::NAN /* Y_MAX sentinel */)
        .stroke();
}

IndicatorOutput::new().with_custom_layer(layer.into_spec())
```

### Notes

- Hard cap of 50,000 ops per spec at the renderer.
- Use coord sentinels (`CoordRef::YMin`/`YMax`/`XMin`/`XMax`) to span the viewport without knowing the price/time scale.

<a id="with_tag-with_tag_config"></a>
## with_tag / with_tag_config

```rust
with_tag / with_tag_config.with_tag(name, values) / .with_tag_config(name, label, color)
```

Attach boolean tag arrays to the indicator output for preventive trade filtering.

Tags from indicators feed the same preventive-tag-filter pipeline as strategy tags. `with_tag_config` is optional: undeclared tags get auto-generated UI labels and colors. Declared tags via `#[tag(...)]` give you control.

### Example

```rust
let breakout = above_series(&data.mid, &upper_band);
let breakdown = below_series(&data.mid, &lower_band);

IndicatorOutput::new()
    .plot_line("Upper", upper_band, "#7aa2f7")
    .plot_line("Lower", lower_band, "#7aa2f7")
    .with_tag("breakout", breakout)
    .with_tag("breakdown", breakdown)
    .with_tag_config("breakout",  "Upper Break", "#26A69A")
    .with_tag_config("breakdown", "Lower Break", "#EF5350")
```
