# Replay, linking and alerts

> Bar replay with higher-timeframe followers, linked charts, and draggable price alerts.

Section: Charts. Source: https://ortexcharts.com/docs/replay

## Three Features, One Principle

Bar replay, linked charts and price alerts are the three most-requested additions to a
charting library after indicators and drawings. All three are implemented the same way here:
no server, no account, no ORTEX service. The host keeps whatever state survives a reload,
and the library does the rest in the browser.

## Bar Replay

Replay steps through history as if it were arriving live. The controller keeps the full
data, shows a prefix of it, and appends each next bar **through the realtime path** rather
than by redrawing a slice. That detail is what makes it useful: indicators take their
incremental fast paths, the volume pane mirrors each bar, and drawings and alerts see
exactly the sequence they would see on a live feed.

```ts
const replay = chart.replay({ speed: 4 });     // created once, reused after that

replay.start({ time: someTime });              // or start(barIndex)
replay.step(1);
replay.step(-1);
replay.play(10);                               // 10 bars per second
replay.pause();
replay.stop();                                 // put the full history back

replay.subscribe((s) => {
  if (!s.active) return;
  setPosition(`${s.index + 1} / ${s.total}`, new Date(s.time), s.playing, s.speed);
});
```

`ReplayState` is `{ active, playing, index, total, time, speed }`. `index` is the position
in the full history and is −1 while replay is inactive.

| Method | What it does |
|---|---|
| `start(at)` | Begin at a bar index, or at `{ time }`. Defaults to the beginning. |
| `step(count)` | Move forward or backward by whole bars. |
| `seek(index)` | Jump to a bar. |
| `play(speed?)` | Advance automatically at `speed` bars per second. |
| `pause()` | Stop advancing, stay in replay. |
| `stop()` | Leave replay and restore the full history. |
| `link(target, resolution?)` | Make another chart follow the clock. |
| `history()` | The full bar array the controller snapshotted. |
| `subscribe(cb)` | State changes; returns an unsubscribe function. |

Options are `speed` (bars per second, default two) and `follow` (keep the current bar in
view while stepping, default true).

### Live Data During Replay

A datafeed or WebSocket can keep running while the user replays. Updates that arrive are
**held on the series** and applied in order when replay stops, so nothing is lost and
nothing jumps into the middle of the replayed sequence. Under the hood this is
`SeriesModel.holdUpdates()` and `releaseUpdates()`, which you can use directly for any other
reason you might want to freeze a series.

Markers and events past the last replayed bar are not drawn, so the future stays hidden
rather than spoiling what the replay is for.

### Backward Steps

Forward steps append one bar. Backward steps and seeks reset the prefix, which is a rebuild
rather than an append. That asymmetry is deliberate: stepping forward is the common case and
is cheap, and stepping backward is rare and costs a full recompute.

