Vela, our next-generation charting platform, is live at vela.luxalgo.com.
LuxAlgo

Scripting engines

Vela™ ships no engine. Install the Pine addon or write your own against the port.

View as Markdown

Vela™ ships no scripting engine. It defines the ScriptingEngine port, owns the bars, and routes whatever an engine produces onto panes — but the language runtime itself is always something you plug in. A bare chart draws candles, drawings and native indicators; addIndicator() without a matching engine raises an actionable error.

There are two ways to get one:

Vela-PineTS

Vela-PineTS is the official Pine addon: it runs indicator() and strategy() scripts through Vela™'s ScriptingEngine port on top of the PineTS runtime. The PineTS-side walkthrough is Using within charts.

npm install @luxalgo/vela-pinets pinets

pinets is the Pine Script® runtime the addon executes; it is a peer dependency, so you install it alongside.

import { Vela } from '@luxalgo/vela';
import { PineEngine } from '@luxalgo/vela-pinets';

const chart = new Vela('#chart', { symbol: 'BTCUSDT', timeframe: '60' });
chart.registerEngine('pine', new PineEngine());

chart.addIndicator(`//@version=5
indicator("EMA 20", overlay=true)
plot(ta.ema(close, 20), color=color.orange, linewidth=2)`);

The addon exports two engines with identical Pine semantics:

ExportWhere scripts runUse it when
PineEnginethe main threadsimplest setup; light scripts
PineWorkerEnginea Web Workerheavy scripts must never block the chart

PineWorkerEngine needs no configuration: the worker source is inlined into the addon's bundle at build time and spawned from a Blob URL. Under a Content-Security-Policy that blocks blob:, host the worker file yourself and pass new PineWorkerEngine({ workerUrl: '/vela-pine-worker.js' }).

Both engines report { streaming: true, visibleRange: true, inputs: true } — live incremental re-execution, viewport-dependent scripts, and an inputs schema that drives the settings dialog. request.security (HTF/LTF/cross-symbol, extended-ticker aware) resolves through Vela™'s own cached data feed: the engine never fetches market data.

Importing Pine libraries

For your own chart integration, Vela-PineTS 0.3.1 with PineTS 0.13.1 supports scripts that use import user/library/version as alias. Pass an importResolver to PineEngine or PineWorkerEngine to supply each library's Pine Script® source. The engine does not download libraries automatically.

import { PineWorkerEngine, type PineLibraryResolver } from '@luxalgo/vela-pinets';

const importResolver: PineLibraryResolver = async (_path, { user, name, version }) => {
    const url = `/pine-libraries/${encodeURIComponent(user)}/${encodeURIComponent(name)}/${version}.pine`;
    const response = await fetch(url);
    if (response.status === 404) return null;
    if (!response.ok) throw new Error(`Library request failed: ${response.status}`);
    return response.text();
};

chart.registerEngine('pine', new PineWorkerEngine({ importResolver }));

Serve the library sources at the URLs your resolver uses. Return null for an unknown library; preparation then fails with an error naming the import. Imported libraries can have their own imports, which use the same resolver. The worker engine calls the resolver on the main thread during preparation, so it does not fetch libraries again for each bar.

If you use Vela Pro 0.4.33, its pineLibraryResolver helper supplies an HTTP resolver:

import { enablePineEngine, pineLibraryResolver } from '@luxalgo/vela-pro';

// Register before constructing your charts or workspace.
enablePineEngine({
    importResolver: pineLibraryResolver({
        url: '/pine-libraries/{user}/{name}/{version}.pine',
    }),
});

Without a resolver, scripts with imports cannot run. This is setup for developers embedding the chart libraries; it does not enable library imports in Vela's hosted Pine editor.

In the widget and the workspace

The shells take engine factories, keyed by language — one instance per chart, so a worker engine gets its own thread and dies with its chart:

import { VelaWorkspace } from '@luxalgo/vela/workspace';
import { PineWorkerEngine } from '@luxalgo/vela-pinets';

new VelaWorkspace('#app', {
    layout: false, // one chart; any grid layout takes the same options
    symbol: 'BTCUSDT',
    engines: { pine: () => new PineWorkerEngine() },
    indicators: [{ name: 'EMA 20', enabled: true, script: '…' }], // manifest entries default to `pine`
});

The engines factories are instantiated once per chart (each workspace cell gets its own — a worker engine per chart). See the workspace.

Registering it once, app-wide

registerDefaultEngine (from @luxalgo/vela/plugin) installs a language for every chart built afterwards — widgets, workspace cells, everything — so feature packages can wire Pine in once instead of threading an engines option through every call site:

import { registerDefaultEngine } from '@luxalgo/vela/plugin';
import { PineWorkerEngine } from '@luxalgo/vela-pinets';

registerDefaultEngine('pine', () => new PineWorkerEngine());

An explicit engines entry still wins for its language.

Script tag / CDN

The addon publishes browser globals next to Vela™'s. Load order is the contract — vela.global.js first, because the addon resolves @luxalgo/vela to the page's window.Vela rather than bundling a second copy of the library:

<script src="vela.global.js"></script>
<script src="vela-pinets.global.js"></script>
<script>
    const chart = new Vela.Vela('#chart', { data: bars, timeframe: '60' });
    chart.registerEngine('pine', new VelaPinets.PineEngine());
</script>

Licensing

pinets is AGPL-3.0, so @luxalgo/vela-pinets is AGPL-3.0 too. That is precisely why it is a separate package: Vela™ itself stays Apache-2.0 and contains no Pine code. Installing the addon brings the AGPL obligations into your deployment — decide that deliberately. If you cannot take them on, write an engine for the language you need against the port instead.

Writing your own engine

The port is small — prepare() (parse a script: inputs + declaration metadata, no market data) and execute() (return a session handle; emit models through the handlers). Vela™ owns the bars and passes them in; the engine never fetches candles.

Adding an engine is the full contract, and playground/demo-engine.ts in this repo is a ~300-line working engine you can read in one sitting — the one the Vela™ playground itself runs.

On this page