Initialization and Usage

This guide explains how to initialize PineTS and run indicators or strategies with detailed documentation of all available options and return values.

View as Markdown

This guide explains how to initialize PineTS and run indicators or strategies with detailed documentation of all available options and return values.

Table of Contents


Installation

npm install pinets

PineTS Constructor

The PineTS class is the main entry point for working with indicators and strategies.

Syntax

const pineTS = new PineTS(
    source: IProvider | any[],
    tickerId?: string,
    timeframe?: string,
    limit?: number,
    sDate?: number,
    eDate?: number
);

Parameters

ParameterTypeRequiredDescription
sourceIProvider | any[]YesEither a data provider instance (e.g., Provider.Binance) or an array of OHLCV data
tickerIdstringNo*The trading pair symbol (e.g., 'BTCUSDT'). Required when using a provider
timeframestringNo*The timeframe/interval for the data. Required when using a provider
limitnumberNoMaximum number of candles to fetch (default: provider-specific, max 5000)
sDatenumberNoStart date in milliseconds timestamp. Used for date range queries
eDatenumberNoEnd date in milliseconds timestamp. Used for date range queries

* Required when using a provider, optional when passing an array of data

Understanding Candle Fetching and Ordering

How limit Works

When you specify a limit without date ranges, PineTS fetches the most recent candles working backwards from the current time:

// Fetches the last 100 daily candles (most recent)
const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);
// Result: 100 candles from ~100 days ago until now

Important notes:

  • Data is fetched from newest to oldest from the exchange
  • Maximum limit is 5000 candles (hard cap, might be changed in the future as we optimize the runtime performance)
  • If no limit is specified, the provider's default is used (varies by provider)

How Date Ranges Work

When you specify sDate and eDate, PineTS fetches all candles within that date range:

const startDate = new Date('2024-01-01').getTime(); // Start: Jan 1, 2024
const endDate = new Date('2024-12-31').getTime(); // End: Dec 31, 2024

const pineTS = new PineTS(
    Provider.Binance,
    'BTCUSDT',
    'D',
    undefined, // No limit - use date range instead
    startDate,
    endDate
);
// Result: All daily candles from Jan 1 to Dec 31, 2024

Date range behavior:

  • Fetches all candles between sDate and eDate
  • If the date range spans more than 1000 candles, PineTS automatically handles pagination
  • Still subject to the 5000 candle maximum
  • Data is ordered chronologically (oldest to newest)

Priority and Combinations

ScenarioBehavior
Only limit specifiedFetches the last limit candles from now
Only sDate and eDate specifiedFetches all candles in the date range (up to 5000)
Both limit and date rangeDate range is used, limit is ignored
Neither specifiedUses provider default (typically 500-1000 candles)

Data Ordering After Fetching

Regardless of how data is fetched, PineTS ensures the data is in chronological order:

// After initialization, data is ordered: [oldest ... newest]
const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);

await pineTS.run((context) => {
    const { close } = context.data;

    // close[0] = current bar (most recent)
    // close[1] = previous bar
    // close[2] = 2 bars ago
    // ... and so on

    console.log('Current close:', close[0]);
    console.log('Previous close:', close[1]);
});

Time series indexing:

  • [0] = current/most recent bar
  • [1] = previous bar
  • [2] = 2 bars ago
  • This matches Pine Script's time series behavior

Examples of Different Fetching Scenarios

// Example 1: Last 100 candles (from now backwards)
const recent = new PineTS(Provider.Binance, 'BTCUSDT', '1h', 100);
// Gets: ~100 hours of data up to current time

// Example 2: Specific date range (all candles in range)
const historical = new PineTS(Provider.Binance, 'ETHUSDT', 'D', undefined, new Date('2023-01-01').getTime(), new Date('2023-12-31').getTime());
// Gets: All daily candles in 2023 (365 candles)

// Example 3: Large limit (will be capped at 5000)
const maxData = new PineTS(Provider.Binance, 'BTCUSDT', '1h', 10000);
// Gets: Only 5000 most recent hourly candles (max cap)

// Example 4: No limit (provider default)
const defaultData = new PineTS(Provider.Binance, 'BTCUSDT', 'D');
// Gets: Provider default amount (typically 500-1000 candles)

Initialization Options

Option 1: Using a Data Provider

