Skip to content

The library

Host the chart yourself

The chart as a software library rather than a frame: you host the files, you write the datafeed, and the whole API is in your own page with no boundary in the middle. It is built for trading platforms and exchanges. If what you want is a chart in an article, you want the embed instead, and this page will try to talk you into it.

Which one do you want

Both draw the same chart, from the same engine, with the same drawing tools and indicators. What differs is who holds the files and where your code runs.

EmbedLibrary
What you add to your pageA <div> and one <script>A bundle you serve, plus the code that mounts it
Who hosts the chart filesWe doYou do
Where your code runsYour page talks to a frame across an origin boundaryIn your page, calling the chart directly
Your market dataOptional. Public feeds by default, or your own via data-datafeedRequired. There is no default feed
Getting itNothing to ask for. Copy the snippetOn request, under an agreement
CostFree, no account, no keyCommercial
Our mark on the chartAlways. It is what makes the embed freeYours to configure, including off
UpgradesAutomatic. We ship, your chart changesYou choose when, by replacing the files
The last two rows are the reason most integrations should be embeds and the reason a few cannot be. Automatic upgrades are a feature until the day a chart on your trading screen changes without you shipping anything; then they are the thing you needed control of. If that sentence describes your product, read on. If it does not, the embed is a smaller, cheaper, faster answer and it is not a lesser one.

What you are taking on

Named before anything else, because these are the parts that are invisible from a feature list and are most of the work.

YoursWhat it means in practice
Serving the filesA bundle, a stylesheet and around ninety lazily-loaded chunks, served from your own origin with correct caching. The chunks are content-hashed, so they are safe to cache forever; the entry is not.
The datafeedThere is no default feed and no fallback. Until you implement 10 methods, two of them required, the chart draws nothing. See Methods you implement, which is the same contract in both products.
UpgradesA new version is a new set of files you deploy. Nothing reaches your users until you do it, which is the point, and it also means a fix does not reach them until you do it.
Untrusted indicators, if you offer themRunning code your users wrote needs the sandbox guest page on a separate site. It ships in the package; deploying it is yours, and skipping it means a script can freeze the tab it runs in.
Support for your own integrationWe answer questions about the library. The chart inside your product is your product.

What you get

One framework-agnostic bundle with no runtime dependencies, and the full API rather than the subset a message channel can carry.

SurfaceCountWhat it covers
Widget options63Everything settable at construction: the symbol, the interval, the theme, the locale, the feature set, the save-and-load adapter.
Widget methods81The chart as an object: symbol and interval, theme, layout, screenshots, save and load, subscriptions.
Chart methods78One chart within it: drawings, studies, trading lines, the price scale, coordinate conversion, marks.
Datafeed methods10The contract you implement. Two required, the rest optional and each one a feature that switches itself off when absent.
Every entry carries a status of exact, compatible or partial, and the package ships that table as compatibility-report.json alongside the code. Read it before promising a specific behaviour to your own users: partial means the method exists and answers, not that it does everything its name suggests. getCompatibilityManifest() returns the same object at runtime.

Use your own pair selector

If your application already has a pair selector, include 'header_symbol_search' in the widget's disabled_features array. This removes the chart toolbar's symbol button while keeping its other controls. Your own selector can still switch the chart through widget.setSymbol(symbol, interval).

Hide the built-in pair selector
disabled_features: ['header_symbol_search']

Chart settings close when you click outside the floating panel. Changes apply immediately; closing the panel keeps them. Its colour pickers and menus remain usable.

Trading line styles

Order and position adapters expose matching set…() and get…() methods for every style. Customize colors, fonts, line dashes, horizontal and vertical padding, left and right insets, outline width, independent body, quantity and button radii, button width and icon stroke. Position adapters also expose P&L colors and protection-label and drag-out button styles. All are included in the compiled and minified distributions. See the embed styling guide for the complete property table and defaults; turn a property such as bodyPaddingHorizontal into setBodyPaddingHorizontal() or getBodyPaddingHorizontal().

Style a position adapter
const position = widget.createPositionLine({ bodyAlignment: 'left', bodyPaddingLeft: 16 })
  .setPrice(100).setQuantity('2').setText('Long')
  .setBodyFont('600 14px system-ui').setQuantityFont('12px system-ui')
  .setBodyPaddingHorizontal(10).setBodyPaddingVertical(6)
  .setBodyPaddingRight(16).setBodyBorderWidth(2)
  .setBodyBorderRadius(12).setQuantityBorderRadius(4).setButtonBorderRadius(100)
  .setButtonWidth(28).setButtonIconWidth(2)
  .setBodyBackgroundColor('#ffffff').setBodyTextColor('#166534')
  .setPnlPositiveColor('#16a34a').setPnlNegativeColor('#dc2626')
  .setProtectLabelFont('600 12px system-ui')
  .setProtectLabelPaddingHorizontal(8).setProtectLabelPaddingVertical(4)
  .setProtectLabelBorderRadius(8).setProtectLabelBorderWidth(1)
  .setProtectLabelBackgroundColor('#ffffff').setProtectLabelTextColor('#334155');

position.setBodyBorderRadius(null); // inherit the shared borderRadius again
position.setPnlPositiveColor(null); // follow the chart’s up color again

Keep the latest candles clear

