# Initialization and Usage (/developers/pinets/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.

## Table of Contents [#table-of-contents]

* [Installation](#installation)
* [PineTS Constructor](#pinets-constructor)
* [Initialization Options](#initialization-options)
* [The run() Method](#the-run-method)
* [The stream() Method](#the-stream-method)
* [The update() Method](#the-update-method)
* [Host Environment (Visible Range)](#host-environment-visible-range)
* [Non-Standard Chart Types (Heikin Ashi)](#non-standard-chart-types-heikin-ashi)
* [Context Object](#context-object)
* [Return Values](#return-values)
* [Alerts](#alerts)
* [Complete Examples](#complete-examples)

***

## Installation [#installation]

```bash
npm install pinets
```

***

## PineTS Constructor [#pinets-constructor]

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

### Syntax [#syntax]

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

### Parameters [#parameters]

| Parameter   | Type                 | Required | Description                                                                          |
| ----------- | -------------------- | -------- | ------------------------------------------------------------------------------------ |
| `source`    | `IProvider \| any[]` | Yes      | Either a data provider instance (e.g., `Provider.Binance`) or an array of OHLCV data |
| `tickerId`  | `string`             | No\*     | The trading pair symbol (e.g., `'BTCUSDT'`). Required when using a provider          |
| `timeframe` | `string`             | No\*     | The timeframe/interval for the data. Required when using a provider                  |
| `limit`     | `number`             | No       | Maximum number of candles to fetch (default: provider-specific, max 5000)            |
| `sDate`     | `number`             | No       | Start date in milliseconds timestamp. Used for date range queries                    |
| `eDate`     | `number`             | No       | End 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 [#understanding-candle-fetching-and-ordering]

#### How `limit` Works [#how-limit-works]

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

```typescript
// 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 [#how-date-ranges-work]

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

```typescript
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 [#priority-and-combinations]

| Scenario                           | Behavior                                           |
| ---------------------------------- | -------------------------------------------------- |
| Only `limit` specified             | Fetches the last `limit` candles from now          |
| Only `sDate` and `eDate` specified | Fetches all candles in the date range (up to 5000) |
| Both `limit` and date range        | Date range is used, `limit` is ignored             |
| Neither specified                  | Uses provider default (typically 500-1000 candles) |

#### Data Ordering After Fetching [#data-ordering-after-fetching]

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

```typescript
// 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 [#examples-of-different-fetching-scenarios]

```typescript
// 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 [#initialization-options]

### Option 1: Using a Data Provider [#option-1-using-a-data-provider]

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

```typescript
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 [#available-providers]

| Provider           | Usage                 | API Key                 | Best For       |
| ------------------ | --------------------- | ----------------------- | -------------- |
| `Provider.Binance` | Crypto market data    | Not required            | Cryptocurrency |
| `Provider.FMP`     | Stocks, forex, crypto | Required                | US/intl stocks |
| `Provider.Alpaca`  | US stocks, crypto     | Required (key + secret) | US equities    |

Providers that require API keys must be configured before use. See the &#x2A;*[Data Providers](/developers/pinets/../data-providers)** page for detailed setup instructions, configuration options, and examples for each provider.

#### Supported Timeframes [#supported-timeframes]

All providers support the standard timeframe strings: `'1'`, `'3'`, `'5'`, `'15'`, `'30'`, `'60'`, `'120'`, `'240'`, `'D'`, `'W'`, `'M'&#x60;. Timeframes not natively supported by a provider are automatically aggregated from smaller candles. See the &#x2A;*[Timeframe Reference](/developers/pinets/../data-providers#timeframe-reference)** for the full support matrix.

### Option 2: Using Custom Data [#option-2-using-custom-data]

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

```typescript
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 [#custom-data-format]

Each data point in the array must include:

| Field       | Type     | Required | Description                           |
| ----------- | -------- | -------- | ------------------------------------- |
| `open`      | `number` | Yes      | Opening price                         |
| `high`      | `number` | Yes      | Highest price                         |
| `low`       | `number` | Yes      | Lowest price                          |
| `close`     | `number` | Yes      | Closing price                         |
| `volume`    | `number` | Yes      | Trading volume                        |
| `openTime`  | `number` | No       | Opening time (milliseconds timestamp) |
| `closeTime` | `number` | No       | Closing time (milliseconds timestamp) |

***

## The run() Method [#the-run-method]

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

### Syntax [#syntax-1]

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

### Parameters [#parameters-1]

| Parameter    | Type                              | Default     | Description                                                                              |
| ------------ | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------- |
| `pineTSCode` | `Indicator \| Function \| String` | Required    | The indicator/strategy function to execute. Use `Indicator` class to pass runtime inputs |
| `n`          | `number`                          | All periods | Number of most recent periods to process. If not specified, processes all available data |

### Running with Runtime Inputs [#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:

```typescript
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 &#x2A;*`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()&#x60;. See &#x2A;*[Indicator](/developers/pinets/indicator)** for the complete API surface.

### Return Value [#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]

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

### Syntax [#syntax-2]

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

### Options [#options]

| Option     | Type      | Default           | Description                                                                                    |
| ---------- | --------- | ----------------- | ---------------------------------------------------------------------------------------------- |
| `pageSize` | `number`  | `undefined` (all) | Number of bars per chunk. If not specified, processes all available historical data in one go. |
| `live`     | `boolean` | `true`            | Whether to continue fetching live data after processing historical data.                       |
| `interval` | `number`  | `1000`            | Polling interval in milliseconds for live data.                                                |

### Usage [#usage]

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

```typescript
// 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:**

| Event       | Payload                                             | Description                   |
| ----------- | --------------------------------------------------- | ----------------------------- |
| `'data'`    | `Context`                                           | New bar data processed        |
| `'alert'`   | `{ type, message, title?, freq?, bar_index, time }` | Alert or alertcondition fired |
| `'warning'` | `{ message, method?, bar }`                         | Non-blocking runtime warning  |
| `'error'`   | `Error`                                             | Fatal error (script halted)   |

***

## The update() Method [#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 [#syntax-3]

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

### Behavior [#behavior]

| Call                                                                                  | Action                                                           |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| First call (no cached result)                                                         | Executes — equivalent to `run()`. `pineTSCode` is required here. |
| Subsequent call, script **does not** use visible-range built-ins                      | Returns the cached `Context` immediately (no work).              |
| Subsequent call, script uses visible-range AND viewport changed since last cached run | Re-executes against the new viewport, returns fresh `Context`.   |
| Subsequent call, viewport unchanged                                                   | Returns 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 [#example]

```typescript
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) [#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-in                  | PineTS behavior                                                                 |
| ------------------------------ | ------------------------------------------------------------------------------- |
| `chart.left_visible_bar_time`  | Defaults to `marketData[0].openTime`; host can override via `setVisibleRange()` |
| `chart.right_visible_bar_time` | Defaults 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)` [#setvisiblerangeleft-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.

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

### `usesVisibleRange(): boolean` [#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:

```typescript
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 [#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 [#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) [#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:

```javascript
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 [#context-object]

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

### Available Properties [#available-properties]

```typescript
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 [#quick-access-to-common-data]

```typescript
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 [#return-values]

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

### Single Value Return [#single-value-return]

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

```typescript
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) [#object-return-multiple-values]

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

```typescript
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 [#accessing-the-full-context]

You can access the entire context object for more information:

```typescript
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 [#complete-examples]

### Example 1: Simple Moving Average [#example-1-simple-moving-average]

```typescript
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 [#example-2-multiple-indicators]

```typescript
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 [#example-3-with-date-range]

```typescript
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 [#example-4-custom-data]

```typescript
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 [#example-5-processing-last-n-periods-only]

```typescript
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 [#example-6-using-ta-cache-for-performance]

```typescript
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 [#example-7-complex-strategy]

```typescript
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 [#tips-and-best-practices]

### 1. Waiting for Data to Load [#1-waiting-for-data-to-load]

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

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

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

### 2. Destructuring for Cleaner Code [#2-destructuring-for-cleaner-code]

Destructure the context for more readable code:

```typescript
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 [#3-return-objects-for-multiple-values]

When calculating multiple indicators, return them as an object:

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

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

### 4. Performance Optimization [#4-performance-optimization]

For large datasets or complex calculations:

```typescript
// 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 [#5-error-handling]

Always wrap your PineTS code in try-catch blocks:

```typescript
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 [#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 [#quick-example]

```typescript
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 [#backtest-mode]

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

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

For full documentation including frequency constants, alert modes, and complete examples, see the &#x2A;*[Alerts](/developers/pinets/../alerts)** page.

***

## Next Steps [#next-steps]

* Check [API Coverage](/developers/pinets/../api-coverage) to see all available technical analysis functions
* Explore [Language Coverage](/developers/pinets/../lang-coverage) to understand Pine Script compatibility
* Try our demo indicators: [WillVixFix](/developers/pinets/../indicators/willvixfix) and [Squeeze Momentum](/developers/pinets/../indicators/sqzmom)
* Contribute on [GitHub](https://github.com/alaa-eddine/PineTS)
