# Data Providers (/developers/pinets/data-providers)



PineTS supports multiple market data providers out of the box. Each provider fetches OHLCV candle data and symbol metadata, which PineTS uses to run your indicators.

***

## Overview [#overview]

There are two ways to use a provider:

**1. Singleton** (from the `Provider` registry):

```typescript
import { PineTS, Provider } from 'pinets';

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

**2. Direct instantiation** (for provider-specific config):

```typescript
import { PineTS, BinanceProvider } from 'pinets';

const binance = new BinanceProvider();
const pine = new PineTS(binance, 'BTCUSDT', 'D', 100);
```

Providers that require API keys must be configured before use, either via `.configure()` on the singleton or by passing config to the constructor.

### Timeframe Aggregation [#timeframe-aggregation]

If a provider doesn't natively support a requested timeframe, PineTS automatically selects the best sub-timeframe and aggregates candles. For example, requesting `'45'` (45 min) from Binance will fetch `'15'` minute candles and aggregate 3:1. This is handled transparently by the `BaseProvider` class.

***

## Binance [#binance]

The Binance provider fetches cryptocurrency market data. &#x2A;*No API key required.**

### Quick Start [#quick-start]

```typescript
import { PineTS, Provider } from 'pinets';

// Fetch 100 daily BTC candles and compute SMA
const pine = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);

const { result, plots } = await pine.run(`
//@version=6
indicator("BTC SMA Cross")
sma20 = ta.sma(close, 20)
sma50 = ta.sma(close, 50)
plot(sma20, "SMA 20", color.blue)
plot(sma50, "SMA 50", color.red)
`);
```

### Symbols [#symbols]

| Format   | Example              | Market |
| -------- | -------------------- | ------ |
| `SYMBOL` | `BTCUSDT`, `ETHUSDC` | Spot   |

### Supported Timeframes [#supported-timeframes]

`'1'`, `'3'`, `'5'`, `'15'`, `'30'`, `'60'`, `'120'`, `'240'`, `'D'`, `'W'`, `'M'`

Timeframes not in this list (e.g., `'45'`, `'180'`) are automatically aggregated from smaller candles.

### Features [#features]

* **No API key** required
* **Auto-pagination** for fetching >1000 candles
* **5-minute cache** for repeated identical requests
* **Dual endpoint fallback**: tries `api.binance.com`, falls back to `api.binance.us`

### Examples [#examples]

```typescript
import { PineTS, Provider, BinanceProvider } from 'pinets';

// Using the singleton
const pine1 = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);

// Direct instantiation (equivalent)
const binance = new BinanceProvider();
const pine2 = new PineTS(binance, 'ETHUSDT', '1W', 50);

// With date range
const sDate = new Date('2024-01-01').getTime();
const eDate = new Date('2024-06-30').getTime();
const pine3 = new PineTS(Provider.Binance, 'BTCUSDT', 'D', undefined, sDate, eDate);
```

***

## FMP (Financial Modeling Prep) [#fmp-financial-modeling-prep]

The FMP provider fetches stock, forex, and crypto data from [Financial Modeling Prep](https://financialmodelingprep.com/). &#x2A;*Requires an API key.**

### Quick Start [#quick-start-1]

```typescript
import { PineTS, Provider } from 'pinets';

// Configure with your API key
Provider.FMP.configure({ apiKey: 'your-api-key' });

// Fetch 200 daily AAPL candles
const pine = new PineTS(Provider.FMP, 'AAPL', 'D', 200);

const { result } = await pine.run(`
//@version=6
indicator("AAPL RSI")
rsi = ta.rsi(close, 14)
plot(rsi, "RSI")
hline(70, "Overbought")
hline(30, "Oversold")
`);
```

### Configuration [#configuration]

```typescript
// Option 1: Configure the singleton
Provider.FMP.configure({ apiKey: 'your-api-key' });