The easiest way to initialize PineTS is using a built-in data provider:

import { PineTS, Provider } from 'pinets';

// Basic initialization with limit
const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);

// With date range
const startDate = new Date('2024-01-01').getTime();
const endDate = new Date('2024-12-31').getTime();
const pineTSWithDateRange = new PineTS(
    Provider.Binance,
    'ETHUSDT',
    '1h',
    undefined, // no limit
    startDate,
    endDate
);

Available Providers

ProviderUsageAPI KeyBest For
Provider.BinanceCrypto market dataNot requiredCryptocurrency
Provider.FMPStocks, forex, cryptoRequiredUS/intl stocks
Provider.AlpacaUS stocks, cryptoRequired (key + secret)US equities

Providers that require API keys must be configured before use. See the Data Providers page for detailed setup instructions, configuration options, and examples for each provider.

Supported Timeframes

All providers support the standard timeframe strings: '1', '3', '5', '15', '30', '60', '120', '240', 'D', 'W', 'M'. Timeframes not natively supported by a provider are automatically aggregated from smaller candles. See the Timeframe Reference for the full support matrix.

Option 2: Using Custom Data

You can also provide your own OHLCV data as an array:

import { PineTS } from 'pinets';

const customData = [
    {
        openTime: 1640995200000,
        open: 46000,
        high: 47000,
        low: 45500,
        close: 46500,
        volume: 1234.56,
        closeTime: 1641081599999,
    },
    // ... more candles
];

const pineTS = new PineTS(customData);

Custom Data Format

Each data point in the array must include:

FieldTypeRequiredDescription
opennumberYesOpening price
highnumberYesHighest price
lownumberYesLowest price
closenumberYesClosing price
volumenumberYesTrading volume
openTimenumberNoOpening time (milliseconds timestamp)
closeTimenumberNoClosing time (milliseconds timestamp)

The run() Method

The run() method executes your indicator or strategy code across all candles in the dataset.

Syntax

const context = await pineTS.run(
    pineTSCode: Indicator | Function | String,
    n?: number
): Promise<Context>

Parameters

ParameterTypeDefaultDescription
pineTSCodeIndicator | Function | StringRequiredThe indicator/strategy function to execute. Use Indicator class to pass runtime inputs
nnumberAll periodsNumber of most recent periods to process. If not specified, processes all available data

Running with Runtime Inputs

To pass custom input values to your indicator at runtime, wrap the source in an Indicator instance. Two equivalent forms — pick whichever is more ergonomic:

import { PineTS, Provider, Indicator } from 'pinets';

// Your indicator code (Native Pine Script or PineTS syntax)
const code = `
//@version=5
indicator("My Indicator")
len = input.int(14, "Length")
src = input.source(close, "Source")
plot(ta.sma(src, len))
`;

// Initialize PineTS
const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);

// (a) Per-key override via .input — preferred, validates against the input's schema
const ind = new Indicator(code);
ind.input['Length'] = 50;
const { result } = await pineTS.run(ind);

// (b) Constructor overrides map — legacy, still supported
const ind2 = new Indicator(code, { Length: 50 });

In both forms, the keys must match the title argument of the corresponding input.* call in the source.

The Indicator class also lets you override indicator() / strategy() declaration arguments (e.g. initial_capital, pyramiding, overlay) via ind.prop["name"] = value, and exposes the script's schema for UI builders via getInputsMeta() / getPropsMeta(). See Indicator for the complete API surface.

Return Value

Returns a Promise<Context> object containing:

  • result: The computed indicator values
  • data: Market data arrays (open, high, low, close, volume, etc.)
  • plots: Any plot data generated
  • Additional context properties

The stream() Method

The stream() method provides an event-based interface for handling live data streams, making it easy to integrate with real-time applications.

Syntax

const evt = pineTS.stream(
    pineTSCode: Indicator | Function | String,
    options?: {
        pageSize?: number,
        live?: boolean,
        interval?: number
    }
);

Options

OptionTypeDefaultDescription
pageSizenumberundefined (all)Number of bars per chunk. If not specified, processes all available historical data in one go.
livebooleantrueWhether to continue fetching live data after processing historical data.
intervalnumber1000Polling interval in milliseconds for live data.

Usage

The method returns an object with on() and stop() methods:

