Custom Canvas

CanvasLayer: free-form 2D overlays from Rust indicators and strategies. Mirrors the Python custom_layer API; same wire format.

CanvasLayer

CanvasLayerCanvasLayer::new(key)

Free-form 2D canvas overlay built on the chart side.

Mirrors the Python custom_layer(...) builder. Build a sequence of style + path + render ops, then attach to an IndicatorOutput or SignalOutput via .with_custom_layer(layer.into_spec()).

Two coordinate spaces:

  • Chart: (ts_ms, price) resolved through timeToX / priceToY at draw time so pan/zoom respects them.
  • Pixel: raw CSS pixels for HUDs glued to the viewport.

Viewport sentinels (CoordRef::YMin, YMax, XMin, XMax) span the visible price/time range without knowing the scale.

Use for visuals no predefined plot type covers: candle-style overlays, custom histograms with arbitrary geometry, text annotations, vertical TF boundaries, R-multiple labels above trades.

Parameters

  • key (&str, default required): Unique key. Same key in two indicators collides.

Returns

CanvasLayer (chainable)

Example

use qc_strategy_api::prelude::*;

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

for ms in boundaries {
    layer = layer
        .begin()
        .move_to_chart_sentinel(ms, CoordRef::YMin)
        .line_to_chart_sentinel(ms, CoordRef::YMax)
        .stroke();
}

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

Notes

  • Hard cap of 50,000 ops per spec at the renderer.
  • Unknown ops are silently ignored at render time.

CanvasLayer.style

CanvasLayer.style.style(StyleDelta)

Set sticky drawing style. Folds into defaults if called before any path op.

Style is sticky: every following stroke/fill/text uses the most recent values. Calling .style(...) before the first op folds into defaults; after that, each call emits a setStyle op.

Example

layer = layer.style(StyleDelta::default()
    .fill_style("#08080bcc")
    .stroke_style("#1f1f26")
    .line_width(1.0)
    .global_alpha(0.9)
    .font("12px Inter")
    .line_dash(vec![4.0, 4.0]));

CanvasLayer path/render ops

begin / close / stroke / fill / move_to_* / line_to_* / rect_chart / arc_chart / text_chart / *_pixel builder methods.

Two flavors per coordinate space: *_chart(ts_ms, price) and *_pixel(x_px, y_px). Use *_chart_sentinel(v, CoordRef::YMin) to anchor an axis at the viewport edge.

Width/height/radius args are always CSS pixels even in chart space - keeps HUD-like badges the same size at any zoom.

Example

// triangle marker at every entry tick
for &i in &entry_indices {
    let p = data.mid[i];
    layer = layer
        .style(StyleDelta::default().fill_style("#22c55e88").stroke_style("#22c55e"))
        .begin()
        .move_to_chart(data.timestamp[i],         p - 0.5)
        .line_to_chart(data.timestamp[i] + 60_000, p)
        .line_to_chart(data.timestamp[i],         p + 0.5)
        .close()
        .fill()
        .stroke();
}

CoordRef sentinels

YMin / YMax / XMin / XMax resolve to the current viewport edges.

Drop a sentinel into the y or x slot when you want the layer to span the visible range without knowing the price/time scale.

Rules:

  • YMin/YMax -> y component of a chart-space point.
  • XMin/XMax -> x component.
  • Mixing axes (e.g. YMin in the x slot) is rejected at the renderer with a one-time console warning.

Example

// vertical line that always spans the visible price range
layer = layer
    .begin()
    .move_to_chart_sentinel(ts_ms, CoordRef::YMin)
    .line_to_chart_sentinel(ts_ms, CoordRef::YMax)
    .stroke();

CanvasLayer.into_spec

CanvasLayer.into_spec.into_spec() -> CustomDrawSpec

Finalize the layer for attachment to IndicatorOutput / SignalOutput.

Consumes the builder. Pass the result to .with_custom_layer(spec). Building two specs from one layer requires re-cloning before each into_spec().

ZOrder

Bottom / Normal / Top controls draw order vs. price series.

Bottom draws under candles (regime backgrounds). Normal draws between candles and overlays. Top draws above everything (HUDs, TF boundaries, annotations).

Default is Normal. Set via .z(ZOrder::Top) on the builder.