// Option 2: Direct instantiation with config
import { FMPProvider } from 'pinets';
const fmp = new FMPProvider({ apiKey: 'your-api-key' });
const pine = new PineTS(fmp, 'AAPL', 'D', 200);
```

| Option    | Type     | Required | Description               |
| --------- | -------- | -------- | ------------------------- |
| `apiKey`  | `string` | Yes      | Your FMP API key          |
| `baseUrl` | `string` | No       | Override the API base URL |

### Symbols [#symbols-1]

| Format | Example               | Market         |
| ------ | --------------------- | -------------- |
| Ticker | `AAPL`, `MSFT`, `SPY` | US Stocks      |
| Crypto | `BTCUSD`, `ETHUSD`    | Cryptocurrency |
| Forex  | `EURUSD`              | Currency pairs |

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

| Tier     | Timeframes                                    |
| -------- | --------------------------------------------- |
| **Free** | `'D'` (daily only)                            |
| **Paid** | `'1'`, `'5'`, `'15'`, `'30'`, `'60'`, `'240'` |

### Features [#features-1]

* **Exchange-aware session times**: maps exchanges to IANA timezones (e.g., NYSE = `America/New_York`, `0930-1600`)
* **Session-aware closeTime**: correctly computes bar close times including early-close days
* **Profile caching**: symbol metadata is fetched once and cached

### Examples [#examples-1]

```typescript
import { PineTS, Provider, FMPProvider } from 'pinets';

// Configure singleton
Provider.FMP.configure({ apiKey: 'your-api-key' });

// US Stock
const pine1 = new PineTS(Provider.FMP, 'AAPL', 'D', 200);

// Crypto via FMP
const pine2 = new PineTS(Provider.FMP, 'BTCUSD', 'D', 100);

// Forex
const pine3 = new PineTS(Provider.FMP, 'EURUSD', 'D', 100);

// Direct instantiation (separate instance with its own config)
const fmp = new FMPProvider({ apiKey: 'different-api-key' });
const pine4 = new PineTS(fmp, 'MSFT', 'D', 100);
```

***

## Alpaca [#alpaca]

The Alpaca provider fetches US stock and crypto data from [Alpaca Markets](https://alpaca.markets/). &#x2A;*Requires an API key and secret.**

### Quick Start [#quick-start-2]

```typescript
import { PineTS, Provider } from 'pinets';

// Configure with your credentials
Provider.Alpaca.configure({
    apiKey: 'PK...',
    apiSecret: '...',
    paper: true,
    feed: 'iex',
});

// Fetch 200 daily AAPL candles
const pine = new PineTS(Provider.Alpaca, 'AAPL', 'D', 200);

const { result } = await pine.run(`
//@version=6
indicator("AAPL Bollinger Bands")
[middle, upper, lower] = ta.bb(close, 20, 2)
plot(middle, "Middle", color.blue)
plot(upper, "Upper", color.green)
plot(lower, "Lower", color.red)
`);
```

### Configuration [#configuration-1]

```typescript
// Option 1: Configure the singleton
Provider.Alpaca.configure({
    apiKey: 'PK...',
    apiSecret: '...',
    paper: true,
    feed: 'sip',
});