// Start streaming
const evt = pineTS.stream(indicator, { pageSize: 1, live: true, interval: 2000 });

// Handle data updates
evt.on('data', (context) => {
    // Process new data
    console.log('New data:', context.result);
});

// Handle alert events (from alert() and alertcondition() calls)
evt.on('alert', (alert) => {
    console.log('Alert:', alert.message);
});

// Handle runtime warnings (non-blocking, e.g. array OOB)
evt.on('warning', (warning) => {
    console.warn('Warning:', warning.message);
});

// Handle errors
evt.on('error', (error) => {
    console.error('Stream error:', error);
});

// Stop streaming
// evt.stop();

Available events:

EventPayloadDescription
'data'ContextNew bar data processed
'alert'{ type, message, title?, freq?, bar_index, time }Alert or alertcondition fired
'warning'{ message, method?, bar }Non-blocking runtime warning
'error'ErrorFatal error (script halted)

The update() Method

update() is a smart wrapper around run() that skips execution when the output cannot have changed. Use it instead of run() in event-driven flows (viewport changes, settings tweaks, etc.) so non-affected indicators are free to call.

Syntax

const context = await pineTS.update(
    pineTSCode?: Indicator | Function | String,
): Promise<Context>

Behavior

CallAction
First call (no cached result)Executes — equivalent to run(). pineTSCode is required here.
Subsequent call, script does not use visible-range built-insReturns the cached Context immediately (no work).
Subsequent call, script uses visible-range AND viewport changed since last cached runRe-executes against the new viewport, returns fresh Context.
Subsequent call, viewport unchangedReturns the cached Context.

The pineTSCode argument is optional after the first call — the previously seen code is reused. Pass it again only when the script source itself changes.

Example

const pine = new PineTS(Provider.Binance, 'BTCUSDT', '1W', 500);

// First call: behaves like run()
await pine.update(code);

// User pans the chart → host computes new visible range
pine.setVisibleRange(t1, t2);
await pine.update();   // re-runs ONLY if the script uses visible-range

// User pans again, but to the same range
await pine.update();   // returns cached result, no compute

Host Environment (Visible Range)

PineTS is renderer-agnostic — it has no UI. But Pine Script has a small set of built-ins whose values come from the chart's UI state (the user's current zoom/pan), notably:

Pine built-inPineTS behavior
chart.left_visible_bar_timeDefaults to marketData[0].openTime; host can override via setVisibleRange()
chart.right_visible_bar_timeDefaults to marketData[marketData.length - 1].openTime; host can override