Order and position lines support setBodyAlignment('left' | 'right') and getBodyAlignment(). You can also pass { bodyAlignment: 'left' } to either creation method, including on widget.activeChart(). Right is the default. Left moves the whole group to the plot's left edge: quantity, label, P&L and available action buttons. Position TP/SL, breakeven and drag-preview labels follow. Line length and price-axis placement stay independent. Use bodyPaddingLeft: 16 or setBodyPaddingLeft(16) to inset left-aligned controls by 16 pixels. This API is available in the standalone build, including the minified distribution.

Left-align trading controls
const position = widget.createPositionLine({ bodyAlignment: 'left', bodyPaddingLeft: 16 })
  .setPrice(100).setQuantity('1').setText('Long');
const order = widget.createOrderLine({ bodyAlignment: 'left', bodyPaddingLeft: 16 })
  .setPrice(95).setText('Buy limit');

// Call on container resize; choose the breakpoint for your layout.
function alignLineControls(containerWidth: number) {
  const alignment = containerWidth < 600 ? 'left' : 'right';
  position.setBodyAlignment(alignment);
  order.setBodyAlignment(alignment);
}

Tint trading sessions

widget.setSessions(bands) tints sessions behind the bars and paints a matching ribbon on the time axis; widget.getSessions() reads them back, and DEFAULT_US_SESSIONS is exported for the two US equity bands. The chart ships with none, because crypto trades around the clock. A band's from and to are HH:mm read on the symbol's own clock, the zone resolveSymbol reported, unless the band names its own timezone; to at or before from wraps midnight, and days (0 is Sunday, 6 is Saturday) defaults to weekdays. The colour carries its own alpha, in rgba() or hex-with-alpha form.

Session bands on the symbol's clock
import { DEFAULT_US_SESSIONS } from '@warburgai/vp-charts';

widget.setSessions(DEFAULT_US_SESSIONS);
widget.setSessions([
  { name: 'Frankfurt', from: '09:00', to: '17:30', color: 'rgba(41, 98, 255, 0.05)' },
  { name: 'Tokyo', from: '09:00', to: '15:00', color: '#ff98000d', timezone: 'Asia/Tokyo' },
]);
widget.setSessions([]);   // clears them

Earlier builds read the hours in UTC whatever the symbol’s zone, so a US pre-market band landed in the middle of the New York night. A band with no zone on a UTC symbol draws exactly as before.

Feed the footprint chart

The footprint chart type draws volume per price level inside each bar, and it draws only what your datafeed supplies; it never invents a distribution from the candle. Three optional ways to supply it, and they compose: levels on the bars getBars returns, getFootprint(symbolInfo, resolution, period, onResult, onError) for a history window, and subscribeTrades / unsubscribeTrades for live prints. The last two are called only while the footprint type is showing, so implementing them costs nothing on a candle chart.

Order flow in the datafeed
const feed = {
  ...bars,
  getFootprint(symbolInfo, resolution, { from, to }, onResult, onError) {
    // from / to in SECONDS; each slice's time is the bar's open in MILLISECONDS
    fetchFlow(symbolInfo.ticker, resolution, from, to).then(
      (rows) => onResult(rows.map((r) => ({ time: r.openMs, levels: r.levels, levelStep: 0.5 }))),
      (e) => onError(String(e)),
    );
  },
  subscribeTrades(symbolInfo, resolution, onTrade, guid) {
    tapes.set(guid, openTape(symbolInfo.ticker, (t) =>
      onTrade({ price: t.px, size: t.qty, side: t.aggressor, timestamp: t.ms })));
  },
  unsubscribeTrades(guid) { tapes.get(guid)?.close(); tapes.delete(guid); },
};

On a bar, absent and empty are different facts: no levels means you supplied no order flow for that bar and the column is left blank; levels: [] means nothing traded. levelStep is the price grid the levels sit on (absent: the symbol's tick, then the levels' own spacing) and footprintSource grades the view as exchange_trades, sub_candles or synthetic, with anything below the first drawn with a quality ring. A slice from getFootprint requires levels; a trade's side is the aggressor and it lands in the bar its timestamp falls in. The same three methods are available to a hosted embed through its datafeed bridge, and the fields are described on the embed datafeed reference.

What is deliberately not in it

Not includedWhy
Market dataWe distribute a charting engine, not a data licence. The embed's public feeds are a convenience attached to a free product, and they do not come with the library.
A serverNothing in the package calls home. There is no licence check at runtime, no telemetry and no request to us, which is also why an air-gapped deployment works and why we cannot tell you how many charts you are running.
The community indicator shopReading and running indicators other people published is a hosted service tied to an account. The scripting ENGINE is in the package; the catalogue is not.

Asking for it

The library is not a download link, and that is the one thing it has in common with every other product of its kind: it ships under an agreement, so there is a conversation before there are files.

Write to [support@verticalprop.com](mailto:support@verticalprop.com) with what you are building, roughly how many people will use it, and where the market data is coming from. That last one matters more than it looks: it is the part with no default, and it is where an evaluation usually stalls.

You do not need access to start. Every method, event and object on this site behaves identically in both products, so an integration written against the embed is most of the way to a library one: what changes is how the chart is created and that the calls stop being asynchronous. Installing it describes exactly what moves.