// Option 2: Direct instantiation
import { AlpacaProvider } from 'pinets';
const alpaca = new AlpacaProvider({
    apiKey: 'PK...',
    apiSecret: '...',
    paper: true,
    feed: 'iex',
});
const pine = new PineTS(alpaca, 'AAPL', 'D', 200);
```

| Option       | Type             | Required | Default | Description                                                      |
| ------------ | ---------------- | -------- | ------- | ---------------------------------------------------------------- |
| `apiKey`     | `string`         | Yes      | -       | Alpaca API key ID (`PK...`)                                      |
| `apiSecret`  | `string`         | Yes      | -       | Alpaca secret key                                                |
| `paper`      | `boolean`        | No       | `true`  | `true` for paper trading, `false` for live                       |
| `feed`       | `'sip' \| 'iex'` | No       | `'sip'` | `'sip'` = full market data (paid), `'iex'` = IEX exchange (free) |
| `dataUrl`    | `string`         | No       | -       | Override the market data API URL                                 |
| `tradingUrl` | `string`         | No       | -       | Override the trading/asset API URL                               |

### Symbols [#symbols-2]

| Format | Example               | Market                          |
| ------ | --------------------- | ------------------------------- |
| Ticker | `AAPL`, `MSFT`, `SPY` | US Stocks                       |
| Crypto | `BTC/USD`, `ETH/USD`  | Cryptocurrency (note the slash) |

> **Note:** Crypto symbols use slash notation (`BTC/USD`), not concatenated (`BTCUSD`).

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

`'1'`, `'3'`, `'5'`, `'15'`, `'30'`, `'45'`, `'60'`, `'120'`, `'180'`, `'240'`, `'D'`, `'W'`, `'M'`

Alpaca natively supports all common timeframes including `'45'` and `'180'` which other providers aggregate.

### Features [#features-2]

* **Calendar-aware closeTime** for stocks: uses Alpaca's trading calendar API for exact session times (handles early closes and DST)
* **Fixed-duration closeTime** for crypto: 24/7 markets use computed period boundaries
* **Auto-pagination**: handles large requests (10,000+ candles) via page tokens
* **Asset and calendar caching**: symbol info and trading calendar fetched once per session

### Data Feed Options [#data-feed-options]

| Feed    | Cost            | Coverage                                            |
| ------- | --------------- | --------------------------------------------------- |
| `'sip'` | Paid ($9/month) | Full consolidated market data from all US exchanges |
| `'iex'` | Free            | IEX exchange data only (may have gaps or lag)       |

### Examples [#examples-2]

```typescript
import { PineTS, Provider, AlpacaProvider } from 'pinets';

// Configure singleton
Provider.Alpaca.configure({
    apiKey: 'PK...',
    apiSecret: '...',
    paper: true,
    feed: 'iex',
});

// US Stock
const pine1 = new PineTS(Provider.Alpaca, 'AAPL', 'D', 200);

// Crypto (note the slash notation)
const pine2 = new PineTS(Provider.Alpaca, 'BTC/USD', 'D', 100);

// Direct instantiation
const alpaca = new AlpacaProvider({
    apiKey: 'PK...',
    apiSecret: '...',
    feed: 'sip',
});
const pine3 = new PineTS(alpaca, 'MSFT', '60', 500);
```

***

## Custom Data [#custom-data]

You can bypass providers entirely by passing your own OHLCV data array:

```typescript
import { PineTS } from 'pinets';

const customData = [
    {
        openTime: new Date('2024-01-01').getTime(),
        open: 42000,
        high: 43000,
        low: 41500,
        close: 42500,
        volume: 1234.56,
        closeTime: new Date('2024-01-02').getTime(),
    },
    {
        openTime: new Date('2024-01-02').getTime(),
        open: 42500,
        high: 44000,
        low: 42000,
        close: 43800,
        volume: 2345.67,
        closeTime: new Date('2024-01-03').getTime(),
    },
    // ... more candles
];

const pine = new PineTS(customData);
const { result } = await pine.run(`
//@version=6
indicator("Custom Data SMA")
plot(ta.sma(close, 10))
`);
```

### Data Format [#data-format]

Each candle object 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       | Bar open time (ms timestamp)  |
| `closeTime` | `number` | No       | Bar close time (ms timestamp) |

If `openTime` / `closeTime` are omitted, time-dependent functions (`time`, `hour`, `dayofmonth`, etc.) will not work correctly.

***

## Custom Providers [#custom-providers]

You can create your own provider by implementing the `IProvider` interface or extending `BaseProvider`:

```typescript
import { BaseProvider, PineTS } from 'pinets';
import type { ISymbolInfo, Kline } from 'pinets';

class MyProvider extends BaseProvider {
    constructor() {
        super({ requiresApiKey: false, providerName: 'MyProvider' });
    }

    protected getSupportedTimeframes() {
        return new Set(['1', '5', '15', '60', 'D']);
    }

