Skip to main content

Market Data Types

Your backend already produces market data in some shape. Translating it is a three-column problem: which kind of data drives which chart surface, which method serves it, and which capability flag must be set before the chart will ask for it at all.

The chart never guesses. A method that exists but whose flag is missing is never called, and a flag set without the method behind it is ignored.

What the chart can render

The full mapping

Data you haveMethodCapability flagRenders
Historical OHLCVloadBarsrequiredCandles, indicators
Live bar or tick streamsubscribeRealTimeBarssupportsRealTimeDeveloping candle
Top of book / NBBOgetQuotes, subscribeQuotessupportsQuotesLegend quotes, bid/ask lines, market status
Order-book levelsgetDepth, subscribeDepthsupportsDepthDepth ladder, liquidity heatmap
Recorded book snapshotsgetBookHistorysupportsBookHistoryHeatmap replay over history
Trade printsgetTimeAndSales, subscribeTimeAndSalessupportsTimeAndSalesTape, footprint, market profile
Instrument masterresolveSymbol, searchSymbolssupportsSearchSymbol search and resolution
Instrument profilegetInstrumentDetailssupportsDetailsInstrument details panel
Trading calendarsresolveSessionInfo, resolveSessionCalendarsupportsSessionInfo, supportsSessionCalendarSession shading, gap filling
Corporate actions, halts, releasesgetEvents, subscribeEventssupportsEvents plus eventCatalogTimeline event markers
Per-bar annotationsgetMarks, getTimescaleMarkssupportsMarks, supportsTimescaleMarksBar and time-scale marks
HeadlinesgetNewssupportsNewsNews panel
Option chainsgetOptionContracts, getOptionQuotessupportsOptionContracts, supportsOptionQuotesOption surfaces
Derived or alternative seriesgetExternalSeriessupportsExternalSeriesAuxiliary plotted series
Symbol listsgetWatchlist and the watchlist writerssupportsWatchlistWatchlist
Arbitrary per-bar valuesgetDataWindowsupportsDataWindowData window
Venue clockgetServerTimesupportsServerTimeSession and countdown accuracy

Declare only what you serve:

import type { MarketDataFeedConfig } from '@tradescript/pro/sdk';

export function capabilities(): MarketDataFeedConfig {
return {
supportedIntervals: ['1m', '5m', '15m', '1H', '1D'],
providedIntervals: ['1m', '1D'],
resolutionRebuildPolicy: 'aggregate',
supportsRealTime: true,
supportsSearch: true,
supportsQuotes: true,
supportsDepth: true,
supportsTimeAndSales: true,
};
}

providedIntervals are the intervals your backend can return directly. supportedIntervals are the ones a user may pick. With resolutionRebuildPolicy: 'aggregate', the SDK builds the difference by aggregating a smaller provided interval — so a backend that stores only 1-minute and daily bars can still offer 5m, 15m, and 1H. See Resolutions.

Normalization rules

These four rules cause most integration bugs. They apply to every data type.

Time is Unix milliseconds, UTC

Vendors publish seconds, microseconds, nanoseconds, and ISO strings. Convert at the boundary. Databento timestamps are UTC nanoseconds; FIX SendingTime is a UTC string; Alpaca uses RFC-3339. The chart accepts exactly one form.

const timeMs = Number(nanoseconds / 1_000_000n);

Bar timestamps are the bar's opening time, not its close.

Prices are floating point in display units

Feeds that carry fixed-precision integers must be scaled before the chart sees them. Databento encodes prices as int64 scaled by 1e-9; many exchange binary protocols use a per-instrument exponent from the security definition. Scale in the normalizer, never in a chart callback.

Bars ascend and never duplicate

loadBars returns bars in ascending time order with no repeated timestamps. For live updates, a bar whose time matches the current bar replaces it; a later time appends a new bar. Out-of-order vendor events must be dropped or reordered before they reach the callback.

Symbol identity is stable across transports

History, stream, depth, and tape must resolve the same instrument to the same identity. If the vendor uses AAPL for history and NASDAQ:AAPL for streaming, normalize both through one application-owned map and put the result in ticker. Use canonicalSymbol when the display ticker must stay human-readable while the SDK keys on something else, and brokerSymbol for the execution-side identifier — the SDK never parses or repairs these values.

Serving each type

A bars-plus-quotes feed covers most products. Depth and tape are additive.

import type { MarketDataFeed } from '@tradescript/pro/sdk/core';
import type { Quote, SdkMarketDepth, TimeAndSalesEntry } from '@tradescript/pro/sdk';

interface VendorLevel { px: number; qty: number }

async function api<T>(path: string): Promise<T> {
const response = await fetch(path);
if (!response.ok) throw new Error(`${path} failed: ${response.status}`);
return response.json() as Promise<T>;
}

export const datafeed: MarketDataFeed = {
onReady: () => ({ supportsQuotes: true, supportsDepth: true, supportsTimeAndSales: true }),

async loadBars(symbol, interval, request) {
const query = new URLSearchParams({
symbol: symbol.ticker,
interval,
from: String(request.startTime),
to: String(request.endTime),
});
return api(`/api/bars?${query}`);
},

async getQuotes(request) {
const tickers = request.symbols.map((entry) =>
typeof entry === 'string' ? entry : entry.ticker,
);
return api<Quote[]>(`/api/quotes?symbols=${tickers.join(',')}`);
},

async getDepth(request) {
const book = await api<{ bids: VendorLevel[]; asks: VendorLevel[]; ts: number }>(
`/api/depth?symbol=${request.symbol.ticker}`,
);
const levels = (side: VendorLevel[]) => side.map((l) => ({ price: l.px, size: l.qty }));
const depth: SdkMarketDepth = {
symbol: request.symbol,
bids: levels(book.bids),
asks: levels(book.asks),
timestamp: book.ts,
};
return depth;
},

async getTimeAndSales(request) {
const prints = await api<TimeAndSalesEntry[]>(
`/api/tape?symbol=${request.symbol.ticker}&limit=${request.limit ?? 200}`,
);
return prints;
},
};

Data the chart does not convert

Currency and unit conversion is feed-owned. The SDK never multiplies prices or volumes after bars arrive. When a user switches the price scale to another currency, the chart re-enters resolveSymbol with a currencyCode, and your feed returns a symbol whose subsequent history and stream are already converted. Advertise the available targets with currencyCodes and units.

Next steps