Skip to content
EliteChart

Datafeed contract

The four-method interface that pipes bars + symbol metadata into ChartForge from any backend.

EliteChart talks to your backend through a single TypeScript interface — Datafeed. You implement four methods (two required, two optional); the chart handles caching, viewport refetch, pagination, and live-bar merging.

Quick example

code
import type {
  Bar, Datafeed, GetBarsRequest, SubscribeRequest,
} from '@elitechart/elitechart';
import { asPrice, asTimestampMs, asVolume } from '@elitechart/elitechart';

export const myDatafeed: Datafeed = {
  async getBars(req: GetBarsRequest): Promise<ReadonlyArray<Bar>> {
    const r = await fetch(`/api/bars?symbol=${req.symbol}&from=${req.from}&to=${req.to}&tf=${req.resolution}`);
    const json = (await r.json()) as ReadonlyArray<{ t: number; o: number; h: number; l: number; c: number; v: number }>;
    return json.map((b) => ({
      time: asTimestampMs(b.t),
      open: asPrice(b.o),
      high: asPrice(b.h),
      low: asPrice(b.l),
      close: asPrice(b.c),
      volume: asVolume(b.v),
    }));
  },
  subscribe(req: SubscribeRequest, onBar: (bar: Bar) => void): () => void {
    const ws = new WebSocket(`wss://example.com/stream?symbol=${req.symbol}&tf=${req.resolution}`);
    ws.onmessage = (e) => {
      const b = JSON.parse(e.data) as { t: number; o: number; h: number; l: number; c: number; v: number };
      onBar({
        time: asTimestampMs(b.t),
        open: asPrice(b.o),
        high: asPrice(b.h),
        low: asPrice(b.l),
        close: asPrice(b.c),
        volume: asVolume(b.v),
      });
    };
    return () => ws.close();
  },
};

How it works

getBars(req) fetches a closed range of historical bars. The chart calls this once on mount, then again whenever the user pans into uncached territory. Return bars in ascending time order.

subscribe(req, onBar) opens a live stream. Call onBar once per realtime update — for an in-progress bar this fires every tick; for a closed bar it fires once. Return an unsubscribe function so the chart can shut the stream down on unmount.

searchSymbols(query) (optional) powers the Symbol Search modal. Return up to ~50 matches; the modal applies its own filtering.

resolveSymbol(id) (optional) returns metadata (tick size, display name, exchange) for a single symbol. Required if you want the price axis to format with the right decimals.

Bar shape

code
interface Bar {
  readonly time: TimestampMs;
  readonly open: Price;
  readonly high: Price;
  readonly low: Price;
  readonly close: Price;
  readonly volume: Volume;
}

Use the as* constructors (asPrice, asTimestampMs, asVolume) to brand raw numbers — this rules out unit-mismatch bugs at compile time.

Variations

REST + polling

If you don't have WebSocket, return a setInterval from subscribe:

code
subscribe(req, onBar) {
  const id = setInterval(async () => {
    const b = await fetchLatest(req.symbol, req.resolution);
    onBar(b);
  }, 1000);
  return () => clearInterval(id);
}

Static fixture (tests / demos)

code
const fixtureFeed: Datafeed = {
  async getBars() { return FIXTURE_BARS; },
  subscribe() { return () => {}; },
};

API

MethodRequiredReturns
getBars(req)yesPromise<ReadonlyArray<Bar>>
subscribe(req, onBar)yes() => void (unsubscribe)
searchSymbols(query)noPromise<ReadonlyArray<SymbolInfo>>
resolveSymbol(id)noPromise<SymbolInfo>

See the API reference for the full type definitions.