Skip to content

Charts

Replay, linking and alerts

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

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.

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.

Bar replay@ortex-charts/financial
Loading bar replay
Step or play the history forward from any bar, with the higher-timeframe chart following along.

Open this example with its source

Multi-Timeframe Replay

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.

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

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.

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

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.

Draggable price alerts@ortex-charts/financial
Loading draggable price alerts
Alert lines the user can drag, firing once or repeatedly as price crosses them.

Open this example with its source

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.

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.