@mathieuc/tradingview 4.0.0-beta.4 → 4.0.0-rc.0
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/README.md +3 -1
- package/assets/readme-demo.gif +0 -0
- package/dist/client/client.js +8 -2
- package/dist/http/account.js +5 -2
- package/dist/http/request.js +2 -1
- package/docs/README.es.md +1 -1
- package/docs/README.fr.md +1 -1
- package/docs/README.pt.md +1 -1
- package/docs/v4-backlog-triage.md +24 -12
- package/docs/v4-coverage.md +22 -3
- package/docs/v4-reliability.md +58 -0
- package/docs/v4-stabilization.md +87 -0
- package/examples/README.md +37 -0
- package/examples/builtin-indicator.js +22 -0
- package/examples/candles.js +15 -0
- package/examples/custom-chart-types.js +17 -0
- package/examples/custom-timeframe.js +14 -0
- package/examples/drawings.js +16 -0
- package/examples/errors.js +44 -0
- package/examples/fake-replay.js +24 -0
- package/examples/from-to.js +25 -0
- package/examples/graphic-indicator.js +18 -0
- package/examples/indicator-data.js +23 -0
- package/examples/multiple-indicators.js +16 -0
- package/examples/pine-permissions.js +18 -0
- package/examples/private-indicators.js +27 -0
- package/examples/quote-session.js +17 -0
- package/examples/quotes.js +15 -0
- package/examples/replay.js +36 -0
- package/examples/screener.js +10 -0
- package/examples/search.js +11 -0
- package/examples/simple-chart.js +36 -0
- package/examples/strategy-report.js +30 -0
- package/examples/user-login.js +15 -0
- package/examples/watch-candles.js +16 -0
- package/llms.txt +26 -0
- package/package.json +12 -3
- package/scripts/endurance.mjs +93 -0
- package/scripts/parity.mjs +54 -0
- package/scripts/probe-account.mjs +55 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Every error is a TradingViewError with a `code`.
|
|
2
|
+
// Run: npm run build && node --env-file=.env examples/errors.js
|
|
3
|
+
import {
|
|
4
|
+
getCandles, getIndicator, getIndicatorData, TradingViewClient, TradingViewError,
|
|
5
|
+
} from '@mathieuc/tradingview';
|
|
6
|
+
|
|
7
|
+
const credentials = process.env.SESSION
|
|
8
|
+
? { session: process.env.SESSION, signature: process.env.SIGNATURE }
|
|
9
|
+
: undefined;
|
|
10
|
+
|
|
11
|
+
async function show(title, run) {
|
|
12
|
+
try {
|
|
13
|
+
await run();
|
|
14
|
+
console.log(`${title}: no error`);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (!(error instanceof TradingViewError)) throw error;
|
|
17
|
+
console.log(`${title}: [${error.code}] ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await show('Wrong credentials', () => new TradingViewClient({ credentials: { session: 'FAKE' } }).ready);
|
|
22
|
+
await show('Invalid symbol', () => getCandles('XXXXX'));
|
|
23
|
+
await show('Invalid timeframe', () => getCandles({ symbol: 'BINANCE:BTCEUR', timeframe: 'XX' }));
|
|
24
|
+
await show('Premium chart type without account', () => getCandles({ symbol: 'BINANCE:BTCEUR', timeframe: '15', chartType: 'Renko' }));
|
|
25
|
+
await show('Inexistent indicator', () => getIndicator('STD;XXXXXXX'));
|
|
26
|
+
await show('Timeout', () => getCandles({ symbol: 'BINANCE:BTCEUR', timeoutMs: 1 }));
|
|
27
|
+
|
|
28
|
+
// Low-level sessions report errors through their `error` event.
|
|
29
|
+
const client = new TradingViewClient();
|
|
30
|
+
const chart = client.createChart();
|
|
31
|
+
await show('Invalid timezone', () => new Promise((resolve, reject) => {
|
|
32
|
+
chart.on('error', reject);
|
|
33
|
+
chart.setMarket('BINANCE:BTCEUR');
|
|
34
|
+
chart.setTimezone('Nowhere/Nowhere');
|
|
35
|
+
setTimeout(resolve, 5_000);
|
|
36
|
+
}));
|
|
37
|
+
await show('Timeframe before market', async () => client.createChart().setTimeframe('15'));
|
|
38
|
+
await client.close();
|
|
39
|
+
|
|
40
|
+
if (credentials) {
|
|
41
|
+
const supertrend = await getIndicator('STD;Supertrend');
|
|
42
|
+
supertrend.setInput('Factor', -1);
|
|
43
|
+
await show('Invalid study input', () => getIndicatorData({ symbol: 'BINANCE:BTCEUR', indicator: supertrend, credentials }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// "Fake replay": walk forward from a past date by loading newer bars, which
|
|
2
|
+
// also works on intraday timeframes without an account.
|
|
3
|
+
// Run: npm run build && node examples/fake-replay.js
|
|
4
|
+
import { TradingViewClient } from '@mathieuc/tradingview';
|
|
5
|
+
|
|
6
|
+
const client = new TradingViewClient();
|
|
7
|
+
const chart = client.createChart();
|
|
8
|
+
|
|
9
|
+
chart.setMarket('BINANCE:BTCEUR', {
|
|
10
|
+
timeframe: '240',
|
|
11
|
+
count: -1, // Negative: load bars after `to` instead of before
|
|
12
|
+
to: Math.round(Date.now() / 1000) - 86_400 * 7, // Seven days ago
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
chart.on('seriesCompleted', async () => {
|
|
16
|
+
const last = chart.lastCandle;
|
|
17
|
+
console.log('Next ->', last && new Date(last.time * 1000).toISOString(), `(${chart.candles.length} bars)`);
|
|
18
|
+
if (!last || last.time > Date.now() / 1000 - 86_400) {
|
|
19
|
+
await client.close();
|
|
20
|
+
console.log('Done');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
chart.fetchMore(-2); // Two newer bars
|
|
24
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Bars before or after a reference time, with an indicator on the same chart.
|
|
2
|
+
// The reference time needs an account with enough history access.
|
|
3
|
+
// Run: npm run build && node --env-file=.env examples/from-to.js
|
|
4
|
+
import { getCandles, getIndicator, TradingViewClient } from '@mathieuc/tradingview';
|
|
5
|
+
|
|
6
|
+
if (!process.env.SESSION || !process.env.SIGNATURE) throw new Error('Please set your SESSION and SIGNATURE cookies');
|
|
7
|
+
const credentials = { session: process.env.SESSION, signature: process.env.SIGNATURE };
|
|
8
|
+
|
|
9
|
+
// High level: every 4h bar between two dates.
|
|
10
|
+
const range = await getCandles({
|
|
11
|
+
symbol: 'BINANCE:BTCEUR', timeframe: '240', from: new Date('2023-11-01'), to: new Date('2023-11-14'), credentials,
|
|
12
|
+
});
|
|
13
|
+
console.log(`${range.length} bars from ${new Date(range[0].time * 1000).toISOString()}`);
|
|
14
|
+
|
|
15
|
+
// Low level: 2 bars before a timestamp (negative counts load bars after it) and a study.
|
|
16
|
+
const client = new TradingViewClient({ credentials });
|
|
17
|
+
const chart = client.createChart();
|
|
18
|
+
chart.setMarket('BINANCE:BTCEUR', { timeframe: '240', count: 2, to: 1_700_000_000 });
|
|
19
|
+
|
|
20
|
+
const supertrend = chart.createStudy(await getIndicator('STD;Supertrend'));
|
|
21
|
+
supertrend.on('ready', async () => {
|
|
22
|
+
console.log('Prices:', chart.candles);
|
|
23
|
+
console.log('Study:', supertrend.values.slice(-2));
|
|
24
|
+
await client.close();
|
|
25
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Indicators that draw labels, lines, boxes, tables, polygons...
|
|
2
|
+
// Run: npm run build && node --env-file=.env examples/graphic-indicator.js
|
|
3
|
+
import { getIndicatorData } from '@mathieuc/tradingview/data';
|
|
4
|
+
|
|
5
|
+
if (!process.env.SESSION || !process.env.SIGNATURE) throw new Error('Please set your SESSION and SIGNATURE cookies');
|
|
6
|
+
|
|
7
|
+
const { indicator, graphics } = await getIndicatorData({
|
|
8
|
+
symbol: 'BINANCE:BTCEUR',
|
|
9
|
+
timeframe: '5',
|
|
10
|
+
count: 1_000,
|
|
11
|
+
indicator: 'STD;Zig_Zag',
|
|
12
|
+
credentials: { session: process.env.SESSION, signature: process.env.SIGNATURE },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
console.log(`'${indicator.description}' graphics:`);
|
|
16
|
+
console.log('Lines:', graphics.lines.slice(0, 5)); // x positions are bars back from the latest bar
|
|
17
|
+
console.log('Labels:', graphics.labels.slice(0, 5));
|
|
18
|
+
console.log('Tables:', graphics.tables.map((t) => t.cells.map((row) => row.map((cell) => cell.text))));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Indicator values with the high-level data API.
|
|
2
|
+
// Pine indicators (STD;..., PUB;..., USER;...) need an account: set SESSION and SIGNATURE.
|
|
3
|
+
// Run: npm run build && node --env-file=.env examples/indicator-data.js
|
|
4
|
+
import { getIndicatorData } from '@mathieuc/tradingview/data';
|
|
5
|
+
|
|
6
|
+
const credentials = process.env.SESSION
|
|
7
|
+
? { session: process.env.SESSION, signature: process.env.SIGNATURE }
|
|
8
|
+
: undefined;
|
|
9
|
+
|
|
10
|
+
// A built-in study works without an account.
|
|
11
|
+
const volume = await getIndicatorData({ symbol: 'BINANCE:BTCEUR', indicator: 'Volume@tv-basicstudies-241', count: 5 });
|
|
12
|
+
console.log('Volume rows:', volume.values);
|
|
13
|
+
|
|
14
|
+
if (credentials) {
|
|
15
|
+
const rsi = await getIndicatorData({
|
|
16
|
+
symbol: 'BINANCE:BTCEUR',
|
|
17
|
+
timeframe: '60',
|
|
18
|
+
indicator: 'STD;RSI',
|
|
19
|
+
inputs: { Length: 21 }, // Input ID, number, inline name or internal ID
|
|
20
|
+
credentials,
|
|
21
|
+
});
|
|
22
|
+
console.log('Last RSI row:', rsi.values.at(-1));
|
|
23
|
+
} else console.log('Set SESSION and SIGNATURE to run a Pine indicator');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Several indicators in parallel over one shared connection.
|
|
2
|
+
// Run: npm run build && node --env-file=.env examples/multiple-indicators.js
|
|
3
|
+
import { getIndicatorData, TradingViewClient } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
if (!process.env.SESSION || !process.env.SIGNATURE) throw new Error('Please set your SESSION and SIGNATURE cookies');
|
|
6
|
+
const credentials = { session: process.env.SESSION, signature: process.env.SIGNATURE };
|
|
7
|
+
|
|
8
|
+
const client = new TradingViewClient({ credentials });
|
|
9
|
+
try {
|
|
10
|
+
const results = await Promise.all(['STD;RSI', 'STD;MACD', 'STD;Bollinger_Bands'].map((indicator) => (
|
|
11
|
+
getIndicatorData({ symbol: 'BINANCE:DOTUSDT', indicator, client, credentials })
|
|
12
|
+
)));
|
|
13
|
+
for (const { indicator, values } of results) console.log(indicator.description, values.at(-1));
|
|
14
|
+
} finally {
|
|
15
|
+
await client.close();
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Manage who can use an invite-only script you own.
|
|
2
|
+
// Run: npm run build && node --env-file=.env examples/pine-permissions.js <PUB;pineId>
|
|
3
|
+
import { PinePermissionManager } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
if (!process.env.SESSION || !process.env.SIGNATURE) throw new Error('Please set your SESSION and SIGNATURE cookies');
|
|
6
|
+
const pineId = process.argv[2];
|
|
7
|
+
if (!pineId) throw new Error('Please specify a Pine ID as first argument');
|
|
8
|
+
|
|
9
|
+
const manager = new PinePermissionManager(pineId, {
|
|
10
|
+
credentials: { session: process.env.SESSION, signature: process.env.SIGNATURE },
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
console.log('Users:', await manager.getUsers());
|
|
14
|
+
console.log("Adding 'TradingView':", await manager.addUser('TradingView')); // 'ok' or 'exists'
|
|
15
|
+
console.log('Expiring tomorrow:', await manager.modifyExpiration('TradingView', new Date(Date.now() + 86_400_000)));
|
|
16
|
+
console.log('Removing expiration:', await manager.modifyExpiration('TradingView'));
|
|
17
|
+
console.log('Removing user:', await manager.removeUser('TradingView'));
|
|
18
|
+
console.log('Users:', await manager.getUsers());
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Run every private (saved) indicator of your account on a chart.
|
|
2
|
+
// Run: npm run build && node --env-file=.env examples/private-indicators.js
|
|
3
|
+
import { getIndicator, getPrivateIndicators, TradingViewClient } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
if (!process.env.SESSION || !process.env.SIGNATURE) throw new Error('Please set your SESSION and SIGNATURE cookies');
|
|
6
|
+
const credentials = { session: process.env.SESSION, signature: process.env.SIGNATURE };
|
|
7
|
+
|
|
8
|
+
const list = await getPrivateIndicators(credentials);
|
|
9
|
+
if (!list.length) {
|
|
10
|
+
console.log('Your account has no private indicators');
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const client = new TradingViewClient({ credentials });
|
|
15
|
+
const chart = client.createChart();
|
|
16
|
+
chart.setMarket('BINANCE:BTCEUR', { timeframe: 'D' });
|
|
17
|
+
|
|
18
|
+
for (const item of list) {
|
|
19
|
+
const study = chart.createStudy(await getIndicator(item.id, { version: item.version, credentials }));
|
|
20
|
+
study.on('error', (error) => console.error(item.name, error.message));
|
|
21
|
+
study.on('ready', () => {
|
|
22
|
+
console.log(item.name, 'last values:', study.values.at(-1));
|
|
23
|
+
if (study.strategyReport.performance.all) console.log(item.name, 'strategy:', study.strategyReport.performance.all);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setTimeout(() => client.close(), 15_000);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Low-level quote session: several symbols sharing one subscription list.
|
|
2
|
+
// Run: npm run build && node examples/quote-session.js
|
|
3
|
+
import { TradingViewClient } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
const client = new TradingViewClient();
|
|
6
|
+
const quotes = client.createQuoteSession({ fields: ['lp', 'ch', 'chp', 'volume'] });
|
|
7
|
+
|
|
8
|
+
for (const symbol of ['BINANCE:BTCEUR', 'BINANCE:ETHEUR']) {
|
|
9
|
+
const subscription = quotes.subscribe(symbol);
|
|
10
|
+
subscription.on('data', (quote, changes) => console.log(symbol, changes));
|
|
11
|
+
subscription.on('error', (error) => console.error(symbol, error.message));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setTimeout(async () => {
|
|
15
|
+
quotes.delete();
|
|
16
|
+
await client.close();
|
|
17
|
+
}, 10_000);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Quotes with the high-level data API.
|
|
2
|
+
// Run: npm run build && node examples/quotes.js
|
|
3
|
+
import { getQuote, getQuotes, watchQuotes } from '@mathieuc/tradingview/data';
|
|
4
|
+
|
|
5
|
+
const btc = await getQuote('BINANCE:BTCUSDT');
|
|
6
|
+
console.log('BTC/USDT', btc.lp, `${btc.chp}%`);
|
|
7
|
+
|
|
8
|
+
const prices = await getQuotes({ symbols: ['NASDAQ:AAPL', 'NASDAQ:MSFT', 'FX:EURUSD'], fields: 'price' });
|
|
9
|
+
for (const [symbol, quote] of Object.entries(prices)) console.log(symbol, quote.lp);
|
|
10
|
+
|
|
11
|
+
const watcher = await watchQuotes({ symbols: ['BINANCE:BTCUSDT', 'BINANCE:ETHUSDT'], fields: ['lp', 'chp'] }, {
|
|
12
|
+
onData: (symbol, quote) => console.log(symbol, quote.lp),
|
|
13
|
+
onError: (error) => console.error(error.message),
|
|
14
|
+
});
|
|
15
|
+
setTimeout(() => watcher.stop(), 15_000);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Replay mode: step through history bar by bar with indicators.
|
|
2
|
+
// Run: npm run build && node --env-file=.env examples/replay.js
|
|
3
|
+
import { BuiltInIndicator, getIndicator, TradingViewClient } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
const credentials = process.env.SESSION
|
|
6
|
+
? { session: process.env.SESSION, signature: process.env.SIGNATURE }
|
|
7
|
+
: undefined;
|
|
8
|
+
|
|
9
|
+
const client = new TradingViewClient({ credentials });
|
|
10
|
+
const chart = client.createChart();
|
|
11
|
+
chart.setMarket('BINANCE:BTCEUR', {
|
|
12
|
+
timeframe: 'D',
|
|
13
|
+
replay: Math.round(Date.now() / 1000) - 86_400 * 7, // Start seven days ago
|
|
14
|
+
count: 1,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const studies = { Volume: chart.createStudy(new BuiltInIndicator('Volume@tv-basicstudies-241')) };
|
|
18
|
+
if (credentials) {
|
|
19
|
+
const ema = await getIndicator('STD;EMA');
|
|
20
|
+
studies.EMA_50 = chart.createStudy(ema.clone().setInput('Length', 50));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let ended = false;
|
|
24
|
+
chart.on('replayEnd', () => { ended = true; });
|
|
25
|
+
await new Promise((resolve) => { chart.once('replayLoaded', resolve); });
|
|
26
|
+
|
|
27
|
+
while (!ended) {
|
|
28
|
+
await chart.replayStep(1);
|
|
29
|
+
const bar = chart.lastCandle;
|
|
30
|
+
const values = Object.fromEntries(Object.entries(studies).map(([name, study]) => [name, study.values.at(-1)]));
|
|
31
|
+
console.log(bar && new Date(bar.time * 1000).toISOString(), bar?.close, values);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// chart.replayStart(1000) plays automatically; chart.replayStop() pauses.
|
|
35
|
+
await client.close();
|
|
36
|
+
console.log('Done');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getScreener } from '@mathieuc/tradingview/data';
|
|
2
|
+
|
|
3
|
+
const page = await getScreener({
|
|
4
|
+
market: 'america',
|
|
5
|
+
columns: ['name', 'close', 'volume', 'Stoch.RSI.D'],
|
|
6
|
+
filter: [{ left: 'type', operation: 'equal', right: 'stock' }],
|
|
7
|
+
sort: { sortBy: 'volume', sortOrder: 'desc' },
|
|
8
|
+
range: [0, 10],
|
|
9
|
+
});
|
|
10
|
+
console.table(page.rows.map(({ symbol, values }) => ({ symbol, ...values })));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Market search, indicator search and technical ratings.
|
|
2
|
+
// Run: npm run build && node examples/search.js
|
|
3
|
+
import { getTechnicalAnalysis, searchIndicators, searchMarkets } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
const markets = await searchMarkets('BINANCE:BTC', { type: 'crypto' });
|
|
6
|
+
console.log('Markets:', markets.slice(0, 5).map((m) => `${m.id} (${m.description})`));
|
|
7
|
+
|
|
8
|
+
const indicators = await searchIndicators('RSI');
|
|
9
|
+
console.log('Indicators:', indicators.slice(0, 5).map((i) => `${i.id} - ${i.name} [${i.access}]`));
|
|
10
|
+
|
|
11
|
+
console.log('Daily ratings for', markets[0].id, (await getTechnicalAnalysis(markets[0].id))?.['1D']);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Low-level chart session: change market, timeframe and chart type on one chart.
|
|
2
|
+
// Run: npm run build && node examples/simple-chart.js
|
|
3
|
+
import { TradingViewClient } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
const client = new TradingViewClient();
|
|
6
|
+
const chart = client.createChart();
|
|
7
|
+
|
|
8
|
+
chart.on('error', (error) => console.error('Chart error:', error.code, error.message));
|
|
9
|
+
chart.on('symbolLoaded', (info) => console.log(`Market "${info.description}" loaded`));
|
|
10
|
+
chart.on('update', () => {
|
|
11
|
+
const last = chart.lastCandle;
|
|
12
|
+
if (last) console.log(`[${chart.symbolInfo?.description}] ${last.close} ${chart.symbolInfo?.currency_id}`);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
chart.setMarket('BINANCE:BTCEUR', { timeframe: 'D' });
|
|
16
|
+
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
console.log('\nSetting market to BINANCE:ETHEUR...');
|
|
19
|
+
chart.setMarket('BINANCE:ETHEUR', { timeframe: 'D' });
|
|
20
|
+
}, 5_000);
|
|
21
|
+
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
console.log('\nSetting timeframe to 15 minutes...');
|
|
24
|
+
chart.setTimeframe('15');
|
|
25
|
+
}, 10_000);
|
|
26
|
+
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
console.log('\nSetting the chart type to Heikin Ashi...');
|
|
29
|
+
chart.setMarket('BINANCE:ETHEUR', { timeframe: 'D', type: 'HeikinAshi' });
|
|
30
|
+
}, 15_000);
|
|
31
|
+
|
|
32
|
+
setTimeout(async () => {
|
|
33
|
+
console.log('\nClosing...');
|
|
34
|
+
chart.delete();
|
|
35
|
+
await client.close();
|
|
36
|
+
}, 20_000);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Run: npm run build && node --env-file=.env examples/strategy-report.js
|
|
2
|
+
// Computes a public Pine strategy; does not place orders or export the TradingView UI.
|
|
3
|
+
import { getIndicatorData, summarizeStrategyReport } from '@mathieuc/tradingview';
|
|
4
|
+
|
|
5
|
+
if (!process.env.SESSION || !process.env.SIGNATURE) {
|
|
6
|
+
console.error('Set SESSION and SIGNATURE in your environment (see .env.sample).');
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
} else {
|
|
9
|
+
try {
|
|
10
|
+
const { strategyReport } = await getIndicatorData({
|
|
11
|
+
symbol: 'BINANCE:BTCEUR',
|
|
12
|
+
timeframe: '60',
|
|
13
|
+
count: 300,
|
|
14
|
+
indicator: 'STD;Supertrend%Strategy',
|
|
15
|
+
inputs: { commission_type: 'percent', default_qty_value: 20 },
|
|
16
|
+
credentials: { session: process.env.SESSION, signature: process.env.SIGNATURE },
|
|
17
|
+
timeoutMs: 30_000,
|
|
18
|
+
signal: AbortSignal.timeout(45_000),
|
|
19
|
+
});
|
|
20
|
+
if (!strategyReport.performance.all) throw new Error('No strategy performance received');
|
|
21
|
+
// Missing aggregates remain undefined, not zero. totalPnL requires both components.
|
|
22
|
+
console.log('Summary:', summarizeStrategyReport(strategyReport));
|
|
23
|
+
// Most recent first. An exit valuation alone does NOT prove that a trade is closed.
|
|
24
|
+
console.log('Latest trade records:', strategyReport.trades.slice(0, 5));
|
|
25
|
+
console.log('Available history:', Object.keys(strategyReport.history));
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(error.code ?? 'STRATEGY_ERROR', error.message);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Sign in to get session cookies (accounts with 2FA/captcha are not supported).
|
|
2
|
+
// Never commit or share the printed cookies.
|
|
3
|
+
// Run: npm run build && node examples/user-login.js <username/email> <password>
|
|
4
|
+
import { getUser, loginUser } from '@mathieuc/tradingview';
|
|
5
|
+
|
|
6
|
+
const [username, password] = process.argv.slice(2);
|
|
7
|
+
if (!username || !password) throw new Error('Usage: node examples/user-login.js <username/email> <password>');
|
|
8
|
+
|
|
9
|
+
const user = await loginUser({ username, password, remember: false });
|
|
10
|
+
console.log('Signed in as', user.username, `(id ${user.id})`);
|
|
11
|
+
console.log('Put these in .env as SESSION and SIGNATURE:', { session: user.session, signature: user.signature });
|
|
12
|
+
|
|
13
|
+
// Later, the account can be loaded from the cookies alone:
|
|
14
|
+
const again = await getUser({ session: user.session, signature: user.signature });
|
|
15
|
+
console.log('Reloaded', again.username);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Real-time candles with the high-level data API.
|
|
2
|
+
// Run: npm run build && node examples/watch-candles.js
|
|
3
|
+
import { watchCandles } from '@mathieuc/tradingview/data';
|
|
4
|
+
|
|
5
|
+
const watcher = await watchCandles({ symbol: 'BINANCE:BTCUSDT', timeframe: '1', count: 10 }, {
|
|
6
|
+
onData: (candles) => {
|
|
7
|
+
const last = candles.at(-1);
|
|
8
|
+
console.log(new Date(last.time * 1000).toISOString(), 'close', last.close);
|
|
9
|
+
},
|
|
10
|
+
onError: (error) => console.error('Watcher error:', error.code, error.message),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
console.log(`Watching ${watcher.symbolInfo?.description}... (stops after 30 seconds)`);
|
|
14
|
+
setTimeout(() => watcher.stop(), 30_000);
|
|
15
|
+
await watcher.closed;
|
|
16
|
+
console.log('Stopped');
|
package/llms.txt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# TradingView-API
|
|
2
|
+
# https://github.com/Mathieu2301/TradingView-API
|
|
3
|
+
# https://www.npmjs.com/package/@mathieuc/tradingview
|
|
4
|
+
|
|
5
|
+
Independent community library for TradingView market data. Not an official TradingView API.
|
|
6
|
+
Version 4 beta: TypeScript, ESM, Node >= 20 or Bun. Breaking rewrite of v3 (no compatibility layer). Install the prerelease with npm install @mathieuc/tradingview@beta; the default latest tag remains v3.
|
|
7
|
+
|
|
8
|
+
Simple data API (import from '@mathieuc/tradingview/data' or the root entry):
|
|
9
|
+
- getCandles({ symbol, timeframe?, count?, from?, to?, chartType?, ... }) -> Candle[] oldest first, time in Unix seconds.
|
|
10
|
+
- watchCandles(query, { onData, onError? }) -> watcher { latest, stop(), closed }.
|
|
11
|
+
- getQuote(symbol), getQuotes({ symbols }), watchQuotes({ symbols }, { onData }).
|
|
12
|
+
- getSymbolInfo(symbol), getIndicatorData({ symbol, indicator, inputs? }), watchIndicator(...).
|
|
13
|
+
- getScreener({ columns, market?, filter?, sort?, range?, symbols? }) -> { totalCount, rows }; one HTTP page, not a streaming screener.
|
|
14
|
+
- getHotlist({ kind, ... }) -> scanner-ranked rows, not guaranteed TradingView UI parity.
|
|
15
|
+
- getWatchlists({ credentials }) -> read-only account lists with ordered symbols and section markers.
|
|
16
|
+
- searchMarkets(text), searchIndicators(text), getTechnicalAnalysis(symbol).
|
|
17
|
+
- Websocket data calls accept timeoutMs, signal (AbortSignal), credentials ({ session, signature }) and client (shared TradingViewClient). HTTP lookups accept signal in options.
|
|
18
|
+
- Candle calls can use a custom MarketDataProvider; TradingViewProvider is the default source adapter.
|
|
19
|
+
- Errors are TradingViewError with a code (SYMBOL_ERROR, TIMEOUT, STUDY_ERROR, NO_DATA...).
|
|
20
|
+
|
|
21
|
+
Low-level API: TradingViewClient, client.createChart() (ChartSession: setMarket, setTimeframe, fetchMore, replay, createStudy), client.createQuoteSession(), PineIndicator, BuiltInIndicator, HTTP functions (loginUser, getUser, getIndicator, getPrivateIndicators, getDrawings, PinePermissionManager).
|
|
22
|
+
|
|
23
|
+
Pine studies need a TradingView account (session cookies). Built-in studies and candles/quotes work anonymously, with history limits.
|
|
24
|
+
Docs: docs/data-api.md, docs/low-level-api.md, docs/migration-v4.md, docs/v4-coverage.md, docs/v4-reliability.md, docs/protocol-contributing.md. Examples: examples/.
|
|
25
|
+
No CLI, MCP server, backtesting engine or hosted trading API is shipped. Do not claim otherwise.
|
|
26
|
+
Questions and bugs: https://github.com/Mathieu2301/TradingView-API/issues/new/choose
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mathieuc/tradingview",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-rc.0",
|
|
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",
|
|
@@ -21,7 +21,13 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
23
|
"README.md",
|
|
24
|
-
"docs"
|
|
24
|
+
"docs",
|
|
25
|
+
"scripts/endurance.mjs",
|
|
26
|
+
"scripts/probe-account.mjs",
|
|
27
|
+
"assets",
|
|
28
|
+
"llms.txt",
|
|
29
|
+
"examples",
|
|
30
|
+
"scripts/parity.mjs"
|
|
25
31
|
],
|
|
26
32
|
"sideEffects": false,
|
|
27
33
|
"scripts": {
|
|
@@ -36,7 +42,9 @@
|
|
|
36
42
|
"check": "npm run typecheck && npm run lint && npm test && npm run build && npm run smoke",
|
|
37
43
|
"prepack": "npm run build",
|
|
38
44
|
"probe:endurance": "npm run build && node scripts/endurance.mjs",
|
|
39
|
-
"probe:parity": "npm run build && node scripts/parity.mjs"
|
|
45
|
+
"probe:parity": "npm run build && node scripts/parity.mjs",
|
|
46
|
+
"probe:account": "npm run build && node scripts/probe-account.mjs",
|
|
47
|
+
"test:coverage": "vitest run --coverage"
|
|
40
48
|
},
|
|
41
49
|
"repository": {
|
|
42
50
|
"type": "git",
|
|
@@ -67,6 +75,7 @@
|
|
|
67
75
|
"@eslint/js": "^9.39.0",
|
|
68
76
|
"@types/node": "^22.20.5",
|
|
69
77
|
"@types/ws": "^8.18.1",
|
|
78
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
70
79
|
"eslint": "^9.39.0",
|
|
71
80
|
"typescript": "~5.9.3",
|
|
72
81
|
"typescript-eslint": "^8.46.0",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Opt-in live endurance probe. No credentials are read or logged. */
|
|
2
|
+
import { TradingViewClient } from '../dist/index.js';
|
|
3
|
+
import { watchCandles, watchQuotes } from '../dist/data/index.js';
|
|
4
|
+
|
|
5
|
+
const argument = (name, fallback) => {
|
|
6
|
+
const raw = process.argv.find((value) => value.startsWith(`--${name}=`))?.split('=')[1];
|
|
7
|
+
const number = raw === undefined ? fallback : Number(raw);
|
|
8
|
+
if (!Number.isFinite(number) || number <= 0) throw new Error(`--${name} must be positive`);
|
|
9
|
+
return number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const textArgument = (name, fallback) => process.argv.find((value) => value.startsWith(`--${name}=`))?.split('=')[1] ?? fallback;
|
|
13
|
+
const minutes = argument('minutes', 120);
|
|
14
|
+
const cycleSeconds = argument('cycle-seconds', 600);
|
|
15
|
+
const heartbeatThreshold = argument('heartbeat-threshold', 30);
|
|
16
|
+
const requirePostThresholdUpdates = process.argv.includes('--require-post-threshold-updates');
|
|
17
|
+
const symbol = textArgument('symbol', 'BINANCE:BTCUSDT');
|
|
18
|
+
const timeframe = textArgument('timeframe', '1');
|
|
19
|
+
const chartType = textArgument('chart-type', undefined);
|
|
20
|
+
const deadline = Date.now() + minutes * 60_000;
|
|
21
|
+
const metrics = { cycles: 0, starts: 0, failures: 0, candleUpdates: 0, quoteUpdates: 0, heartbeats: 0, postThresholdCandleUpdates: 0, postThresholdQuoteUpdates: 0, errors: [] };
|
|
22
|
+
let interrupted = false;
|
|
23
|
+
for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => { interrupted = true; });
|
|
24
|
+
|
|
25
|
+
const report = (event, extra = {}) => console.log(JSON.stringify({
|
|
26
|
+
at: new Date().toISOString(), event, ...extra,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
function waitForCycle(ms, watchers) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const timer = setTimeout(() => resolve('elapsed'), ms);
|
|
32
|
+
Promise.race(watchers.map((watcher) => watcher.closed)).then(() => {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
resolve('closed');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const status = setInterval(() => report('status', { ...metrics, errors: metrics.errors.slice(-3) }), 60_000);
|
|
40
|
+
report('start', { minutes, cycleSeconds, symbol, timeframe, chartType, heartbeatThreshold, requirePostThresholdUpdates });
|
|
41
|
+
try {
|
|
42
|
+
while (!interrupted && Date.now() < deadline) {
|
|
43
|
+
metrics.cycles += 1;
|
|
44
|
+
const client = new TradingViewClient();
|
|
45
|
+
let cycleHeartbeats = 0;
|
|
46
|
+
client.on('heartbeat', () => { metrics.heartbeats += 1; cycleHeartbeats += 1; });
|
|
47
|
+
client.on('error', (error) => metrics.errors.push(error.code));
|
|
48
|
+
let candles;
|
|
49
|
+
let quotes;
|
|
50
|
+
try {
|
|
51
|
+
candles = await watchCandles({ symbol, timeframe, chartType, count: 5, client }, {
|
|
52
|
+
onData: () => {
|
|
53
|
+
metrics.candleUpdates += 1;
|
|
54
|
+
if (cycleHeartbeats > heartbeatThreshold) metrics.postThresholdCandleUpdates += 1;
|
|
55
|
+
},
|
|
56
|
+
onError: (error) => metrics.errors.push(error.code),
|
|
57
|
+
});
|
|
58
|
+
quotes = await watchQuotes({ symbols: [symbol], fields: 'price', client }, {
|
|
59
|
+
onData: () => {
|
|
60
|
+
metrics.quoteUpdates += 1;
|
|
61
|
+
if (cycleHeartbeats > heartbeatThreshold) metrics.postThresholdQuoteUpdates += 1;
|
|
62
|
+
},
|
|
63
|
+
onError: (error) => metrics.errors.push(error.code),
|
|
64
|
+
});
|
|
65
|
+
if (candles.latest.length !== 5 || typeof quotes.latest[symbol]?.lp !== 'number') {
|
|
66
|
+
throw new Error('Initial market snapshots incomplete');
|
|
67
|
+
}
|
|
68
|
+
metrics.starts += 1;
|
|
69
|
+
report('connected', { cycle: metrics.cycles, candles: candles.latest.length, quotePricePresent: true });
|
|
70
|
+
const duration = Math.min(cycleSeconds * 1000, deadline - Date.now());
|
|
71
|
+
const outcome = await waitForCycle(duration, [candles, quotes]);
|
|
72
|
+
if (outcome === 'closed' && !interrupted) throw new Error('Watcher closed before planned rotation');
|
|
73
|
+
report('rotation', { cycle: metrics.cycles, outcome });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
metrics.failures += 1;
|
|
76
|
+
metrics.errors.push(error?.code ?? error?.message ?? String(error));
|
|
77
|
+
report('failure', { cycle: metrics.cycles, code: error?.code, message: error?.message });
|
|
78
|
+
} finally {
|
|
79
|
+
await Promise.allSettled([candles?.stop(), quotes?.stop()]);
|
|
80
|
+
await client.close();
|
|
81
|
+
}
|
|
82
|
+
if (metrics.failures >= 3) break;
|
|
83
|
+
if (metrics.failures && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 3_000));
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
clearInterval(status);
|
|
87
|
+
report('summary', { ...metrics, errors: metrics.errors.slice(-10), interrupted });
|
|
88
|
+
}
|
|
89
|
+
if (interrupted || metrics.failures || metrics.errors.length || metrics.starts === 0
|
|
90
|
+
|| metrics.quoteUpdates === 0 || metrics.candleUpdates === 0
|
|
91
|
+
|| (requirePostThresholdUpdates && (!metrics.postThresholdCandleUpdates || !metrics.postThresholdQuoteUpdates))) {
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Opt-in live comparison with the published v3 package. */
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const entry = process.env.TV_V3_ENTRY;
|
|
5
|
+
if (!entry) throw new Error('Set TV_V3_ENTRY to the installed v3 main.js path');
|
|
6
|
+
const { Client } = require(entry);
|
|
7
|
+
const { getCandles } = await import('../dist/data/index.js');
|
|
8
|
+
const symbol = 'BINANCE:BTCEUR';
|
|
9
|
+
|
|
10
|
+
async function legacyCandles(timeframe) {
|
|
11
|
+
const client = new Client();
|
|
12
|
+
const chart = new client.Session.Chart();
|
|
13
|
+
try {
|
|
14
|
+
return await new Promise((resolve, reject) => {
|
|
15
|
+
const timer = setTimeout(() => reject(new Error('V3 chart timed out')), 20_000);
|
|
16
|
+
const fail = (...parts) => { clearTimeout(timer); reject(new Error(parts.map(String).join(' '))); };
|
|
17
|
+
chart.onError(fail);
|
|
18
|
+
client.onError(fail);
|
|
19
|
+
chart.onUpdate(() => {
|
|
20
|
+
if (chart.periods.length < 20) return;
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
resolve(chart.periods.map(({ time, open, max, min, close, volume }) => ({
|
|
23
|
+
time, open, high: max, low: min, close, volume,
|
|
24
|
+
})).sort((a, b) => a.time - b.time));
|
|
25
|
+
});
|
|
26
|
+
chart.setMarket(symbol, { timeframe, range: 20 });
|
|
27
|
+
});
|
|
28
|
+
} finally {
|
|
29
|
+
chart.delete();
|
|
30
|
+
await client.end();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let total = 0;
|
|
35
|
+
let mismatches = 0;
|
|
36
|
+
for (const timeframe of ['D', '60']) {
|
|
37
|
+
const oldBars = await legacyCandles(timeframe);
|
|
38
|
+
const newBars = await getCandles({ symbol, timeframe, count: 20 });
|
|
39
|
+
// Last bar can change between requests. V3 rounds volume to two decimals.
|
|
40
|
+
const oldClosed = oldBars.slice(0, -1);
|
|
41
|
+
const newClosed = newBars.slice(0, -1);
|
|
42
|
+
const differences = oldClosed.flatMap((bar, index) => {
|
|
43
|
+
const next = newClosed[index];
|
|
44
|
+
const comparable = next && { ...next, volume: Math.round(next.volume * 100) / 100 };
|
|
45
|
+
return JSON.stringify(bar) === JSON.stringify(comparable) ? [] : [index];
|
|
46
|
+
});
|
|
47
|
+
if (oldBars.length !== 20 || newBars.length !== 20) differences.push(-1);
|
|
48
|
+
total += oldClosed.length;
|
|
49
|
+
mismatches += differences.length;
|
|
50
|
+
console.log(JSON.stringify({ timeframe, v3Bars: oldBars.length, v4Bars: newBars.length,
|
|
51
|
+
closedBarsCompared: oldClosed.length, mismatchIndexes: differences }));
|
|
52
|
+
}
|
|
53
|
+
console.log(JSON.stringify({ closedBarsCompared: total, mismatches }));
|
|
54
|
+
if (mismatches) process.exitCode = 1;
|