Live data (OpenAlgo)
The chart depends only on the DataFeed / TradeFeed interfaces, so any source fits behind
a small adapter. OpenAlgo adapters are included.
Historical bars (REST)
import { OpenAlgoDataFeed } from 'openalgo-charts';
const feed = new OpenAlgoDataFeed({ baseUrl: 'http://127.0.0.1:5000', apiKey: 'YOUR_KEY' });
const day = 86400;
const bars = await feed.getBars({
symbol: 'RELIANCE', exchange: 'NSE', interval: '1m',
from: Math.floor(Date.now() / 1000) - 7 * day,
to: Math.floor(Date.now() / 1000),
});
series.setData(bars);from / to are UTC seconds; the adapter converts them to the IST date range OpenAlgo’s
/api/v1/history expects, and normalizes returned timestamps back to UTC seconds.
Epoch seconds and milliseconds are accepted. Strings ending in Z or an explicit
numeric offset use that offset; strings without an offset are interpreted as IST.
The REST adapter translates these exact interval aliases at the request boundary:
| Chart interval | OpenAlgo history interval |
|---|---|
1d, 1D | D |
1w, 1W | W |
1M, MN | M |
Other codes pass through unchanged, including D, W, M and the minute code 1m.
Non-OK HTTP responses reject the load. A JSON response with status: 'error' also
rejects, using its message when present, even when the HTTP status is 200.
Realtime (WebSocket)
LTP / Quote / Depth come from the WS adapter. Feed its LTP ticks through a CandleBuilder
and call series.update():
import { OpenAlgoWsFeed, CandleBuilder } from 'openalgo-charts';
const ws = new OpenAlgoWsFeed({ url: 'ws://127.0.0.1:8765', apiKey: 'YOUR_KEY' });
const builder = new CandleBuilder({ intervalSec: 60, volumeMode: 'ltq-sum' });
// Continue the bucket history already ended in, rather than opening a new one.
builder.seed(bars[bars.length - 1]);
ws.connect();
const stopLtp = ws.onLtp((e) => {
const u = builder.onTick({ time: e.timeSec, price: e.ltp, ltq: e.ltq });
if (u) series.update(u.bar);
});
ws.subscribe('LTP', 'RELIANCE', 'NSE');
function disposeChart() {
stopLtp();
ws.close();
chart.destroy();
}Seed the builder, and seed it again after every reconnect. History normally ends
inside the bar currently forming. An unseeded builder has no current bar, so its first
tick opens a fresh one for that same bucket, which opens at whatever tick price arrived
first instead of the bucket’s true open, and restarts its volume at zero. seed hands it
the last historical bar so ticks fold into it instead.
If you keep your own bar array and push the builder’s output into it, an unseeded builder
also leaves you two entries for that time. setData de-duplicates by time as of 1.0.12
(keeping the later bar), so it no longer draws two candles on top of each other, but the
open is still wrong until the bucket rolls over. Seeding is the actual fix.
When history stops one bucket short of the one forming, the seed is an older bar and
the first tick still opens the current bucket at its own price. The builder knows: the
update carries provisional: true, and builder.reconcile(bar) adopts the open
history reports once a refresh brings it (the extremes widen, the volume takes the
larger value, the close stays with the ticks). DataLoadingController.pushBar(bar, { provisional: true }) keeps that open across
later ticks. After a reconnect, reseed with builder.seed(builder.current()) so the
bucket opened after the gap is provisional too, then refresh.
The WS adapter speaks the documented OpenAlgo protocol (authenticate → numeric-mode subscribe
→ market_data), with connection and control callbacks (onState / onControl).
Market-data frames can carry symbol and exchange on the top-level envelope with
prices inside data. Non-empty identity fields inside data take precedence, followed
by the envelope fields and then the legacy topic fallback.
Real-time order updates
OpenAlgo also streams order lifecycle events (fills, partial fills,
rejections, cancellations) over the same socket (subscribe_orders). This is
an account-level stream (no symbols/modes), pushed by the broker in live mode
or by the sandbox engine in analyze mode. Use it to update on-chart order lines
instantly instead of polling the order book:
ws.onOrderUpdate((e) => {
// e: { orderId, symbol, action, quantity, price, triggerPrice?, pricetype,
// status, filledQuantity, averagePrice, rejectionReason, mode, ... }
const working = e.status === 'open' || e.status === 'trigger pending' || e.status === 'pending';
if (working) upsertOrderLine(e);
else removeOrderLine(e.orderId); // complete / cancelled / rejected
});
ws.subscribeOrders(); // replayed automatically on reconnect
// ws.unsubscribeOrders() to stopstatus is OpenAlgo’s lowercase order vocabulary (open, trigger pending,
complete, rejected, cancelled, …); the adapter maps it to the chart’s
Order['status'] internally (the mapping itself is not a package export). Keep a slow
order-book poll as reconciliation: brokers can deliver the same transition
twice (dedupe on orderId + status + filledQuantity).
After an unexpected close it auto-reconnects with exponential backoff, then
re-authenticates and replays every active subscription. close() is treated as
intentional and never reconnects. onState reports a 'reconnecting' phase in
between. Tune or disable it:
new OpenAlgoWsFeed({
url, apiKey,
reconnect: { enabled: true, baseDelayMs: 1000, maxDelayMs: 30000, maxAttempts: Infinity },
});All-in-one live feed
OpenAlgoLiveDataFeed composes REST history + WebSocket + the candle builder so you get a
seamless history → live seam from a single object:
import { OpenAlgoLiveDataFeed } from 'openalgo-charts';
const live = new OpenAlgoLiveDataFeed({
baseUrl: 'http://127.0.0.1:5000', wsUrl: 'ws://127.0.0.1:8765', apiKey: 'YOUR_KEY',
});
const bars = await live.getBars({ symbol: 'RELIANCE', exchange: 'NSE', interval: '1m', from, to });
series.setData(bars);
// Pass seedFrom to continue the last history bar's bucket seamlessly.
// Pass cumDayVolumeSoFar if using volumeMode: 'day-delta' to diff against the right baseline.
const stopBars = live.subscribeBars(
{ symbol: 'RELIANCE', exchange: 'NSE', interval: '1m', from, to },
(bar) => series.update(bar),
{ seedFrom: bars[bars.length - 1] },
);
function disposeChart() {
stopBars();
live.close();
chart.destroy();
}The optional third argument implements the shared BarSubscriptionOptions contract:
seedFrom, cumDayVolumeSoFar, and onResync. After a successful reconnect,
OpenAlgoLiveDataFeed forwards the socket’s STREAM_RESYNC notification to onResync.
Subscription replay restores delivery; refresh history as well to repair any missed
bars. A bare-chart host should buffer arriving live bars while fetching the current
window with noCache: true, merge those observations before series.setData, and seed
the next subscription from the merged last bar. Keep reconnect monitoring active during
the fetch so a second interruption can supersede the pending repair.
See Custom Data Feeds.
With createWidget(container, { feed: live, ... }), the widget seeds every subscription
and performs this history refresh automatically on onResync, preserving the visible
logical range. It merges live bars received during the fetch into refreshed history.
It keeps the previous chart visible with a stale-history error if the refresh fails or
returns no bars, with display updates paused but buffering and reconnect monitoring
still active. Call widget.reload() to retry; the request keeps noCache: true
until a load succeeds. Manual reload retains its usual fit-to-data behavior. The widget
owns its subscription; the host still owns live.close() during teardown.
Long-running chart lifecycle
Keep every unsubscribe returned by a feed or chart event, and release it before
the chart view goes away. chart.destroy() cleans up chart-owned canvases,
observers, and listeners, but it cannot close a feed that your application
created:
const stopBars = live.subscribeBars(request, onBar, { seedFrom: bars[bars.length - 1] });
const stopState = chart.on('pan', persistViewport);
function disposeLiveChart() {
stopBars();
stopState();
live.close();
chart.destroy();
}For a direct OpenAlgoWsFeed, also keep the functions returned by onLtp,
onDepth, onState, and onControl. Call them during teardown, then call
ws.close() to stop reconnect and heartbeat timers. See
Performance & Operations for bounded
history and browser-memory guidance.
Your API key lives only in your app/browser - never commit it. Verify the exact REST paths and WS message schema against your running OpenAlgo build before production use, and prefer analyzer mode while testing the trade path.
A complete, runnable example (history + WebSocket + chart trading) lives in the repository
under examples/live, which runs on DataLoadingController with refreshOnBarClose, refreshOnGap and refreshWindowBars.