# Markers

Markers are small annotations (circles, squares, or arrows) attached to a series at specific points in time. Use them to flag individual events (a fill, a signal, a regime change) directly on the chart without disrupting the underlying price curve.

There are three ways to put markers on a chart. Use [`tvl.marker()`](#api-reference) for static, code-defined markers; [`tvl.markers_from_table()`](#api-reference) when each row of a Deephaven table should become one marker (and tick live as the table updates); and [`tvl.up_down_markers()`](#api-reference) as a convenience for the common “buys are up arrows, sells are down arrows” pattern.

<!-- coverage-seen-elsewhere:
  size / size_column -> exercised below
-->

## What are markers useful for?

- **Annotating fills**: Mark every executed order on the price line so traders can see where positions changed hands.
- **Highlighting signals**: A strategy that generates buy/sell signals can render them as up/down arrows directly on the candlestick.
- **Flagging events**: News releases, earnings announcements, or system events can be pinned to their timestamps as labeled circles.
- **Picking out levels**: Price-based positions (`at_price_top`, `at_price_middle`, `at_price_bottom`) let a marker float independent of the bar: useful for marking option strikes or support/resistance touches.

## Examples

### Add a single static marker

`tvl.marker()` builds one `Marker` object. Pass a list of them via the `markers=` keyword on any per-type constructor.

```python order=single_marker_chart,ohlc
import deephaven.plot.tradingview_lightweight as tvl

ohlc = tvl.data.ohlc()

m = tvl.marker(
    time="2024-01-15",
    position="above_bar",
    shape="arrow_down",
    color="#d32f2f",
    text="High",
    size=2,
    id="m1",
)

single_marker_chart = tvl.candlestick(ohlc, markers=[m])
```

The single marker hovers above the bar for 2024-01-15 with a downward red arrow.

### Show every marker shape

`MarkerShape` has four values: `"circle"`, `"square"`, `"arrow_up"`, `"arrow_down"`. The example below puts one of each on a single chart at staggered times.

```python order=shapes_chart,ohlc
import deephaven.plot.tradingview_lightweight as tvl

ohlc = tvl.data.ohlc()

shapes = [
    tvl.marker(time="2024-01-05", shape="circle",     color="#1976d2", text="circle"),
    tvl.marker(time="2024-01-15", shape="square",     color="#2e7d32", text="square"),
    tvl.marker(time="2024-01-25", shape="arrow_up",   color="#ef6c00", text="up"),
    tvl.marker(time="2024-02-05", shape="arrow_down", color="#c62828", text="down"),
]

shapes_chart = tvl.line(ohlc, timestamp="Timestamp", value="Close", markers=shapes)
```

The four markers march across the chart, one shape per timestamp.

### Show every marker position

`MarkerPosition` has six values. The three bar-relative positions (`"above_bar"`, `"below_bar"`, `"in_bar"`) anchor to the bar at the given time. The three price-relative positions (`"at_price_top"`, `"at_price_bottom"`, `"at_price_middle"`) float at a specific `price` regardless of the bar.

```python order=positions_chart,ohlc
import deephaven.plot.tradingview_lightweight as tvl

ohlc = tvl.data.ohlc()

positions = [
    tvl.marker(time="2024-01-05", position="above_bar",
               shape="arrow_down", text="above_bar"),
    tvl.marker(time="2024-01-10", position="below_bar",
               shape="arrow_up", text="below_bar"),
    tvl.marker(time="2024-01-15", position="in_bar",
               shape="circle", text="in_bar"),
    # Price-based positions need a `price`.
    tvl.marker(time="2024-01-20", position="at_price_top",
               shape="square", price=110.0, text="at_price_top"),
    tvl.marker(time="2024-01-25", position="at_price_middle",
               shape="square", price=100.0, text="at_price_middle"),
    tvl.marker(time="2024-01-30", position="at_price_bottom",
               shape="square", price=90.0, text="at_price_bottom"),
]

positions_chart = tvl.candlestick(ohlc, markers=positions)
```

All six positions render together. The bar-relative ones move with the candle, the price-relative ones lock to their `price`.

### Drive markers from a Deephaven table

`tvl.markers_from_table()` builds a `MarkerSpec` instead of a list. Each row of the table becomes one marker; columns supply per-row values for any property that has a `*_column` parameter. Because the spec keeps a live reference to the table, markers tick when the table updates.

```python order=table_markers_chart,signals,ohlc
import deephaven.plot.tradingview_lightweight as tvl

ohlc = tvl.data.ohlc()

# Build a signals table off of the OHLC data: tag every 10th row alternately.
signals = ohlc.update_view([
    "Side = (Index % 20 < 10) ? `BUY` : `SELL`",
    "Pos  = Side == `BUY` ? `below_bar` : `above_bar`",
    "Sym  = Side == `BUY` ? `arrow_up` : `arrow_down`",
    "Col  = Side == `BUY` ? `#2e7d32` : `#c62828`",
    "Lbl  = Side",
]).where("Index % 10 == 0")