For most scripts these are unused, and the defaults are "the full loaded range is the viewport" — perfectly defensible since PineTS does compute over everything it loaded. For scripts that do reference them (e.g. LuxAlgo's Supply-and-Demand Visible Range), a host like QFChart can wire its actual viewport in.

setVisibleRange(left: number, right: number)

Stores host viewport values. The setter only updates internal state; it does not trigger a re-run by itself. Call update() afterwards to apply.

pine.setVisibleRange(
    new Date('2024-01-01').getTime(),
    new Date('2024-06-30').getTime(),
);
await pine.update(code);

usesVisibleRange(): boolean

Static-analysis flag set during transpile. Returns true if the loaded script references any visible-range built-in.

Use this to short-circuit fan-out logic across many indicators on one chart — only viewport-dependent indicators need re-runs on user zoom:

function onChartPan(left, right) {
    for (const p of indicators) {
        if (!p.usesVisibleRange()) continue;   // skip — output unaffected
        p.setVisibleRange(left, right);
        chart.clear();                          // QFChart helper
        const ctx = await p.update();
        chart.addIndicator(p.id, ctx.plots);
    }
}

Detection is performed by scanning the transpiled function body (comments are stripped during pine2js, so accidental references inside comments do not flip the flag).

visibleRangeLeft / visibleRangeRight getters

Read back the current values stored by setVisibleRange(). Return undefined when the setter has never been called (the default-falls-back case).

Streaming integration

stream() already handles continuous data input. Combining stream() with setVisibleRange() is a planned follow-up — for the batch path, use run() / update().


Non-Standard Chart Types (Heikin Ashi)

The chart type is the ticker. To run a script "on a Heikin Ashi chart", construct PineTS with an extended ticker — the plain symbol plus a chart-type modifier suffix:

const pineTS = new PineTS(myHaAwareSource, 'BTCUSDT;heikinashi', 'D', 500);

Everything derives from that one setting:

  • chart.is_heikinashi is true (and chart.is_standard is false);
  • syminfo.tickerid carries the modifier ("BINANCE:BTCUSDT;heikinashi"), while syminfo.ticker stays clean;
  • request.security(syminfo.tickerid, tf, expr) requests chart-typed data from the data source, and request.security(ticker.standard(syminfo.tickerid), tf, expr) explicitly requests standard data — including at the chart's own timeframe.

PineTS never transforms bars. The extended ticker is a routing marker, not a conversion request:

  • A host data source that owns the transform (e.g. a charting library embedding PineTS and serving Heikin Ashi views) receives the extended ticker verbatim from getMarketData() / getSymbolInfo() and must serve the derived bars for it — and raw bars for the plain symbol.
  • PineTS' bundled providers (Binance, Alpaca, FMP, Mock) strip the modifier at their boundary and always serve standard candles — so on a bundled provider the extended ticker is a documented no-op (the chart reports Heikin Ashi but runs on standard data).

Consequently, a consistent standalone Heikin Ashi run requires a data source that distinguishes "SYM;heikinashi" from "SYM". Renko / Kagi / Line Break / Point & Figure have no such source and remain unsupported (chart.is_renko etc. are always false; ticker.renko() etc. return the plain symbol).


Context Object

The context object is passed to your indicator function and contains all the data and utilities needed for calculations.

Available Properties

interface Context {
    // Market data (time-series arrays)
    data: {
        open: number[]; // Opening prices
        high: number[]; // Highest prices
        low: number[]; // Lowest prices
        close: number[]; // Closing prices
        volume: number[]; // Volume data
        hl2: number[]; // (high + low) / 2
        hlc3: number[]; // (high + low + close) / 3
        ohlc4: number[]; // (open + high + low + close) / 4
        openTime: number[]; // Opening timestamps
        closeTime: number[]; // Closing timestamps
    };

    // Pine Script namespaces
    ta: TechnicalAnalysis; // Technical analysis functions
    math: PineMath; // Mathematical operations
    input: Input; // Input parameters
    request: PineRequest; // Data requests
    array: PineArray; // Array operations
    core: {
        plot: Function; // Plot data
        plotchar: Function; // Plot characters
        na: Function; // Not-a-number handling
        nz: Function; // Replace NaN with zero
        color: any; // Color utilities
    };

    // Execution state
    idx: number; // Current bar index
    NA: any; // Not-a-number constant (NaN)

    // Variable scopes (for Pine Script compatibility)
    params: any; // Parameter variables
    const: any; // Constant variables
    var: any; // Var-scoped variables
    let: any; // Let-scoped variables

    // Results
    result: any; // Computed results
    plots: any; // Plot data
    alerts: any[]; // Alert events from alert() and alertcondition()
    warnings: any[]; // Runtime warnings (e.g. array OOB)

    // Market context
    marketData: any[]; // Raw market data
    source: IProvider | any[]; // Data source
    tickerId: string; // Trading pair
    timeframe: string; // Timeframe
    limit: number; // Data limit
    sDate: number; // Start date
    eDate: number; // End date
}

Quick Access to Common Data

const { result } = await pineTS.run((context) => {
    // Destructure commonly used items
    const { ta, math, core } = context;
    const { close, open, high, low, volume } = context.data;

    // Your indicator logic here
    const ema9 = ta.ema(close, 9);
    const ema21 = ta.ema(close, 21);

    return { ema9, ema21 };
});

Return Values

The run() method returns different formats depending on what your indicator returns:

Single Value Return

If your indicator returns a single value, context.result will be an array:

const { result } = await pineTS.run((context) => {
    const { ta } = context;
    const { close } = context.data;

    const sma = ta.sma(close, 20);
    return sma; // Single value
});

// result is an array of numbers
console.log(result); // [45123.5, 45234.2, 45345.8, ...]

Object Return (Multiple Values)

If your indicator returns an object, context.result will be an object with arrays:

const { result } = await pineTS.run((context) => {
    const { ta } = context;
    const { close } = context.data;

    const ema9 = ta.ema(close, 9);
    const ema21 = ta.ema(close, 21);
    const rsi = ta.rsi(close, 14);

    return { ema9, ema21, rsi }; // Object with multiple values
});

// result is an object with arrays
console.log(result.ema9); // [45123.5, 45234.2, ...]
console.log(result.ema21); // [44987.3, 45098.7, ...]
console.log(result.rsi); // [65.4, 67.2, ...]

Accessing the Full Context

You can access the entire context object for more information:

const context = await pineTS.run((context) => {
    const { ta } = context;
    const { close } = context.data;

    const ema = ta.ema(close, 9);
    return { ema };
});

console.log(context.result); // The indicator results
console.log(context.data); // Market data
console.log(context.tickerId); // 'BTCUSDT'
console.log(context.timeframe); // 'D'
console.log(context.marketData); // Raw OHLCV data

Complete Examples

Example 1: Simple Moving Average

import { PineTS, Provider } from 'pinets';

async function runSMA() {
    // Initialize with 200 daily candles
    const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 200);

    // Calculate 20-period SMA
    const { result } = await pineTS.run((context) => {
        const { ta } = context;
        const { close } = context.data;

        const sma20 = ta.sma(close, 20);
        return sma20;
    });

    console.log('SMA(20):', result);
}

