@quatick/quatick-charts · Version 2.0.0 · WebGL-accelerated
Complete reference for the Quatick Charts SDK. Professional-grade GPU-accelerated charting engine designed for institutional trading platforms. Render 10M+ data points at 60fps with sub-10ms WebSocket-to-pixel latency. TypeScript-native, plugin-first architecture built on WebGL 2.0 and Web Workers.
Getting Started
Installation
Install via npm, yarn, or pnpm. The package ships as an ES module with full TypeScript declarations.
bash
npm install @quatick/quatick-charts
# or
pnpm add @quatick/quatick-charts
# or
yarn add @quatick/quatick-charts
Peer dependencies: React ≥ 18, TypeScript ≥ 5.0. WebGL 2.0 is required for GPU rendering. The library gracefully falls back to Canvas 2D on unsupported hardware.
Chart Constructor
Import Chart or use the convenience factory createChart(). Both accept the same configuration object.
Prop
Type
Default
Description
container*
string | HTMLElement
—
CSS selector or DOM element to mount the chart canvas into.
chartType
RendererType
"candlestick"
Initial chart type. Can be changed at runtime via chart.setChartType().
theme
"dark" | "light"
"dark"
Built-in theme preset. Custom themes via ThemeAPI.
renderer
"webgl" | "canvas"
"webgl"
Rendering backend. WebGL 2.0 for maximum performance, Canvas 2D as fallback.
showVolume
boolean
true
Render volume histogram bars in the lower section of the main pane.
debug
boolean
false
Enable debug overlay showing FPS, render times, and WebGL diagnostics.
Pass an array of Candle objects to the chart. The data must be sorted chronologically by time (Unix timestamp in seconds).
Prop
Type
Default
Description
time*
number
—
Unix timestamp in seconds (number of seconds since epoch).
open*
number
—
Opening price of the bar.
high*
number
—
Highest price of the bar.
low*
number
—
Lowest price of the bar.
close*
number
—
Closing price.
volume*
number
—
Volume traded during the bar period.
typescript
import { Chart } from '@quatick/quatick-charts';
const chart = createChart({ container: '#chart', theme: 'dark' });
// Candle data: sorted array of { time, open, high, low, close, volume }
const candleData = [
{ time: 1710000000, open: 43200, high: 43850, low: 43100, close: 43750, volume: 1250 },
{ time: 1710003600, open: 43750, high: 44200, low: 43600, close: 44100, volume: 1800 },
// ... more candles
];
// Load data (replaces existing)
chart.setData(candleData);
// Render the chart
chart.render();
// Update a single bar in real-time (append or update last bar)
chart.updateCandle({
time: Date.now() / 1000,
open: 44100,
high: 44300,
low: 44050,
close: 44250,
volume: 320,
});
setChartType() — Switching Chart Types
Change chart type at runtime without losing data or indicators:
typescript
// Switch chart type at runtime
chart.setChartType('renko'); // Renko bricks (ATR auto-sizing)
chart.setChartType('kagi'); // Kagi lines
chart.setChartType('candlestick'); // Back to candlestick
// Supported types:
type RendererType =
| 'candlestick'
| 'hollow-candle'
| 'volume-candle'
| 'line'
| 'step-line'
| 'baseline'
| 'heikin-ashi'
| 'area'
| 'renko'
| 'line-break'
| 'point-figure'
| 'kagi';
// Programmatic view control
chart.resetView(); // Reset zoom to fit all data
chart.fitContent(); // Fit data to viewport
// Get current visible time range
const range = chart.getVisibleRange();
// Returns: { from: number, to: number, barCount: number }
// Set visible range explicitly
chart.setVisibleRange({ from: 1710000000, to: 1710086400 });
Plugin Architecture
The chart engine is built entirely around composable plugins: renderers, indicators, overlays, interactions. The PluginRegistry manages the plugin lifecycle.
Renderers
• Candlestick
• Hollow Candle
• Renko
• Kagi
• Line Break
• P&F
• Volume Candle
• Area
• Heikin-Ashi
Indicators
• MA/EMA/SMA/WMA
• Bollinger Bands
• RSI
• MACD
• Stochastic
• ATR
• ADX
• VWAP
• Ichimoku
• Supertrend
Overlays & Tools
• Crosshair + Tooltip
• Grid
• Trendlines
• Fibonacci
• Annotations
• Volume Profile
• Order Book
typescript
import { PluginRegistry, PluginState } from '@quatick/quatick-charts';
// Access the plugin registry
const registry = chart.getPluginRegistry();
// Check if a plugin is loaded
const state = registry.getPluginState('rsi-14');
// PluginState: UNLOADED | LOADING | INITIALIZED | ACTIVE | ERROR | DESTROYED
// List all registered plugins
const plugins = registry.getAllPlugins();
plugins.forEach(p => {
console.log(p.id, p.category, p.state);
});
Chart Types & Renderers
Over 40 first-class chart types across four renderer families. Switch at runtime with chart.setChartType(id). Each renderer transforms the underlying OHLCV stream without any data re-fetch.
Standard (Canvas/WebGL)
• candlestick
• hollow-candle
• volume-candle
• heikin-ashi
• line
• step-line
• area
• baseline
• ohlc
• hlc
• hlc-area
• bars
• columns
Price Action (Canvas2D)
• renko
• kagi
• line-break
• point-figure
• range-bars
Orderflow / Profile
• orderflow-chart
• cluster-chart
• dom-surface
• tpo-profile
• volume-profile
• session-volume-profile
Native WebGL GPU
• webgl-neon-candles
• webgl-glass-candles
• webgl-holographic-candles
• webgl-depth-candles
• webgl-volume-candles
• webgl-aurora-candles
• …14 more
Candlestick
Traditional OHLCV representation. Bodies represent open–close range; wicks extend to high and low. WebGL-optimised for 60fps at any zoom depth.
Prop
Type
Default
Description
upColor
string
"#34d399"
Fill color for bullish candles (close ≥ open).
downColor
string
"#f87171"
Fill color for bearish candles (close < open).
wickUpColor
string
auto
Wick color for bullish bars. Inherits upColor if unset.
wickDownColor
string
auto
Wick color for bearish bars. Inherits downColor if unset.
Time-independent bricks. New bricks only form when price moves by a fixed amount (or ATR-derived). Reversal bricks require a minimum 2-brick move in the opposite direction. Renders via Canvas2D for correct synthetic-time x-axis handling.
Prop
Type
Default
Description
brickSize
number
0
Brick size in price units. 0 = automatic ATR-based sizing.
method
"close" | "hl"
"close"
"close" tracks closing prices; "hl" uses high/low for earlier signals.
N-line break reversal pattern. A new block only forms when price breaks the high or low of the previous N lines. Classic 3-line break filters most noise.
Prop
Type
Default
Description
lines
number
3
Number of prior lines that must be broken for a reversal.
Each bar has an exact price range — bars only complete when price travels the full range. Eliminates time gaps; all bars have equal price height, making range contraction/expansion immediately visible.
Enterprise-grade Canvas2D orderflow renderer. Displays historical footprint cells derived from OHLCV data alongside a live orderbook depth ladder. Requires chart.setOrderbookState() for the live panel; a synthetic fallback is used when no depth is bound.
Footprint Surface
• Bid × Ask cells per price row
• Cell volume heat-map fill
• POC (Point of Control) highlight
• Imbalance markers (bid/ask dominance)
• Absorption / rejection highlight
Delta Analytics
• Per-bar delta strip
• Delta footer per bar
• CVD ribbon (cumulative delta line)
• Delta histogram bars (bidirectional)
Live Depth Panel
• Stable price-keyed bid/ask maps
• Cumulative depth bars
• Whale detection highlight
• Flow delta pulse footer
• Bid/ask total + spread header
• Imbalance meter
Prop
Type
Default
Description
rows
number
18
Number of price rows per bar.
showDeltaRibbon
boolean
true
Show CVD / cumulative-delta ribbon.
showPOC
boolean
true
Highlight the Point of Control row in each bar.
showImbalances
boolean
true
Show bid/ask imbalance edge markers.
showAbsorption
boolean
true
Highlight absorption / rejection wicks.
showLivePanel
boolean
true
Show the live depth ladder panel.
livePanelWidth
number
290
Width of the live depth panel in CSS pixels.
imbalanceRatio
number
2.4
Minimum ask/bid ratio to flag imbalance.
absorptionRatio
number
0.62
Wick/body ratio threshold for absorption detection.
Real-time bid × ask depth heatmap with a live depth ladder. Volume and orderbook depth rendered as a heat-map overlay on top of candlesticks. Requires live orderbook data.
Time-Price-Opportunity Market Profile. Each letter represents a 30-min period. Displays POC (Point of Control), Value Area High/Low, and Initial Balance.
typescript
chart.setChartType('tpo-profile');
DOM Surface
Depth of Market heatmap rendered on the price axis. Bids display left of mid, asks right. Historical DOM levels fade over time; large orders highlighted. Requires live orderbook.
Fixed-range horizontal volume distribution with POC, Value Area, and optional numeric labels. session-volume-profile renders a color-coded profile for each session (day/week).
Full GPU-shader chart types with custom GLSL programs. Each type has a unique visual aesthetic built into the shader. All require renderMode: 'webgl'.
These chart types are rendered entirely in GLSL on the GPU. They cannot fall back to Canvas2D. Use chart.configure({ renderMode: 'webgl' }) before switching to a WebGL type.
Register custom chart types without forking the library. Two APIs are available: Canvas2D for 2D primitives and WebGL2 for GPU shaders. See the Custom Renderers section for the full plugin API.
typescript
import { registerChartType } from '@quatick/quatick-charts';
// Register a Canvas2D custom chart type
registerChartType({
id: 'my-candles',
rendererType: 'canvas',
render(ctx, context) {
const { chartArea, dpr, timeToX, priceToY, barSpacing } = context;
for (const bar of context.visibleBars) {
const x = timeToX(bar.time) * dpr;
const top = priceToY(bar.high) * dpr;
const btm = priceToY(bar.low) * dpr;
ctx.beginPath();
ctx.strokeStyle = bar.close >= bar.open ? '#34d399' : '#f87171';
ctx.moveTo(x, top); ctx.lineTo(x, btm);
ctx.stroke();
}
},
});
chart.setChartType('my-candles');
Orderbook & Depth Data
Several chart types render live Level-2 orderbook depth alongside historical OHLCV. Bind depth data through the setOrderbookState() API and the active renderer receives real-time updates on every call.
Orderbook chart types
• orderflow-chart (footprint + CVD + ladder)
• cluster-chart (bid×ask heatmap + ladder)
• dom-surface (DOM depth heatmap on price axis)
OHLCV + Volume combined
• volume-profile (horizontal distribution)
• session-volume-profile (per-session)
• tpo-profile (time-price letters)
Depth data events
• chart.on('orderbookUpdate', cb)
• chart.on('depthChange', cb)
• Auto-throttled to 60fps internal
IOrderbookState Type
All orderbook-aware chart types expect this shape. Pass partial updates — the renderer merges incoming levels into a stable, price-keyed map to prevent row blinking.
typescript
interface IOrderbookState {
timestamp: number; // Unix ms — used for staleness detection
bids: IOrderLevel[]; // Sorted descending by price
asks: IOrderLevel[]; // Sorted ascending by price
bestBid: number;
bestAsk: number;
spread: number; // bestAsk - bestBid
midPrice: number; // (bestBid + bestAsk) / 2
totalBidVolume: number; // Sum of all bid sizes
totalAskVolume: number; // Sum of all ask sizes
imbalance: number; // -1 to +1 (positive = bid-heavy)
maxSize: number; // Largest level size (for normalisation)
}
interface IOrderLevel {
price: number;
size: number;
previousSize: number; // Used to detect delta / absorbed size
cumulativeSize: number; // Running depth total from best
orderCount: number; // Number of resting orders at this price
side: 0 | 1; // 0 = bid, 1 = ask
intensity: number; // 0–1 normalised heat
delta: number; // size - previousSize
age: number; // ms since first seen
isWhale: boolean; // Exceeds whale threshold
}
Binding Orderbook Data
Call chart.setOrderbookState(state) on every tick from your WebSocket feed. The chart batches updates to the current RAF cycle.
The orderflow-chart combines all three data streams into a single canvas surface. OHLCV bars drive the footprint row distribution, volume normalises the heat intensity, and orderbook depth feeds the live side panel.
When no live orderbook is bound, the engine synthesises a realistic depth ladder from the most recent OHLCV bar (using ATR and directional bias). The chart is always fully rendered.
The IndicatorApi controls all technical indicators. 30+ built-in indicators with Web Worker computation — indicator math never blocks the main thread. Custom indicators extend BaseIndicator.
addIndicator()
Add a technical indicator via the chart's indicator API. The engine calculates and overlays the result automatically.
Prop
Type
Default
Description
type*
string
—
Indicator ID string: "sma", "ema", "rsi", "macd", "bbands", "vwap", etc.
Pane index. 0 = main price pane. 1+ = separate sub-pane.
color
string
auto
Plot line color. Auto-assigned from theme palette if omitted.
overlay
boolean
true
Overlay on main pane (true) or in separate sub-chart pane (false).
visible
boolean
true
Initial visibility toggle.
id
string
auto
Custom stable ID for referencing this indicator instance.
typescript
// SMA-20 overlay on main pane
const sma = chart.api.indicator.add({
type: 'sma',
params: { period: 20, source: 'close' },
color: '#8b5cf6',
pane: 0,
overlay: true,
});
// EMA with custom ID
chart.api.indicator.add({
type: 'ema',
params: { period: 50 },
color: '#f59e0b',
id: 'ema-50',
});
// Bollinger Bands in main pane
chart.api.indicator.add({
type: 'bbands',
params: { period: 20, stdDev: 2 },
pane: 0,
});
// RSI in sub-pane
chart.api.indicator.add({
type: 'rsi',
params: { period: 14, source: 'close' },
pane: 1,
overlay: false,
});
// MACD in sub-pane
chart.api.indicator.add({
type: 'macd',
params: { fast: 12, slow: 26, signal: 9 },
pane: 2,
overlay: false,
});
// Remove by ID
chart.api.indicator.remove('ema-50');
// Toggle visibility
chart.api.indicator.setVisible('rsi', false);
Built-in Indicator Reference
Trend Following
• SMA (Simple MA)
• EMA (Exponential MA)
• WMA (Weighted MA)
• DEMA
• TEMA
• HMA (Hull MA)
• VWAP
• ALMA
• SMMA
Volatility
• Bollinger Bands
• Keltner Channel
• Donchian Channel
• ATR
• Std Dev
• Chaikin Volatility
Momentum
• RSI (14)
• Stochastic %K/%D
• CCI
• MFI
• ROC
• Williams %R
• TSI
• CMO
• Momentum
Oscillators
• MACD (12,26,9)
• DMI/ADX
• Aroon (Up/Down)
• Parabolic SAR
• Supertrend
• Ultimate Oscillator
Volume Analysis
• OBV (On-Balance)
• A/D Line
• CMF
• VWMA
• RVOL
• Volume Delta
• Cumulative Delta
Advanced / AI
• Ichimoku Cloud
• Pivot Points
• ZigZag
• Elliott Wave
• Support/Resistance
• Market Structure
Common Indicator Parameters
Prop
Type
Default
Description
period
number
—
Look-back period for calculation (e.g., 14 for RSI-14).
source
"open"|"high"|"low"|"close"|"hl2"|"hlc3"|"ohlc4"
"close"
Price source field to feed into the calculation.
fast
number
—
Fast period (MACD, Stochastic, ADX).
slow
number
—
Slow period (MACD, PSAR, etc).
signal
number
—
Signal smoothing period (MACD).
stdDev
number
—
Standard deviation multiplier (Bollinger Bands, Keltner).
multiplier
number
—
ATR multiplier (Supertrend, Keltner).
kPeriod
number
—
Stochastic %K smoothing.
dPeriod
number
—
Stochastic %D period.
stepSize
number
—
PSAR acceleration step increment.
maxStep
number
—
PSAR maximum acceleration factor.
Custom Indicator
Extend BaseIndicator to create custom indicators. The calculate() method runs inside a Web Worker.
typescript
import { BaseIndicator } from '@quatick/quatick-charts';
export class ATRStopIndicator extends BaseIndicator {
get name() { return 'ATR Stop'; }
get id() { return 'atr-stop'; }
get defaultParams() { return { period: 14, multiplier: 2 }; }
calculate(candles, params) {
const { period, multiplier } = params;
const atr = this.calculateATR(candles, period);
return candles.map((bar, i) => {
if (i < period) return null;
const hiLookback = candles.slice(i - period, i + 1).map(b => b.high);
const hi = Math.max(...hiLookback);
return {
time: bar.time,
value: hi - atr[i] * multiplier,
};
}).filter(Boolean);
}
// Helper: True Range → ATR
private calculateATR(candles, period) {
const trs = candles.map((b, i) => {
if (i === 0) return b.high - b.low;
const prev = candles[i - 1];
return Math.max(b.high - b.low, Math.abs(b.high - prev.close), Math.abs(b.low - prev.close));
});
// Wilder's smoothing
const atr = new Array(candles.length).fill(0);
atr[period - 1] = trs.slice(0, period).reduce((a, b) => a + b, 0) / period;
for (let i = period; i < trs.length; i++) {
atr[i] = (atr[i - 1] * (period - 1) + trs[i]) / period;
}
return atr;
}
}
// Register and use
import { IndicatorRegistry } from '@quatick/quatick-charts';
IndicatorRegistry.register(ATRStopIndicator);
chart.api.indicator.add({
type: 'atr-stop',
params: { period: 14, multiplier: 2 },
color: '#f59e0b',
pane: 0,
});
VWAP & Anchored VWAP
VWAP (Volume Weighted Average Price) is a special indicator that resets at session open. Anchored VWAP resets at a user-defined time/price anchor.
typescript
// Standard daily VWAP
chart.api.indicator.add({
type: 'vwap',
params: { resetType: 'session' }, // 'session' | 'week' | 'month'
color: '#8b5cf6',
pane: 0,
});
// Anchored VWAP (anchored to a specific bar's timestamp)
chart.api.indicator.add({
type: 'vwap',
params: {
resetType: 'anchored',
anchorTime: 1710000000, // Unix timestamp to anchor from
stdDevBands: [1, 2], // Show ±1 and ±2 std dev bands
},
color: '#f59e0b',
pane: 0,
});
Streaming & Real-Time Data
The DataApi abstracts data sources behind a unified interface. Supports WebSocket, Server-Sent Events, REST polling, and custom feed implementations. Incremental rendering ensures 60fps is maintained even at 1,000 ticks/second.
updateCandle() — Live Tick Updates
The simplest streaming API — push individual bar updates directly. The engine decides whether to update the last bar or append a new one based on the timestamp and candle interval.
typescript
// Feed real-time ticks from any source
function onWebSocketMessage(event) {
const tick = JSON.parse(event.data);
chart.updateCandle({
time: tick.timestamp, // Unix seconds
open: tick.open,
high: tick.high,
low: tick.low,
close: tick.close,
volume: tick.volume,
});
}
// Connect WebSocket manually
const ws = new WebSocket('wss://api.example.com/stream/BTCUSDT/1m');
ws.onmessage = onWebSocketMessage;
// Or with Binance-style kline format
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
const k = msg.k; // kline object
chart.updateCandle({
time: Math.floor(k.t / 1000), // Convert ms → seconds
open: parseFloat(k.o),
high: parseFloat(k.h),
low: parseFloat(k.l),
close: parseFloat(k.c),
volume: parseFloat(k.v),
});
};
Subscribe to chart-level events using the event bus. All callbacks receive typed event objects.
typescript
import { EventBus } from '@quatick/quatick-charts';
// Crosshair movement (fires on every mouse move)
chart.on('crosshairMove', ({ time, price, bar }) => {
console.log('Price:', price.toFixed(2), '| Volume:', bar?.volume);
// Update custom tooltip, status bar, order entry, etc.
});
// Click on chart canvas
chart.on('click', ({ time, price, screenX, screenY }) => {
console.log('Clicked:', new Date(time * 1000).toISOString(), price);
});
// Double-click → reset view
chart.on('dblClick', () => {
chart.resetView();
});
// Visible range changed (after zoom/pan)
chart.on('visibleRangeChange', ({ from, to, barCount }) => {
console.log(`Showing ${barCount} bars from ${new Date(from * 1000).toDateString()}`);
// Trigger lazy loading of older historical data
});
// New tick arrived from stream
chart.on('tick', (candle) => {
console.log('New bar:', candle);
});
// Indicator calculation completed
chart.on('indicatorUpdate', ({ indicatorId, data }) => {
console.log('Indicator computed:', indicatorId, data.length, 'points');
});
// Remove listener
const handler = ({ price }) => console.log(price);
chart.on('crosshairMove', handler);
chart.off('crosshairMove', handler);
Historical Data Loading
Combine historical data with live streaming for seamless full-history charts.
Best practice: Load historical data first with chart.setData(), then start the live stream with chart.updateCandle(). The chart engine merges new ticks with historical candles automatically — no deduplication logic required.
typescript
// 1. Fetch historical data from your API
const response = await fetch('/api/candles?symbol=BTCUSDT&interval=1m&limit=500');
const historical = await response.json();
// 2. Load into chart (must be sorted by time ascending)
chart.setData(historical.map(c => ({
time: c.timestamp, // Unix seconds
open: c.open, high: c.high, low: c.low, close: c.close, volume: c.volume,
})));
chart.render();
// 3. Open WebSocket for live updates
const ws = new WebSocket('wss://stream.exchange.com/btcusdt@kline_1m');
ws.onmessage = ({ data }) => {
const { k } = JSON.parse(data);
chart.updateCandle({
time: Math.floor(k.t / 1000),
open: +k.o, high: +k.h, low: +k.l, close: +k.c, volume: +k.v,
});
};
// 4. Lazy-load older history when user scrolls left
chart.on('visibleRangeChange', async ({ from }) => {
if (from <= chart.getEarliestTime() + 3600) {
const older = await fetchOlderHistory(from - 86400);
chart.prependData(older); // Prepend without re-rendering
}
});
Custom Overlays & Drawings
The DrawingApi provides both built-in drawing tools and a low-level Canvas 2D API for fully custom overlays. Extend BaseOverlay for arbitrary rendering on top of the chart canvas.
The Quatick Charts engine is fully open to user-defined chart types. Register a custom renderer via registerChartType() and it becomes a first-class citizen — accessible by setChartType(id), included in exports and screenshots, and compatible with all indicators and overlays.
Canvas2D renderer
• Full CanvasRenderingContext2D access
• Render context with priceToY / timeToX
• visibleBars, barSpacing, barWidth helpers
• Access to live orderbook state
• DPR-aware coordinate helpers
WebGL2 renderer
• Raw WebGL2RenderingContext access
• Custom GLSL vertex + fragment shaders
• Upload custom uniforms per frame
• Shared attribute buffers (OHLCV VBOs)
• Blend / depth / stencil full control
Lifecycle hooks
• onActivate(chart) — setup
• onDeactivate(chart) — teardown
• onDataChange(bars) — new data
• onOrderbookChange(state) — depth update
• onThemeChange(theme) — re-skin
registerChartType() API
Prop
Type
Default
Description
id*
string
—
Unique chart type identifier. Used with setChartType().
Called when setData() or updateCandle() changes the dataset.
onOrderbookChange
(state: IOrderbookState | null) => void
—
Called when setOrderbookState() updates depth data.
onThemeChange
(config: IChartConfig) => void
—
Called when the chart theme changes.
requiresOrderbook
boolean
false
If true, the chart will subscribe to orderbook feed automatically.
canvasOnly
boolean
false
If true, WebGL canvas is hidden and Canvas2D is the sole surface.
label
string
—
Human-readable name shown in the chart type selector.
icon
string
—
Optional icon identifier for the chart type selector.
Canvas2D Custom Renderer
The render function receives a CanvasRenderingContext2D and a rich IPriceActionRenderContext that exposes all viewport helpers.
typescript
import { registerChartType } from '@quatick/quatick-charts';
registerChartType({
id: 'price-bands',
rendererType: 'canvas',
label: 'Price Bands',
canvasOnly: true,
onActivate(chart) {
// one-time setup — subscribe to events, allocate buffers, etc.
this._colors = { bull: '#34d399', bear: '#f87171', neutral: '#8b5cf6' };
},
onDataChange(bars) {
// pre-compute derived values when data changes
this._sma = computeSMA(bars, 20);
this._bands = computeBollinger(bars, 20, 2);
},
render(ctx, context) {
const {
chartArea, dpr,
visibleBars, // IOHLCBar[] — only bars in the current viewport
timeToX, // (time: number) => number (CSS px)
priceToY, // (price: number) => number (CSS px)
barSpacing, // CSS px between bar centers
barWidth, // CSS px bar slot width
timeRange, // { start, end } — visible time range
} = context;
// Clip to chart area
ctx.save();
ctx.beginPath();
ctx.rect(
chartArea.left * dpr, chartArea.top * dpr,
chartArea.width * dpr, chartArea.height * dpr,
);
ctx.clip();
// Draw Bollinger Band fill
if (this._bands) {
ctx.fillStyle = 'rgba(139, 92, 246, 0.07)';
ctx.beginPath();
visibleBars.forEach((bar, i) => {
const x = timeToX(bar.time) * dpr;
const y = priceToY(this._bands.upper[bar.time]) * dpr;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
[...visibleBars].reverse().forEach((bar) => {
ctx.lineTo(
timeToX(bar.time) * dpr,
priceToY(this._bands.lower[bar.time]) * dpr,
);
});
ctx.closePath();
ctx.fill();
}
// Draw candlestick bars
const hw = (barSpacing * dpr) / 2 - 1;
for (const bar of visibleBars) {
const x = timeToX(bar.time) * dpr;
const isBull = bar.close >= bar.open;
// Wick
ctx.strokeStyle = isBull ? this._colors.bull : this._colors.bear;
ctx.lineWidth = dpr;
ctx.beginPath();
ctx.moveTo(x, priceToY(bar.high) * dpr);
ctx.lineTo(x, priceToY(bar.low) * dpr);
ctx.stroke();
// Body
ctx.fillStyle = isBull ? this._colors.bull : this._colors.bear;
const bodyTop = priceToY(Math.max(bar.open, bar.close)) * dpr;
const bodyBtm = priceToY(Math.min(bar.open, bar.close)) * dpr;
ctx.fillRect(x - hw, bodyTop, hw * 2, Math.max(1, bodyBtm - bodyTop));
}
ctx.restore();
},
});
IPriceActionRenderContext Reference
Prop
Type
Default
Description
ctx
CanvasRenderingContext2D
—
The 2D drawing context (Canvas mode only).
gl
WebGL2RenderingContext
—
The WebGL2 context (WebGL mode only).
chartArea
{ left, top, width, height }
—
Viewport rectangle in CSS pixels (excluding axes).
dpr
number
—
Device pixel ratio. Multiply CSS px by dpr for physical px.
visibleBars
IOHLCBar[]
—
OHLCV bars in the current time viewport.
timeRange
{ start: number, end: number }
—
Visible time range (Unix seconds).
priceRange
{ min: number, max: number }
—
Visible price range.
timeToX
(t: number) => number
—
Map Unix timestamp → CSS x position.
priceToY
(p: number) => number
—
Map price → CSS y position.
xToTime
(x: number) => number
—
Inverse: CSS x → Unix timestamp.
yToPrice
(y: number) => number
—
Inverse: CSS y → price.
barSpacing
number
—
CSS pixel distance between bar centers.
barWidth
number
—
CSS pixel width of one bar slot.
maxVolume
number
—
Largest volume value in the visible window (for normalisation).
orderbookState
IOrderbookState | null
—
Current orderbook snapshot if bound.
WebGL2 Custom Renderer
Full GPU shader access. The chart package provides pre-built VBOs for OHLCV data and a standard set of uniforms (viewport matrix, price range, time range, DPR). You supply the GLSL vertex and fragment shaders.
WebGL renderers must handle their own shader compilation, linking, and cleanup inside onActivate / onDeactivate. The chart will not compile shaders on your behalf.
typescript
import { registerChartType } from '@quatick/quatick-charts';
const VERT = `#version 300 es
precision highp float;
// Provided by the chart engine
uniform mat4 u_mvp; // model-view-projection matrix
uniform float u_barWidth; // normalised bar slot width
uniform float u_priceMin; // visible price range min
uniform float u_priceMax; // visible price range max
// Per-instance OHLCV attributes
in float a_open; in float a_high;
in float a_low; in float a_close;
in float a_time; in float a_volume;
out float v_delta;
out float v_volume;
void main() {
float normPrice = (a_close - u_priceMin) / max(1.0, u_priceMax - u_priceMin);
gl_Position = u_mvp * vec4(a_time, normPrice, 0.0, 1.0);
gl_PointSize = u_barWidth;
v_delta = a_close - a_open;
v_volume = a_volume;
}
`;
const FRAG = `#version 300 es
precision highp float;
in float v_delta;
in float v_volume;
out vec4 fragColor;
void main() {
// Volume-weighted color: green for bull, red for bear
float vol = clamp(v_volume / 5000.0, 0.0, 1.0);
vec3 bull = mix(vec3(0.12, 0.55, 0.45), vec3(0.20, 0.83, 0.60), vol);
vec3 bear = mix(vec3(0.55, 0.12, 0.12), vec3(0.97, 0.44, 0.44), vol);
fragColor = vec4(v_delta >= 0.0 ? bull : bear, 0.92);
}
`;
registerChartType({
id: 'volume-weighted-glow',
rendererType: 'webgl',
label: 'Volume-Weighted Glow',
onActivate(chart) {
const { gl } = chart.getWebGLContext();
this._program = compileProgram(gl, VERT, FRAG); // your helper
this._vao = gl.createVertexArray();
// bind attribute pointers to the shared OHLCV VBO provided by the engine
const ohlcvBuffer = chart.getSharedOHLCVBuffer();
bindAttributes(gl, this._vao, this._program, ohlcvBuffer);
},
onDeactivate(chart) {
const { gl } = chart.getWebGLContext();
gl.deleteProgram(this._program);
gl.deleteVertexArray(this._vao);
},
render(ctx, context) {
const { gl, visibleBars, chartArea, dpr } = context;
if (!gl || !this._program) return;
gl.useProgram(this._program);
gl.bindVertexArray(this._vao);
// Upload uniforms
const mvp = context.getMVPMatrix(); // provided by engine
const uMVP = gl.getUniformLocation(this._program, 'u_mvp');
gl.uniformMatrix4fv(uMVP, false, mvp);
gl.uniform1f(gl.getUniformLocation(this._program, 'u_barWidth'),
context.barWidth * dpr);
gl.uniform1f(gl.getUniformLocation(this._program, 'u_priceMin'),
context.priceRange.min);
gl.uniform1f(gl.getUniformLocation(this._program, 'u_priceMax'),
context.priceRange.max);
// Draw instanced — one point per bar
gl.drawArrays(gl.POINTS, context.visibleStartIndex, visibleBars.length);
gl.bindVertexArray(null);
},
});
chart.configure({ renderMode: 'webgl' });
chart.setChartType('volume-weighted-glow');
Extending Existing Chart Types
Use the extendChartType() helper to wrap an existing type and add extra Canvas2D overlays without re-implementing the base renderer.
typescript
import { extendChartType } from '@quatick/quatick-charts';
// Add a spread ribbon on top of the DOM Surface chart
extendChartType('dom-surface', {
id: 'dom-surface-spread',
label: 'DOM Surface + Spread',
afterRender(ctx, context) {
const ob = context.orderbookState;
if (!ob) return;
const { chartArea, dpr, priceToY } = context;
const bidY = priceToY(ob.bestBid) * dpr;
const askY = priceToY(ob.bestAsk) * dpr;
// Highlight the spread zone in amber
ctx.fillStyle = 'rgba(251, 191, 36, 0.12)';
ctx.fillRect(chartArea.left * dpr, askY, chartArea.width * dpr, bidY - askY);
// Spread label
ctx.fillStyle = '#fbbf24';
ctx.font = `${11 * dpr}px ui-monospace, monospace`;
ctx.textAlign = 'right';
ctx.fillText(`spr ${ob.spread.toFixed(2)}`,
(chartArea.left + chartArea.width) * dpr - 8 * dpr,
(askY + bidY) / 2);
},
});
chart.setChartType('dom-surface-spread');
Composite Chart from Scratch
Build a chart that combines multiple visual layers — OHLCV, volume histogram, volume profile, and a live depth curve — all within one Canvas2D renderer.
The InteractionApi manages all user input: zoom, pan, crosshair, drawing tools, and keyboard shortcuts. All interactions can be enabled/disabled at runtime.
Zoom & Pan
Prop
Type
Default
Description
enableZoom
boolean
true
Mouse-wheel and pinch-to-zoom.
enablePan
boolean
true
Click-drag to pan the chart.
minBarsVisible
number
5
Minimum number of bars visible when zoomed in.
maxBarsVisible
number
5000
Maximum number of bars visible when zoomed out.
zoomSensitivity
number
1.0
Mouse wheel sensitivity multiplier.
enableDoubleClick
boolean
true
Double-click to reset chart view.
enableKeyboard
boolean
true
Arrow-key panning. +/- keys for zoom.
typescript
// Configure via constructor
const chart = createChart({
container: '#chart',
interactions: {
enableZoom: true,
enablePan: true,
minBarsVisible: 5,
maxBarsVisible: 2000,
zoomSensitivity: 1.2,
enableDoubleClick: true,
enableKeyboard: true,
},
});
// Programmatic control
chart.api.interaction.zoomIn(1.5); // 1.5x zoom in
chart.api.interaction.zoomOut(2); // 2x zoom out
chart.api.interaction.resetZoom(); // Reset to default range
chart.api.interaction.fitContent(); // Fit all data in view
chart.api.interaction.pan(-50); // Pan 50 pixels left
// Set precise visible range (Unix timestamps in seconds)
chart.api.interaction.setVisibleRange({
from: Date.now() / 1000 - 7 * 86400, // Last 7 days
to: Date.now() / 1000,
});
// Lock/unlock interaction
chart.api.interaction.setEnabled(false); // Freeze chart (presentation mode)
chart.api.interaction.setEnabled(true); // Re-enable
Crosshair & Tooltip
Prop
Type
Default
Description
visible
boolean
true
Show/hide crosshair.
mode
"free" | "magnetic"
"magnetic"
"free" follows cursor exactly; "magnetic" snaps to bar centers.
renderer: 'webgl' — Use WebGL for maximum GPU-accelerated throughput (default)
performanceHint: 'performance' — Reduces visual quality for best FPS on low-end devices
Limit visible indicator count. Use sub-panes for oscillators to reduce main pane overdraw.
Call chart.api.data.prependData() for history loading — avoids full re-render.
Avoid calling chart.setData() during streaming — use chart.updateCandle() for tick updates.
Destroy charts when unmounted: chart.destroy() frees GPU memory, WebSocket, and Workers.
For dashboards with 10+ charts, use renderer: 'canvas' on background charts to save GPU budget.
typescript
// Performance-tuned config for embedded/widget use
const chart = createChart({
container: '#chart',
renderer: 'webgl',
performanceHint: 'performance',
showVolume: false, // Disable volume to save one render pass
grid: { visible: false }, // Disable grid lines
crosshair: { visible: false }, // Disable crosshair if not needed
interactions: {
enableZoom: false, // Lock chart in presentation mode
enablePan: false,
enableCrosshair: false,
},
});
// Cleanup on unmount (React / Vue)
useEffect(() => {
return () => chart.destroy();
}, []);
Options Payoff Chart
PayoffChart is a fully standalone 2D Canvas renderer for options strategy payoff diagrams. It ships separately from the financial chart and requires no OHLCV data. It renders P&L curves, breakeven lines, a spot price marker, standard deviation bands, crosshair tooltips, and supports pan/zoom interactions out of the box.
PayoffChart is exported directly from @quatick/quatick-charts. It does not depend on the financial Chart class and can be used independently.
Live Strategy Demo
Select a strategy below to see its payoff curve, breakevens, and 1σ / 2σ standard deviation bands. Spot: 19 500 · IV: 15% · DTE: 30.
Iron Condor — Short two OTM options + long two further OTM options. Max profit in a range-bound market.
Open interest bar overlay. enabled, showCalls, showPuts, heightRatio, position.
Strategy Leg — IStrategyLeg
Prop
Type
Default
Description
id
string
—
Unique identifier for this leg.
instrumentType
"option" | "future" | "perpetual"
"option"
Instrument type.
optionType
"call" | "put"
—
Required when instrumentType is 'option'.
positionType
"long" | "short"
—
Direction of the leg.
strike
number
—
Strike price for option legs.
expiry
string
—
ISO-8601 expiry date string (e.g. '2025-06-26').
quantity
number
1
Number of lots.
premium
number
—
Premium per unit (in the same currency as price). Used to compute net cost.
lotSize
number
1
Contract lot size. P&L is scaled by quantity × lotSize.
iv
number
—
Implied volatility as a decimal (e.g. 0.15 for 15%). Used for Greeks and target-date curve.
greeks
IGreeks
auto
Optional pre-computed Greeks (delta, gamma, theta, vega, rho). If omitted, calculated from iv.
Calculator — calculatePayoffChartData()
Helper that takes strategy legs and market params and returns a complete IPayoffChartData object ready to pass to chart.setData(). Computes the at-expiry curve, a target-date curve, breakevens, max profit/loss, net premium and net Greeks.
typescript
import { calculatePayoffChartData } from '@quatick/quatick-charts';
const data = calculatePayoffChartData(
legs, // IStrategyLeg[]
spotPrice, // Current underlying price
impliedVol, // IV as a decimal (0.15 = 15%)
daysToExpiry, // Days until front-month expiry
{
numPoints: 200, // Resolution of the curve
priceRangePercent: 0.12, // ±% around spot for the X-axis
daysToExpiry: 15, // DTE for the target-date curve
ivChange: 0, // IV shift for scenario analysis
},
);
// data.curves[0] → At-Expiry curve (green/filled)
// data.curves[1] → Target-date curve (blue/dashed)
// data.breakevens → Array of breakeven prices
// data.maxProfit → Max profit (per lot)
// data.maxLoss → Max loss (per lot)
// data.netPremium → Net premium received/paid
// data.netGreeks → Aggregate Greeks for the strategy
Data Object — IPayoffChartData
Prop
Type
Default
Description
legs
IStrategyLeg[]
—
Strategy leg definitions (passed through from input).
curves
IPayoffCurve[]
—
At-expiry and target-date payoff curves. Each curve has id, label, points[], color, lineWidth, dash, fillArea, fillOpacity.
spotPrice
number
—
Current underlying price used to draw the spot marker.
breakevens
number[]
[]
Prices at which strategy P&L equals zero.
maxProfit
number
—
Max profit per lot. Infinity for unlimited-profit strategies.
maxProfitPrice
number
—
Underlying price at which max profit occurs.
maxLoss
number
—
Max loss per lot (negative). -Infinity for unlimited-risk strategies.
netPremium
number
—
Net premium received (positive) or paid (negative).
netGreeks
IGreeks
—
Aggregate delta, gamma, theta, vega, rho for the strategy.
sdBands
IStdDevBand[]
[]
Standard deviation bands. Each band has sigma, lower, upper, color, opacity, label.
daysToExpiry
number
—
Days to front-month expiry.
impliedVolatility
number
—
IV used for the target-date curve calculation.
oiData
IOIDataPoint[]
[]
Optional open interest data per strike for the OI overlay bar chart.
Chart Methods
Prop
Type
Default
Description
setData(data)
void
—
Set or update the full payoff data. Triggers fade-in animation for new curves.
setOIData(oiData)
void
—
Update the open interest overlay independently without re-computing payoff curves.
setOIEnabled(enabled)
void
—
Toggle the OI overlay visibility.
setConfig(config)
void
—
Apply partial config updates at runtime (theme, axis visibility, etc.).
setTheme(theme)
void
—
Switch dark/light or apply a custom IPayoffChartTheme.
requestRender()
void
—
Force an immediate re-render (e.g. after external state change).
on(event, callback)
void
—
Subscribe to events: 'hover', 'breakevenHover', 'resize', 'destroy'.
off(event, callback)
void
—
Unsubscribe a previously registered event listener.
destroy()
void
—
Tear down the chart, remove the canvas from the DOM, and release all resources.
Standard Deviation Bands
SD bands visually show the expected move for the underlying over the life of the strategy. Calculate σ from IV and DTE and pass them via data.sdBands.
Render call and put open interest as horizontal bars below the payoff curves. Useful for identifying support/resistance clusters through OI concentration.
// Hover — fired on every mousemove with nearest P&L value
chart.on('hover', (e) => {
console.log('price:', e.price, 'pnl:', e.pnl);
});
// Breakeven hover — fired when crosshair crosses a breakeven line
chart.on('breakevenHover', (e) => {
console.log('breakeven at:', e.price);
});
// Resize — fired when the chart redraws due to container resize
chart.on('resize', (e) => {
console.log('new size:', e.width, e.height);
});
// Clean up
chart.on('destroy', () => {
console.log('chart destroyed');
});
For React usage, wrap createPayoffChart in a useEffect and call chart.destroy() in the cleanup function. Dynamic import with import('@quatick/quatick-charts') avoids SSR issues.
Advanced API Reference
Chart Instance Methods
Complete list of methods on each Chart instance.
Prop
Type
Default
Description
setData(candles)
void
—
Load or replace entire dataset. Input must be sorted by time.
updateCandle(candle)
void
—
Append or update the last bar with a live tick. O(1) update.
prependData(candles)
void
—
Prepend historical candles without re-rendering the view.
setChartType(type)
void
—
Switch renderer at runtime. Preserves data and indicators.
render()
void
—
Force a full re-render.
resetView()
void
—
Reset zoom/pan to full data view.
fitContent()
void
—
Fit all data into the visible viewport.
getVisibleRange()
Range
—
Return { from, to, barCount } of the current viewport.
getEarliestTime()
number
—
Return Unix timestamp of earliest candle in dataset.
getLatestTime()
number
—
Return Unix timestamp of most recent candle.
on(event, cb)
void
—
Register event listener.
off(event, cb)
void
—
Remove event listener.
getMetrics()
Metrics
—
Return performance metrics object (FPS, render time, memory).
Every chart feature is a plugin. Create renderer plugins, indicator plugins, or overlay plugins by extending the appropriate base class and registering with PluginRegistry.
typescript
import { BasePlugin, PluginState, PluginCategory, PluginRegistry } from '@quatick/quatick-charts';
export class OrderFlowPlugin extends BasePlugin {
get name() { return 'Order Flow Footprint'; }
get id() { return 'order-flow'; }
get category() { return PluginCategory.RENDERER; }
async initialize(context) {
this.context = context;
this.state = PluginState.INITIALIZED;
this._registerEventHandlers();
}
async render(series, renderState) {
const { visibleBars, priceToY, timeToX, barWidth, canvasHeight } = renderState;
const ctx = this.context.canvas.getContext('2d');
for (const bar of visibleBars) {
const x = timeToX(bar.time);
const y = priceToY(bar.close);
const delta = (bar.buyVolume || 0) - (bar.sellVolume || 0);
const color = delta >= 0 ? '#34d399' : '#f87171';
ctx.fillStyle = color;
ctx.font = '9px monospace';
ctx.fillText(Math.abs(delta).toFixed(0), x - barWidth / 2 + 2, y - 4);
}
}
async cleanup() {
this.state = PluginState.DESTROYED;
}
}
// Register globally
PluginRegistry.register(OrderFlowPlugin);
// Use in chart
chart.api.indicator.add({ type: 'order-flow' });
// Export chart as PNG data URL
const dataUrl = await chart.api.export.screenshot();
const link = document.createElement('a');
link.href = dataUrl;
link.download = 'chart.png';
link.click();
// Export chart data as JSON
const data = chart.api.export.getData();
// Returns: { candles: Candle[], indicators: Record<id, IndicatorValue[]>, range: TimeRange }
// Export chart config (for persistence)
const config = chart.api.export.getConfig();
localStorage.setItem('chart-config', JSON.stringify(config));
// Restore from saved config
const saved = JSON.parse(localStorage.getItem('chart-config')!);
chart.api.export.applyConfig(saved);
Multi-Pane Layout
The layout engine supports multiple stacked panes, each with their own price scale. Sub-panes are created automatically when indicators use pane: 1 or higher.
Prop
Type
Default
Description
panes[n].height
number | string
"auto"
Pane height in pixels or '%'. Main pane fills remaining space.