spec = tvl.markers_from_table(
    signals, timestamp="Timestamp",
    position_column="Pos",
    shape_column="Sym",
    color_column="Col",
    text_column="Lbl",
    size=1,
    id_column="Side",
)

table_markers_chart = tvl.candlestick(ohlc, marker_spec=spec)
```

Pass the spec via the series’ `marker_spec=` argument. As the source table ticks, new markers appear automatically.

### Use fixed defaults with markers_from_table

If every marker shares the same shape and color, omit the per-row column and set the fixed default directly. Mix-and-match is fine. Fixed values fill in for any property without a `*_column`.

```python order=fixed_marker_chart,events,ohlc
import deephaven.plot.tradingview_lightweight as tvl

ohlc = tvl.data.ohlc()
events = ohlc.where("Index % 7 == 0").update_view(["Note = `event ` + Index"])

spec = tvl.markers_from_table(
    events, timestamp="Timestamp",
    position="above_bar",   # fixed for every row
    shape="circle",         # fixed for every row
    color="#1976d2",        # fixed
    text_column="Note",     # per-row
    size=2,                 # fixed
)

fixed_marker_chart = tvl.candlestick(ohlc, marker_spec=spec)
```

The chart shows one blue circle per event, each labeled with its row’s `Note`.

### Price-driven markers from a table

For `at_price_*` positions, supply a `price_column` so each row’s price comes from the data.

```python order=price_marker_chart,levels,ohlc
import deephaven.plot.tradingview_lightweight as tvl

ohlc = tvl.data.ohlc()

levels = ohlc.where("Index % 12 == 0").update_view([
    "Level = Close + 2.0",  # mark 2 above the close
])

spec = tvl.markers_from_table(
    levels, timestamp="Timestamp",
    position="at_price_top",
    shape="square",
    color="#ef6c00",
    price_column="Level",
    size_column=None,
)

price_marker_chart = tvl.candlestick(ohlc, marker_spec=spec)
```

The orange squares float at `Level`, independent of the candle’s `High`/`Low`.

### Use up_down_markers for buy/sell signals

`tvl.up_down_markers()` is the shortcut for the most common pattern: a list of up timestamps and a list of down timestamps, rendered as arrows below/above the bar with theme-derived colors.

```python order=updown_chart,ohlc
import deephaven.plot.tradingview_lightweight as tvl

ohlc = tvl.data.ohlc()

ups = ["2024-01-05", "2024-01-18", "2024-02-03"]
downs = ["2024-01-12", "2024-01-25", "2024-02-10"]

markers = tvl.up_down_markers(
    up_times=ups,
    down_times=downs,
    up_color="#2e7d32",
    down_color="#c62828",
    up_text="BUY",
    down_text="SELL",
    up_size=2,
    down_size=2,
)

