Datafeed implementation
Implement history and symbol resolution. After this step the chart loads candles; live updates come in Streaming implementation.
Required methods
| Method | Role |
|---|---|
resolveSymbol | Map a symbol string → GoCharting security / SymbolInfo (precision, session, intervals, exchange_info, …) |
getBars | Return historical OHLCV for a time range |
Optional but useful: searchSymbols so the top-bar search works.
resolveSymbol
onResolve receives a security / SymbolInfo object in GoCharting’s native shape (not a minimal ticker stub). Production backends typically return this JSON as-is from symbol lookup.
resolveSymbol(symbolName, onResolve, onError) {
// Example: "BYBIT:FUTURE:BTCUSDT"
const parts = symbolName.split(":");
const exchange = parts[0] || "BYBIT";
const segment = parts[1] || "FUTURE";
const symbol = parts[parts.length - 1] || "BTCUSDT";
try {
// Prefer fetching this object from your symbol API.
// Shape below matches what GoCharting uses internally.
onResolve({
exchange,
segment,
symbol,
name: "BTC / USDT PERPETUAL FUTURES",
asset_type: "CRYPTO",
source_id: symbol,
pair: { from: "BTC", to: "USDT" },
tradeable: true,
is_index: false,
is_formula: false,
delay_seconds: 0,
data_status: "streaming", // or "delayed_streaming" | "endofday" | "pulsed"
industry: "technology",
symbol_logo_urls: [
"https://upload.wikimedia.org/wikipedia/commons/a/a4/Flag_of_the_United_States.svg",
],
contract_size: 1,
tick_size: 0.1,
display_tick_size: 1,
volume_size_increment: 0.001,
max_tick_precision: 1,
max_volume_precision: 3,
quote_currency: "USDT",
future_type: "PERP",
data_source_location: "blr1",
supports: {
footprint: true,
},
exchange_info: {
name: "bybit",
code: "BYBIT",
country_cd: "US",
zone: "UTC",
has_unique_trade_id: true,
logo_url: "https://gocharting.com/avatar/Bybit_Logo.svg",
holidays: null,
hours: [
{ open: true },
{ open: true },
{ open: true },
{ open: true },
{ open: true },
{ open: true },
{ open: true },
],
contains_ambiguous_symbols: false,
valid_intervals: [
"1m",
"3m",
"5m",
"10m",
"15m",
"30m",
"1h",
"2h",
"4h",
"12h",
"1D",
"1W",
"1M",
],
},
// Compatibility fields some chart paths still read
ticker: symbol,
full_name: `${exchange}:${segment}:${symbol}`,
description: "BTC / USDT PERPETUAL FUTURES",
type: "crypto",
session: "24x7",
timezone: "UTC",
has_intraday: true,
has_daily: true,
supported_resolutions: [
"1m",
"3m",
"5m",
"10m",
"15m",
"30m",
"1h",
"2h",
"4h",
"12h",
"1D",
"1W",
"1M",
],
});
} catch (e) {
onError(String(e?.message || e));
}
}Call exactly one of onResolve or onError.
Important fields for chart behaviour:
| Field | Why it matters |
|---|---|
exchange / segment / symbol | Identity used in streams and history requests |
name | Display title (also used as fallback if symbol is missing) |
tick_size, max_tick_precision, max_volume_precision | Price/volume formatting |
exchange_info.valid_intervals | Interval picker allowlist |
exchange_info.hours / zone | Session and timezone |
data_status, delay_seconds | Live vs delayed labelling |
supports.footprint | Whether footprint features are enabled for the symbol |
See Datafeed API — Symbol Info Format for the full field reference.
getBars
async getBars(symbolInfo, resolution, periodParams) {
const { from, to, firstDataRequest, rows } = periodParams;
const qs = new URLSearchParams({
symbol: symbolInfo.full_name || symbolInfo.name,
interval: String(resolution),
from: String(from.getTime()),
to: String(to.getTime()),
});
if (rows != null) qs.set("rows", String(rows));
const res = await fetch(`https://api.example.com/bars?${qs}`);
if (!res.ok) {
return { bars: [], meta: { noData: true } };
}
const raw = await res.json();
const bars = (raw.bars || raw).map((b) => ({
time: b.time ?? b.timestamp, // Unix timestamp in milliseconds
open: Number(b.open),
high: Number(b.high),
low: Number(b.low),
close: Number(b.close),
volume: Number(b.volume ?? 0),
}));
return {
bars,
meta: {
noData: bars.length === 0,
// nextTime: optional unix ms hint for pagination
},
};
}Period params
| Field | Meaning |
|---|---|
from / to | Date range requested |
firstDataRequest | true on the first load for this symbol/interval |
rows | Optional bar count hint |
Return { bars: [], meta: { noData: true } } when there is nothing in range (do not throw for empty history).
For the hosted demo history/tick protocol, see Demo WebSocket.
Optional: searchSymbols
searchSymbols(userInput, exchange, symbolType, onResultReadyCallback) {
onResultReadyCallback({ searchInProgress: true, items: [] });
fetch(`https://api.example.com/search?q=${encodeURIComponent(userInput)}`)
.then((r) => r.json())
.then((items) => {
onResultReadyCallback({
searchInProgress: false,
items: items.map((it) => ({
symbol: it.symbol,
full_name: it.full_name,
description: it.description,
exchange: it.exchange,
type: it.type || "crypto",
})),
});
})
.catch(() => {
onResultReadyCallback({ searchInProgress: false, items: [] });
});
}Optional: searchSymbolsPaginated
Preferred when present (chart search uses offset: 0, limit: 50). Lab: phase6-searchSymbolsPaginated-lab.html.
searchSymbolsPaginated(options, onResult) {
const { userInput, exchange, symbolType, limit, offset } = options;
const page = yourBackend.searchPage({ userInput, exchange, symbolType, limit, offset });
onResult(page); // SearchResult[] or { searchInProgress, items }
}The callback receives an object { searchInProgress, items } — not a bare array (same for searchSymbols).
Optional: onReady / UDF adapter
Implement onReady to advertise exchanges, symbol types, and supported_resolutions (used by getIntervals() when the symbol has no valid_intervals). Or use UDFCompatibleDatafeed against a UDF HTTP backend. Lab: phase6-onReady-udf-lab.html.
Limitation: GoCharting does not client-aggregate higher timeframes from daily the way TradingView does with daily_multipliers (2D, 6M, …). Return bars for the requested resolution. Details: TV → GC migration — Known limitations.
Putting it together
const datafeed = {
resolveSymbol(symbolName, onResolve, onError) {
/* as above */
},
async getBars(symbolInfo, resolution, periodParams) {
/* as above */
},
searchSymbols(userInput, exchange, symbolType, onResultReadyCallback) {
/* as above */
},
};
createChart("#chart", {
datafeed,
symbol: "BYBIT:FUTURE:BTCUSDT",
interval: "1m",
licenseKey: "demo-550e8400-e29b-41d4-a716-446655440000",
});Checklist
-
resolveSymbolalways callsonResolveoronError - Resolved security includes
exchange/segment/symbol, precision fields, and preferablyexchange_info - Bars include
open/high/low/close(and usuallyvolume+ time) - Empty ranges use
meta.noData: true - Search (if implemented) uses
{ searchInProgress, items }
Next
→ Streaming implementation — push live ticks with subscribeTicks