Skip to content

DefaultDataFeed

Reference adapter for local evaluation — not a managed data service. For production, implement DataFeed. See also Type Reference.

Naming: APIs use CandleStickData. Wire protocol uses dataType: "kline" — keep that string in subscribe frames and payloads.

Essentials

What you need to know before wiring a backend.

  • HTTP: searchSymbols, getHistoricalTimeSeries, getPrice
  • WebSocket: live kline, orderbook, trades
  • TLS by default; http:// / ws:// need security.allowInsecureTransport: true
  • One backend channel per { dataType, symbol, exchange, type, interval } (interval only for kline)
  • subscriberUID is required on subscribe/unsubscribe

Configuration

Pass a DefaultDataFeedConfig to the constructor.

typescript
import DefaultDataFeed, { type DefaultDataFeedConfig } from '@chartspire/ui/datafeeds/default'

const dataFeed = new DefaultDataFeed({
  http: {
    baseUrl: 'https://api.example.com',
    endpoints: {
      search: '/v1/market-data/symbols',
      historical: '/v1/market-data/candles',
      price: '/v1/market-data/price'
    },
    headers: async () => ({ 'X-Tenant-Id': tenantId }),
    timeoutMs: 15000
  },
  websocket: {
    enabled: true,
    url: 'wss://stream.example.com/v1/market-data',
    reconnect: { enabled: true, initialDelayMs: 1000, maxDelayMs: 30000, maxAttempts: 5 }
  },
  auth: {
    type: 'bearer',
    token: async () => getCurrentSessionToken(),
    applyTo: ['http', 'websocket'],
    websocketMode: 'message'
  },
  security: {
    allowedHttpOrigins: ['https://api.example.com'],
    allowedWebSocketOrigins: ['wss://stream.example.com']
  },
  protocol: {
    searchDefaults: { default: 'AAPL', crypto: 'BTC' }
  }
})

Local non-TLS:

typescript
const dataFeed = new DefaultDataFeed({
  http: {
    baseUrl: 'http://localhost:3000',
    endpoints: {
      search: '/v1/market-data/symbols',
      historical: '/v1/market-data/candles',
      price: '/v1/market-data/price'
    }
  },
  websocket: { enabled: true, url: 'ws://localhost:3000/v1/market-data' },
  security: { allowInsecureTransport: true }
})
typescript
interface DefaultDataFeedConfig {
  http: {
    baseUrl: string
    endpoints: { search: string; historical: string; price?: string }
    headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)
    credentials?: RequestCredentials
    timeoutMs?: number
  }
  websocket?: {
    enabled?: boolean
    url?: string
    reconnect?: { enabled?: boolean; initialDelayMs?: number; maxDelayMs?: number; maxAttempts?: number }
    heartbeat?: { enabled?: boolean; intervalMs?: number; timeoutMs?: number }
  }
  auth?: {
    type: 'bearer'
    token: string | (() => string | Promise<string>)
    applyTo?: Array<'http' | 'websocket'>
    websocketMode?: 'message' | 'query'
  }
  security?: {
    allowInsecureTransport?: boolean
    allowedHttpOrigins?: string[]
    allowedWebSocketOrigins?: string[]
  }
  protocol?: { searchDefaults?: Record<string, string> }
  debug?: boolean
  onError?: (error: DefaultDataFeedError) => void
  onStatusChange?: (status: DefaultDataFeedStatus) => void
}

http

REST transport for search, history, and price.

  • baseUrl — REST origin (required)
  • endpoints.search / historical / price — paths under baseUrl (price defaults to price)
  • headers — extra request headers (object or async factory)
  • credentialsfetch credentials mode
  • timeoutMs — request abort timeout (default 15000)

websocket

Live streaming for candles, order book, and trades.

  • enabled — turn streaming on; defaults to true when url is set
  • url — WebSocket endpoint (required when enabled)
  • reconnect.enabled — auto-reconnect after unexpected close (default true)
  • reconnect.initialDelayMs — first reconnect delay (default 1000)
  • reconnect.maxDelayMs — backoff cap (default 30000)
  • reconnect.maxAttempts — give up after N attempts (default 5)
  • heartbeat.enabled — send ping / expect pong (default false)
  • heartbeat.intervalMs — ping interval (default 30000)
  • heartbeat.timeoutMs — close if no pong (default 10000)

auth

Optional bearer auth for HTTP and/or WebSocket.

  • type — only bearer is supported
  • token — static string or async token provider
  • applyTo — attach auth to http, websocket, or both (default both)
  • websocketModemessage = auth frame after open (preferred); query = token in URL