updown_chart = tvl.candlestick(ohlc, markers=markers)
```

The helper returns a `list[Marker]` that drops straight into the series’ `markers=` argument. The library sorts markers by time, so the order of `up_times` and `down_times` doesn’t matter. (Two type-only Literal aliases, `MarkerSign` and `MismatchDirection`, are exported for annotations.)

## API Reference

Create a single static marker to place on a series.

**Returns:** `Marker` A Marker instance with snake_case input
values translated to the camelCase JS form expected by the
wire protocol.

**Raises:** ValueError -- If position is a price-anchored variant and
    price is not supplied.

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "marker", "parameters": [{"name": "time", "type": "Any", "description": "Time anchor for the marker.  UTC timestamp, ISO string, or BusinessDay dict."}, {"name": "position", "type": "MarkerPosition", "description": "Anchor position.  See MarkerPosition.  For \"at_price_*\" positions you must also supply price.", "default": "'above_bar'"}, {"name": "shape", "type": "MarkerShape", "description": "Glyph drawn for the marker.  See MarkerShape.", "default": "'circle'"}, {"name": "color", "type": "Optional[Color]", "description": "CSS color for the glyph fill.", "default": "None"}, {"name": "text", "type": "str", "description": "Optional label text drawn near the marker.", "default": "''"}, {"name": "size", "type": "Optional[int]", "description": "Glyph size multiplier (default 1).", "default": "None"}, {"name": "id", "type": "Optional[str]", "description": "Optional string identifier.", "default": "None"}, {"name": "price", "type": "Optional[float]", "description": "Required when position is one of the \"at_price_*\" variants.", "default": "None"}]}} />
timestamp is always a column name.  For each other property, you
may pass a fixed value (e.g. color="#FF0000") that applies to
every marker, or a *_column name (e.g. color_column="Color")
to read the value per-row from the table.  For price-based
positions ("at_price_top" etc.) supply either price (same
price for every row) or price_column (different price per row).

**Returns:** `MarkerSpec` A MarkerSpec ready to attach to a series
via the marker_spec= parameter of any series factory.

**Raises:** ValueError -- If both price and price_column are
    supplied, or if a price-anchored position is requested
    without either.

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "markers_from_table", "parameters": [{"name": "table", "type": "Any", "description": "Deephaven table whose rows drive the markers."}, {"name": "timestamp", "type": "str", "description": "Column name supplying each marker's time value.", "default": "'Timestamp'"}, {"name": "position", "type": "MarkerPosition", "description": "Default marker position; see MarkerPosition.", "default": "'above_bar'"}, {"name": "shape", "type": "MarkerShape", "description": "Default marker shape; see MarkerShape.", "default": "'circle'"}, {"name": "color", "type": "Optional[Color]", "description": "Default marker color.", "default": "None"}, {"name": "text", "type": "str", "description": "Default label text.", "default": "''"}, {"name": "size", "type": "Optional[int]", "description": "Default size multiplier.", "default": "None"}, {"name": "price", "type": "Optional[float]", "description": "Default price for price-anchored positions.", "default": "None"}, {"name": "position_column", "type": "Optional[str]", "description": "Column supplying per-row MarkerPosition values.", "default": "None"}, {"name": "shape_column", "type": "Optional[str]", "description": "Column supplying per-row MarkerShape values.", "default": "None"}, {"name": "color_column", "type": "Optional[str]", "description": "Column supplying per-row CSS color strings.", "default": "None"}, {"name": "text_column", "type": "Optional[str]", "description": "Column supplying per-row text labels.", "default": "None"}, {"name": "size_column", "type": "Optional[str]", "description": "Column supplying per-row size values.", "default": "None"}, {"name": "id_column", "type": "Optional[str]", "description": "Column supplying per-row marker IDs.", "default": "None"}, {"name": "price_column", "type": "Optional[str]", "description": "Column supplying per-row price values for price-anchored positions.", "default": "None"}]}} />
The returned list can be passed directly to the markers= parameter
of any series factory.  The library sorts markers by time, so
Python ordering does not matter.

**Returns:** `list[Marker]` Flat list of Marker objects — up events followed by down events.

<ParamTable param={{"module_name": "deephaven.plot.tradingview_lightweight.", "name": "up_down_markers", "parameters": [{"name": "up_times", "type": "list[Any]", "description": "Time values for bullish / up events."}, {"name": "down_times", "type": "list[Any]", "description": "Time values for bearish / down events."}, {"name": "up_color", "type": "Literal['gray-50', 'gray-75', 'gray-100', 'gray-200', 'gray-300', 'gray-400', 'gray-500', 'gray-600', 'gray-700', 'gray-800', 'gray-900', 'red-100', 'red-200', 'red-300', 'red-400', 'red-500', 'red-600', 'red-700', 'red-800', 'red-900', 'red-1000', 'red-1100', 'red-1200', 'red-1300', 'red-1400', 'orange-100', 'orange-200', 'orange-300', 'orange-400', 'orange-500', 'orange-600', 'orange-700', 'orange-800', 'orange-900', 'orange-1000', 'orange-1100', 'orange-1200', 'orange-1300', 'orange-1400', 'yellow-100', 'yellow-200', 'yellow-300', 'yellow-400', 'yellow-500', 'yellow-600', 'yellow-700', 'yellow-800', 'yellow-900', 'yellow-1000', 'yellow-1100', 'yellow-1200', 'yellow-1300', 'yellow-1400', 'chartreuse-100', 'chartreuse-200', 'chartreuse-300', 'chartreuse-400', 'chartreuse-500', 'chartreuse-600', 'chartreuse-700', 'chartreuse-800', 'chartreuse-900', 'chartreuse-1000', 'chartreuse-1100', 'chartreuse-1200', 'chartreuse-1300', 'chartreuse-1400', 'celery-100', 'celery-200', 'celery-300', 'celery-400', 'celery-500', 'celery-600', 'celery-700', 'celery-800', 'celery-900', 'celery-1000', 'celery-1100', 'celery-1200', 'celery-1300', 'celery-1400', 'green-100', 'green-200', 'green-300', 'green-400', 'green-500', 'green-600', 'green-700', 'green-800', 'green-900', 'green-1000', 'green-1100', 'green-1200', 'green-1300', 'green-1400', 'seafoam-100', 'seafoam-200', 'seafoam-300', 'seafoam-400', 'seafoam-500', 'seafoam-600', 'seafoam-700', 'seafoam-800', 'seafoam-900', 'seafoam-1000', 'seafoam-1100', 'seafoam-1200', 'seafoam-1300', 'seafoam-1400', 'cyan-100', 'cyan-200', 'cyan-300', 'cyan-400', 'cyan-500', 'cyan-600', 'cyan-700', 'cyan-800', 'cyan-900', 'cyan-1000', 'cyan-1100', 'cyan-1200', 'cyan-1300', 'cyan-1400', 'blue-100', 'blue-200', 'blue-300', 'blue-400', 'blue-500', 'blue-600', 'blue-700', 'blue-800', 'blue-900', 'blue-1000', 'blue-1100', 'blue-1200', 'blue-1300', 'blue-1400', 'indigo-100', 'indigo-200', 'indigo-300', 'indigo-400', 'indigo-500', 'indigo-600', 'indigo-700', 'indigo-800', 'indigo-900', 'indigo-1000', 'indigo-1100', 'indigo-1200', 'indigo-1300', 'indigo-1400', 'purple-100', 'purple-200', 'purple-300', 'purple-400', 'purple-500', 'purple-600', 'purple-700', 'purple-800', 'purple-900', 'purple-1000', 'purple-1100', 'purple-1200', 'purple-1300', 'purple-1400', 'fuchsia-100', 'fuchsia-200', 'fuchsia-300', 'fuchsia-400', 'fuchsia-500', 'fuchsia-600', 'fuchsia-700', 'fuchsia-800', 'fuchsia-900', 'fuchsia-1000', 'fuchsia-1100', 'fuchsia-1200', 'fuchsia-1300', 'fuchsia-1400', 'magenta-100', 'magenta-200', 'magenta-300', 'magenta-400', 'magenta-500', 'magenta-600', 'magenta-700', 'magenta-800', 'magenta-900', 'magenta-1000', 'magenta-1100', 'magenta-1200', 'magenta-1300', 'magenta-1400', 'negative', 'notice', 'positive', 'info', 'accent', 'accent-100', 'accent-200', 'accent-300', 'accent-400', 'accent-500', 'accent-600', 'accent-700', 'accent-800', 'accent-900', 'accent-1000', 'accent-1100', 'accent-1200', 'accent-1300', 'accent-1400', 'bg', 'content-bg', 'subdued-content-bg', 'surface-bg', 'fg'] | str | None", "description": "Fill color for up-markers.  Default: theme OHLC increase color.", "default": "None"}, {"name": "down_color", "type": "Literal['gray-50', 'gray-75', 'gray-100', 'gray-200', 'gray-300', 'gray-400', 'gray-500', 'gray-600', 'gray-700', 'gray-800', 'gray-900', 'red-100', 'red-200', 'red-300', 'red-400', 'red-500', 'red-600', 'red-700', 'red-800', 'red-900', 'red-1000', 'red-1100', 'red-1200', 'red-1300', 'red-1400', 'orange-100', 'orange-200', 'orange-300', 'orange-400', 'orange-500', 'orange-600', 'orange-700', 'orange-800', 'orange-900', 'orange-1000', 'orange-1100', 'orange-1200', 'orange-1300', 'orange-1400', 'yellow-100', 'yellow-200', 'yellow-300', 'yellow-400', 'yellow-500', 'yellow-600', 'yellow-700', 'yellow-800', 'yellow-900', 'yellow-1000', 'yellow-1100', 'yellow-1200', 'yellow-1300', 'yellow-1400', 'chartreuse-100', 'chartreuse-200', 'chartreuse-300', 'chartreuse-400', 'chartreuse-500', 'chartreuse-600', 'chartreuse-700', 'chartreuse-800', 'chartreuse-900', 'chartreuse-1000', 'chartreuse-1100', 'chartreuse-1200', 'chartreuse-1300', 'chartreuse-1400', 'celery-100', 'celery-200', 'celery-300', 'celery-400', 'celery-500', 'celery-600', 'celery-700', 'celery-800', 'celery-900', 'celery-1000', 'celery-1100', 'celery-1200', 'celery-1300', 'celery-1400', 'green-100', 'green-200', 'green-300', 'green-400', 'green-500', 'green-600', 'green-700', 'green-800', 'green-900', 'green-1000', 'green-1100', 'green-1200', 'green-1300', 'green-1400', 'seafoam-100', 'seafoam-200', 'seafoam-300', 'seafoam-400', 'seafoam-500', 'seafoam-600', 'seafoam-700', 'seafoam-800', 'seafoam-900', 'seafoam-1000', 'seafoam-1100', 'seafoam-1200', 'seafoam-1300', 'seafoam-1400', 'cyan-100', 'cyan-200', 'cyan-300', 'cyan-400', 'cyan-500', 'cyan-600', 'cyan-700', 'cyan-800', 'cyan-900', 'cyan-1000', 'cyan-1100', 'cyan-1200', 'cyan-1300', 'cyan-1400', 'blue-100', 'blue-200', 'blue-300', 'blue-400', 'blue-500', 'blue-600', 'blue-700', 'blue-800', 'blue-900', 'blue-1000', 'blue-1100', 'blue-1200', 'blue-1300', 'blue-1400', 'indigo-100', 'indigo-200', 'indigo-300', 'indigo-400', 'indigo-500', 'indigo-600', 'indigo-700', 'indigo-800', 'indigo-900', 'indigo-1000', 'indigo-1100', 'indigo-1200', 'indigo-1300', 'indigo-1400', 'purple-100', 'purple-200', 'purple-300', 'purple-400', 'purple-500', 'purple-600', 'purple-700', 'purple-800', 'purple-900', 'purple-1000', 'purple-1100', 'purple-1200', 'purple-1300', 'purple-1400', 'fuchsia-100', 'fuchsia-200', 'fuchsia-300', 'fuchsia-400', 'fuchsia-500', 'fuchsia-600', 'fuchsia-700', 'fuchsia-800', 'fuchsia-900', 'fuchsia-1000', 'fuchsia-1100', 'fuchsia-1200', 'fuchsia-1300', 'fuchsia-1400', 'magenta-100', 'magenta-200', 'magenta-300', 'magenta-400', 'magenta-500', 'magenta-600', 'magenta-700', 'magenta-800', 'magenta-900', 'magenta-1000', 'magenta-1100', 'magenta-1200', 'magenta-1300', 'magenta-1400', 'negative', 'notice', 'positive', 'info', 'accent', 'accent-100', 'accent-200', 'accent-300', 'accent-400', 'accent-500', 'accent-600', 'accent-700', 'accent-800', 'accent-900', 'accent-1000', 'accent-1100', 'accent-1200', 'accent-1300', 'accent-1400', 'bg', 'content-bg', 'subdued-content-bg', 'surface-bg', 'fg'] | str | None", "description": "Fill color for down-markers.  Default: theme OHLC decrease color.", "default": "None"}, {"name": "up_text", "type": "str", "description": "Label text for up-markers.  Default: \"\" (no label).", "default": "''"}, {"name": "down_text", "type": "str", "description": "Label text for down-markers.  Default: \"\" (no label).", "default": "''"}, {"name": "up_size", "type": "int | None", "description": "Size multiplier for up-markers.  Default: library default (1).", "default": "None"}, {"name": "down_size", "type": "int | None", "description": "Size multiplier for down-markers.  Default: library default (1).", "default": "None"}]}} />
