ROMACO CHARTS
High-performance trading charts built with TypeScript and HTML5 Canvas. Optimized Canvas rendering with automatic data conflation (LTTB) for 100K+ candles at 60 FPS.
Blazing Fast
Optimized Canvas rendering with automatic LTTB conflation — 100K+ candles at 60 FPS.
29+ Indicators
EMA, SMA, MACD, RSI, BOLL and many more. Tree-shakeable — bundle only what you use.
Drawing Tools
Professional tools: trendlines, Fibonacci, Elliott Waves, parallel channels, and more.
Plugin System
Hook into the render cycle, add overlays, react to events with a clean plugin API.
AI Integration
ChartAgentController exposes a stable AI-facing API for LLM and agent workflows.
Minimal Deps
Lean core with optional React and Web Component integrations.
| Feature | Romaco Charts | Other Libraries |
|---|---|---|
| Built-in Indicators | 29+ | Varies |
| Drawing Tools | 14+ | Limited |
| Data Conflation | Automatic (LTTB) | Varies |
| AI Agent API | ChartAgentController | None |
| Plugin System | Full lifecycle hooks | Varies |
| License | BUSL-1.1 / Commercial | Varies |
Installation
Package Manager
npm install romaco-charts
yarn add romaco-charts
pnpm add romaco-charts
CDN (quick prototyping)
<script type="module">
import { createChart } from 'https://unpkg.com/romaco-charts@latest/dist/index.js';
const chart = createChart('#chart', { preset: 'dark', data: yourData });
</script>
Entry Points
Use only the entry points you need — bundlers tree-shake the rest.
import { createChart } from 'romaco-charts'
import { Chart, MinimalChart, TradingTerminal, ChartGrid, useRomacoChart } from 'romaco-charts/react'
import { ChartAgentController } from 'romaco-charts/agent'
import type { IDatafeed } from 'romaco-charts/datafeed'
import { FootprintRenderer } from 'romaco-charts/renderers'
import type { PersistableChartState } from 'romaco-charts/profile'
import type { ReplayStatus } from 'romaco-charts/replay'
The root package re-exports many common types, but dedicated subpaths keep imports narrow and intent explicit:
| Entry Point | Purpose |
|---|---|
| romaco-charts | Core chart, indicators, renderers, paper trading, presets, and drawings. |
| romaco-charts/react | React wrappers, terminal UI, hooks, grid helpers, and datafeed type re-exports. |
| romaco-charts/agent | AI controller and action/context types. |
| romaco-charts/datafeed | Datafeed contracts, history types, realtime status, and createNoopRealtimeSubscription helper. |
| romaco-charts/drawings | Drawing manager, toolbar, templates, and drawing types. |
| romaco-charts/indicators | Tree-shakeable indicator entry point. |
| romaco-charts/profile | State persistence, visual state, and validation helpers. |
| romaco-charts/replay | Replay controller types and session state. |
| romaco-charts/renderers | Standalone renderers and renderer option types. |
| romaco-charts/ui | Vanilla UI components and tokens. |
| romaco-charts/web-components | Custom element wrapper for non-React embeds. |
Peer Dependencies
| Package | Required | Purpose |
|---|---|---|
| lit | Optional | Web Components (<romaco-chart>) |
| react | Optional | React integration (romaco-charts/react) |
| react-dom | Optional | React rendering peer |
Quick Start
Create your first chart in under 5 minutes.
1. Create a Container
<div id="chart" style="width: 800px; height: 400px;"></div>
2. Import and Create
import { createChart } from 'romaco-charts';
const chart = createChart('#chart', {
preset: 'dark',
data: [
{ time: 1672531200000, open: 100, high: 105, low: 98, close: 103, volume: 1000 },
{ time: 1672617600000, open: 103, high: 110, low: 101, close: 108, volume: 1500 },
]
});
Adding Indicators
const chart = createChart('#chart', {
preset: 'dark',
data: candleData,
indicators: ['EMA', 'RSI', 'MACD']
});
Enabling Drawing Tools
import { allTemplates } from 'romaco-charts/drawings';
const chart = createChart('#chart', {
preset: 'dark',
data: candleData,
drawings: {
templates: allTemplates,
showToolbar: true,
toolbarVisibility: 'edge-hover',
}
});
Large Datasets — Data Conflation
const chart = createChart('#chart', {
preset: 'dark',
data: largeDataset, // 100,000 candles
dataConflation: true // Automatic LTTB downsampling
});
Complete Example
import {
createChart,
allTemplates,
registerIndicators,
EMAIndicator,
RSIIndicator,
MACDIndicator,
} from 'romaco-charts';
registerIndicators(EMAIndicator, RSIIndicator, MACDIndicator);
const chart = createChart('#chart', {
preset: 'professional',
data: candleData,
dataConflation: true,
indicators: [
{ name: 'EMA', params: [12, 26] },
{ name: 'RSI', params: [14] },
'MACD'
],
drawings: {
templates: allTemplates,
position: 'left'
},
onCandleClick: (candle, index) => {
console.log('Clicked candle:', candle);
}
});
Chart Types
11 renderer styles — match the chart to the analysis task instead of forcing every workflow into a standard candle view.
| Renderer | Best For | Notes |
|---|---|---|
| CandleRenderer | Detailed OHLC analysis | Default. Most trading screens. |
| HeikinAshiRenderer | Trend filtering | Smooths noise, highlights sustained direction. |
| VolumeRenderer | Dedicated volume panes | Focuses on participation without overlaying price candles. |
| LineRenderer | Clean trend overview | Uses close values only. |
| AreaRenderer | Lightweight dashboards | Adds visual emphasis under the close line. |
| BaselineRenderer | Relative move vs anchor | Good for above/below reference displays. |
| HollowCandleRenderer | Candle detail, lighter visual | Useful for dense layouts. |
| StepLineRenderer | Event-like price changes | Keeps discrete transitions visible. |
| RenkoRenderer | Noise reduction | Focuses on movement size instead of time. |
| FootprintRenderer | Order flow and imbalance views | Volume-at-price and bid/ask detail. |
| SessionRenderer | Session overlays | Highlights market sessions and intraday structure. |
Switching Renderer at Runtime
import {
RomacoChart,
CandleRenderer,
HeikinAshiRenderer,
LineRenderer,
} from 'romaco-charts';
const chart = RomacoChart.create('#chart', { data: candleData });
const canvas = document.querySelector('#chart canvas');
const ctx = canvas?.getContext('2d');
if (ctx) {
chart.setRenderer(new HeikinAshiRenderer(ctx));
chart.setRenderer(new LineRenderer(ctx, { lineColor: '#0f766e' }));
}
Practical Guidance
- Use candles when you need precise execution context.
- Use Heikin-Ashi when raw noise is making trend reading harder than it should be.
- Use line, area, or volume when chart density is high or the view is mostly presentational.
- Use footprint and session renderers when the workflow depends on order-flow structure or market-session context rather than plain candles alone.
- Use Renko and baseline only when the analysis model explicitly benefits from them.
Indicators
Register only what you need — the catalog is tree-shakeable.
import { RomacoChart } from 'romaco-charts';
import { EMAIndicator, RSIIndicator, MACDIndicator } from 'romaco-charts/indicators';
RomacoChart.registerIndicators(EMAIndicator, RSIIndicator, MACDIndicator);
Built-in Catalog
Trend & Overlay
Oscillators
Volume Studies
Add at Runtime
const macdId = chart.addIndicator('MACD', { params: [12, 26, 9] });
chart.setIndicatorVisible(macdId, false);
chart.toggleIndicatorVisible(macdId);
chart.updateIndicator(macdId, {
params: [10, 24, 8],
styles: {
macd: { color: '#0ea5e9', lineWidth: 2 },
signal: { color: '#f97316', lineWidth: 2 },
},
});
chart.removeIndicator(macdId);
Create-time Configuration
const chart = RomacoChart.create('#chart', {
data: candleData,
indicators: [
{ name: 'EMA', params: [12, 26] },
{ name: 'RSI', params: [14] },
'MACD'
]
});
Placement Behavior
| Family | Placement | Examples |
|---|---|---|
| Trend overlays | Main price pane | EMA, SMA, MA, BOLL, SAR, AVP, BBI, AVWAP |
| Oscillators | Dedicated subpanel | RSI, MACD, KDJ, CCI, DMI, ROC, TRIX, WR, AO |
| Volume studies | Dedicated subpanel | VOL, OBV, EMV, PVT, VR |
Drawing Tools
Drawing operations are handled through the DrawingManager — not flat methods on the chart instance.
import { RomacoChart } from 'romaco-charts';
import { allTemplates } from 'romaco-charts/drawings';
const chart = RomacoChart.create('#chart', {
data: candleData,
drawings: {
templates: allTemplates,
showToolbar: true,
toolbarVisibility: 'edge-hover',
},
});
const drawingManager = chart.getDrawingManager();
const toolbar = chart.getDrawingToolbar();
DrawingManager API
getDrawing(id)Retrieve a single drawing by ID.getAllDrawings()Return all active drawings.updateDrawing(id, override)Update drawing properties.removeDrawing(id)Remove a single drawing.clearAllDrawings() / clearAll()Remove all drawings.undo() / redo()Undo/redo drawing operations.canUndo() / canRedo()Check undo/redo availability.setActiveTool(name)Activate a drawing tool programmatically.setMode(mode) / getMode()Drawing mode control.exportDrawings() / importDrawings(json)Serialize and restore drawings.addDrawingFromState(type, points, styles?)Add drawing from raw state.Chart-level Drawing Helpers
chart.setDrawingToolbarVisibilityMode(mode); chart.setDrawingLineColor(color); chart.setDrawingLineWidth(width); chart.setDrawingStyles(styles); chart.updateDrawingStyle(drawingId, styles);
Persistence-friendly Workflows
const json = drawingManager?.exportDrawings();
if (json) {
localStorage.setItem('romaco:drawings', json);
}
const saved = localStorage.getItem('romaco:drawings');
if (saved) {
drawingManager?.importDrawings(saved);
}
This is the preferred app-level API. AI workflows can still use addDrawing, removeDrawing, and clearDrawings as protocol actions through ChartAgentController.
Themes
Built-in Presets
| Preset | Description |
|---|---|
| dark | Dark background with vibrant colors. |
| light | Light background for daytime use. |
| bloomberg | Black terminal with amber accents and monospace text. |
| neon | Vibrant neon colors on dark background. |
| professional | Clean, muted tones for professional use. |
| classic | MT4-style monochrome chart with dashed grid lines. |
| romaco | ROMACO brand — dark void, gold accents, JetBrains Mono. |
const chart = RomacoChart.create('#chart', {
preset: 'romaco',
data: candleData
});
Custom Colors
const chart = RomacoChart.create('#chart', {
preset: 'dark',
bullishColor: '#00ff88',
bearishColor: '#ff4466',
background: '#0a0a1a'
});
Full Theme Configuration
const chart = new RomacoChart(container, {
theme: {
background: '#050505',
textColor: '#d4c5a0',
fontFamily: 'JetBrains Mono, monospace',
fontSize: 12,
crosshair: {
color: '#b8954f',
lineStyle: 'dashed'
},
border: {
color: 'rgba(42,42,42,0.6)',
width: 1,
radius: 0
},
xAxis: {
gridColor: 'rgba(42, 42, 42, 0.6)',
labelColor: '#888888'
},
yAxis: {
gridColor: 'rgba(42, 42, 42, 0.6)',
labelColor: '#888888'
}
}
});
Runtime Theme Changes
chart.setTheme({
background: '#000000',
textColor: '#ffffff'
});
chart.applyPreset('romaco');
Subpanels
Oscillators and volume studies automatically get dedicated panes when they don't share the main price scale.
chart.addIndicator('EMA', { params: [20] }); // main pane
chart.addIndicator('MACD', { params: [12, 26, 9] }); // subpanel
chart.addIndicator('RSI', { params: [14] }); // subpanel
chart.addIndicator('VOL', { params: [5, 10] }); // subpanel
Inspect and Resize
const panels = chart.getPanels();
panels.forEach((panel) => {
console.log(panel.id, panel.indicatorIds);
});
// Resize a panel to 30% of chart height
if (panels[0]) {
chart.setPanelHeight(panels[0].id, 0.3);
}
Users can resize panels interactively by dragging panel separators — the chart recalculates layout and redraws immediately.
Data Conflation
Data conflation (downsampling) reduces render work while preserving visual appearance. When you have 100,000+ candles, rendering all of them is wasteful — you can't see 100K points on a 1000px canvas.
How It Works
Romaco uses the LTTB (Largest Triangle Three Buckets) algorithm:
- Divides data into buckets
- For each bucket, selects the point that forms the largest triangle with neighbors
- Preserves peaks, valleys, and significant price movements
Simple Mode
const chart = RomacoChart.create('#chart', {
data: largeDataset,
dataConflation: true // Uses LTTB, threshold 2x
});
Advanced Configuration
const chart = RomacoChart.create('#chart', {
data: largeDataset,
dataConflation: {
enabled: true,
algorithm: 'lttb', // 'lttb' (accurate) or 'decimation' (fast)
threshold: 2 // Downsample when points > (canvas width × threshold)
}
});
Algorithms
| Algorithm | Speed | Visual Quality | Best For |
|---|---|---|---|
| lttb | Fast | Excellent | Most cases |
| decimation | Faster | Good | Real-time data |
Manual Optimization
import { optimizeDataLTTB, optimizeData } from 'romaco-charts';
const optimized = optimizeDataLTTB(largeData, 1000);
const decimated = optimizeData(largeData, 1000);
chart.setData(optimized);
Performance
| Data Points | Without Conflation | With LTTB |
|---|---|---|
| 10,000 | 60 FPS | 60 FPS |
| 50,000 | 45 FPS | 60 FPS |
| 100,000 | 20 FPS | 60 FPS |
| 500,000 | 5 FPS | 60 FPS |
Optimization
Romaco Charts is designed to benefit from tree-shaking, selective registration, and runtime performance controls.
Keep the Bundle Small
Core Only
import { createChart } from 'romaco-charts'
Selective Drawing Templates
import {
trendlineTemplate,
horizontalLineTemplate,
rectangleTemplate,
} from 'romaco-charts/drawings'
createChart('#chart', {
data: candleData,
drawings: {
templates: [trendlineTemplate, horizontalLineTemplate, rectangleTemplate],
},
})
Selective Indicator Registration
import { RomacoChart, EMAIndicator, RSIIndicator } from 'romaco-charts'
RomacoChart.registerIndicators(EMAIndicator, RSIIndicator)
Do not register every indicator or import allTemplates unless the screen truly needs the full catalogue.
Large Dataset Controls
createChart('#chart', {
data: candleData,
dataConflation: true,
renderBackend: 'auto',
quality: 'high',
})
dataConflationdownsamples large visible windows.renderBackend: 'auto'lets the chart decide between Canvas2D and WebGPU.qualitytrades visual fidelity for frame budget.
Developer Diagnostics
createChart('#chart', {
developerMode: true,
performanceMonitoring: true,
performanceDebug: {
enabled: true,
position: 'top-right',
updateIntervalMs: 500,
},
})
Use this only for internal diagnostics. These tools are intentionally gated behind developerMode.
Practical Advice
- Start with quality: "high" for dense dashboards.
- Use "magnifique" only where visual polish matters more than raw frame headroom.
- Register only the indicators and drawing templates required by the current product surface.
- Combine selective imports with panel-aware indicator layouts so the chart is cheaper to read and cheaper to render.
Plugin System
Extend functionality with custom plugins. Hook into the render cycle, add overlays, react to events.
ChartPlugin Interface
interface ChartPlugin extends ChartPluginHooks {
name: string;
version?: string;
description?: string;
}
Lifecycle Hooks
onInstall(ctx)Called once when the plugin is registered.onBeforeRender(ctx, state)Before the chart renders each frame.onAfterStaticRender(ctx, state)After static elements are drawn.onAfterRender(ctx, state)After the full render cycle completes.onDataUpdate(data)When the chart data changes.onResize(width, height)When the canvas is resized.onZoom(zoomLevel)When zoom level changes.onPan(panX, panY)When pan offset changes.onDestroy()When the plugin or chart is destroyed.Plugin Context
interface ChartPluginContext {
getData(): CandleData[];
getZoomLevel(): number;
getPan(): { x: number; y: number };
getDimensions(): { width: number; height: number };
priceToY(price: number): number;
yToPrice(y: number): number;
timestampToX(timestamp: number): number;
xToTimestamp(x: number): number;
requestRender(): void;
on(event: string, callback: Function): void;
off(event: string, callback: Function): void;
}
PluginRenderState
interface PluginRenderState {
visibleData: CandleData[];
startIndex: number;
priceRange: { min: number; max: number };
scaleY: number;
candleWidth: number;
panX: number;
panY: number;
zoomLevel: number;
width: number;
height: number;
}
Example Plugin
const crosshairPlugin: ChartPlugin = {
name: 'crosshair-overlay',
onAfterRender(ctx, state) {
const { width, height } = state;
ctx.strokeStyle = 'rgba(184, 149, 79, 0.4)';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
// Draw vertical line at mouse X
ctx.beginPath();
ctx.moveTo(mouseX, 0);
ctx.lineTo(mouseX, height);
ctx.stroke();
// Draw horizontal line at mouse Y
ctx.beginPath();
ctx.moveTo(0, mouseY);
ctx.lineTo(width, mouseY);
ctx.stroke();
},
};
chart.installPlugin(crosshairPlugin);
React Integration
romaco-charts/react gives you declarative chart components, hooks, grid layouts, AI helpers, and paper trading helpers.
What the React entry exports
-
MinimalChartLean chart-only wrapper. No auto-registered indicators. Register them explicitly before rendering. -
Chart / RomacoChartReactConvenience wrapper that auto-registers the default React indicator set. -
TradingTerminalFull plug-and-play terminal — TopBar, DrawingSidebar, IndicatorModal, SettingsModal, SymbolSearch built-in. -
useRomacoChartHook returning chart, isReady, error, setData, resize for full imperative control. -
ChartGridMulti-chart synchronized grid layouts. -
useChartAgentAI agent WebSocket hook with auto-execution. -
usePaperTradingPaper trading hook with positions, PnL and order helpers. -
TopBar, DrawingSidebar, IndicatorModal, SettingsModal, SymbolSearchModal, IndicatorSettingsModal, PaperTradingPanelComponentPackaged UI building blocks. -
IDatafeed, IRealtimeSubscription, HistoryRequest, HistoryResponse, ResolvedSymbol, SymbolSearchResultDatafeed contract types — re-exported from romaco-charts/react for convenience.
MinimalChart
The lean chart-only wrapper. Renders the chart surface without the default React indicator set. Register them explicitly.
import { MinimalChart, registerDefaultReactIndicators } from 'romaco-charts/react'
registerDefaultReactIndicators()
export function ChartView({ candles }: { candles: Array<any> }) {
return <MinimalChart data={candles} preset="romaco" style={{ height: 360 }} />
}
Chart component
The default choice for React applications. Auto-registers the default React indicator set.
import { useState } from 'react'
import { Chart } from 'romaco-charts/react'
import type { RomacoChart } from 'romaco-charts'
export function TradingView({ candles }: { candles: Array<any> }) {
const [chart, setChart] = useState<RomacoChart | null>(null)
return (
<div style={{ height: 520 }}>
<Chart
data={candles}
preset="dark"
indicators={['EMA', 'RSI']}
drawings={true}
onReady={setChart}
/>
<button onClick={() => chart?.downloadAsPNG('snapshot.png')}>
Export PNG
</button>
</div>
)
}
Ref access
import { useRef } from 'react'
import { Chart } from 'romaco-charts/react'
import type { RomacoChartRef } from 'romaco-charts/react'
function ExportableChart({ data }: { data: Array<any> }) {
const chartRef = useRef<RomacoChartRef>(null)
return (
<>
<button onClick={() => chartRef.current?.api?.resetZoom()}>
Reset view
</button>
<Chart ref={chartRef} data={data} preset="dark" />
</>
)
}
The ref exposes api (the RomacoChart instance) and container (the host div).
TradingTerminal
One component mounts the complete trading workspace. Pass a datafeed and everything else is handled internally.
import { useMemo, useRef } from 'react';
import { TradingTerminal } from 'romaco-charts/react';
import type { TradingTerminalRef, IDatafeed } from 'romaco-charts/react';
import { createNoopRealtimeSubscription } from 'romaco-charts/datafeed';
export function Desk() {
const terminalRef = useRef<TradingTerminalRef>(null);
const datafeed: IDatafeed = useMemo(() => ({
async searchSymbols(query) {
const res = await fetch(`/api/symbols?q=${query}`);
return res.json();
},
async resolveSymbol(symbol) {
return { symbol, supportedResolutions: ['1m','5m','1h','1d'] };
},
async getHistory({ symbol, resolution }) {
const res = await fetch(`/api/history?symbol=${symbol}&tf=${resolution}`);
return { bars: await res.json() };
},
subscribeBars() {
return createNoopRealtimeSubscription();
},
}), []);
return (
<div style={{ height: '100vh' }}>
<TradingTerminal
ref={terminalRef}
symbol="AAPL"
datafeed={datafeed}
preset="romaco"
onReady={(chart) => console.log('chart ready', chart)}
onTimeframeChange={(tf) => console.log('tf', tf)}
onSymbolChange={(sym) => console.log('sym', sym)}
onError={(err, ctx) => console.error(ctx, err)}
/>
</div>
);
}
TradingTerminal Props
| Prop | Type | Description |
|---|---|---|
| data | CandleDataInput[] | Manual mode. Optional when datafeed is provided. |
| datafeed | IDatafeed | Feed-driven mode. Handles symbol search, history, and realtime. |
| symbol | string | Display symbol. Default 'BTC/USDT'. Reactive in feed-driven mode. |
| preset | ThemePresetName | Theme preset. Default 'dark'. |
| developerMode | boolean | Enables debug overlays and partial-history overlay. |
| historyBackfillTrigger | 'pan' | 'pan-and-zoom' | Default 'pan'. Controls what triggers loading more history. |
| maxBars | number | Caps total bars in memory across initial load and backfill. |
| onReady(chart) | (chart) => void | Fired when active chart instance is ready. |
| onError(error, context) | (Error, 'history'|'realtime'|'search') => void | Fired on datafeed failures. |
| onTimeframeChange(tf) | (string) => void | Emits canonical lowercase: 1h, 4h, 1d, 1w, 1mo. |
| onSymbolChange(symbol) | (string) => void | Fired when user changes symbol from terminal UI. |
TradingTerminalRef
ref.apiActive RomacoChart instance (single chart).ref.getActiveChart()Active chart in grid — follows focused cell, falls back to first ready.ref.getCharts()Map<string, RomacoChart> of all charts in layout.ref.getGrid()ChartGridRef handle when using multi-chart layout.ref.containerHost HTMLDivElement.useRomacoChart()
Full imperative lifecycle control over the container element.
import { useEffect, useRef } from 'react'
import { useRomacoChart } from 'romaco-charts/react'
function RealtimeChart({ stream }) {
const containerRef = useRef<HTMLDivElement>(null)
const { chart, isReady, error, resize } = useRomacoChart(containerRef, {
preset: 'dark',
indicators: ['VOL'],
autoResize: true,
registerCommonIndicators: true,
})
useEffect(() => {
if (!chart || !isReady) return
return stream.onCandle((bar) => chart.appendData([bar]))
}, [chart, isReady, stream])
return <div ref={containerRef} style={{ height: 500 }} />
}
Returns: chart, isReady, error, setData(), resize(). Options: autoResize, resizeDebounce, registerCommonIndicators.
ChartGrid
import { ChartGrid } from 'romaco-charts/react'
const cells = [
{ id: 'btc-1m', symbol: 'BTC/USDT', timeframe: '1m', data: candles1m },
{ id: 'btc-15m', symbol: 'BTC/USDT', timeframe: '15m', data: candles15m },
]
<ChartGrid
cells={cells}
layout="2x1"
preset="dark"
showHeaders={true}
onCellTimeframeChange={(cellId, tf) => console.log(cellId, tf)}
syncOptions={{ symbol: true, timeRange: true }}
/>
Key props: showHeaders (renders timeframe selector), onCellTimeframeChange(cellId, timeframe) (emits canonical lowercase), syncOptions, onCellFocus, onChartReady.
Default React Indicators
Chart, RomacoChartReact, and useRomacoChart auto-register: SMA, EMA, MA, RSI, MACD, BOLL, KDJ, VOL. For other studies (DMI, WR, AVWAP), register manually:
import { registerIndicators } from 'romaco-charts/react'
import { DMIIndicator, WRIndicator } from 'romaco-charts/indicators'
registerIndicators(DMIIndicator, WRIndicator)
AI and Paper Trading hooks
const [chart, setChart] = useState<RomacoChart | null>(null)
const agent = useChartAgent({
chart,
websocketUrl: 'wss://example.com/ws/chat',
sessionId: 'desk-01',
})
const { positions, balance, equity, stats, openLong, openShort, closePosition }
= usePaperTrading(chart, {
autoEnable: true,
config: { initialBalance: 10_000 },
})
Datafeed
The IDatafeed contract keeps market data fetching in the host app.
Import types from romaco-charts/datafeed or from romaco-charts/react.
IDatafeed interface
import type {
IDatafeed,
HistoryRequest,
HistoryResponse,
IRealtimeSubscription,
RealtimeConnectionState,
ResolvedSymbol,
SymbolSearchResult,
} from 'romaco-charts/react'; // or 'romaco-charts/datafeed'
interface IDatafeed<TBar = CandleDataInput> {
searchSymbols(query: string, signal?: AbortSignal): Promise<SymbolSearchResult[]>
resolveSymbol(symbol: string, signal?: AbortSignal): Promise<ResolvedSymbol>
getHistory(request: HistoryRequest, signal?: AbortSignal): Promise<HistoryResponse<TBar>>
subscribeBars(
symbol: string,
resolution: string,
onBar: (bar: TBar) => void,
onStatusChange?: (state: RealtimeConnectionState) => void
): IRealtimeSubscription
}
Related Types
type RealtimeConnectionState = 'connected' | 'disconnected' | 'stale'
interface SymbolSearchResult {
symbol: string
name: string
exchange?: string
type?: 'crypto' | 'stock' | 'forex' | 'futures' | 'index'
}
interface ResolvedSymbol {
symbol: string
priceScale?: number
timezone?: string
session?: string
sessionMetadata?: {
timezone?: string
sessions?: Array<{
id: string
label?: string
start: string
end: string
days?: number[]
color?: string
}>
}
supportedResolutions: string[]
}
interface HistoryRequest {
symbol: string
resolution: string
from: number
to: number
cursor?: string
limit?: number
}
interface HistoryResponse<TBar = CandleDataInput> {
bars: TBar[]
nextCursor?: string
partial?: boolean
}
interface IRealtimeSubscription {
unsubscribe(): void
readonly closed?: boolean
}
supportedResolutions can use the host's native tokens. If your backend prefers 60 instead of 1h, advertise 60 there; TradingTerminal normalizes requested timeframe values and picks the closest supported resolution when an exact match is unavailable.
Use nextCursor when your data service supports cursor-based pagination. TradingTerminal consumes it automatically for left-edge history backfill.
Return partial: true when the loaded window is intentionally incomplete. The chart keeps that signal internal by default and only surfaces the partial-history overlay when developerMode is enabled.
Minimal implementation
import type { IDatafeed } from 'romaco-charts/react';
import { createNoopRealtimeSubscription } from 'romaco-charts/datafeed';
export const datafeed: IDatafeed = {
async searchSymbols(query) {
const res = await fetch(`/api/symbols?q=${encodeURIComponent(query)}`);
return res.json();
},
async resolveSymbol(symbol) {
const res = await fetch(`/api/symbols/${encodeURIComponent(symbol)}`);
return res.json();
},
async getHistory(request) {
const res = await fetch('/api/history', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
return res.json();
},
subscribeBars(symbol, resolution, onBar, onStatusChange) {
const ws = new WebSocket(`wss://example.com/stream?symbol=${symbol}&resolution=${resolution}`);
ws.onopen = () => onStatusChange?.('connected');
ws.onclose = () => onStatusChange?.('disconnected');
ws.onerror = () => onStatusChange?.('stale');
ws.onmessage = (event) => onBar(JSON.parse(event.data));
return {
unsubscribe() {
ws.close();
},
get closed() {
return ws.readyState === WebSocket.CLOSED;
},
};
},
};
Wiring a chart manually
setDatafeed() only injects the contract. It does not fetch automatically. This manual flow applies to the core chart API. TradingTerminal automates symbol resolution, history loading, backfill, and realtime subscriptions when you pass datafeed directly.
import { createChart } from 'romaco-charts'
const chart = createChart('#chart', { preset: 'dark' })
chart.setDatafeed(datafeed)
const symbol = await datafeed.resolveSymbol('BTCUSDT')
chart.setSymbol(symbol.symbol)
const history = await datafeed.getHistory({
symbol: symbol.symbol,
resolution: '1h',
from: 1717000000,
to: 1717600000,
})
chart.setData(history.bars)
const sub = datafeed.subscribeBars(symbol.symbol, '1h', (bar) => {
chart.updateData(bar)
})
window.addEventListener('beforeunload', () => sub.unsubscribe(), { once: true })
No-op subscriptions
import { createNoopRealtimeSubscription } from 'romaco-charts/datafeed';
subscribeBars() {
return createNoopRealtimeSubscription();
}
Design guidance
- Keep symbol search and history fetching in your host app or data service.
- Let the chart own rendering, view state, and realtime candle updates.
- Use ResolvedSymbol.sessionMetadata when your UI needs session windows or timezone-aware overlays.
AI Integration
Use romaco-charts/agent when you want an LLM, rules engine, or remote agent service to reason about the live chart state and return structured actions.
ChartAgentController
import { ChartAgentController } from 'romaco-charts/agent';
const controller = new ChartAgentController(chart);
controller.getChartContext(); // snapshot of runtime state
controller.getChartCapabilities(); // available actions and flags
controller.executeAction({ action: 'addIndicator', indicatorType: 'EMA', params: [20] });
controller.executeActions([{ action: 'setRenderQuality', quality: 'high' }]);
controller.exportSnapshot({ includeSubpanels: true, format: 'png' });
controller.saveChartState();
controller.loadChartState(savedState);
ChartContext — What it contains
This is the object you send to your agent when you want the model to reason about the current chart state.
Available Actions
| Category | Actions |
|---|---|
| Drawings | addDrawing, removeDrawing, clearDrawings |
| Indicators | addIndicator, removeIndicator, setIndicatorVisibility |
| View | setZoom, zoomIn, zoomOut, resetView, scrollTo, setAutoFit |
| Styling | setTheme, setCandleStyle, setRenderQuality, highlightCandles |
| Paper Trading | enablePaperTrading, disablePaperTrading, openPaperLong, openPaperShort, closePaperPosition, closeAllPaperPositions |
| Persistence | loadChartState, addAlert, updateAlert, removeAlert, clearAlerts |
Transport Contract
interface AgentRequest {
requestId?: string
protocolVersion?: number
dryRun?: boolean
type: 'chat_message' | 'context_update' | 'snapshot'
sessionId: string
message?: string
chartContext?: ChartContext
snapshot?: string
}
interface AgentResponse {
requestId?: string
protocolVersion?: number
dryRun?: boolean
type: 'chat_response' | 'action_batch' | 'error'
sessionId: string
textResponse?: string
chartActions?: ChartAction[]
actionResults?: ActionResult[]
executedChartActions?: ChartAction[]
pendingChartActions?: ChartAction[]
capabilities?: ChartCapabilities
error?: string
}
useChartAgent()
The React hook handles WebSocket lifecycle, chart context capture, optional auto-execution, and exponential reconnect.
import { useState } from 'react'
import { Chart, useChartAgent } from 'romaco-charts/react'
import type { RomacoChart } from 'romaco-charts'
function AgentTerminal({ data }: { data: Array<any> }) {
const [chart, setChart] = useState<RomacoChart | null>(null)
const {
sendMessage,
sendSnapshot,
executeActions,
isConnected,
isLoading,
lastResponse,
} = useChartAgent({
chart,
websocketUrl: 'wss://example.com/ws/chat',
sessionId: 'desk-01',
autoExecuteActions: false,
})
return (
<>
<Chart data={data} preset="dark" onReady={setChart} />
<button disabled={!isConnected || isLoading} onClick={() => sendMessage('Add RSI and a trendline')}>
Ask AI
</button>
<button onClick={() => executeActions(lastResponse?.pendingChartActions)}>
Apply pending actions
</button>
</>
)
}
Paper Trading
Built-in paper trading with virtual positions, PnL tracking, and chart-linked order visualization.
Enable
import { createChart } from 'romaco-charts'
const chart = createChart('#chart', { preset: 'dark' })
const manager = chart.enablePaperTrading({
initialBalance: 10_000,
defaultSymbol: 'BTC/USDT',
commissionRate: 0.001,
})
Open and Close Positions
chart.openLong(0.1, 40_000, 45_000);
chart.openShort(0.2, 44_000, 39_500);
chart.openPosition({
side: 'long',
quantity: 1,
stopLoss: 41_000,
takeProfit: 47_000,
});
chart.closePosition(positionId);
Events and Manager Access
manager.on('positionOpened', (p) => console.log(p.id));
manager.on('positionClosed', (p) => console.log(p.realizedPnL));
manager.on('stopLossHit', (p) => console.log(p.symbol));
manager.on('takeProfitHit', (p) => console.log(p.symbol));
// Recover later
chart.getPaperTradingManager();
chart.isPaperTradingEnabled();
Disable Overlay and Drive Your Own UI
chart.enablePaperTrading({
initialBalance: 10_000,
renderOverlay: false,
});
React hook — usePaperTrading
import { usePaperTrading } from 'romaco-charts/react'
function TradingPanel({ chart }) {
const {
positions,
balance,
equity,
stats, // winRate, avgWin, avgLoss, profitFactor, drawdown
openLong,
openShort,
closePosition,
} = usePaperTrading(chart, {
autoEnable: true,
config: { initialBalance: 10_000 },
})
return (
<div>
<p>Balance: ${balance} · Equity: ${equity}</p>
<p>Win rate: {stats.winRate}%</p>
<button onClick={() => openLong(0.1)}>Long</button>
<button onClick={() => openShort(0.1)}>Short</button>
{positions.map((p) => (
<button key={p.id} onClick={() => closePosition(p.id)}>
Close {p.id}
</button>
))}
</div>
)
}
Real-World Integration
End-to-end example showing how to wire TradingTerminal to a real production stack: REST API for history, WebSocket for realtime, full lifecycle management, and proper cleanup.
Stack assumed
- React 18+ frontend
- REST endpoint that returns
{ bars, nextCursor?, partial? } - WebSocket endpoint that pushes price ticks
- Auth via JWT (Supabase, Auth0, custom — doesn't matter)
Architecture
React component
└─ TradingTerminal (datafeed={datafeed})
└─ createDatafeed() — IDatafeed adapter
├─ REST SDK (/history)
└─ WebSocket layer (ws://.../prices)
1. The datafeed adapter
import type { IDatafeed } from 'romaco-charts/react'
const SUPPORTED_RESOLUTIONS = ['1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w', '1mo']
function isAbortError(error: unknown): boolean {
return Boolean(error && typeof error === 'object' && 'name' in error && error.name === 'AbortError')
}
function normalizeError(error: unknown, fallback: string): Error {
if (error instanceof Error) return error
if (typeof error === 'string' && error.trim()) return new Error(error)
return new Error(fallback)
}
export function createDatafeed(): IDatafeed {
return {
async searchSymbols(query, signal) {
if (!query || query.length < 2) return []
try {
const res = await fetch(`/api/symbols?q=${query}`, { signal })
const results = await res.json()
return (results ?? []).map((r: any) => ({
symbol: r.symbol,
name: r.name ?? r.symbol,
type: r.type ?? 'stock',
}))
} catch (error) {
if (isAbortError(error)) throw error
return []
}
},
async resolveSymbol(symbol) {
return { symbol, supportedResolutions: SUPPORTED_RESOLUTIONS }
},
async getHistory(request, signal) {
const { symbol, resolution, from, to, cursor, limit } = request
const toMs = (t: number) => (t > 1e12 ? t : t * 1000)
const startDate = from ? new Date(toMs(from)).toISOString() : undefined
const endDate = to ? new Date(toMs(to)).toISOString() : undefined
try {
const res = await fetch('/api/history', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ symbol, resolution, startDate, endDate, cursor, limit }),
signal,
})
const raw = await res.json()
if (!raw || !Array.isArray(raw.bars)) {
throw new Error(`Invalid history response for ${symbol} (${resolution})`)
}
const bars = raw.bars.map((d: any) => ({
time: new Date(d.date).getTime(),
open: Number(d.open),
high: Number(d.high),
low: Number(d.low),
close: Number(d.close),
volume: Number(d.volume ?? 0),
}))
return {
bars,
nextCursor: raw.nextCursor,
partial: Boolean(raw.partial),
}
} catch (error) {
if (isAbortError(error)) throw error
throw normalizeError(error, `Failed to load history for ${symbol} (${resolution})`)
}
},
subscribeBars(symbol, resolution, onBar, onStatusChange) {
const normalizedSymbol = symbol.trim().toUpperCase()
let closed = false
const ws = new WebSocket(`wss://example.com/stream?symbol=${symbol}&resolution=${resolution}`)
ws.onopen = () => { if (!closed) onStatusChange?.('connected') }
ws.onclose = () => { if (!closed) onStatusChange?.('disconnected') }
ws.onerror = () => { if (!closed) onStatusChange?.('stale') }
ws.onmessage = (event) => {
if (closed) return
onBar(JSON.parse(event.data))
}
return {
get closed() { return closed },
unsubscribe() {
if (closed) return
closed = true
ws.close()
},
}
},
}
}
2. The React component
'use client'
import { useMemo, useRef } from 'react'
import { TradingTerminal } from 'romaco-charts/react'
import type { TradingTerminalRef } from 'romaco-charts/react'
import { createDatafeed } from '../lib/chartDatafeed'
interface ChartWidgetProps {
symbol: string
onSymbolChange?: (symbol: string) => void
onTimeframeChange?: (timeframe: string) => void
className?: string
}
export function ChartWidget({ symbol, onSymbolChange, onTimeframeChange, className = '' }: ChartWidgetProps) {
const terminalRef = useRef<TradingTerminalRef>(null)
const datafeed = useMemo(() => createDatafeed(), [])
return (
<div className={`w-full h-full flex flex-col overflow-hidden ${className}`}>
<TradingTerminal
ref={terminalRef}
symbol={symbol}
datafeed={datafeed}
preset="romaco"
onSymbolChange={onSymbolChange}
onTimeframeChange={onTimeframeChange}
onError={(error, context) => {
console.error(`[chart:${context}]`, error.message)
}}
historyBackfillTrigger="pan"
// maxBars={5000}
/>
</div>
)
}
3. Mounting in your app
import { useState } from 'react'
import { ChartWidget } from './components/ChartWidget'
export function Dashboard() {
const [symbol, setSymbol] = useState('AAPL')
return (
<div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
<header style={{ padding: 12 }}>
<h1>Active: {symbol}</h1>
</header>
<main style={{ flex: 1, minHeight: 0 }}>
<ChartWidget symbol={symbol} onSymbolChange={setSymbol} />
</main>
</div>
)
}
minHeight: 0 on the flex item is the canonical fix for charts disappearing inside a flex container.
4. Next.js / SSR
'use client' // Required — TradingTerminal touches document, window, and DOM canvas
import dynamic from 'next/dynamic'
const ChartWidget = dynamic(
() => import('./ChartWidget').then((m) => m.ChartWidget),
{ ssr: false, loading: () => <div style={{ height: '100%' }}>Loading chart…</div> }
)
5. Error handling pattern
function handleChartError(error: Error, context: 'history' | 'realtime' | 'search') {
switch (context) {
case 'history':
toast.error(`Could not load chart history: ${error.message}`)
break
case 'realtime':
toast.warn('Realtime feed disconnected, retrying…')
break
case 'search':
console.debug('[chart:search]', error)
break
}
}
6. Authentication
The datafeed runs in your app context — it has access to whatever auth state you maintain. The chart does not need an auth API.
async function fetchPriceHistory(symbol, resolution, opts) {
const token = await getAccessToken()
const res = await fetch(`/api/history?symbol=${symbol}&resolution=${resolution}`, {
headers: { Authorization: `Bearer ${token}` },
signal: opts.signal,
})
if (!res.ok) throw new Error(`History request failed: ${res.status}`)
return res.json()
}
7. History backfill (infinite scroll)
If your backend supports cursor pagination, TradingTerminal automatically loads more history as the user drags the chart left. Return nextCursor from getHistory.
async getHistory(request, signal) {
const response = await fetch('/api/history', {
method: 'POST',
body: JSON.stringify(request),
signal,
})
const data = await response.json()
return {
bars: data.bars,
nextCursor: data.next_cursor, // chart passes this back on next backfill request
}
}
Backfill trigger
| Value | Behavior |
|---|---|
| 'pan' (default) | Loads more history on left-drag only. Matches TradingView behavior. |
| 'pan-and-zoom' | Also triggers when zoom-out pushes the left edge into view. |
maxBars tuning
| Timeframe | maxBars |
|---|---|
| Daily / weekly | omit (no limit) |
| 1h | 3,000–5,000 |
| 15m / 5m | 5,000–10,000 |
| 1m | 2,000–5,000 |
8. Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| Chart renders blank, no errors | Container has no height | Wrap it in a container with explicit height or use flex with minHeight: 0 |
| Real-time bar jumps off chart | Timestamp not aligned to resolution | Apply intervalFloor in both getHistory and subscribeBars |
| Subscription fires after unmount | Missing closed flag check | Set closed = true in unsubscribe() and check it in every async callback |
| Datafeed recreated every render | createDatafeed() called in component body | Wrap in useMemo(() => createDatafeed(), []) |
| ReferenceError: document is not defined | SSR rendering | Use 'use client' or dynamic(..., { ssr: false }) |
| Two candles at same timestamp | Backend returns overlapping bars on pagination | Deduplicate by timestamp in getHistory before returning |
| Resolution mismatch with backend | Backend uses 1H/60/1hour format | Advertise those values in resolveSymbol().supportedResolutions |
Chart Options
Romaco exposes two main configuration layers. Use the simplified config for presets and a shorter setup path. Use the full config when you want direct control over the runtime surface.
SimpleChartConfig
interface SimpleChartConfig {
preset?: 'dark' | 'light' | 'bloomberg' | 'neon' | 'professional' | 'classic' | 'romaco'
data?: CandleDataInput[]
width?: number
height?: number
background?: string
bullishColor?: string
bearishColor?: string
wickColor?: string
gridColor?: string
textColor?: string
enableZoom?: boolean
enablePanning?: boolean
autoFit?: boolean
touchpadOptions?: {
naturalScrolling?: boolean
pinchZoomSensitivity?: number
enableHorizontalScroll?: boolean
scrollSensitivity?: number
}
drawings?: boolean | DrawingsConfig
indicators?: Array<string | IndicatorConfig>
renderBackend?: 'auto' | 'canvas2d' | 'webgpu'
quality?: 'low' | 'high' | 'magnifique'
developerMode?: boolean
performanceMonitoring?: boolean
performanceDebug?: boolean | PerformanceDebugOptions
dataConflation?: boolean
locale?: string
host?: ChartHostContracts
onCandleClick?: (candle: CandleData, index: number) => void
onZoom?: (zoomLevel: number) => void
onPan?: (panX: number, panY: number) => void
onCrosshairMove?: (candle: CandleData | null, x: number, y: number) => void
}
ChartOptions
interface ChartOptions {
width?: number
height?: number
theme?: ChartTheme
candleStyle?: CandleStyle
enableZoom?: boolean
enablePanning?: boolean
touchpadOptions?: { /* same as SimpleChartConfig */ }
candleRenderer?: CandleRendererInterface
minCandleWidth?: number
maxCandleWidth?: number
priceScaleWidth?: number
timeScaleHeight?: number
resizablePriceScale?: boolean
resizableTimeScale?: boolean
margins?: [number, number, number, number]
watermark?: Watermark
showPriceScale?: boolean
showTimeScale?: boolean
autoFitEnabled?: boolean
autoFitPadding?: number
onCandleClick?: (candle: CandleData, index: number) => void
onZoom?: (zoomLevel: number) => void
onPan?: (panX: number, panY: number) => void
onCrosshairMove?: (candle: CandleData | null, x: number, y: number) => void
renderQuality?: 'low' | 'high' | 'magnifique'
developerMode?: boolean
performanceMonitoring?: boolean
performanceDebug?: boolean | PerformanceDebugOptions
dataConflation?: boolean | DataConflationConfig
renderBackend?: 'auto' | 'canvas2d' | 'webgpu'
}
Confirmed Constructor Defaults
| Option | Default |
|---|---|
| width | 800 |
| height | 400 |
| enableZoom | true |
| enablePanning | true |
| minCandleWidth | 1 |
| maxCandleWidth | 50 |
| margins | [10, 10, 10, 10] |
| autoFitEnabled | true |
| autoFitPadding | 0.05 |
| priceScaleWidth | 80 |
| timeScaleHeight | 40 |
| renderQuality | 'magnifique' |
| renderBackend | 'auto' |
| theme.background | '#ffffff' |
| theme.textColor | '#000000' |
| theme.fontFamily | 'Arial' |
| theme.fontSize | 12 |
| candleStyle.bullishColor | '#00ff00' |
| candleStyle.bearishColor | '#ff0000' |
| candleStyle.borderColor | '#000000' |
| candleStyle.borderWidth | 1 |
| candleStyle.opacity | 1 |
renderQuality Levels
| Level | Meaning |
|---|---|
| low | Favor speed over fidelity. |
| high | Balanced quality and performance. |
| magnifique | Highest visual quality. This is the default. |
renderBackend
'auto': lets the chart decide between Canvas2D and WebGPU.'canvas2d': forces Canvas2D rendering.'webgpu': requests the WebGPU renderer.
Key Types
interface CandleData {
time: number
timestamp: number
open: number
high: number
low: number
close: number
volume?: number
}
type CandleDataInput =
| { time: number; timestamp?: number; open: number; high: number; low: number; close: number; volume?: number }
| { timestamp: number; time?: number; open: number; high: number; low: number; close: number; volume?: number }
interface ChartTheme {
background?: string | string[]
textColor?: string
fontFamily?: string
fontSize?: number
crosshair?: CrosshairOptions
border?: { color?: string; width?: number; radius?: number }
xAxis?: AxisOptions
yAxis?: AxisOptions
}
interface CandleStyle {
bullishColor?: string
bearishColor?: string
borderColor?: string
borderWidth?: number
wickColor?: string
opacity?: number
}
interface DrawingsConfig {
enabled?: boolean
position?: 'left' | 'right'
templates?: DrawingTemplate[]
}
interface IndicatorConfig {
name: string
params?: number[]
styles?: Record<string, unknown>
}
RomacoChart Class
The low-level class behind createChart() and the React wrapper. Use it when you need direct runtime control.
Pick the right entry point
createChart()— shortest vanilla setup.RomacoChart.create()— same simplified config from the class.new RomacoChart()— fullChartOptionssurface.
Static API
RomacoChart.create(container, config?)Factory with SimpleChartConfig.RomacoChart.registerIndicators(...ctors)Register indicator classes for later use.RomacoChart.registerIndicator(name, ctor)Register a single indicator under a specific name.RomacoChart.getRegisteredIndicators()Returns currently registered indicator names.RomacoChart.getAvailableQualityLevels()Returns supported render quality presets.Constructor
new RomacoChart(container: HTMLElement, options?: ChartOptions)
Data API
setData(data)Replace the full series.appendData(candles)Preferred API for small realtime batches.updateData(candle)Updates last candle when timestamp matches, appends otherwise.getData()Returns the current CandleData[].setDatafeed(datafeed | null)Inject the host-provided data contract. Does not fetch automatically.getDatafeed()Returns the current IDatafeed or null.setSymbol(symbol)Set the display symbol.View and Interaction
setZoom(level)Set zoom level directly.getZoom()Get current zoom level.zoomIn(factor?)Zoom in by optional factor.zoomOut(factor?)Zoom out by optional factor.setPan(panX, panY?)Set pan offset.getPan()Get current pan {x, y}.resetZoom()Reset zoom to default.setAutoFit(enabled)Toggle auto-fit behavior.isAutoFitEnabled()Check auto-fit state.resize(width?, height?)Force canvas resize.destroy()Teardown the chart and release resources.Indicators and Panels
addIndicator(name, options?)Returns runtime indicator id.updateIndicator(id, options)Update params or styles.removeIndicator(id)Remove indicator by id.getIndicator(id)Get indicator instance or null.getIndicatorManager()Get the IndicatorManager.setIndicatorVisible(id, visible)Show/hide indicator.toggleIndicatorVisible(id)Toggle indicator visibility.getIndicatorVisibility(id)Check visibility state.getPanels()Get all subpanels.setPanelHeight(panelId, ratio)Resize panel by ratio (0-1).Drawings
getDrawingManager()Get DrawingManager or null.getDrawingToolbar()Get DrawingToolbar or null.setDrawingToolbarVisibilityMode(mode)Set toolbar visibility mode.setDrawingLineColor(color)Set default drawing line color.setDrawingLineWidth(width)Set default drawing line width.setDrawingStyles(styles)Set default drawing styles.updateDrawingStyle(drawingId, styles)Update specific drawing styles.Theme and Runtime Controls
setTheme(theme)Apply partial theme override.applyPreset(name)Apply a built-in preset.setCandleStyle(style)Override candle appearance.setRenderer(renderer)Switch renderer at runtime.setRenderQuality(quality)'low' | 'high' | 'magnifique'.getRenderQuality()Get current render quality.setRenderBackend(option)Async: 'auto' | 'canvas2d' | 'webgpu'.getRenderBackendOption()Get requested backend.getActiveRenderBackendType()'canvas2d' | 'webgpu'.setDeveloperMode(enabled)Toggle developer mode.isDeveloperModeEnabled()Check developer mode.Alerts
getAlertManager()Get AlertManager.addAlert(price, options?)Create a price alert.removeAlert(id)Remove alert by id.getAlert(id)Get alert by id.getAlerts()Get all alerts.clearAlerts()Remove all alerts.State and Paper Trading
saveState()Returns PersistableChartState.loadState(state, options?)Restore chart state.enablePaperTrading(config?)Enable paper trading.disablePaperTrading()Disable paper trading.getPaperTradingManager()Get PaperTradingManager or null.isPaperTradingEnabled()Check if paper trading is active.openLong(qty, sl?, tp?)Open long position.openShort(qty, sl?, tp?)Open short position.openPosition(params)Open position with full params.closePosition(positionId)Close position by id.Public Types
Main exported contracts outside the RomacoChart class. Use this as the map for subpath imports.
Agent Protocol
Import from romaco-charts/agent.
interface ChartContext {
visibleRange: VisibleRange
visibleCandles: CandleData[]
existingDrawings: DrawingSummary[]
existingIndicators: IndicatorSummary[]
chartDimensions: { width: number; height: number }
currentPrice?: number
currentTheme?: string
pan?: { x: number; y: number }
locale?: string
timezone?: string
renderBackend?: 'canvas2d' | 'webgpu'
renderQuality?: RenderQuality
availableDrawingTypes?: string[]
availableIndicatorTypes?: string[]
activePlugins?: string[]
panels?: PanelSummary[]
capabilities?: ChartCapabilities
paperTrading?: PaperTradingSummary | null
alerts?: AlertSummary[]
performance?: PerformanceSummary
totalCandles: number
zoomLevel: number
}
type ChartAction =
| { action: 'addDrawing'; drawingType: string; points: Point[]; styles?: Record<string, unknown> }
| { action: 'removeDrawing'; drawingId: string }
| { action: 'clearDrawings' }
| { action: 'addIndicator'; indicatorType: string; params?: number[]; styles?: Record<string, unknown> }
| { action: 'removeIndicator'; indicatorId: string }
| { action: 'setIndicatorVisibility'; indicatorId: string; visible: boolean }
| { action: 'setTheme'; theme: string }
| { action: 'setCandleStyle'; style: Partial<CandleStyle> }
| { action: 'setZoom'; zoomLevel: number }
| { action: 'zoomIn'; factor?: number }
| { action: 'zoomOut'; factor?: number }
| { action: 'resetView' }
| { action: 'scrollTo'; timestamp: number }
| { action: 'setAutoFit'; enabled: boolean }
| { action: 'setRenderQuality'; quality: 'low' | 'high' | 'magnifique' }
| { action: 'highlightCandles'; timestamps: number[] }
| { action: 'enablePaperTrading'; config?: PaperTradingConfig }
| { action: 'disablePaperTrading' }
| { action: 'openPaperPosition'; params: OpenPositionParams }
| { action: 'openPaperLong'; quantity: number; stopLoss?: number; takeProfit?: number }
| { action: 'openPaperShort'; quantity: number; stopLoss?: number; takeProfit?: number }
| { action: 'closePaperPosition'; positionId: string }
| { action: 'closeAllPaperPositions' }
| { action: 'loadChartState'; state: PersistableChartState }
| { action: 'addAlert'; price: number; options?: CreateAlertOptions }
| { action: 'updateAlert'; alertId: string; options?: Partial<CreateAlertOptions> }
| { action: 'removeAlert'; alertId: string }
| { action: 'clearAlerts' }
interface AgentRequest {
requestId?: string
protocolVersion?: number
dryRun?: boolean
type: 'chat_message' | 'context_update' | 'snapshot'
sessionId: string
message?: string
chartContext?: ChartContext
snapshot?: string
}
interface AgentResponse {
requestId?: string
protocolVersion?: number
dryRun?: boolean
type: 'chat_response' | 'action_batch' | 'error'
sessionId: string
textResponse?: string
chartActions?: ChartAction[]
actionResults?: ActionResult[]
executedChartActions?: ChartAction[]
pendingChartActions?: ChartAction[]
capabilities?: ChartCapabilities
error?: string
}
Datafeed Types
Import from romaco-charts/datafeed or romaco-charts/react.
type RealtimeConnectionState = 'connected' | 'disconnected' | 'stale'
interface SymbolSearchResult {
symbol: string
name: string
exchange?: string
type?: 'crypto' | 'stock' | 'forex' | 'futures' | 'index'
}
interface ResolvedSymbol {
symbol: string
priceScale?: number
timezone?: string
session?: string
sessionMetadata?: ResolvedSessionMetadata
supportedResolutions: string[]
}
interface HistoryRequest {
symbol: string
resolution: string
from: number
to: number
cursor?: string
limit?: number
}
interface HistoryResponse<TBar = CandleDataInput> {
bars: TBar[]
nextCursor?: string
partial?: boolean
}
interface IRealtimeSubscription {
unsubscribe(): void
readonly closed?: boolean
}
interface IDatafeed<TBar = CandleDataInput> {
searchSymbols(query: string, signal?: AbortSignal): Promise<SymbolSearchResult[]>
resolveSymbol(symbol: string, signal?: AbortSignal): Promise<ResolvedSymbol>
getHistory(request: HistoryRequest, signal?: AbortSignal): Promise<HistoryResponse<TBar>>
subscribeBars(
symbol: string,
resolution: string,
onBar: (bar: TBar) => void,
onStatusChange?: (state: RealtimeConnectionState) => void
): IRealtimeSubscription
}
Replay Types
Import from romaco-charts/replay.
type ReplayMode = 'idle' | 'playing' | 'paused' | 'ended'
interface ReplayStatus {
mode: ReplayMode
cursor: number
speed: number
totalBars: number
}
interface IReplayController<TBar = CandleDataInput> {
loadBars(bars: TBar[]): void
play(speed?: number): void
pause(): void
reset(): void
stepForward(count?: number): void
seek(index: number): void
getStatus(): ReplayStatus
onStatusChange(listener: (status: ReplayStatus) => void): () => void
onTick(listener: (tick: ReplayTick<TBar>, status: ReplayStatus) => void): () => void
exportState(): ReplaySessionState<TBar> | null
restoreState(state: ReplaySessionState<TBar> | null): void
destroy(): void
}
Persistence / Profile Types
Import from romaco-charts/profile.
type VisualStateKind =
| 'empty'
| 'loading'
| 'ready'
| 'partial-history'
| 'disconnected'
| 'error'
| 'replay'
interface ChartStateLoadOptions {
restorePreferences?: boolean
restoreIndicators?: boolean
restoreDrawings?: boolean
restoreTheme?: boolean
restoreViewport?: boolean
restoreMetadata?: boolean
restorePanels?: boolean
restoreVisualState?: boolean
restoreReplay?: boolean
}
interface IChartStateApi {
saveState(): PersistableChartState
loadState(state: PersistableChartState, options?: ChartStateLoadOptions): void
}
React-Facing Types
Import from romaco-charts/react.
RomacoChartPropsChart component props.RomacoChartRefChart component ref shape.MinimalChartPropsMinimalChart component props.MinimalChartRefMinimalChart component ref shape.TradingTerminalPropsTradingTerminal component props.TradingTerminalRefTradingTerminal component ref shape.UseRomacoChartConfiguseRomacoChart hook options.UseRomacoChartResultuseRomacoChart hook return.UseChartAgentOptionsuseChartAgent hook options.UseChartAgentReturnuseChartAgent hook return.UsePaperTradingOptionsusePaperTrading hook options.UsePaperTradingResultusePaperTrading hook return.ChartGridPropsChartGrid component props.ChartGridRefChartGrid component ref shape.GridLayoutGrid layout type.ChartCellConfigGrid cell configuration.SyncOptionsGrid synchronization options.The following datafeed types are also re-exported from romaco-charts/react for convenience:
- IDatafeed
- IRealtimeSubscription
- HistoryRequest
- HistoryResponse
- ResolvedSymbol
- SymbolSearchResult
Examples
Simple Candlestick Chart
import { RomacoChart } from 'romaco-charts';
const chart = RomacoChart.create('#chart', {
preset: 'dark',
data: candleData
});
With Indicators
import { RomacoChart, EMAIndicator, RSIIndicator } from 'romaco-charts';
RomacoChart.registerIndicators(EMAIndicator, RSIIndicator);
const chart = RomacoChart.create('#chart', {
preset: 'dark',
data: candleData,
indicators: ['EMA', 'RSI']
});
With Drawing Tools
import { RomacoChart, allTemplates } from 'romaco-charts';
const chart = RomacoChart.create('#chart', {
preset: 'dark',
data: candleData,
drawings: {
templates: allTemplates
}
});
React TradingTerminal
import { TradingTerminal } from 'romaco-charts/react';
export function App() {
return (
<div style={{ height: '100vh' }}>
<TradingTerminal
data={candles}
symbol="BTC/USDT"
preset="romaco"
/>
</div>
);
}
Changelog
Breaking
Added
Fixed
Added
Fixed
Added
Fixed
Added
Fixed
Added
Changed
Fixed
Initial Release
Licensing
Romaco Charts is source-available under BUSL-1.1. Commercial production use requires a separate license from Romaco.