security

Transport and origin checks for the configured URLs.

  • allowInsecureTransport — allow http:// / ws:// (default false)
  • allowedHttpOrigins — optional allowlist for http.baseUrl
  • allowedWebSocketOrigins — optional allowlist for websocket.url

Other

Protocol defaults and diagnostics hooks.

  • protocol.searchDefaults — fallback search query by type when search text is empty
  • debug — channel logging / verbose console output
  • onError — config, HTTP, auth, and subscription errors
  • onStatusChange — WebSocket lifecycle (connected, authenticated, reconnecting, …)

HTTP

Paths are under http.baseUrl. Params are query strings.

Symbol lookup for the picker and watchlists.

GET …/search?query={query}&type={type}

Empty queryprotocol.searchDefaults[type] or .default.

typescript
{ data: Array<{ symbol: string; type?: string; name?: string; shortName?: string; exchange?: string; pricePrecision?: number; volumePrecision?: number; priceCurrency?: string; currency?: string; logo?: string }> }

Historical

OHLCV bars for the requested interval and time window.

GET …/historical?symbol=&type=&exchange=&interval=&from=&to=

from / to are Unix ms. Return bars in range; client sorts ascending. Malformed bars are dropped. Body may use data or values; time field may be timestamp or datetime.

typescript
{
  data?: Array<{ timestamp?: number | string; datetime?: number | string; open: number | string; high: number | string; low: number | string; close: number | string; volume?: number | string; turnover?: number | string }>
  values?: /* same as data */
  meta?: { symbol?: string; exchange?: string; interval?: string; nextPageToken?: string }
}

Price

Latest quote for HTTP watchlists.

GET …/price?symbol=&type=&exchange=

typescript
{ price: number; symbol?: string; exchange?: string; timestamp?: number }

Intervals

History and live use the chart interval (no client rebucketing):

1m 3m 5m 15m 30m · 1h 2h 4h 6h 8h 12h · 1d 3d 5d · 1w · 1M 3M 6M · 1y 5y

WebSocket

JSON control frames and market_data pushes over a single socket.

Auth (websocketMode: 'message')

Client → { "event": "authenticate", "data": { "type": "bearer", "token": "…", "clientId": "…", "timestamp": 0 } }
Server → { "event": "auth_success", "data": { "timestamp": 0 } }

Subscribe frames queue until auth_success. On auth_error, the socket closes (no reconnect). Prefer message over query (tokens in URLs may be logged).

Subscribe / unsubscribe

dataType: kline | orderbook | trades. interval is set for kline only.

json
{
  "event": "subscribe",
  "data": {
    "subscriptionId": "cs_kline|BTCUSDT|binance|crypto|BTCUSDT%7Cbinance%7Ccrypto|1m",
    "clientId": "client_xxx",
    "dataType": "kline",
    "symbol": "BTCUSDT",
    "type": "crypto",
    "exchange": "binance",
    "interval": "1m"
  }
}
json
{
  "event": "unsubscribe",
  "data": {
    "subscriptionId": "cs_kline|BTCUSDT|binance|crypto|BTCUSDT%7Cbinance%7Ccrypto|1m",
    "clientId": "client_xxx"
  }
}

subscriptionId is opaque — echo it on market_data. Format: cs_ + dataType|symbol|exchange|type|identityKey|interval (literal |; each segment URI-encoded).

Market data

Server push for live updates; prefer routing by subscriptionId.

json
{
  "event": "market_data",
  "data": {
    "subscriptionId": "cs_kline|BTCUSDT|binance|crypto|BTCUSDT%7Cbinance%7Ccrypto|1m",
    "dataType": "kline",
    "symbol": "BTCUSDT",
    "type": "crypto",
    "exchange": "binance",
    "interval": "1m",
    "payload": {
      "timestamp": 1710000000000,
      "open": "100.00",
      "high": "101.00",
      "low": "99.00",
      "close": "100.50",
      "volume": "1234.5"
    }
  }
}

Routing prefers subscriptionId; symbol/exchange/interval/type is fallback only.

Shared channels

Same channel → one backend subscribe; local handlers share it. Backend unsubscribe only when the last handler leaves.

  • Different exchange, type, or (for kline) interval → different channel
  • kline, orderbook, and trades are separate

Diagnostics

Inspect and tear down feed subscriptions.

typescript
dataFeed.debugChannels()              // logs when debug: true
dataFeed.getActiveSubscriptions()     // backend channel count
dataFeed.unsubscribeAllForChart(id)
dataFeed.closeWebsocket()