_Live example: [replay](https://ortexcharts.com/examples/replay)_

### Multi-Timeframe Replay

```ts
const unlink = replay.link(fiveMinuteChart);          // a FinancialChart, or { chart, main }
```

A linked chart follows the clock. It shows its own bars that closed before the current
period, plus the current period **aggregated from the master bars so far**, so a five-minute
candle forms in front of the viewer as the one-minute replay advances. This is the behavior
traders mean by multi-timeframe replay, and it is the reason the higher timeframe cannot
simply be a second replay.

Link charts at the master resolution or coarser; linking a finer chart has nothing to
aggregate from. Pair it with `linkCharts` when you also want a shared crosshair.

### In the Shell

The toolbar's Replay button is off by default because most charts do not want it.

```ts
createChartShell(el, { symbol: "AAPL", datafeed, toolbar: { replay: true } });
```

Clicking it asks for a start bar: the next click on the chart begins the replay there, and a
control strip appears under the toolbar with step back, play and pause, step forward, a
speed selector from 1× to 30×, the position and an exit button.

From code, `shell.replay()` returns the same `ReplayController`, and
`shell.startReplay(index?)` and `shell.stopReplay()` drive it without the click.

## Linked Charts

```ts
import { linkCharts } from "@ortex-charts/financial";

const unlink = linkCharts([daily.chart, hourly.chart, minute.chart], {
  timeRange: true,
  crosshair: true,
});
```

Panning or zooming any chart sets the same **time** span on the others, and the crosshair
mirrors by time. Because the link is by time rather than by bar index, it works across
resolutions: a daily chart and a one-minute chart stay on the same window and the bar
spacing adapts on each. Echoes are suppressed, so there are no feedback loops.

Both options default to true; set either to `false` to link only the other. The returned
function unlinks everything.

There is no grid container in the library, so a two-by-two layout is your own CSS. The link
works with any arrangement of containers.

## Price Alerts

Alerts are draggable horizontal lines that report crossings to the host. Nothing here talks
to a server, sends an email or schedules anything: the library detects the crossing and
tells you, and what happens next is your product's decision.

```ts
const alerts = chart.alerts({ draggable: true });

alerts.add({ price: 182.5, label: "Breakout", color: "#22C08A" });
alerts.add({ price: 178, label: "Support", repeat: true });

alerts.subscribe((e) => {
  switch (e.type) {
    case "triggered":
      notify(`${e.alert.label} crossed ${e.direction} at ${e.price}`);
      break;
    case "added":
    case "removed":
    case "moved":
      saveAlerts(alerts.list());
      break;
  }
});

alerts.set(savedAlerts);        // on load
```

### Behavior

- The line is dashed with a labeled tag on the right, drawn on the main series scale.
- Dragging it moves the price and re-arms the alert, and emits a `moved` event carrying the
  previous price in `from`.
- Every data update — live, replayed or set in bulk — is checked for a crossing of the main
  series last value, in either direction.
- An alert is one-shot by default: it fires once, `triggered` is set, and it does not fire
  again until it is moved or patched. `repeat: true` fires on every crossing.
- `hideTriggered: true` hides the line after a one-shot alert fires.

### The Alert Shape and the API

```ts
interface Alert {
  id: string;
  price: number;
  label?: string;
  color?: string;
  repeat?: boolean;
  triggered?: boolean;
  data?: unknown;         // anything you want back with the events
}
```

| Method | What it does |
|---|---|
| `add(alert)` | Add one; `price` is required, the id is generated. |
| `remove(id)` | Remove one. |
| `patch(id, patch)` | Change price, label, color, repeat or triggered. |
| `list()` | Plain JSON for your store. |
| `set(list)` | Replace everything, for example after loading. |
| `subscribe(cb)` | Alert events; returns an unsubscribe function. |

`list()` and `set()` are the whole persistence contract. Store the array against a user and
a symbol, restore it when the chart loads, and re-save on `added`, `removed` and `moved`.

Alerts have no toolbar chrome in the shell. `shell.chart.alerts()` is the API and the host
decides how to present adding one — a button, a context menu, a right-click on the price
scale.

_Live example: [alerts](https://ortexcharts.com/examples/alerts)_

## Putting the Three Together

A common arrangement: a master chart the user replays, a higher-timeframe chart following
the clock, both linked for the crosshair, and alerts that fire during the replay so a
strategy can be checked against them.

```ts
import { createFinancialChart, linkCharts } from "@ortex-charts/financial";

const minute = createFinancialChart(topEl, { resolution: "1", timeZone: "America/New_York", data: minuteBars });
const fiveMin = createFinancialChart(bottomEl, { resolution: "5", timeZone: "America/New_York", data: fiveMinuteBars });

const unlinkCharts = linkCharts([minute.chart, fiveMin.chart], { crosshair: true, timeRange: false });

const replay = minute.replay({ speed: 8 });
const unlinkReplay = replay.link(fiveMin);

const alerts = minute.alerts();
alerts.set(savedAlerts);
alerts.subscribe((e) => e.type === "triggered" && toast(`${e.alert.label ?? "Alert"} at ${e.price}`));

replay.start({ time: sessionOpen });
replay.play();
```

Note that `timeRange` is switched off in this arrangement: the follower chart is driven by
the replay clock, so a second synchronization of the visible range would fight it. Keep the
crosshair link, drop the range link.
