@mathieuc/tradingview 4.0.0-beta.2 → 4.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.js +2 -0
- package/dist/http/index.d.ts +4 -0
- package/dist/http/index.js +2 -0
- package/dist/http/screener.d.ts +32 -0
- package/dist/http/screener.js +36 -0
- package/dist/http/watchlists.d.ts +22 -0
- package/dist/http/watchlists.js +42 -0
- package/docs/data-api.md +11 -0
- package/docs/screener.md +42 -0
- package/docs/v4-backlog-triage.md +12 -0
- package/docs/watchlists.md +56 -0
- package/package.json +1 -1
package/dist/data/index.d.ts
CHANGED
|
@@ -28,3 +28,7 @@ export type { GraphicsData } from '../chart/graphics.js';
|
|
|
28
28
|
export type { StrategyReport } from '../chart/strategy.js';
|
|
29
29
|
export { summarizeStrategyReport } from '../chart/strategy.js';
|
|
30
30
|
export type { StrategySummary } from '../chart/strategy.js';
|
|
31
|
+
export { getScreener } from '../http/screener.js';
|
|
32
|
+
export type { ScreenerFilter, ScreenerQuery, ScreenerResult, ScreenerRow } from '../http/screener.js';
|
|
33
|
+
export { getWatchlists, getHotlist } from '../http/watchlists.js';
|
|
34
|
+
export type { Watchlist, WatchlistOptions, HotlistKind, HotlistQuery } from '../http/watchlists.js';
|
package/dist/data/index.js
CHANGED
|
@@ -14,3 +14,5 @@ export { DEFAULT_TIMEOUT_MS } from './operation.js';
|
|
|
14
14
|
export { getTechnicalAnalysis, searchIndicators, searchMarkets, } from '../http/index.js';
|
|
15
15
|
export { TradingViewError } from '../errors.js';
|
|
16
16
|
export { summarizeStrategyReport } from '../chart/strategy.js';
|
|
17
|
+
export { getScreener } from '../http/screener.js';
|
|
18
|
+
export { getWatchlists, getHotlist } from '../http/watchlists.js';
|
package/dist/http/index.d.ts
CHANGED
|
@@ -9,3 +9,7 @@ export { getChartToken, getDrawings } from './layouts.js';
|
|
|
9
9
|
export type { Drawing, DrawingPoint, GetDrawingsOptions, LayoutOptions, } from './layouts.js';
|
|
10
10
|
export { PinePermissionManager } from './pine-permissions.js';
|
|
11
11
|
export type { AuthorizedUser, AuthorizedUserOrder, PinePermissionOptions } from './pine-permissions.js';
|
|
12
|
+
export { getScreener } from './screener.js';
|
|
13
|
+
export type { ScreenerFilter, ScreenerQuery, ScreenerResult, ScreenerRow } from './screener.js';
|
|
14
|
+
export { getWatchlists, getHotlist } from './watchlists.js';
|
|
15
|
+
export type { Watchlist, WatchlistOptions, HotlistKind, HotlistQuery } from './watchlists.js';
|
package/dist/http/index.js
CHANGED
|
@@ -3,3 +3,5 @@ export { getTechnicalAnalysis, searchMarkets } from './market.js';
|
|
|
3
3
|
export { clearIndicatorCache, getIndicator, getPrivateIndicators, parseIndicatorDefinition, searchIndicators, } from './indicators.js';
|
|
4
4
|
export { getChartToken, getDrawings } from './layouts.js';
|
|
5
5
|
export { PinePermissionManager } from './pine-permissions.js';
|
|
6
|
+
export { getScreener } from './screener.js';
|
|
7
|
+
export { getWatchlists, getHotlist } from './watchlists.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type AuthHttpOptions } from './request.js';
|
|
2
|
+
export interface ScreenerFilter {
|
|
3
|
+
left: string;
|
|
4
|
+
operation: string;
|
|
5
|
+
right?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface ScreenerQuery {
|
|
8
|
+
/** Scanner universe, e.g. america, crypto, forex or global. */
|
|
9
|
+
market?: string;
|
|
10
|
+
/** Exact scanner field names, including optional timeframe suffixes. */
|
|
11
|
+
columns: string[];
|
|
12
|
+
filter?: ScreenerFilter[];
|
|
13
|
+
sort?: {
|
|
14
|
+
sortBy: string;
|
|
15
|
+
sortOrder: 'asc' | 'desc';
|
|
16
|
+
};
|
|
17
|
+
/** Zero-based, end-exclusive page range. Defaults to [0, 50]. */
|
|
18
|
+
range?: [number, number];
|
|
19
|
+
/** Optional exchange-qualified symbols. */
|
|
20
|
+
symbols?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface ScreenerRow {
|
|
23
|
+
symbol: string;
|
|
24
|
+
/** Values retain upstream types and nulls; keys match requested columns. */
|
|
25
|
+
values: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface ScreenerResult {
|
|
28
|
+
totalCount: number;
|
|
29
|
+
rows: ScreenerRow[];
|
|
30
|
+
}
|
|
31
|
+
/** One scanner page. This does not subscribe to live prices or grant exchange entitlements. */
|
|
32
|
+
export declare function getScreener(query: ScreenerQuery, options?: AuthHttpOptions): Promise<ScreenerResult>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { TradingViewError } from '../errors.js';
|
|
2
|
+
import { request } from './request.js';
|
|
3
|
+
/** One scanner page. This does not subscribe to live prices or grant exchange entitlements. */
|
|
4
|
+
export async function getScreener(query, options = {}) {
|
|
5
|
+
const market = query.market ?? 'global';
|
|
6
|
+
const range = query.range ?? [0, 50];
|
|
7
|
+
if (!/^[a-z][a-z0-9_-]*$/i.test(market)
|
|
8
|
+
|| !Array.isArray(query.columns) || !query.columns.length
|
|
9
|
+
|| query.columns.some((column) => typeof column !== 'string' || !column.trim())
|
|
10
|
+
|| new Set(query.columns).size !== query.columns.length
|
|
11
|
+
|| range.length !== 2 || !range.every(Number.isSafeInteger) || range[0] < 0 || range[1] <= range[0]) {
|
|
12
|
+
throw new TradingViewError('INVALID_ARGUMENT', 'Invalid screener market, columns or page range');
|
|
13
|
+
}
|
|
14
|
+
const { data, status } = await request(`https://scanner.tradingview.com/${market}/scan`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
credentials: options.credentials,
|
|
17
|
+
headers: { origin: 'https://www.tradingview.com' },
|
|
18
|
+
json: {
|
|
19
|
+
columns: query.columns, range, filter: query.filter ?? [], sort: query.sort,
|
|
20
|
+
symbols: query.symbols === undefined ? undefined : { tickers: query.symbols },
|
|
21
|
+
},
|
|
22
|
+
}, options);
|
|
23
|
+
if (status < 200 || status >= 300 || !data || !Number.isSafeInteger(data.totalCount)
|
|
24
|
+
|| data.totalCount < 0 || !Array.isArray(data.data)
|
|
25
|
+
|| data.data.some((row) => !row || typeof row.s !== 'string'
|
|
26
|
+
|| !Array.isArray(row.d) || row.d.length !== query.columns.length)) {
|
|
27
|
+
throw new TradingViewError('HTTP_ERROR', `Unexpected screener response (HTTP ${status})`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
totalCount: data.totalCount,
|
|
31
|
+
rows: data.data.map((row) => ({
|
|
32
|
+
symbol: row.s,
|
|
33
|
+
values: Object.fromEntries(query.columns.map((column, index) => [column, row.d[index]])),
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Credentials, type HttpOptions } from './request.js';
|
|
2
|
+
import { type ScreenerQuery, type ScreenerResult } from './screener.js';
|
|
3
|
+
export interface Watchlist {
|
|
4
|
+
id: number;
|
|
5
|
+
name: string;
|
|
6
|
+
/** Ordered upstream entries, including any section markers. */
|
|
7
|
+
symbols: string[];
|
|
8
|
+
/** Additional upstream metadata, preserved without coercion. */
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface WatchlistOptions extends HttpOptions {
|
|
12
|
+
credentials: Credentials;
|
|
13
|
+
}
|
|
14
|
+
/** Reads all watchlists belonging to the authenticated account. Never modifies them. */
|
|
15
|
+
export declare function getWatchlists(options: WatchlistOptions): Promise<Watchlist[]>;
|
|
16
|
+
export type HotlistKind = 'gainers' | 'losers' | 'mostActive' | 'volumeGainers';
|
|
17
|
+
export interface HotlistQuery extends Omit<ScreenerQuery, 'columns' | 'sort'> {
|
|
18
|
+
kind: HotlistKind;
|
|
19
|
+
columns?: string[];
|
|
20
|
+
}
|
|
21
|
+
/** Scanner-ranked lists, not a promise of parity with the TradingView UI hotlist widget. */
|
|
22
|
+
export declare function getHotlist(query: HotlistQuery, options?: HttpOptions): Promise<ScreenerResult>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TradingViewError } from '../errors.js';
|
|
2
|
+
import { request } from './request.js';
|
|
3
|
+
import { getScreener } from './screener.js';
|
|
4
|
+
/** Reads all watchlists belonging to the authenticated account. Never modifies them. */
|
|
5
|
+
export async function getWatchlists(options) {
|
|
6
|
+
if (!options?.credentials?.session) {
|
|
7
|
+
throw new TradingViewError('INVALID_ARGUMENT', 'getWatchlists requires account credentials');
|
|
8
|
+
}
|
|
9
|
+
const { data, status } = await request('https://www.tradingview.com/api/v1/symbols_list/all/', {
|
|
10
|
+
credentials: options.credentials,
|
|
11
|
+
headers: { origin: 'https://www.tradingview.com' },
|
|
12
|
+
redirect: 'manual',
|
|
13
|
+
}, options);
|
|
14
|
+
if (status === 401 || status === 403 || (status >= 300 && status < 400)) {
|
|
15
|
+
throw new TradingViewError('AUTH_ERROR', `Watchlist access rejected (HTTP ${status})`);
|
|
16
|
+
}
|
|
17
|
+
if (status < 200 || status >= 300 || !Array.isArray(data)
|
|
18
|
+
|| data.some((list) => !list || !Number.isSafeInteger(list.id) || typeof list.name !== 'string'
|
|
19
|
+
|| !Array.isArray(list.symbols) || list.symbols.some((symbol) => typeof symbol !== 'string'))) {
|
|
20
|
+
throw new TradingViewError('HTTP_ERROR', `Unexpected watchlist response (HTTP ${status})`);
|
|
21
|
+
}
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
/** Scanner-ranked lists, not a promise of parity with the TradingView UI hotlist widget. */
|
|
25
|
+
export async function getHotlist(query, options = {}) {
|
|
26
|
+
const rankings = {
|
|
27
|
+
gainers: { sortBy: 'change', sortOrder: 'desc' },
|
|
28
|
+
losers: { sortBy: 'change', sortOrder: 'asc' },
|
|
29
|
+
mostActive: { sortBy: 'volume', sortOrder: 'desc' },
|
|
30
|
+
volumeGainers: { sortBy: 'relative_volume_10d_calc', sortOrder: 'desc' },
|
|
31
|
+
};
|
|
32
|
+
if (!Object.hasOwn(rankings, query.kind)) {
|
|
33
|
+
throw new TradingViewError('INVALID_ARGUMENT', 'Unknown hotlist kind');
|
|
34
|
+
}
|
|
35
|
+
return getScreener({
|
|
36
|
+
...query,
|
|
37
|
+
market: query.market ?? 'america',
|
|
38
|
+
columns: query.columns ?? ['name', 'close', 'change', 'volume', 'relative_volume_10d_calc'],
|
|
39
|
+
filter: query.filter ?? [{ left: 'type', operation: 'equal', right: 'stock' }],
|
|
40
|
+
sort: rankings[query.kind],
|
|
41
|
+
}, options);
|
|
42
|
+
}
|
package/docs/data-api.md
CHANGED
|
@@ -283,3 +283,14 @@ Every failure is a `TradingViewError` with a `code`:
|
|
|
283
283
|
| `CALLBACK_ERROR` | A watcher callback threw; the stream remains active. |
|
|
284
284
|
|
|
285
285
|
`error.details` keeps the raw server payload when there is one.
|
|
286
|
+
|
|
287
|
+
## Screener
|
|
288
|
+
|
|
289
|
+
Use [`getScreener`](screener.md) for a single scanner page with custom columns,
|
|
290
|
+
filters, ranking and explicit pagination. Available from this high-level entry
|
|
291
|
+
point as well as the root package; no chart or quote session is required.
|
|
292
|
+
|
|
293
|
+
## Watchlists and rankings
|
|
294
|
+
|
|
295
|
+
[`getWatchlists` and `getHotlist`](watchlists.md) provide read-only account lists
|
|
296
|
+
and scanner-based gainers, losers and volume rankings. Available from both entry points.
|
package/docs/screener.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Screener
|
|
2
|
+
|
|
3
|
+
`getScreener` is available from both the root package and `@mathieuc/tradingview/data`.
|
|
4
|
+
It performs one HTTP scan, without creating WebSocket sessions.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
import { getScreener } from '@mathieuc/tradingview/data';
|
|
8
|
+
|
|
9
|
+
const page = await getScreener({
|
|
10
|
+
market: 'america',
|
|
11
|
+
columns: ['name', 'close', 'volume', 'Stoch.RSI.D'],
|
|
12
|
+
filter: [{ left: 'type', operation: 'equal', right: 'stock' }],
|
|
13
|
+
sort: { sortBy: 'volume', sortOrder: 'desc' },
|
|
14
|
+
range: [0, 20],
|
|
15
|
+
});
|
|
16
|
+
console.log(page.totalCount, page.rows);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Each row has an exchange-qualified `symbol` and a `values` object keyed by the
|
|
20
|
+
requested columns. Values retain their upstream types, including null. Columns
|
|
21
|
+
must be nonempty and unique. Unknown fields and invalid server filters throw
|
|
22
|
+
`TradingViewError` with code `HTTP_ERROR`; they do not silently become empty results.
|
|
23
|
+
|
|
24
|
+
The default market is `global`. Other upstream universes include `america`,
|
|
25
|
+
`crypto` and `forex`. Exact column names, filter operations and available markets
|
|
26
|
+
are controlled by TradingView, not a stable schema owned by this package.
|
|
27
|
+
Use `symbols: ['NASDAQ:AAPL']` to restrict the scan to explicit tickers.
|
|
28
|
+
|
|
29
|
+
`range` is zero-based and end-exclusive, defaulting to `[0, 50]`. Request subsequent
|
|
30
|
+
pages explicitly. Rankings can change between requests; pages are not a transactionally
|
|
31
|
+
consistent snapshot. There is no automatic unbounded pagination, polling or retry.
|
|
32
|
+
|
|
33
|
+
The second argument accepts `credentials`, `fetch`, `headers` and `signal`.
|
|
34
|
+
Account cookies may be supplied but do not grant paid exchange rights. This is
|
|
35
|
+
**not a real-time screener subscription**: delays and entitlements remain upstream.
|
|
36
|
+
Use `watchQuotes` separately when you need quote updates for selected symbols.
|
|
37
|
+
|
|
38
|
+
## Live evidence
|
|
39
|
+
|
|
40
|
+
On 3 October 2026 an anonymous America scan with stock filters, volume sorting and
|
|
41
|
+
`name`, `close`, `volume`, `Stoch.RSI.D` returned a total count and two typed rows.
|
|
42
|
+
This establishes the request/response contract, not paid real-time availability.
|
|
@@ -138,3 +138,15 @@ No issues/legacy PRs were closed, and no contributor comments were sent by this
|
|
|
138
138
|
- Dependency audit: baseline had two moderate entries for the same Vitest/mocker advisory; patched development dependency in this change.
|
|
139
139
|
- No new authenticated, private-script, Premium or long-duration claims are made.
|
|
140
140
|
- No npm publication or promotion of the `latest` tag is part of this change.
|
|
141
|
+
|
|
142
|
+
## Screener follow-up
|
|
143
|
+
|
|
144
|
+
Beta.3 adds `getScreener` for #53 and scanner fields relevant to #280. See
|
|
145
|
+
[screener documentation](screener.md). #85 real-time exchange entitlement is not
|
|
146
|
+
resolved by a successful HTTP scan. Watchlists and paid/private cases remain open.
|
|
147
|
+
|
|
148
|
+
## Watchlist follow-up
|
|
149
|
+
|
|
150
|
+
Beta.4 adds read-only `getWatchlists` and scanner-ranked `getHotlist` (#87).
|
|
151
|
+
Authenticated discovery returned two empty lists; populated entries are fixture-tested.
|
|
152
|
+
Exact TradingView UI hotlist parity is not claimed. See [watchlists](watchlists.md).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Watchlists and ranked lists
|
|
2
|
+
|
|
3
|
+
## Read your watchlists
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { getWatchlists } from '@mathieuc/tradingview/data';
|
|
7
|
+
|
|
8
|
+
const lists = await getWatchlists({
|
|
9
|
+
credentials: {
|
|
10
|
+
session: process.env.TV_SESSION,
|
|
11
|
+
signature: process.env.TV_SIGNATURE,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
for (const list of lists) console.log(list.id, list.name, list.symbols);
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This is a read-only account API. Each list preserves its numeric ID, name, ordered
|
|
18
|
+
`symbols` and additional upstream metadata. Entries can include section markers;
|
|
19
|
+
do not blindly pass every entry to `getQuotes`. Filter for exchange-qualified
|
|
20
|
+
symbols appropriate to your application. No lists are created, edited or deleted.
|
|
21
|
+
|
|
22
|
+
Credentials are required. Access rejection (401/403 or login redirects) produces
|
|
23
|
+
`AUTH_ERROR`; malformed responses and other failed statuses produce `HTTP_ERROR`.
|
|
24
|
+
Authenticated redirects are not followed. The options also accept `fetch`,
|
|
25
|
+
`headers` and `signal`. Treat returned names/symbols as private account data.
|
|
26
|
+
|
|
27
|
+
## Scanner-ranked lists
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { getHotlist } from '@mathieuc/tradingview/data';
|
|
31
|
+
|
|
32
|
+
const page = await getHotlist({ kind: 'gainers', range: [0, 10] });
|
|
33
|
+
console.log(page.rows);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Kind | Ranking |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `gainers` | Percentage change descending |
|
|
39
|
+
| `losers` | Percentage change ascending |
|
|
40
|
+
| `mostActive` | Volume descending |
|
|
41
|
+
| `volumeGainers` | Relative volume (`relative_volume_10d_calc`) descending |
|
|
42
|
+
|
|
43
|
+
These are convenient [screener](screener.md) queries, **not an exact replica of the
|
|
44
|
+
TradingView hotlist widget**. UI universe, session and liquidity filters may differ.
|
|
45
|
+
Default universe: America stocks; default fields: name, close, change, volume and
|
|
46
|
+
relative volume. Override `market`, `columns`, `filter`, `range` or `symbols` as
|
|
47
|
+
needed. For a non-stock universe, supply appropriate filters (or `filter: []`),
|
|
48
|
+
since changing the market does not remove the default stock filter. Exchange data
|
|
49
|
+
may be delayed. No automatic polling or paid entitlement is implied.
|
|
50
|
+
|
|
51
|
+
## Evidence and limits
|
|
52
|
+
|
|
53
|
+
On 3 October 2026, the account endpoint returned two watchlists successfully.
|
|
54
|
+
Both were empty, so nonempty ordered entries and section preservation are covered
|
|
55
|
+
by deterministic fixtures, not claimed as live-tested account content. An anonymous
|
|
56
|
+
America relative-volume scan returned populated rows. No account data was modified.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mathieuc/tradingview",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.4",
|
|
4
4
|
"description": "TradingView market data for JavaScript and TypeScript: candles, quotes, indicators and strategies, with a simple data API and full low-level access.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|