Commercial
Moving from TradingView or Highcharts
Six ways products embed a chart today, and what the move costs from each one.
Read this page as Markdown, or take the whole documentation in one file.
Find Your Integration, Not Your API
Almost every product that draws financial charts has arrived at one of six shapes. The API mapping is on the migration page; this page is about the shape you are in, because that is what decides how long the move takes and what is easy to miss.
Find the row that describes you.
| Your shape | Typical signs | Effort |
|---|---|---|
| A widget in a trading terminal | TradingView Advanced Charts in an iframe, one chart, saved layouts per user | A week |
| A chart on every instrument page | One chart per symbol, server-rendered pages, no drawings | A day or two |
| A research or screening product | Highcharts Stock, many small charts, comparisons, exports | Two to four days |
| A portfolio tracker | Performance lines, allocation pies, risk matrices | Two to four days |
| A white-label platform you resell | One codebase, many brokers or prop firms, your customers' branding | A week, plus a licensing conversation |
| Charts in a dashboard | Highcharts for everything, finance is one section | A day per chart family |
Everything below assumes one fact that catches everybody once: timestamps are milliseconds since the Unix epoch, in UTC. Not seconds, not business-day objects, not local dates.
A Widget in a Trading Terminal
You embed TradingView Advanced Charts, users draw on it, and you save their layouts, studies and drawings against their account so a chart follows them between devices.
What carries over as it is. Your datafeed. The resolveSymbol, getBars, subscribeBars
and searchSymbols shapes are deliberately close, and tradingViewDatafeed wraps an existing
one so you can run it unchanged on day one.
import { tradingViewWidget } from "@ortex-charts/migrate";
// Your existing widget options and your existing datafeed.
const shell = tradingViewWidget({
container: "chart",
symbol: "BTCUSD",
interval: "60",
datafeed: myTradingViewDatafeed,
disabled_features: ["header_symbol_search"],
});
What you have to decide. Where saved state lives. Advanced Charts offers a save/load
server contract and a chart storage service; ORTEX Charts has neither, on purpose.
shell.getLayout() returns JSON and shell.setLayout(json) puts it back, so the store is the
one you already have: a column on the user row, a document, a key in your own service.
const shell = createChartShell(el, {
symbol, datafeed,
layout: await api.loadChartLayout(userId),
onLayoutChange: (layout) => void api.saveChartLayout(userId, layout),
drawingsStore: {
load: (sym) => api.loadDrawings(userId, sym),
save: (sym, drawings) => api.saveDrawings(userId, sym, drawings),
},
});
Drawings are stored per symbol rather than inside the layout, because a trend line on one instrument means nothing on another.
What will bite. Three things, in the order people hit them.
- Callbacks became promises.
getBarsreturns{ bars, noMoreData }instead of callingonHistoryCallback. - There is no iframe. The chart is in your DOM, which is mostly a gift: your fonts, your
CSS variables, your dev tools. It also means your CSS resets now apply to it. If your app
sets
* { box-sizing: content-box }or a globalcanvasrule, you will see it. - Feature flags became options. The
disabled_featuresstring list maps onto typed switches.fromTVWidgetOptionstranslates the common ones and tells you what it could not.
What you gain that matters here. No attribution requirement on a licensed build, and no restriction to services offered free of charge. The free Advanced Charts agreement requires the TradingView mark to stay visible and the service to be free to the public, which a subscription terminal is not.
A Chart on Every Instrument Page
One chart per token, ticker or fund. Users read it; few of them draw on it. The page is often server-rendered and the chart is the only interactive thing on it.
This is the fastest migration of the six, and the one where payload matters most: the chart is on the critical path of a page a search engine sends people to.
import { createFinancialChart } from "@ortex-charts/financial";
const chart = createFinancialChart(el, {
data: bars, // straight from your API
seriesType: "candlestick",
volume: true,
theme: "dark",
tooltip: { mode: "hovered", template: "{name} {value}\n1d {value % value[-1]}" },
});
Drop the shell if you do not need it. createFinancialChart is the chart without the
toolbar; a page with its own period buttons and its own symbol header does not need a second
set. That is 59.0 KB rather than 89.9.
Server rendering. There is no server renderer, here or in the alternatives. Render the page, mount the chart on the client, and give the container a fixed height so the page does not jump when it appears.
Instrument pages are where the free plan usually stops applying. If the page carries advertising or sits behind any kind of subscription, it is a commercial use.
A Research or Screening Product
Highcharts Stock behind a screener: many charts, often small, frequently compared against each other, and usually exportable.
Start with the translator. It takes the options object you already have.
import { translateHighcharts } from "@ortex-charts/migrate";
const { options, data, unsupported } = translateHighcharts(myHighchartsOptions);
console.warn("did not carry over:", unsupported); // read this list once, then delete the line
const chart = createFinancialChart(el, { ...options, data });
unsupported is the point of it. It names what has no equivalent rather than dropping it
silently, so the port is a checklist.
Three things to plan for.
- Comparisons. A Highcharts compare axis becomes a second series plus a percentage or
indexed scale:
rightPriceScale: { mode: "percentage" }, or"indexedTo100". - Exporting. There is no exporting server.
downloadPNG(canvas)androwsToCSVcover image and data export from the browser; a PDF report is your renderer's job. - Many small charts. If a chart is a cell in a results table, it should not be a chart at
all:
@ortex-charts/litedraws a sparkline in 3.3 KB with no engine behind it.
A Portfolio Tracker
Performance lines, allocation by sector or asset, contribution and risk matrices. Only some of what you draw is a price chart.
The financial chart covers the performance line. The rest is @ortex-charts/viz, which shares
the engine, the theme and the maths:
| What you draw now | What draws it here |
|---|---|
| Highcharts line or area with a percentage axis | createFinancialChart with rightPriceScale: { mode: "percentage" } |
| Pie or donut of allocation | createPieChart |
| Heatmap of returns by month or sector | createHeatmap |
| Treemap of holdings | createTreemap |
| Bar chart of contribution | createCategoryChart |
| Scatter of risk against return | createXYChart with kind: "scatter" |
| Sankey of flows in and out | createSankeyChart |
translateHighchartsCategory and translateHighchartsPie take the category and pie options
directly, so the non-price charts move with the same checklist as the price ones.
Watch the benchmark line. A tracker almost always draws the portfolio against an index.
Two series on a percentage scale, both anchored at the same first bar, is the correct shape;
align: "exact" keeps a benchmark with different trading days on the portfolio's timeline
rather than inventing bars for it.
A White-Label Platform You Resell
You build one trading platform and sell it to brokers or prop firms, each with their own branding. The chart inside it is TradingView, and every one of your customers inherits that relationship.
This is the shape where the licence, not the API, is the reason to move.
- The mark is removable. A White-label licence removes the ORTEX mark for one product on one domain. Your customers see your platform, not ours.
- One licence, many deployments. If you ship the same product to fifty brokers, that is an OEM conversation rather than fifty licences: redistribution rights, a source drop, and a price that reflects the deployment rather than the seat count.
- Your customers do not need their own agreement. Under OEM the chart is part of your product.
Technically it is the terminal migration above, with one addition: whatever your branding system already does per customer, the chart follows.
createChartShell(el, {
symbol, datafeed,
theme: brokerTheme, // your customer's palette, as tokens
branding: { visible: false }, // with a white-label or OEM key
toolbar: { symbol: false }, // their header already names the instrument
});
Set the licence key once at start-up with setLicenseKey, not per chart.
Charts in a Dashboard
Highcharts draws everything you have, and the financial charts are one section among many. You are not replacing Highcharts everywhere; you are replacing it where it is weakest.
Move the price and time-series charts, keep the rest, and let the two coexist. Nothing here
registers globals or touches the DOM outside its own container. When the financial section is
done, the rest of @ortex-charts/viz is there if you want it, and the two libraries can run on
one page for as long as you like.
The one thing worth doing on day one is the theme: pull your palette into theme tokens so the new charts match the old ones while both are on screen.
What Every Migration Should Check Before It Ships
- Timestamps in milliseconds, UTC. Every conversion bug traces back here.
- The first paint on your slowest page. That is the number that justified the move.
- A symbol with a short history. Week-over-week comparisons and indicators need enough bars; make sure they degrade rather than throw.
- A symbol with gaps. Halts, holidays and thin instruments.
- Your saved layouts. Load an old one, change something, save it, reload.
- The licence key on the deployed origin, not just on localhost.
- The mark, if you expect it to be gone. It goes when the key says it can, and not before.