tradingview-api-adapter
Version:
Real-time market data from TradingView via WebSocket: quotes, candles, symbol info, streaming, groups.
503 lines (485 loc) • 19.9 kB
TypeScript
import { Q as QuoteField, F as FullQuoteSnapshot, T as TradeTick, B as BarTick, a as QuoteUpdate, b as Timeframe, c as QuoteSnapshot, S as SymbolInfo, C as Candle, d as QuoteErrorInfo, e as SessionManager, R as ReconnectOptions, f as RateLimitOptions, g as QuoteSession } from './errors-B_SFtuRv.js';
export { h as QuoteFieldTypeMap, i as RawTimeframe, j as TIMEFRAME_ALIASES, k as TimeframeAlias, l as TvConnectionError, m as TvError, n as TvErrorOptions, o as TvProtocolError, p as TvSessionError, q as TvSymbolError, r as TvTimeoutError, s as normalizeTimeframe, t as symbolInfoFromRaw } from './errors-B_SFtuRv.js';
/**
* Stream — typed event emitter returned by `TvSymbol.stream()`.
*
* A `Stream` is a thin wrapper around a `TvSymbol`'s ongoing quote
* subscription. It exposes strongly-typed events and a closable
* lifecycle, plus an `AsyncIterator` interface so you can consume
* updates with `for await`.
*
* All event handlers receive a single object argument (never positional
* arguments) so adding new fields in the future is a non-breaking
* change.
*/
interface StreamEventMap {
/** Fires on every quote delta. `data` is the full accumulated snapshot. */
update: {
symbol: string;
data: FullQuoteSnapshot;
};
/** Fires whenever the last traded price (`lp`) changes. */
price: {
price: number;
};
/** Fires whenever `ch`/`chp` change (both present in the accumulated snapshot). */
change: {
value: number;
percent: number;
};
/** Fires when a new bar arrives (`trade`, `minute-bar`, or `daily-bar`). */
bar: {
bar: TradeTick | BarTick;
};
/** Fires on per-symbol errors reported by TradingView. */
error: Error;
}
type StreamEventName = keyof StreamEventMap;
type Handler$2<E extends StreamEventName> = (data: StreamEventMap[E]) => void;
declare class Stream {
readonly tvSymbol: TvSymbol;
/** Fields this stream was opened with. Currently informational only. */
readonly fields: readonly QuoteField[];
private readonly listeners;
private closed;
private iteratorQueue;
private iteratorResolve;
constructor(tvSymbol: TvSymbol,
/** Fields this stream was opened with. Currently informational only. */
fields: readonly QuoteField[]);
/** Register a listener for a specific stream event. */
on<E extends StreamEventName>(event: E, handler: Handler$2<E>): this;
/** Remove a previously registered listener. */
off<E extends StreamEventName>(event: E, handler: Handler$2<E>): this;
/** Close the stream and release its resources. */
close(): void;
[Symbol.dispose](): void;
/**
* Async iterator interface — iterate over `update` events with
* `for await`. Breaking out of the loop automatically closes the
* stream.
*/
[Symbol.asyncIterator](): AsyncIterator<StreamEventMap['update']>;
/** @internal */
_dispatch(u: QuoteUpdate, snapshot: FullQuoteSnapshot): void;
/** @internal */
_dispatchError(err: Error): void;
/** @internal — used by TvSymbol when the parent client shuts down. */
_closeFromSymbol(): void;
private emit;
private pushToIterator;
private flushIteratorOnClose;
}
/**
* TvSymbol — the user-facing handle for a single market pair.
*
* Users never construct this directly. They call `client.symbol(pair)`
* which lazily creates (or retrieves) a pooled instance.
*
* Responsibilities:
* - Aggregate every quote field any caller has asked about for this pair
* - Maintain a local accumulated snapshot of the latest values
* - Dispatch updates to all active Streams on the pair
* - Provide one-shot promise helpers: `price`, `snapshot`, `info`, `candles`
*
* `info()` and `candles()` use a fresh short-lived `ChartSession` per
* call; `price()`, `snapshot()`, and `stream()` share the parent
* Client's pooled quote session.
*/
/** Default fields used for `price()`, `snapshot()` without args, and `stream()`. */
declare const DEFAULT_STREAM_FIELDS: readonly QuoteField[];
interface CandlesOptions {
timeframe: Timeframe;
/** Number of bars to fetch. */
count: number;
}
declare class TvSymbol {
readonly client: Client;
readonly pair: string;
private readonly streams;
private readonly snapshotState;
private readonly fields;
private loaded;
private loadWaiters;
private subscribed;
private disposed;
constructor(client: Client, pair: string);
/** The last traded price, resolving as soon as it is available. */
price(): Promise<number>;
/**
* Resolve a typed snapshot for a specific subset of fields. Passing
* no argument returns every field currently accumulated for this
* symbol (type `FullQuoteSnapshot`).
*/
snapshot<const F extends readonly QuoteField[]>(fields: F): Promise<QuoteSnapshot<F>>;
snapshot(): Promise<FullQuoteSnapshot>;
/** Fetch full symbol metadata via a one-shot chart session resolve. */
info(): Promise<SymbolInfo>;
/** Fetch historical candles via a one-shot chart session. */
candles(opts: CandlesOptions): Promise<Candle[]>;
/** Open a live quote stream for this symbol. */
stream(fields?: readonly QuoteField[]): Stream;
/** Current list of fields this symbol is subscribed to. */
get subscribedFields(): readonly QuoteField[];
/** @internal */
_onUpdate(update: QuoteUpdate): void;
/** @internal */
_onComplete(): void;
/** @internal */
_onError(info: QuoteErrorInfo): void;
/** @internal */
_removeStream(stream: Stream): void;
/** @internal */
_dispose(): void;
private assertAlive;
private ensureSubscribed;
private waitForLoad;
}
/**
* MultiStream — a multi-symbol stream used by `Portfolio`, `Group`,
* and `Client.stream()`.
*
* Internally, a `MultiStream` composes one single-symbol `Stream` per
* participating `TvSymbol`. Updates from each child stream are
* forwarded to the multi-stream's listeners with the `symbol` field
* included on every event, so consumers can distinguish which pair
* ticked.
*
* Dedup: because `Client.symbol(pair)` returns a pooled instance, two
* groups that share a pair share the same child `TvSymbol`. When the
* client-level aggregate stream iterates `symbolCache.values()`, each
* pair appears exactly once, so listeners receive one event per tick
* regardless of how many groups reference the pair.
*/
interface MultiStreamEventMap {
/** Fires on every quote delta, with the symbol and accumulated snapshot. */
update: {
symbol: string;
data: FullQuoteSnapshot;
};
/** Fires when `lp` changes, with the owning symbol. */
price: {
symbol: string;
price: number;
};
/** Fires when both `ch` and `chp` are present in the accumulated snapshot. */
change: {
symbol: string;
value: number;
percent: number;
};
/** Fires when a new bar tick arrives. */
bar: {
symbol: string;
bar: TradeTick | BarTick;
};
/** Fires for per-symbol errors from any child stream. */
error: Error;
}
type MultiEventName = keyof MultiStreamEventMap;
type Handler$1<E extends MultiEventName> = (data: MultiStreamEventMap[E]) => void;
interface MultiStreamOptions {
/** Called when this MultiStream is closed (used by parent Group to untrack). */
onClose?: () => void;
}
declare class MultiStream {
readonly fields: readonly QuoteField[];
private readonly opts;
private readonly children;
private readonly listeners;
private closed;
private iteratorQueue;
private iteratorResolve;
constructor(symbols: Iterable<TvSymbol>, fields: readonly QuoteField[], opts?: MultiStreamOptions);
/** Register a listener for a specific stream event. */
on<E extends MultiEventName>(event: E, handler: Handler$1<E>): this;
/** Remove a previously registered listener. */
off<E extends MultiEventName>(event: E, handler: Handler$1<E>): this;
/** Close the stream, all of its child streams, and release resources. */
close(): void;
[Symbol.dispose](): void;
/**
* Async iterator interface — yields `update` events across all
* attached symbols. Breaking out of the `for await` loop closes the
* stream automatically.
*/
[Symbol.asyncIterator](): AsyncIterator<MultiStreamEventMap['update']>;
/** Currently attached pairs. */
get pairs(): readonly string[];
/** Number of currently attached child streams. */
get size(): number;
/** Whether this multi-stream has been closed. */
get isClosed(): boolean;
/** @internal */
_attachSymbol(sym: TvSymbol): void;
/** @internal */
_detachSymbol(pair: string): void;
private attachSymbol;
private emit;
private pushToIterator;
private flushIteratorOnClose;
}
/**
* Portfolio — a lightweight, ad-hoc collection of symbols.
*
* Created via `client.symbols([...])`. Unlike `Group`, a portfolio is
* immutable: the pairs you pass at construction are the only pairs it
* tracks. Use it for quick, one-off queries ("what are the prices of
* these 5 tickers right now?") or short-lived streaming sessions.
*
* For long-lived, mutable collections with a name, use `Group`.
*/
declare class Portfolio {
readonly client: Client;
readonly tvSymbols: readonly TvSymbol[];
constructor(client: Client, pairs: readonly string[]);
/** Pairs currently tracked (order preserved from construction). */
get pairs(): readonly string[];
/** Number of symbols in the portfolio. */
get size(): number;
/** Last prices keyed by pair. Missing prices are omitted. */
prices(): Promise<Record<string, number>>;
/** Typed snapshots keyed by pair. */
snapshot<const F extends readonly QuoteField[]>(fields: F): Promise<Record<string, QuoteSnapshot<F>>>;
snapshot(): Promise<Record<string, FullQuoteSnapshot>>;
/** Open a multi-symbol live stream over every pair in the portfolio. */
stream(fields?: readonly QuoteField[]): MultiStream;
}
/**
* Group — a named, mutable collection of symbols.
*
* Unlike `Portfolio`, a `Group` is long-lived: it has a name, is
* tracked by the parent `Client` via `GroupRegistry`, and supports
* mutation (`add`, `remove`, `clear`). Active streams stay in sync
* with the group: adding a pair attaches a child stream, removing a
* pair detaches it.
*
* Lifecycle:
* const crypto = client.createGroup('crypto', ['BINANCE:BTCUSDT'])
* crypto.add('BINANCE:ETHUSDT')
* const stream = crypto.stream()
* crypto.add('BINANCE:DOGEUSDT') // ← joins the active stream
* crypto.remove('BINANCE:BTCUSDT') // ← detaches from the active stream
* await crypto.delete() // ← close all streams, remove from registry
*/
declare class Group {
readonly client: Client;
readonly name: string;
private readonly members;
private readonly activeStreams;
private disposed;
constructor(client: Client, name: string, pairs: readonly string[]);
/** Symbols currently in the group. */
get pairs(): readonly string[];
/** Live `TvSymbol` handles for every pair. */
get tvSymbols(): readonly TvSymbol[];
/** Number of pairs in the group. */
get size(): number;
/** Whether a specific pair is in the group. */
has(pair: string): boolean;
/** Add a pair. Active streams automatically pick it up. Returns `this` for chaining. */
add(pair: string): this;
/** Add many pairs at once. */
addAll(pairs: Iterable<string>): this;
/** Remove a pair. Returns `true` if it was present. */
remove(pair: string): boolean;
/** Remove many pairs at once. Returns the count of removed pairs. */
removeAll(pairs: Iterable<string>): number;
/** Remove every pair. */
clear(): this;
/** Last prices keyed by pair. Missing prices are omitted. */
prices(): Promise<Record<string, number>>;
/** Typed snapshots keyed by pair. */
snapshot<const F extends readonly QuoteField[]>(fields: F): Promise<Record<string, QuoteSnapshot<F>>>;
snapshot(): Promise<Record<string, FullQuoteSnapshot>>;
/** Open a multi-symbol live stream that stays in sync with the group. */
stream(fields?: readonly QuoteField[]): MultiStream;
/**
* Delete the group: close every active stream and remove it from the
* parent client's group registry.
*/
delete(): Promise<void>;
private assertAlive;
}
/**
* GroupRegistry — the `Map`-like container for a `Client`'s groups.
*
* Accessible via `client.groups`. Supports lookup, iteration,
* deletion, and presence checks. Creation goes through
* `client.createGroup(name, pairs)` (delegated here) to ensure names
* are unique per client.
*/
declare class GroupRegistry {
private readonly client;
private readonly groups;
constructor(client: Client);
/** Create a new group. Throws if a group with the same name already exists. */
create(name: string, pairs?: readonly string[]): Group;
/** Retrieve a group by name. */
get(name: string): Group | undefined;
/** Check whether a group exists. */
has(name: string): boolean;
/** Delete a group by name. Returns `true` if it existed. */
delete(name: string): Promise<boolean>;
/** List of group names. */
get list(): readonly string[];
/** Number of groups. */
get size(): number;
/** Iterate over all groups. */
[Symbol.iterator](): IterableIterator<Group>;
/** @internal — called from `Group.delete()` during self-unregistration. */
_unregister(name: string): void;
/** @internal — called from `Client.disconnect()` to clean up all groups. */
_disposeAll(): Promise<void>;
}
/**
* Client — the public entry point for the library.
*
* A `Client` owns a `SessionManager` (and therefore a `Transport`) and
* acts as a registry/pool for `TvSymbol` instances. It also lazily
* creates a single shared `QuoteSession` that carries every symbol's
* live subscription, so one open TradingView session services an
* entire portfolio.
*
* Create one via the `tv(options)` factory — a simple function call is
* more idiomatic than `new Client()` and keeps the public surface tiny.
*/
/**
* Authentication options.
*
* TradingView recognises two cooperating pieces of auth state:
*
* 1. The `sessionid` + `sessionid_sign` cookies from an active
* tradingview.com login. These are passed as a `Cookie` header on
* the WebSocket handshake (Node only — browsers don't allow
* custom handshake headers).
* 2. A per-session `authToken` sent via the `set_auth_token` message
* after the server hello. This is a JWT-like token you normally
* obtain by POSTing `sessionid` to `https://www.tradingview.com/…`
* ahead of time.
*
* This library does **not** fetch `authToken` for you — if you need
* premium features, obtain the token through your own auth flow and
* pass it here. Without an `authToken`, public quotes and candles
* still work through the implicit `"unauthorized_user_token"` default.
*/
interface AuthOptions {
/** TradingView auth token. Omit for public/anonymous access. */
authToken?: string;
/** `sessionid` cookie from tradingview.com (Node only). */
sessionid?: string;
/** `sessionid_sign` cookie from tradingview.com (Node only). */
sessionidSign?: string;
}
interface ClientOptions {
/** WebSocket URL. Defaults to TradingView widget endpoint. */
url?: string;
/** Origin header (Node only). Defaults to the TradingView origin. */
origin?: string;
/** HTTP/SOCKS agent for proxy support (Node only). */
agent?: unknown;
/** Authentication credentials (optional). */
auth?: AuthOptions;
/** Locale to advertise to TradingView (`[language, country]`). Defaults to `['en', 'US']`. */
locale?: [language: string, country: string];
/** Reconnect behaviour for the underlying transport. */
reconnect?: ReconnectOptions;
/** Symbol add/remove rate limiting. */
rateLimit?: RateLimitOptions;
/** Abort signal — disconnects the client when fired. */
signal?: AbortSignal;
}
interface ClientEventMap {
open: void;
close: void;
reconnect: {
attempt: number;
delayMs: number;
};
error: Error;
}
type ClientEventName = keyof ClientEventMap;
type Handler<E extends ClientEventName> = (data: ClientEventMap[E]) => void;
/**
* Factory: `tv(options)` → `Client`.
*
* Idiomatic entry point. Equivalent to `new Client(options)`.
*/
declare function tv(options?: ClientOptions): Client;
declare class Client {
readonly manager: SessionManager;
readonly groups: GroupRegistry;
private quotePool;
private readonly symbolCache;
private readonly aggregatedFields;
private readonly listeners;
private disposed;
constructor(opts?: ClientOptions);
/**
* Get the `TvSymbol` handle for a market pair. Repeated calls for
* the same pair return the same instance, so subscriptions pool
* naturally across callers.
*/
symbol(pair: string): TvSymbol;
/**
* Build an ad-hoc `Portfolio` over the given pairs. Unlike
* `createGroup`, a portfolio is not tracked by the client — it only
* lives as long as the caller holds the reference.
*/
symbols(pairs: readonly string[]): Portfolio;
/**
* Create a named `Group` and register it with `client.groups`. Use
* groups for long-lived, mutable collections — e.g. a watchlist the
* user can edit at runtime.
*/
createGroup(name: string, pairs?: readonly string[]): Group;
/**
* Aggregate multi-symbol stream across every symbol currently
* registered on this client. De-duplicates naturally: a pair that
* belongs to multiple groups appears once in the client's symbol
* cache, so listeners receive one event per tick regardless of how
* many groups reference it.
*/
stream(fields?: readonly QuoteField[]): MultiStream;
/** Open the underlying transport and wait until TradingView is ready. */
connect(): Promise<void>;
/** Close everything: groups, pool session, all symbols, transport. */
disconnect(): Promise<void>;
/** Register a client-level event listener. */
on<E extends ClientEventName>(event: E, handler: Handler<E>): this;
/** Remove a previously registered event listener. */
off<E extends ClientEventName>(event: E, handler: Handler<E>): this;
/** Async disposer support (`using client = tv()`). */
[Symbol.asyncDispose](): Promise<void>;
/** @internal */
_getQuotePool(): QuoteSession;
/** @internal */
_requestFields(fields: readonly QuoteField[]): void;
/** @internal — exposed primarily for tests and advanced users. */
_getSymbolCache(): ReadonlyMap<string, TvSymbol>;
private dispatchUpdate;
private dispatchError;
private dispatchComplete;
private emit;
}
/**
* tradingview-api-adapter
*
* Real-time market data from TradingView via WebSocket.
*
* Quick start:
*
* import { tv } from 'tradingview-api-adapter'
*
* const client = tv()
* const btc = client.symbol('BINANCE:BTCUSDT')
*
* console.log(await btc.price())
*
* const stream = btc.stream()
* stream.on('price', ({ price }) => console.log(price))
*
* await client.disconnect()
*/
declare const version = "2.0.0";
export { type AuthOptions, BarTick, Candle, type CandlesOptions, Client, type ClientEventMap, type ClientOptions, DEFAULT_STREAM_FIELDS, FullQuoteSnapshot, Group, GroupRegistry, MultiStream, type MultiStreamEventMap, type MultiStreamOptions, Portfolio, QuoteField, QuoteSnapshot, Stream, type StreamEventMap, SymbolInfo, Timeframe, TradeTick, TvSymbol, tv, version };