    protected async _getMarketDataNative(tickerId: string, timeframe: string, limit?: number, sDate?: number, eDate?: number): Promise<Kline[]> {
        // Fetch data from your API
        const response = await fetch(`https://my-api.com/candles?symbol=${tickerId}&tf=${timeframe}`);
        const data = await response.json();

        return data.map((candle) => ({
            openTime: candle.time,
            open: candle.o,
            high: candle.h,
            low: candle.l,
            close: candle.c,
            volume: candle.v,
            closeTime: candle.time + 86400000, // next bar open
        }));
    }

    async getSymbolInfo(tickerId: string): Promise<ISymbolInfo> {
        // Return symbol metadata
        return { ticker: tickerId /* ... */ } as ISymbolInfo;
    }
}

// Use it
const provider = new MyProvider();
const pine = new PineTS(provider, 'AAPL', 'D', 100);
```

By extending `BaseProvider`, you get automatic timeframe aggregation for free. If you request a timeframe not in `getSupportedTimeframes()`, the base class will automatically fetch a supported sub-timeframe and aggregate.

You can also add your provider to the global registry for convenience:

```typescript
import { Provider } from 'pinets';

Provider['MyProvider'] = new MyProvider();
// Now available as Provider.MyProvider
```

### Chart-Type Modifiers (Extended Tickers) [#chart-type-modifiers-extended-tickers]

A ticker may carry a chart-type modifier suffix — `"BTCUSDT;heikinashi"` (see [Non-Standard Chart Types](/developers/pinets/initialization-and-usage#non-standard-chart-types-heikin-ashi)). The contract at the provider boundary:

* **Providers extending `BaseProvider`** never see the modifier in `getMarketData()` — the base class strips it, because a venue API serves standard candles only. Strip it likewise at the top of your `getSymbolInfo()` (all bundled providers do): `tickerId = stripTickerModifier(tickerId)` (exported from `pinets`).
* **A source that OWNS a chart-type transform** (typically an embedding host implementing `IProvider` directly, not via `BaseProvider`) receives the extended ticker verbatim and must serve the derived bars for `"SYM;heikinashi"` and raw bars for `"SYM"` — the modifier is the only thing distinguishing the two requests.
* **PineTS itself never transforms bars** — the modifier is routing metadata, end to end.

***

## Timeframe Reference [#timeframe-reference]

Standard timeframe strings used across all providers:

| String            | Description | Period   |
| ----------------- | ----------- | -------- |
| `'1'`             | 1 minute    | Intraday |
| `'3'`             | 3 minutes   | Intraday |
| `'5'`             | 5 minutes   | Intraday |
| `'15'`            | 15 minutes  | Intraday |
| `'30'`            | 30 minutes  | Intraday |
| `'45'`            | 45 minutes  | Intraday |
| `'60'`            | 1 hour      | Intraday |
| `'120'`           | 2 hours     | Intraday |
| `'180'`           | 3 hours     | Intraday |
| `'240'` or `'4H'` | 4 hours     | Intraday |
| `'D'` or `'1D'`   | 1 day       | Daily    |
| `'W'` or `'1W'`   | 1 week      | Weekly   |
| `'M'` or `'1M'`   | 1 month     | Monthly  |

### Provider Support Matrix [#provider-support-matrix]

| Timeframe | Binance    | FMP (Free) | FMP (Paid) | Alpaca |
| --------- | ---------- | ---------- | ---------- | ------ |
| `'1'`     | Native     | -          | Native     | Native |
| `'3'`     | Native     | -          | -          | Native |
| `'5'`     | Native     | -          | Native     | Native |
| `'15'`    | Native     | -          | Native     | Native |
| `'30'`    | Native     | -          | Native     | Native |
| `'45'`    | Aggregated | -          | -          | Native |
| `'60'`    | Native     | -          | Native     | Native |
| `'120'`   | Native     | -          | -          | Native |
| `'180'`   | Aggregated | -          | -          | Native |
| `'240'`   | Native     | -          | Native     | Native |
| `'D'`     | Native     | Native     | Native     | Native |
| `'W'`     | Native     | -          | -          | Native |
| `'M'`     | Native     | -          | -          | Native |

**Native** = provider fetches directly. **Aggregated*&#x2A; = PineTS fetches smaller candles and combines them. &#x2A;*-** = not available.
