DefaultDataFeed
DefaultDataFeedis a reference adapter for local development and evaluation. ChartSpire does not provide or guarantee market data. For applications that depend on live or regulated data, implement the[DataFeed](./data-access.md)interface against your own provider and infrastructure.
This page documents the included DefaultDataFeed reference adapter — a way to try ChartSpire against a compatible HTTP and WebSocket API while preserving multi-chart subscription sharing.
For generic DataFeed concepts, see Data Access.
For data shape definitions, see Data Types.
What matters most
- HTTP is used for
searchSymbols,getHistoricalTimeSeries, andgetPrice. - WebSocket is used for live
kline,orderbook, andtradesstreams. - HTTP and WebSocket origins are configured separately so REST and streaming can live on different infrastructure.
- TLS is the default.
http://andws://requiresecurity.allowInsecureTransport: true. - Multiple charts/components subscribing to the same
{ dataType, symbol, exchange, interval }share one backend subscription. - Stable subscriber IDs are required for subscribe/unsubscribe calls.
Constructor Configuration
import { DefaultDataFeed, type DefaultDataFeedConfig } from 'chartspire'
const config: DefaultDataFeedConfig = {
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
},
heartbeat: {
enabled: true,
intervalMs: 30000,
timeoutMs: 10000
}
},
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'
}
}
}
const dataFeed = new DefaultDataFeed(config)Local or private non-TLS deployments
Non-TLS is supported, but it must be explicit:
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
}
})Do not enable insecure transport on internet-facing deployments.
Config Reference
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 Contract
All HTTP endpoints are configured as paths under http.baseUrl. Asset type and exchange are query parameters rather than path segments.
1) Symbol Search
GET {http.baseUrl}{endpoints.search}?query={query}&type={type}&exchange={exchange?}&limit={limit?}
Example:
GET https://api.example.com/v1/market-data/symbols?query=AAPL&type=stocks&limit=25Response:
interface DefaultSearchResponse {
data: Array<{
symbol: string
name?: string
shortName?: string
exchange?: string
market?: string
type: string
pricePrecision?: number
volumePrecision?: number
priceCurrency?: string
currency?: string
logo?: string
}>
}If search is empty, DefaultDataFeed uses protocol.searchDefaults[type] or protocol.searchDefaults.default.
2) Historical Candles
GET {http.baseUrl}{endpoints.historical}?symbol={symbol}&type={type}&exchange={exchange?}&interval={interval}&from={fromMs}&to={toMs}
Example:
GET https://api.example.com/v1/market-data/candles?symbol=BTCUSDT&type=crypto&exchange=binance&interval=1h&from=1710000000000&to=1710100000000Response:
interface DefaultHistoricalResponse {
data: Array<{
timestamp: number | string
open: number | string
high: number | string
low: number | string
close: number | string
volume?: number | string
turnover?: number | string
}>
meta?: {
symbol?: string
exchange?: string
interval?: string
nextPageToken?: string
}
}Rules:
fromandtoare Unix millisecond timestamps.- Return only candles inside the requested window.
- Return candles sorted ascending by timestamp when possible. The client sorts defensively.
- Malformed candles are dropped rather than emitted as
NaNvalues.
3) Current Price
GET {http.baseUrl}{endpoints.price}?symbol={symbol}&type={type}&exchange={exchange?}
Response:
interface DefaultPriceResponse {
price: number
symbol?: string
exchange?: string
timestamp?: number
}Interval Behavior
Historical HTTP requests send the selected chart interval using ChartSpire's interval mapping, for example:
1m,3m,5m,15m,30m1h,2h,4h,6h,8h,12h1d,3d,5d1w1M,3M,6M1y,5y
Live WebSocket chart subscriptions use the selected chart interval (same as historical). The feed must emit OHLCV at that interval; ChartSpire passes bars through without client-side rebucketing.
Note: The reference chartspire-server may not stream all intervals yet. A test backend that matches this contract should emit klines at the subscribed interval.
Custom DataFeed contract
Implement both history and live at the chart period:
| Method | Behavior |
|---|---|
getHistoricalTimeSeries(symbol, period, from, to) | Return candles at period |
subscribeSymbol(symbol, period, callback, subscriberUID) | Stream updates at period |
unsubscribeSymbol(symbol, period, subscriberUID) | Same period as subscribe |
Feeds that only stream 1m live will not populate 5m/1h charts until the feed (or server) aggregates to the requested interval.
WebSocket Contract
Authentication
When auth.websocketMode is message, DefaultDataFeed sends an auth frame after the socket opens:
{
"event": "authenticate",
"data": {
"type": "bearer",
"token": "jwt-token",
"clientId": "client_xxx",
"timestamp": 1710000000000
}
}Subscriptions are queued until the server sends:
{
"event": "auth_success",
"data": {
"timestamp": 1710000000000
}
}On auth_error, the socket closes and subscriptions are not replayed until a new feed/socket lifecycle begins.
Use websocketMode: 'message' whenever possible. websocketMode: 'query' exists for gateways that cannot accept an auth frame, but tokens in URLs may appear in proxy or gateway logs.
Subscribe
{
"event": "subscribe",
"data": {
"subscriptionId": "cs_kline%7CBTCUSDT%7Cbinance%7C1m",
"clientId": "client_xxx",
"dataType": "kline",
"symbol": "BTCUSDT",
"type": "crypto",
"exchange": "binance",
"interval": "1m"
}
}dataType can be:
klineorderbooktrades
Unsubscribe
{
"event": "unsubscribe",
"data": {
"subscriptionId": "cs_kline%7CBTCUSDT%7Cbinance%7C1m",
"clientId": "client_xxx"
}
}Market Data
{
"event": "market_data",
"data": {
"subscriptionId": "cs_kline%7CBTCUSDT%7Cbinance%7C1m",
"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 matching is fallback only and does not rely on splitting symbol strings.
Shared Channel Behavior
DefaultDataFeed maintains one backend subscription per { dataType, symbol, exchange, interval } per feed instance.
Example: if four charts subscribe to BTCUSDT on binance for kline 1m, ChartSpire sends one backend subscribe frame and keeps four local handlers. When one chart unsubscribes, the backend subscription remains active. The backend unsubscribe frame is sent only when the last local handler is removed.
Isolation rules:
- Same symbol on different exchanges uses different channels.
- Same symbol/exchange with different intervals uses different kline channels.
kline,orderbook, andtradesare separate channels.
Diagnostics
dataFeed.debugChannels()
const count = dataFeed.getActiveSubscriptions()debugChannels()logs active channel details whendebugis enabled.getActiveSubscriptions()returns the number of active backend channels across kline, order book, and trades.