Changing the Symbol
SciFinanceChart.setSymbol
setSymbol switches the chart to another symbol, and optionally another exchange. It keeps the current interval and, unless you pass one, the current period. Studies, panes and drawings stay, and every study recalculates on the new data.
/**
* Switches the chart to another symbol, optionally on another exchange, keeping the current
* interval and period. Studies, panes and drawings stay; every study recalculates on the new
* data. Raises {@link dataLoadStarted}, then `dataRenderComplete({ type: "symbol-change" })`.
*
* Shorthand for `createDataFeed(EDataFeedType.OHLCVRealtime, exchangeName, symbol,
* <current interval>, true, <current period>)`.
*
* @param symbol The symbol in the exchange's own format (`BTCUSDT` on Binance, `BTC-USD` on
* Coinbase; `mapSymbol` converts between them).
* @param exchangeName The exchange to load from. Defaults to the current one.
* @param options.period The period to show once loaded, instead of the current one. `null`
* shows all loaded data with no period.
*/
setSymbol(symbol: string, exchangeName?: string, options?: {
period?: TPeriod | null;
}): Promise<IDataFeed>;
import { BinanceExchange } from "scichart-scitrader";
const chart = financeChartPanel.financeApi; // SciFinanceChart
// Switch to another symbol on the same exchange.
// The interval, period, studies and drawings stay the same
await chart.setSymbol("ETH-USD");
// Switch to another exchange too. The exchange must already be registered with
// defaultExchangeProvider.registerExchange, and the symbol is in that exchange's own format
await chart.setSymbol("ETHUSDT", BinanceExchange.name);
// Switch symbol, and show the last month once the new candles are loaded.
// Pass undefined as the exchange to keep the current one
await chart.setSymbol("SOL-USD", undefined, { period: "1M" });
The promise resolves with the chart's new default data feed once the new candles are drawn.
Converting Symbols Between Exchanges with mapSymbol
Each exchange writes symbols its own way: Binance uses BTCUSDT, Coinbase uses BTC-USD. When you switch exchange, mapSymbol converts the symbol the chart is showing into the new exchange's format, so the user stays on the same market:
import { CoinbaseExchange, mapSymbol } from "scichart-scitrader";
// mapSymbol(symbol, from, to) converts a symbol from one exchange's format to another's.
// "from" and "to" are exchange names, as registered: here, the built-in Binance and Coinbase
mapSymbol("BTCUSDT", "Binance", "Coinbase"); // "BTC-USD"
mapSymbol("BTC-USD", "Coinbase", "Binance"); // "BTCUSDT"
// It returns null when it can't convert: an exchange it doesn't know, or a symbol it can't read
mapSymbol("BTCXYZ", "Binance", "Coinbase"); // null
// Example: move the chart from Binance to Coinbase, keeping the same market
const coinbaseSymbol = mapSymbol("BTCUSDT", "Binance", "Coinbase");
if (coinbaseSymbol) {
await chart.setSymbol(coinbaseSymbol, CoinbaseExchange.name);
}
The match is approximate for stablecoin and fiat quotes: Binance's BTCUSDT (priced in the USDT stablecoin) maps to Coinbase's BTC-USD (priced in US dollars). They trade close to 1:1, but they are different markets. mapSymbol knows the built-in Binance and Coinbase exchanges; for your own exchange, add an ISymbolNormalizer to symbolNormalizers.
The mapSymbol function, with its TSDoc
/**
* Convert a symbol from one provider's native format to another's.
*
* Equivalence is approximate for stablecoin/fiat pairs (USDT on Binance maps
* to USD on Coinbase — those are different instruments that trade close to
* 1:1). Returns `null` when the source provider isn't registered, the target
* provider isn't registered, or no quote equivalent exists.
*
* @example
* mapSymbol("BTCUSDT", "Binance", "Coinbase") // "BTC-USD"
* mapSymbol("BTC-USD", "Coinbase", "Binance") // "BTCUSDT"
* mapSymbol("BTCXYZ", "Binance", "Coinbase") // null (unparseable)
*/
function mapSymbol(symbol: string, from: string, to: string): string | null;
What happens when the symbol changes
The chart doesn't hold candles itself. It draws its default data feed: an object that knows one exchange, one symbol and one interval, and keeps the chart's data up to date from them. setSymbol replaces that feed with a new one. In practice:
- The chart raises
dataLoadStarted. ASciFinanceChartViewcovers the old candles with its loading overlay. - The old feed stops. Its live subscription is closed, and a load it still has in flight is cancelled, so late data for the old symbol can't reach the chart.
- The exchange is looked up by name.
exchangeNameis the name the exchange was registered under withdefaultExchangeProvider.registerExchange(...)(see Data sources). Every exchange, built in or your own, is anIRemoteExchange. - History is fetched. The new feed asks for enough candles to fill the period, plus a buffer and whatever warm-up the chart's indicators need. The request goes through the exchange's candle store first: with
idbPriceStore, candles already cached in IndexedDB are read locally, and only the missing ranges are requested from the exchange withIRemoteExchange.getCandles(symbol, interval, start, end, limit). Large ranges are split into requests of at most the exchange'scandleLimitcandles. - The chart redraws. Studies recalculate, the view lands on the period, and the chart raises
dataRenderCompletewithtype: "symbol-change". - Live updates start. The feed subscribes to
IRemoteExchange.getCandleStream(symbol, interval), an RxJSObservableof the current candle, and updates the last candle (or appends a new one) as it changes. Exchanges without live data returnEMPTY. - Older data loads on demand. When the user pans left past the loaded data, the chart fetches the next page of older candles the same way, from the cache first, then the exchange.
So the same setSymbol call works whether the data comes from Binance, a CSV file or your own server: what changes is the IRemoteExchange registered under exchangeName. To connect your own source, see Building a Custom Exchange.
To find out what the chart is currently showing, read the default feed:
// chart.dataFeeds is a Map of every data feed on the chart, keyed by feed id.
// The default feed is the one the price candles are drawn from
const feed = [...chart.dataFeeds.values()].find((f) => f.isDefault);
// Its exchange, symbol and interval are what the chart is showing
console.log(feed.exchangeName, feed.symbol, feed.interval); // e.g. "Coinbase" "BTC-USD" "6h"
SciFinanceChart.createDataFeed: Changing Symbol and Interval Together
setSymbol changes the symbol, and changeInterval changes the interval. Calling one after the other works, but the chart loads candles twice: once for the new symbol at the old interval, then again at the new interval. createDataFeed does both in one step, with one load.
It does this by creating a new data feed: the object that loads one exchange's candles for one symbol at one interval, and keeps them up to date. See Data Manager and Data Feeds for how feeds fill the chart's data. The chart draws its candles from its default feed, so making the new feed the default switches the chart to it. setSymbol does exactly this, with the current interval.
createDataFeed(
feedType: EDataFeedType, // what kind of data: candles, or order-book data
exchangeName: string, // the exchange's registered name, e.g. CoinbaseExchange.name
symbol: string, // in that exchange's own format, e.g. "ETH-USD"
interval: CandleInterval, // the candle size
isDefault: boolean, // true: replace what the chart shows; false: add an extra feed
period?: TPeriod | null // the period to show once loaded; null for none
): Promise<IDataFeed>
| Parameter | What to pass |
|---|---|
feed | EData for the chart's price candles, with live updates. EData for order-book data, which order-book studies use |
exchange | The name the exchange was registered under. Use the exchange's name, e.g. Coinbase |
symbol | The symbol in that exchange's own format. See mapSymbol |
interval | The candle interval, e.g. Candle |
is | true to switch the chart to this feed, as set does. false to add a feed beside the default one, for studies that need other data. The chart must already have a default feed |
period | Optional. The period to show once the candles are loaded, e.g. "1. The first load fetches just enough candles for it |
import { CandleInterval, CoinbaseExchange, EDataFeedType, IDataFeed } from "scichart-scitrader";
// Switch the chart to ETH-USD daily candles over the last year, in one load.
// The chart raises dataLoadStarted now, and dataRenderComplete({ type: "symbol-change" })
// once the new candles are drawn
const feed: IDataFeed = await chart.createDataFeed(
EDataFeedType.OHLCVRealtime, // candles, with live updates
CoinbaseExchange.name, // the exchange, as registered
"ETH-USD", // the symbol, in Coinbase's format
CandleInterval.ONE_DAY, // the new interval
true, // make this the default feed: the chart switches to it
"1Y" // optional: show the last year once loaded
);
// The promise resolves with the new feed, once its first candles are loaded
console.log(feed.id, feed.symbol, feed.interval);
createDataFeed creates the feed, loads its first candles, subscribes it to live updates, and resolves with it.
isDefault: truereplaces the chart's default feed. The old feed stops, as described in What happens when the symbol changes, and the chart raises the same events assetSymbol.isDefault: falseadds a feed beside the default one. To remove it later, pass itsidtochart.removeDataFeed(id).EDataFeedType.OrderBookresolves withundefinedwhen the exchange has no order-book source. See Order Book Data.
The IDataFeed interface and EDataFeedType enum, with their TSDoc
interface IDataFeed extends IDeletable {
readonly feedType: EDataFeedType;
readonly id: string;
readonly exchangeName: string;
readonly symbol: string;
readonly interval: CandleInterval;
readonly dataManager: IDataManager;
readonly sourceIds: DataSourceId[];
readonly isDefault: boolean;
readonly subscriptions: Subscription[];
getName(): string;
/**
* optional cancellation signal. When aborted the in-flight
* `exchange.getCandles` rejects via {@link abortable} and the feed
* returns without writing to `dataManager`. Used by `DataFeedBase.change`
* to keep a stale response from a prior `(symbol, interval)` from
* overwriting the new feed's data.
*/
loadInitialData(signal?: AbortSignal): Promise<void>;
/**
* Load a specific historical batch by explicit range. Plan
* parallel-backward-loads requires this shape so the coordinator can
* compute distinct, non-overlapping ranges per concurrent spawn — the
* old `targetStart`-only signature implicitly used `this.startDate` as
* the right edge, which prevented concurrent calls from claiming
* distinct slices.
*
* @param loadStart Start time (ms) of the batch to fetch. The feed
* may clamp this against its known listing-date floor.
* @param loadEnd End time (ms) of the batch — the boundary just
* AFTER the newest candle the spawn is allowed to claim.
* @param signal Cancellation token.
* @returns A `LoadResult` describing what was actually inserted.
*/
loadPreviousData(loadStart: number, loadEnd: number, signal?: AbortSignal): Promise<LoadResult>;
/**
* load candles to the RIGHT of the existing dataset. Used
* when the live WebSocket can't keep up (parked-overnight reconnect
* gaps, slow networks, symbol-switch subscription lag).
*
* @param targetEnd Optional maximum end time (ms) the caller wants
* covered. When omitted, fetches up to `Date.now()`. Future times
* are clamped — Binance returns nothing beyond now.
* @param signal optional cancellation signal. When
* aborted the caller's await rejects with an AbortError; the
* underlying request still completes (see {@link abortable}).
* @returns A `LoadResult` describing what was actually appended.
* `count: 0` is the steady-state expected return when no gap
* exists (`lastX` is already current).
*/
loadNextData?(targetEnd?: number, signal?: AbortSignal): Promise<LoadResult>;
subscribeToUpdates(): void;
change(symbol: string, interval: CandleInterval): Promise<void>;
/**
* tear down the live-update subscription
* without touching loaded data. Used by the stale-gap recovery to stop a
* reconnected-but-stale-anchored stream from appending wrong-timestamped
* candles while a catch-up back-fill runs. Pairs with
* {@link resumeLiveUpdates}.
*/
pauseLiveUpdates(): void;
/**
* re-open the live-update subscription
* after {@link pauseLiveUpdates}. For the realtime feed this re-seeds the
* trade accumulator from a fresh current candle, so post-wake ticks resume
* at the correct wall-clock time instead of the pre-sleep anchor.
*/
resumeLiveUpdates(): void;
/**
* invoked by the live-update handler when
* an incoming candle reveals the feed has fallen behind the wall clock
* (see {@link isStale}). The host chart wires this to its recovery routine
* (pause live → back-fill → re-seed). When unset, the feed applies the
* incoming candle as normal.
*
* Optional: only meaningful for candle feeds.
*/
onStaleGapDetected?: () => void;
/**
* `true` when the newest loaded candle is
* more than {@link LOAD_BUFFERS.staleCandleThreshold} intervals behind the
* wall clock. Cheap O(1) check. Used by both the live-update handler and
* the host chart's `online` backstop.
*
* Optional: only candle feeds implement it.
*/
isStale?(): boolean;
/**
* Earliest candle openTime (ms since epoch) the feed has confirmed
* for its current `(symbol, interval)`. Populated by an eager listing-
* date probe in `loadInitialData`, or by D11's incremental floor
* detection after a backward load. Returns `null` when the floor is
* unknown (probe failed, feed type doesn't support a listing-date
* concept, or no partial-response signal has fired yet).
*
* Optional: only OHLCV-style feeds with a probe path implement it.
*/
getKnownEarliestCandle?(): number | null;
/**
* force any pending batched
* backward-load chunks to commit immediately. Called by the
* historical-load coordinator at the end of a cascade so the last
* partial batch isn't stranded.
*
* Optional: only feeds that buffer commits implement it.
*/
flushPendingCommits?(): void;
}
enum EDataFeedType {
OHLCVRealtime = "OHLCVRealtime",
OHLCV1second = "OHLCV1second",
Trade = "Trade",
UserOrder = "UserOrder",
OrderBook = "OrderBook"
}
TypeScript Example: Changing the Symbol
This is the example from Symbols, Intervals and Periods. Its symbol buttons call setSymbol. That page also has the React version.
The code for this example is found below.
- TS
import "scichart-scitrader/scitrader.css";
import {
CandleInterval,
carryPeriodToInterval,
CoinbaseExchange,
defaultExchangeProvider,
idbPriceStore,
IntervalSelector,
OHLCStudy,
PeriodSelector,
SciFinanceChartPanel,
VolumeStudy,
} from "scichart-scitrader";
// This example expects this HTML:
// <div style="display: flex; flex-direction: column; height: 100%">
// <div id="toolbar"></div>
// <div id="chart" style="flex: 1; min-height: 0"></div>
// </div>
// Register a data source, once, at app startup
defaultExchangeProvider.registerExchange(CoinbaseExchange, idbPriceStore);
const SYMBOLS = ["BTC-USD", "ETH-USD", "SOL-USD"]; // Coinbase's symbol format
// Coinbase offers 1m, 5m, 15m, 1h, 6h and 1d candles
const INTERVALS = [CandleInterval.FIFTEEN_MINUTES, CandleInterval.ONE_HOUR, CandleInterval.SIX_HOURS, CandleInterval.ONE_DAY];
async function createChart() {
const toolbar = document.getElementById("toolbar");
Object.assign(toolbar.style, toolbarStyle);
const financeChartPanel = await SciFinanceChartPanel.create(document.getElementById("chart"), {
exchangeName: CoinbaseExchange.name, // name="Coinbase" matches the built-in CoinbaseExchange
symbol: SYMBOLS[0],
interval: CandleInterval.SIX_HOURS,
period: "1M", // show the last month
studies: [new OHLCStudy(), new VolumeStudy()],
});
const chart = financeChartPanel.financeApi; // SciFinanceChart
// Symbol: plain buttons. setSymbol keeps the exchange, interval, period and studies
const symbolButtons = SYMBOLS.map((symbol) => {
const button = document.createElement("button");
button.textContent = symbol;
button.onclick = () => {
symbolButtons.forEach((b) => Object.assign(b.style, buttonStyle(b === button)));
chart.setSymbol(symbol);
};
Object.assign(button.style, buttonStyle(symbol === SYMBOLS[0]));
toolbar.appendChild(button);
return button;
});
// Interval: the SDK's IntervalSelector
const intervals = IntervalSelector.create(toolbar, {
value: CandleInterval.SIX_HOURS,
intervals: INTERVALS,
onChange: (interval) => {
// Keep about the same span on screen, adjusted to suit the new interval
const period = carryPeriodToInterval(periods.value, interval);
periods.update({ value: period, currentInterval: interval });
chart.changeInterval(interval, period);
},
});
// Period: the SDK's PeriodSelector. "Free", or clicking the selected period again, gives null
const periods = PeriodSelector.create(toolbar, {
value: "1M",
currentInterval: CandleInterval.SIX_HOURS, // greys out periods that don't suit the interval
onChange: (period) => chart.setPeriod(period),
});
// When the page goes away: intervals.destroy(); periods.destroy(); financeChartPanel.cleanup();
return { financeChartPanel, intervals, periods };
}
// The toolbar uses SciTrader's UI colours: its --st-ui-* CSS variables, with the stylesheet's defaults
const toolbarStyle: Partial<CSSStyleDeclaration> = {
display: "flex",
flexWrap: "wrap",
alignItems: "center",
gap: "8px",
padding: "8px",
background: "var(--st-ui-background, #2b2839)",
borderBottom: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
};
const buttonStyle = (active: boolean): Partial<CSSStyleDeclaration> => ({
padding: "6px 14px",
fontSize: "14px",
fontFamily: "var(--st-font-family, sans-serif)",
color: "var(--st-ui-text, #ffffff)",
background: active ? "var(--st-ui-accent, #7464ff)" : "var(--st-ui-surface, #373447)",
border: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
borderRadius: "4px",
cursor: "pointer",
});
createChart();