runSMA();

Example 2: Multiple Indicators

import { PineTS, Provider } from 'pinets';

async function runMultipleIndicators() {
    const pineTS = new PineTS(Provider.Binance, 'ETHUSDT', '4H', 500);

    const { result } = await pineTS.run((context) => {
        const { ta, math } = context;
        const { close, high, low } = context.data;

        // Calculate multiple indicators
        const rsi = ta.rsi(close, 14);
        const [macd, signal, histogram] = ta.macd(close, 12, 26, 9);
        const [upperBand, middleBand, lowerBand] = ta.bb(close, 20, 2);
        const atr = ta.atr(high, low, close, 14);

        // Return all results
        return {
            rsi,
            macd,
            signal,
            histogram,
            upperBB: upperBand,
            middleBB: middleBand,
            lowerBB: lowerBand,
            atr,
        };
    });

    console.log('RSI:', result.rsi);
    console.log('MACD:', result.macd);
    console.log('ATR:', result.atr);
}

runMultipleIndicators();

Example 3: With Date Range

import { PineTS, Provider } from 'pinets';

async function runWithDateRange() {
    const startDate = new Date('2024-01-01').getTime();
    const endDate = new Date('2024-06-30').getTime();

    const pineTS = new PineTS(
        Provider.Binance,
        'BTCUSDT',
        'D',
        undefined, // No limit, use date range
        startDate,
        endDate
    );

    const { result } = await pineTS.run((context) => {
        const { ta } = context;
        const { close } = context.data;

        const ema50 = ta.ema(close, 50);
        const ema200 = ta.ema(close, 200);

        return {
            ema50,
            ema200,
            bullish: ema50 > ema200,
        };
    });

    console.log('EMA50:', result.ema50);
    console.log('EMA200:', result.ema200);
    console.log('Bullish signals:', result.bullish);
}

runWithDateRange();

Example 4: Custom Data

import { PineTS } from 'pinets';

async function runWithCustomData() {
    const customData = [
        { open: 100, high: 105, low: 99, close: 103, volume: 1000, openTime: Date.now() - 86400000 * 99, closeTime: Date.now() - 86400000 * 98 },
        { open: 103, high: 108, low: 102, close: 107, volume: 1200, openTime: Date.now() - 86400000 * 98, closeTime: Date.now() - 86400000 * 97 },
        // ... more data
    ];

    const pineTS = new PineTS(customData);

    const { result } = await pineTS.run((context) => {
        const { ta } = context;
        const { close } = context.data;

        const sma10 = ta.sma(close, 10);
        return { sma10 };
    });

    console.log('SMA(10):', result.sma10);
}

runWithCustomData();

Example 5: Processing Last N Periods Only

import { PineTS, Provider } from 'pinets';

async function runLastNPeriods() {
    // Fetch 1000 candles
    const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 1000);

    // But only process the last 100
    const { result } = await pineTS.run((context) => {
        const { ta } = context;
        const { close } = context.data;

        const rsi = ta.rsi(close, 14);
        return { rsi };
    }, 100); // Only process last 100 periods

    console.log('RSI (last 100 periods):', result.rsi);
}

runLastNPeriods();

Example 6: Using TA Cache for Performance

import { PineTS, Provider } from 'pinets';

async function runWithCache() {
    const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', '1h', 5000);

    // Enable TA cache for better performance on large datasets
    const { result } = await pineTS.run(
        (context) => {
            const { ta } = context;
            const { close } = context.data;

            const ema20 = ta.ema(close, 20);
            const ema50 = ta.ema(close, 50);

            return { ema20, ema50 };
        },
        undefined,
        true
    ); // Enable cache

    console.log('Results computed with caching enabled');
}

runWithCache();

Example 7: Complex Strategy

import { PineTS, Provider } from 'pinets';

async function runComplexStrategy() {
    const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 365);

    const context = await pineTS.run((ctx) => {
        const { ta, math } = ctx;
        const { close, high, low, volume } = ctx.data;

        // Multiple indicator calculation
        const rsi = ta.rsi(close, 14);
        const [macd, signal, _] = ta.macd(close, 12, 26, 9);
        const atr = ta.atr(high, low, close, 14);
        const volumeSMA = ta.sma(volume, 20);

        // Generate signals
        const buySignal = rsi < 30 && macd > signal && volume > volumeSMA;
        const sellSignal = rsi > 70 && macd < signal;

        // Calculate stop loss and take profit levels
        const stopLoss = close - atr * 2;
        const takeProfit = close + atr * 3;

        return {
            rsi,
            macd,
            signal,
            atr,
            buySignal,
            sellSignal,
            stopLoss,
            takeProfit,
            price: close,
        };
    });

    // Access results
    const { result } = context;

    // Find trading opportunities
    console.log('Last RSI:', result.rsi[result.rsi.length - 1]);
    console.log('Last MACD:', result.macd[result.macd.length - 1]);

    // Count signals
    const buyCount = result.buySignal.filter(Boolean).length;
    const sellCount = result.sellSignal.filter(Boolean).length;
    console.log(`Buy signals: ${buyCount}, Sell signals: ${sellCount}`);
}

runComplexStrategy();

Tips and Best Practices

1. Waiting for Data to Load

Always use await with pineTS.run() since data fetching is asynchronous:

// ✅ Correct
const { result } = await pineTS.run((context) => { ... });

// ❌ Wrong - will not work properly
const { result } = pineTS.run((context) => { ... }); // Missing await

2. Destructuring for Cleaner Code

Destructure the context for more readable code:

const { result } = await pineTS.run((context) => {
    // Destructure for cleaner access
    const { ta, math } = context;
    const { close, open, high, low } = context.data;

    // Now you can use them directly
    const sma = ta.sma(close, 20);
    return sma;
});

3. Return Objects for Multiple Values

When calculating multiple indicators, return them as an object:

// ✅ Return multiple values as object
return { sma, ema, rsi };

// ❌ Less convenient - only returns one value
return sma;

4. Performance Optimization

For large datasets or complex calculations:

// Enable TA cache
const { result } = await pineTS.run(indicatorFn, undefined, true);

// Or process fewer periods
const { result } = await pineTS.run(indicatorFn, 100); // Last 100 periods only

5. Error Handling

Always wrap your PineTS code in try-catch blocks:

try {
    const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);
    const { result } = await pineTS.run((context) => {
        // Your indicator logic
    });
    console.log(result);
} catch (error) {
    console.error('Error running indicator:', error);
}

Alerts

PineTS supports alert() and alertcondition() from Pine Script. Alerts are captured as events that your application can act on — send webhooks, trigger trades, or log signals.

Quick Example

const pine = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);

const code = `
//@version=6
indicator("EMA Cross Alert")
if ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
    alert("Bullish cross!", alert.freq_once_per_bar)
plot(close)
`;

// With run() — alerts on context
const ctx = await pine.run(code);
console.log(ctx.alerts); // [{type: 'alert', message: 'Bullish cross!', ...}]

// With stream() — real-time alert events
const evt = pine.stream(code, { live: true });
evt.on('alert', (a) => console.log('ALERT:', a.message));

Backtest Mode

By default, alerts only fire on the last (realtime) bar. For backtesting, enable alerts on all bars:

pine.setAlertMode('all'); // Fire alerts on every bar

For full documentation including frequency constants, alert modes, and complete examples, see the Alerts page.


Next Steps

On this page