@mathieuc/tradingview 3.5.2 → 4.0.0-beta.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 +112 -51
- package/dist/chart/chart-session.d.ts +114 -0
- package/dist/chart/chart-session.js +339 -0
- package/dist/chart/graphics.d.ts +129 -0
- package/dist/chart/graphics.js +153 -0
- package/dist/chart/index.d.ts +11 -0
- package/dist/chart/index.js +6 -0
- package/dist/chart/strategy.d.ts +100 -0
- package/dist/chart/strategy.js +51 -0
- package/dist/chart/study.d.ts +60 -0
- package/dist/chart/study.js +171 -0
- package/dist/chart/timeframes.d.ts +7 -0
- package/dist/chart/timeframes.js +22 -0
- package/dist/chart/types.d.ts +105 -0
- package/dist/chart/types.js +9 -0
- package/dist/client/client.d.ts +89 -0
- package/dist/client/client.js +254 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +2 -0
- package/dist/client/transport.d.ts +27 -0
- package/dist/client/transport.js +38 -0
- package/dist/data/candles.d.ts +40 -0
- package/dist/data/candles.js +101 -0
- package/dist/data/history.d.ts +61 -0
- package/dist/data/history.js +101 -0
- package/dist/data/index.d.ts +28 -0
- package/dist/data/index.js +15 -0
- package/dist/data/indicators.d.ts +54 -0
- package/dist/data/indicators.js +139 -0
- package/dist/data/operation.d.ts +71 -0
- package/dist/data/operation.js +205 -0
- package/dist/data/provider.d.ts +23 -0
- package/dist/data/provider.js +11 -0
- package/dist/data/quotes.d.ts +33 -0
- package/dist/data/quotes.js +117 -0
- package/dist/data/symbols.d.ts +9 -0
- package/dist/data/symbols.js +19 -0
- package/dist/errors.d.ts +53 -0
- package/dist/errors.js +24 -0
- package/dist/events.d.ts +36 -0
- package/dist/events.js +70 -0
- package/dist/http/account.d.ts +47 -0
- package/dist/http/account.js +127 -0
- package/dist/http/index.d.ts +11 -0
- package/dist/http/index.js +5 -0
- package/dist/http/indicators.d.ts +37 -0
- package/dist/http/indicators.js +144 -0
- package/dist/http/layouts.d.ts +36 -0
- package/dist/http/layouts.js +24 -0
- package/dist/http/market.d.ts +45 -0
- package/dist/http/market.js +59 -0
- package/dist/http/pine-permissions.d.ts +29 -0
- package/dist/http/pine-permissions.js +56 -0
- package/dist/http/request.d.ts +44 -0
- package/dist/http/request.js +83 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +19 -0
- package/dist/indicators/builtin-indicator.d.ts +17 -0
- package/dist/indicators/builtin-indicator.js +75 -0
- package/dist/indicators/index.d.ts +8 -0
- package/dist/indicators/index.js +2 -0
- package/dist/indicators/pine-indicator.d.ts +63 -0
- package/dist/indicators/pine-indicator.js +101 -0
- package/dist/protocol/compression.d.ts +12 -0
- package/dist/protocol/compression.js +83 -0
- package/dist/protocol/framing.d.ts +59 -0
- package/dist/protocol/framing.js +68 -0
- package/dist/protocol/ids.d.ts +2 -0
- package/dist/protocol/ids.js +10 -0
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.js +3 -0
- package/dist/quote/fields.d.ts +35 -0
- package/dist/quote/fields.js +21 -0
- package/dist/quote/index.d.ts +4 -0
- package/dist/quote/index.js +2 -0
- package/dist/quote/quote-session.d.ts +73 -0
- package/dist/quote/quote-session.js +205 -0
- package/docs/README.es.md +58 -0
- package/docs/README.fr.md +58 -0
- package/docs/README.pt.md +58 -0
- package/docs/data-api.md +229 -0
- package/docs/low-level-api.md +213 -0
- package/docs/migration-v4.md +128 -0
- package/docs/v4-coverage.md +192 -0
- package/docs/v4-reliability.md +33 -0
- package/package.json +55 -20
- package/.env.sample +0 -2
- package/.eslintrc.js +0 -28
- package/.gitattributes +0 -2
- package/docs/DOCS.md +0 -3
- package/examples/AllPrivateIndicators.js +0 -44
- package/examples/BuiltInIndicator.js +0 -51
- package/examples/CustomChartType.js +0 -123
- package/examples/CustomTimeframe.js +0 -31
- package/examples/Errors.js +0 -155
- package/examples/FakeReplayMode.js +0 -40
- package/examples/FromToData.js +0 -35
- package/examples/GetDrawings.js +0 -27
- package/examples/GraphicIndicator.js +0 -42
- package/examples/MultipleSyncFetch.js +0 -44
- package/examples/PinePermManage.js +0 -67
- package/examples/ReplayMode.js +0 -103
- package/examples/Search.js +0 -14
- package/examples/SimpleChart.js +0 -63
- package/examples/UserLogin.js +0 -16
- package/main.js +0 -11
- package/src/chart/graphicParser.js +0 -308
- package/src/chart/session.js +0 -553
- package/src/chart/study.js +0 -435
- package/src/classes/BuiltInIndicator.js +0 -137
- package/src/classes/PineIndicator.js +0 -132
- package/src/classes/PinePermManager.js +0 -159
- package/src/client.js +0 -298
- package/src/miscRequests.js +0 -609
- package/src/protocol.js +0 -60
- package/src/quote/market.js +0 -132
- package/src/quote/session.js +0 -125
- package/src/types.js +0 -36
- package/src/utils.js +0 -20
- package/vite.config.js +0 -7
package/README.md
CHANGED
|
@@ -1,76 +1,137 @@
|
|
|
1
|
-
# TradingView
|
|
1
|
+
# TradingView-API
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/overview/main)
|
|
5
|
-
[](https://GitHub.com/Mathieu2301/TradingView-API/commit/)
|
|
6
|
-
[](https://npmjs.com/@mathieuc/tradingview)
|
|
7
|
-
[](https://npmjs.com/@mathieuc/tradingview)
|
|
8
|
-
[](https://npmjs.com/package/@mathieuc/tradingview)
|
|
3
|
+
**Build with market data, from your first chart to a running watcher.** Fetch candles and quotes, run indicators and strategies, and turn ideas into working tools. An independent community project, not an official TradingView API.
|
|
9
4
|
|
|
10
|
-
Get
|
|
5
|
+
[Get started](#get-started) · [Explore examples](examples) · [Read the data API guide](docs/data-api.md)
|
|
11
6
|
|
|
12
|
-
|
|
7
|
+
**Language:** English · [Français](docs/README.fr.md) · [Español](docs/README.es.md) · [Português](docs/README.pt.md)
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
[](https://github.com/Mathieu2301/TradingView-API/actions/workflows/tests.yml) [](https://www.npmjs.com/package/@mathieuc/tradingview) [](https://github.com/Mathieu2301/TradingView-API)
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+

|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
*An actual candle request, recorded on 2 October 2026 with the development preview (`fetchCandles`, called `getCandles` in version 4) and visualized for this demo. Prices are not live.*
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
### One request, real data
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
> **V4 beta is currently available from this repository, not from npm.** Build the source as shown below before running this example. The latest npm release is still v3 and does not export `getCandles`.
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- [x] Get drawings you made on your chart
|
|
27
|
-
- [x] Works with invite-only indicators
|
|
28
|
-
- [x] Unlimited simultaneous indicators
|
|
29
|
-
- [x] Realtime
|
|
30
|
-
- [x] Get TradingView's technical analysis
|
|
31
|
-
- [x] Replay mode + Fake Replay mode (for free plan)
|
|
32
|
-
- [x] Get values from a specific date range
|
|
33
|
-
- [ ] TradingView socket server emulation
|
|
34
|
-
- [ ] Interract with public chats
|
|
35
|
-
- [ ] Get Screener top values
|
|
36
|
-
- [ ] Get Hotlists
|
|
37
|
-
- [ ] Get Calendar
|
|
38
|
-
- IF YOU WANT A FEATURE, ASK ME !
|
|
19
|
+
```js
|
|
20
|
+
import { getCandles } from '@mathieuc/tradingview/data';
|
|
39
21
|
|
|
40
|
-
|
|
22
|
+
const candles = await getCandles({ symbol: 'BINANCE:BTCUSDT', timeframe: 'D', count: 40 });
|
|
23
|
+
console.log(candles.at(-1)); // { time, open, high, low, close, volume }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Prefer to start without code? The setup paths below work too.
|
|
27
|
+
|
|
28
|
+
> **Version 4 is a breaking rewrite** in TypeScript, with a new API and no compatibility layer. Coming from v3? Read the [migration guide](docs/migration-v4.md). Every v3 feature is still available: see the [coverage matrix](docs/v4-coverage.md) and [reliability evidence](docs/v4-reliability.md). npm versions 3.x keep the previous `Client` API; check the [npm page](https://www.npmjs.com/package/@mathieuc/tradingview) for the version you install.
|
|
29
|
+
|
|
30
|
+
## Get started
|
|
31
|
+
|
|
32
|
+
### 1. Molted.cloud — recommended, no local setup
|
|
33
|
+
|
|
34
|
+
[Create an agent on Molted.cloud](https://molted.cloud/) and share this repository with it. Describe what you want to build; the agent can read the docs, set up a workspace, and help you get from an idea to a working project. For example, ask it to build a market-data watcher with this library. [See an example on Molted Studio](https://molted.studio/dreams/market-watch-alerts).
|
|
35
|
+
|
|
36
|
+
### 2. Claude Code, Codex, or another coding assistant
|
|
37
|
+
|
|
38
|
+
Open your project in your preferred coding assistant and give it the [repository link](https://github.com/Mathieu2301/TradingView-API). You can start with:
|
|
39
|
+
|
|
40
|
+
> Read the TradingView-API README and the data API guide. Build the V4 beta from source, make a small example that fetches candles and watches a quote, and show me how to run it.
|
|
41
|
+
|
|
42
|
+
The assistant can handle the setup, but you keep the project in your own environment.
|
|
43
|
+
|
|
44
|
+
### 3. Install manually
|
|
45
|
+
|
|
46
|
+
The V4 beta requires Node.js 20 or later, or Bun. **Until the V4 npm release, install it from source:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/Mathieu2301/TradingView-API.git
|
|
50
|
+
cd TradingView-API
|
|
51
|
+
npm ci && npm run build
|
|
52
|
+
node examples/candles.js
|
|
53
|
+
# Or use Bun: bun install && bun run build && bun examples/candles.js
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Inside this built checkout, the examples and package self-imports use the V4 API. Running `npm install @mathieuc/tradingview` or `bun add @mathieuc/tradingview` in another project currently installs **v3**, which has the old `Client` API. Do not copy the V4 imports into a project that has v3 installed.
|
|
57
|
+
|
|
58
|
+
The V4 package is ESM with TypeScript declarations. CommonJS projects can `require()` it on Node 20.19+ or 22.12+, or use `await import()`.
|
|
59
|
+
|
|
60
|
+
### Want to contribute?
|
|
41
61
|
|
|
42
|
-
-
|
|
43
|
-
- Discord alerts
|
|
44
|
-
- Hard backtest
|
|
45
|
-
- Machine Learning based indicator
|
|
46
|
-
- Free replay mode for all timeframes
|
|
62
|
+
[Open an issue](https://github.com/Mathieu2301/TradingView-API/issues/new/choose) to discuss a feature or report a bug, or send a pull request. Questions and early ideas are welcome; you do not need a perfect reproduction to start a conversation. `npm run check` runs the type check, lint, tests, build and package smoke test.
|
|
47
63
|
|
|
48
|
-
|
|
64
|
+
## Data API
|
|
49
65
|
|
|
50
|
-
|
|
66
|
+
The data API handles connections, sessions, timeouts and cleanup for you. It suits any application: scripts, servers, dashboards, bots or agents.
|
|
51
67
|
|
|
52
|
-
|
|
68
|
+
```ts
|
|
69
|
+
import {
|
|
70
|
+
getCandles, watchCandles, getQuote, getIndicatorData, searchMarkets,
|
|
71
|
+
} from '@mathieuc/tradingview/data';
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
// One-shot: resolves with complete data, then releases everything.
|
|
74
|
+
const hourly = await getCandles({ symbol: 'BINANCE:BTCUSDT', timeframe: '60', count: 500 });
|
|
75
|
+
const lastWeek = await getCandles({ symbol: 'NASDAQ:AAPL', timeframe: '15', from: new Date(Date.now() - 7 * 86_400_000) });
|
|
76
|
+
const quote = await getQuote('BINANCE:BTCUSDT');
|
|
77
|
+
const [market] = await searchMarkets('ethereum', { type: 'crypto' });
|
|
78
|
+
|
|
79
|
+
// Watcher: keeps streaming until stopped.
|
|
80
|
+
const watcher = await watchCandles({ symbol: 'BINANCE:BTCUSDT', timeframe: '1' }, {
|
|
81
|
+
onData: (candles) => console.log(candles.at(-1)?.close),
|
|
82
|
+
onError: (error) => console.error(error.code, error.message),
|
|
83
|
+
});
|
|
84
|
+
await watcher.stop();
|
|
85
|
+
|
|
86
|
+
// Indicators and strategies (Pine scripts need an account).
|
|
87
|
+
const { values } = await getIndicatorData({
|
|
88
|
+
symbol: 'BINANCE:BTCUSDT', indicator: 'STD;RSI', credentials: { session, signature },
|
|
89
|
+
});
|
|
56
90
|
```
|
|
57
91
|
|
|
58
|
-
|
|
92
|
+
| Need | Function |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| Candles: latest bars, deep history, date ranges, Heikin Ashi/Renko/... | `getCandles`, `watchCandles` |
|
|
95
|
+
| Quotes: last price, change, bid/ask, volume... | `getQuote`, `getQuotes`, `watchQuotes` |
|
|
96
|
+
| Indicator values, drawings and strategy reports | `getIndicatorData`, `watchIndicator` |
|
|
97
|
+
| Symbol metadata | `getSymbolInfo` |
|
|
98
|
+
| Search and ratings | `searchMarkets`, `searchIndicators`, `getTechnicalAnalysis` |
|
|
99
|
+
|
|
100
|
+
Websocket data functions accept `timeoutMs`, an `AbortSignal`, account `credentials`, and an optional shared `client`. HTTP lookups accept an `AbortSignal` through their options. Errors are `TradingViewError`s with a `code` such as `SYMBOL_ERROR`, `TIMEOUT` or `STUDY_ERROR`. Read the [data API guide](docs/data-api.md) for every option.
|
|
101
|
+
|
|
102
|
+
## Low-level API
|
|
103
|
+
|
|
104
|
+
For full control (several charts and studies on one connection, replay mode, raw packets), use the client and sessions directly:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { TradingViewClient, getIndicator } from '@mathieuc/tradingview';
|
|
108
|
+
|
|
109
|
+
const client = new TradingViewClient({ credentials: { session, signature } }); // Credentials are optional
|
|
110
|
+
const chart = client.createChart();
|
|
111
|
+
|
|
112
|
+
chart.on('update', () => console.log(chart.lastCandle?.close));
|
|
113
|
+
chart.on('error', (error) => console.error(error.message));
|
|
114
|
+
chart.setMarket('BINANCE:BTCUSDT', { timeframe: '60', count: 300 });
|
|
115
|
+
|
|
116
|
+
const supertrend = chart.createStudy(await getIndicator('STD;Supertrend'));
|
|
117
|
+
supertrend.on('update', () => console.log(supertrend.values.at(-1)));
|
|
59
118
|
|
|
60
|
-
|
|
61
|
-
|
|
119
|
+
// When your application is finished:
|
|
120
|
+
await client.close();
|
|
62
121
|
```
|
|
63
122
|
|
|
64
|
-
|
|
123
|
+
The [low-level API reference](docs/low-level-api.md) covers charts, replay, studies, quotes, account and layout functions, Pine permissions, custom transports and protocol helpers. The [examples](examples) show each feature.
|
|
65
124
|
|
|
66
|
-
|
|
125
|
+
## Accounts and limits
|
|
67
126
|
|
|
68
|
-
|
|
127
|
+
Without an account, TradingView serves limited data: shorter intraday history, no Pine studies, and possibly delayed or substitute feeds. With your `sessionid` and `sessionid_sign` cookies (`credentials`), you get what your account can access. Keep them in environment variables or a secret store; never put them in source files, issues or prompts.
|
|
69
128
|
|
|
70
|
-
|
|
71
|
-
___
|
|
129
|
+
## Project links
|
|
72
130
|
|
|
73
|
-
|
|
131
|
+
- [GitHub repository](https://github.com/Mathieu2301/TradingView-API)
|
|
132
|
+
- [Trendshift community highlight](https://trendshift.io/repositories/26416)
|
|
133
|
+
- [npm package](https://www.npmjs.com/package/@mathieuc/tradingview)
|
|
134
|
+
- [Examples](examples)
|
|
135
|
+
- [Report a bug or ask for a feature](https://github.com/Mathieu2301/TradingView-API/issues/new/choose)
|
|
74
136
|
|
|
75
|
-
|
|
76
|
-
please create an issue [here](https://github.com/Mathieu2301/Tradingview-API/issues).
|
|
137
|
+
TradingView is a trademark of its respective owner. This project is not affiliated with or endorsed by TradingView. Check your data provider's terms and applicable market-data permissions for your use case.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { TradingViewClient } from '../client/client.js';
|
|
2
|
+
import { TradingViewError } from '../errors.js';
|
|
3
|
+
import { Emitter } from '../events.js';
|
|
4
|
+
import type { Indicator } from '../indicators/index.js';
|
|
5
|
+
import { Study } from './study.js';
|
|
6
|
+
import { type Adjustment, type Candle, type ChartType, type ChartTypeInputs, type SymbolInfo, type Timeframe, type Timezone, type TradingSession } from './types.js';
|
|
7
|
+
export interface MarketOptions {
|
|
8
|
+
/** Bar resolution. Default: `D`. */
|
|
9
|
+
timeframe?: Timeframe;
|
|
10
|
+
/**
|
|
11
|
+
* Number of bars to load when the series is created. Default: 100.
|
|
12
|
+
* Negative values load bars *after* `to` instead of before it.
|
|
13
|
+
* Only the first `setMarket` of a chart creates the series; later calls
|
|
14
|
+
* keep the loaded bar count (use `fetchMore` or a new chart).
|
|
15
|
+
*/
|
|
16
|
+
count?: number;
|
|
17
|
+
/** Reference time (Unix seconds) of the last bar to load. Default: now. */
|
|
18
|
+
to?: number;
|
|
19
|
+
/** Price adjustment. Default: `splits`. */
|
|
20
|
+
adjustment?: Adjustment;
|
|
21
|
+
/** Back-adjust continuous futures contracts. */
|
|
22
|
+
backAdjustment?: boolean;
|
|
23
|
+
/** Trading session (`regular` or `extended`). */
|
|
24
|
+
session?: TradingSession;
|
|
25
|
+
/** Convert prices to a currency, e.g. `EUR`. */
|
|
26
|
+
currency?: string;
|
|
27
|
+
/** Custom bar type (Heikin Ashi, Renko...). */
|
|
28
|
+
type?: ChartType;
|
|
29
|
+
/** Inputs for the custom bar type. */
|
|
30
|
+
inputs?: ChartTypeInputs;
|
|
31
|
+
/** Starts replay mode at this Unix time (seconds). */
|
|
32
|
+
replay?: number;
|
|
33
|
+
}
|
|
34
|
+
/** `series_completed` details. */
|
|
35
|
+
export interface SeriesCompletedInfo {
|
|
36
|
+
/** `streaming` for live data, `replay` in replay mode. */
|
|
37
|
+
status: 'streaming' | 'replay' | (string & {});
|
|
38
|
+
/**
|
|
39
|
+
* Set when no more history can be loaded: `end` (start of history) or
|
|
40
|
+
* `limit` (account limit).
|
|
41
|
+
*/
|
|
42
|
+
dataCompleted?: 'end' | 'limit' | (string & {});
|
|
43
|
+
/** Request turnaround ID (`s1`, `s2`...). */
|
|
44
|
+
turnaround: string;
|
|
45
|
+
raw: unknown;
|
|
46
|
+
}
|
|
47
|
+
export interface ChartEvents {
|
|
48
|
+
[event: string]: unknown[];
|
|
49
|
+
/** Symbol metadata was resolved. */
|
|
50
|
+
symbolLoaded: [info: SymbolInfo];
|
|
51
|
+
/** Bars or studies changed. `changes` lists `$prices` and/or study IDs. */
|
|
52
|
+
update: [changes: string[]];
|
|
53
|
+
/** The server started loading bars. */
|
|
54
|
+
seriesLoading: [];
|
|
55
|
+
/** The requested bars were loaded. Realtime updates continue afterwards. */
|
|
56
|
+
seriesCompleted: [info: SeriesCompletedInfo];
|
|
57
|
+
/** The replay session is ready. */
|
|
58
|
+
replayLoaded: [instanceId: string];
|
|
59
|
+
/** The replay cursor moved (Unix seconds). */
|
|
60
|
+
replayPoint: [time: number];
|
|
61
|
+
/** Resolutions reported by the replay server. */
|
|
62
|
+
replayResolution: [resolution: string, stepResolution: string];
|
|
63
|
+
/** The replay reached the present. */
|
|
64
|
+
replayEnd: [];
|
|
65
|
+
error: [error: TradingViewError];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A chart: one symbol series plus any number of studies, with optional
|
|
69
|
+
* replay mode. Create it with `client.createChart()`.
|
|
70
|
+
*/
|
|
71
|
+
export declare class ChartSession extends Emitter<ChartEvents> {
|
|
72
|
+
#private;
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly replayId: string;
|
|
75
|
+
readonly client: TradingViewClient;
|
|
76
|
+
constructor(client: TradingViewClient);
|
|
77
|
+
/** Loaded bars, oldest first. */
|
|
78
|
+
get candles(): Candle[];
|
|
79
|
+
/** Most recent bar. */
|
|
80
|
+
get lastCandle(): Candle | undefined;
|
|
81
|
+
/** Symbol metadata, once resolved. */
|
|
82
|
+
get symbolInfo(): SymbolInfo | undefined;
|
|
83
|
+
/** Current timeframe. */
|
|
84
|
+
get timeframe(): Timeframe;
|
|
85
|
+
/** Current series ID (`ser_1`, `ser_2`...), or undefined before `setMarket`. */
|
|
86
|
+
get seriesId(): string | undefined;
|
|
87
|
+
/** True when the chart is in replay mode. */
|
|
88
|
+
get isReplay(): boolean;
|
|
89
|
+
/** True after `delete()` or when the connection closed. */
|
|
90
|
+
get isDeleted(): boolean;
|
|
91
|
+
/** Studies currently on this chart. */
|
|
92
|
+
get studies(): Study[];
|
|
93
|
+
/** Loads a symbol, optionally with a custom bar type or in replay mode. */
|
|
94
|
+
setMarket(symbol: string, options?: MarketOptions): void;
|
|
95
|
+
/** Changes the timeframe of the loaded symbol, keeping studies. */
|
|
96
|
+
setTimeframe(timeframe: Timeframe): void;
|
|
97
|
+
/** Changes the chart timezone. */
|
|
98
|
+
setTimezone(timezone: Timezone): void;
|
|
99
|
+
/**
|
|
100
|
+
* Loads more history. Positive values load older bars; negative values
|
|
101
|
+
* load newer bars after a past `to` reference.
|
|
102
|
+
*/
|
|
103
|
+
fetchMore(count?: number): void;
|
|
104
|
+
/** Moves the replay forward by `count` bars. */
|
|
105
|
+
replayStep(count?: number): Promise<void>;
|
|
106
|
+
/** Plays the replay automatically, one bar every `intervalMs`. */
|
|
107
|
+
replayStart(intervalMs?: number): Promise<void>;
|
|
108
|
+
/** Pauses automatic replay. */
|
|
109
|
+
replayStop(): Promise<void>;
|
|
110
|
+
/** Adds a study (Pine or built-in indicator) to this chart. */
|
|
111
|
+
createStudy(indicator: Indicator): Study;
|
|
112
|
+
/** Deletes the chart (and its replay session). Idempotent. */
|
|
113
|
+
delete(): void;
|
|
114
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { TradingViewError } from '../errors.js';
|
|
2
|
+
import { Emitter } from '../events.js';
|
|
3
|
+
import { createSessionId } from '../protocol/ids.js';
|
|
4
|
+
import { Study } from './study.js';
|
|
5
|
+
import { CHART_TYPE_STUDIES, } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* A chart: one symbol series plus any number of studies, with optional
|
|
8
|
+
* replay mode. Create it with `client.createChart()`.
|
|
9
|
+
*/
|
|
10
|
+
export class ChartSession extends Emitter {
|
|
11
|
+
id = createSessionId('cs');
|
|
12
|
+
replayId = createSessionId('rs');
|
|
13
|
+
client;
|
|
14
|
+
#candles = new Map();
|
|
15
|
+
/** Bar index (`i`) → bar time, used to position study graphics. */
|
|
16
|
+
#barIndexes = new Map();
|
|
17
|
+
#studies = new Map();
|
|
18
|
+
#replayRequests = new Map();
|
|
19
|
+
#symbolInfo;
|
|
20
|
+
#seriesCount = 0;
|
|
21
|
+
#seriesCreated = false;
|
|
22
|
+
#turnaround = 0;
|
|
23
|
+
#timeframe = 'D';
|
|
24
|
+
#replayMode = false;
|
|
25
|
+
#deleted = false;
|
|
26
|
+
constructor(client) {
|
|
27
|
+
super();
|
|
28
|
+
this.client = client;
|
|
29
|
+
const chartHandler = {
|
|
30
|
+
onPacket: (packet) => this.#onChartPacket(packet),
|
|
31
|
+
onClose: (error, expected) => this.#onClientClose(error, expected),
|
|
32
|
+
};
|
|
33
|
+
client.registerSession(this.id, chartHandler);
|
|
34
|
+
client.registerSession(this.replayId, { onPacket: (packet) => this.#onReplayPacket(packet) });
|
|
35
|
+
client.send('chart_create_session', [this.id]);
|
|
36
|
+
}
|
|
37
|
+
/** Loaded bars, oldest first. */
|
|
38
|
+
get candles() {
|
|
39
|
+
return [...this.#candles.values()].sort((a, b) => a.time - b.time);
|
|
40
|
+
}
|
|
41
|
+
/** Most recent bar. */
|
|
42
|
+
get lastCandle() {
|
|
43
|
+
let last;
|
|
44
|
+
for (const candle of this.#candles.values())
|
|
45
|
+
if (!last || candle.time > last.time)
|
|
46
|
+
last = candle;
|
|
47
|
+
return last;
|
|
48
|
+
}
|
|
49
|
+
/** Symbol metadata, once resolved. */
|
|
50
|
+
get symbolInfo() {
|
|
51
|
+
return this.#symbolInfo;
|
|
52
|
+
}
|
|
53
|
+
/** Current timeframe. */
|
|
54
|
+
get timeframe() {
|
|
55
|
+
return this.#timeframe;
|
|
56
|
+
}
|
|
57
|
+
/** Current series ID (`ser_1`, `ser_2`...), or undefined before `setMarket`. */
|
|
58
|
+
get seriesId() {
|
|
59
|
+
return this.#seriesCount ? `ser_${this.#seriesCount}` : undefined;
|
|
60
|
+
}
|
|
61
|
+
/** True when the chart is in replay mode. */
|
|
62
|
+
get isReplay() {
|
|
63
|
+
return this.#replayMode;
|
|
64
|
+
}
|
|
65
|
+
/** True after `delete()` or when the connection closed. */
|
|
66
|
+
get isDeleted() {
|
|
67
|
+
return this.#deleted;
|
|
68
|
+
}
|
|
69
|
+
/** Studies currently on this chart. */
|
|
70
|
+
get studies() {
|
|
71
|
+
return [...this.#studies.values()];
|
|
72
|
+
}
|
|
73
|
+
/** Loads a symbol, optionally with a custom bar type or in replay mode. */
|
|
74
|
+
setMarket(symbol, options = {}) {
|
|
75
|
+
this.#assertAlive();
|
|
76
|
+
if (!symbol)
|
|
77
|
+
throw new TradingViewError('INVALID_ARGUMENT', 'A symbol is required');
|
|
78
|
+
this.#candles.clear();
|
|
79
|
+
this.#barIndexes.clear();
|
|
80
|
+
this.#symbolInfo = undefined;
|
|
81
|
+
const timeframe = options.timeframe ?? 'D';
|
|
82
|
+
if (this.#replayMode) {
|
|
83
|
+
this.#replayMode = false;
|
|
84
|
+
this.#rejectReplayRequests(new TradingViewError('INVALID_STATE', 'Replay session replaced'));
|
|
85
|
+
this.client.send('replay_delete_session', [this.replayId]);
|
|
86
|
+
}
|
|
87
|
+
const symbolInit = {
|
|
88
|
+
symbol,
|
|
89
|
+
adjustment: options.adjustment ?? 'splits',
|
|
90
|
+
};
|
|
91
|
+
if (options.backAdjustment)
|
|
92
|
+
symbolInit.backadjustment = 'default';
|
|
93
|
+
if (options.session)
|
|
94
|
+
symbolInit.session = options.session;
|
|
95
|
+
if (options.currency)
|
|
96
|
+
symbolInit['currency-id'] = options.currency;
|
|
97
|
+
if (options.replay !== undefined) {
|
|
98
|
+
this.#replayMode = true;
|
|
99
|
+
this.client.send('replay_create_session', [this.replayId]);
|
|
100
|
+
this.client.send('replay_add_series', [
|
|
101
|
+
this.replayId, 'req_replay_addseries', `=${JSON.stringify(symbolInit)}`, timeframe,
|
|
102
|
+
]);
|
|
103
|
+
this.client.send('replay_reset', [this.replayId, 'req_replay_reset', options.replay]);
|
|
104
|
+
}
|
|
105
|
+
let chartInit = symbolInit;
|
|
106
|
+
if (options.type || options.replay !== undefined) {
|
|
107
|
+
chartInit = { symbol: symbolInit };
|
|
108
|
+
if (options.replay !== undefined)
|
|
109
|
+
chartInit.replay = this.replayId;
|
|
110
|
+
if (options.type) {
|
|
111
|
+
const study = CHART_TYPE_STUDIES[options.type];
|
|
112
|
+
if (!study)
|
|
113
|
+
throw new TradingViewError('INVALID_ARGUMENT', `Unknown chart type '${options.type}'`);
|
|
114
|
+
chartInit.type = study;
|
|
115
|
+
chartInit.inputs = { ...options.inputs };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.#seriesCount += 1;
|
|
119
|
+
this.client.send('resolve_symbol', [this.id, `ser_${this.#seriesCount}`, `=${JSON.stringify(chartInit)}`]);
|
|
120
|
+
this.#sendSeries(timeframe, options.count ?? 100, options.to);
|
|
121
|
+
}
|
|
122
|
+
/** Changes the timeframe of the loaded symbol, keeping studies. */
|
|
123
|
+
setTimeframe(timeframe) {
|
|
124
|
+
this.#assertAlive();
|
|
125
|
+
if (!this.#seriesCount) {
|
|
126
|
+
throw new TradingViewError('INVALID_STATE', 'Please set the market before setting the timeframe');
|
|
127
|
+
}
|
|
128
|
+
this.#candles.clear();
|
|
129
|
+
this.#barIndexes.clear();
|
|
130
|
+
this.#sendSeries(timeframe, 100);
|
|
131
|
+
}
|
|
132
|
+
#sendSeries(timeframe, count, to) {
|
|
133
|
+
this.#timeframe = timeframe;
|
|
134
|
+
this.#turnaround += 1;
|
|
135
|
+
const range = to === undefined ? count : ['bar_count', to, count];
|
|
136
|
+
this.client.send(this.#seriesCreated ? 'modify_series' : 'create_series', [
|
|
137
|
+
this.id, '$prices', `s${this.#turnaround}`, `ser_${this.#seriesCount}`, timeframe,
|
|
138
|
+
// modify_series only accepts an empty range: the bar count is kept.
|
|
139
|
+
this.#seriesCreated ? '' : range,
|
|
140
|
+
]);
|
|
141
|
+
this.#seriesCreated = true;
|
|
142
|
+
}
|
|
143
|
+
/** Changes the chart timezone. */
|
|
144
|
+
setTimezone(timezone) {
|
|
145
|
+
this.#assertAlive();
|
|
146
|
+
this.client.send('switch_timezone', [this.id, timezone]);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Loads more history. Positive values load older bars; negative values
|
|
150
|
+
* load newer bars after a past `to` reference.
|
|
151
|
+
*/
|
|
152
|
+
fetchMore(count = 1) {
|
|
153
|
+
this.#assertAlive();
|
|
154
|
+
this.client.send('request_more_data', [this.id, '$prices', count]);
|
|
155
|
+
}
|
|
156
|
+
/** Moves the replay forward by `count` bars. */
|
|
157
|
+
replayStep(count = 1) {
|
|
158
|
+
return this.#replayRequest('replay_step', 'rsq_step', [count]);
|
|
159
|
+
}
|
|
160
|
+
/** Plays the replay automatically, one bar every `intervalMs`. */
|
|
161
|
+
replayStart(intervalMs = 1000) {
|
|
162
|
+
return this.#replayRequest('replay_start', 'rsq_start', [intervalMs]);
|
|
163
|
+
}
|
|
164
|
+
/** Pauses automatic replay. */
|
|
165
|
+
replayStop() {
|
|
166
|
+
return this.#replayRequest('replay_stop', 'rsq_stop', []);
|
|
167
|
+
}
|
|
168
|
+
#replayRequest(method, prefix, params) {
|
|
169
|
+
if (this.#deleted)
|
|
170
|
+
return Promise.reject(new TradingViewError('INVALID_STATE', 'The chart is deleted'));
|
|
171
|
+
if (!this.#replayMode) {
|
|
172
|
+
return Promise.reject(new TradingViewError('INVALID_STATE', 'No replay session: use setMarket(symbol, { replay })'));
|
|
173
|
+
}
|
|
174
|
+
const requestId = createSessionId(prefix);
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
this.#replayRequests.set(requestId, { resolve, reject });
|
|
177
|
+
this.client.send(method, [this.replayId, requestId, ...params]);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** Adds a study (Pine or built-in indicator) to this chart. */
|
|
181
|
+
createStudy(indicator) {
|
|
182
|
+
this.#assertAlive();
|
|
183
|
+
const bridge = {
|
|
184
|
+
chartId: this.id,
|
|
185
|
+
send: (method, params) => this.client.send(method, params),
|
|
186
|
+
barsBack: () => this.#barsBack(),
|
|
187
|
+
log: (...args) => this.client.log(...args),
|
|
188
|
+
unregister: (id) => { this.#studies.delete(id); },
|
|
189
|
+
};
|
|
190
|
+
const study = new Study(bridge, indicator);
|
|
191
|
+
this.#studies.set(study.id, study);
|
|
192
|
+
study.create();
|
|
193
|
+
return study;
|
|
194
|
+
}
|
|
195
|
+
/** Deletes the chart (and its replay session). Idempotent. */
|
|
196
|
+
delete() {
|
|
197
|
+
if (this.#deleted)
|
|
198
|
+
return;
|
|
199
|
+
if (!this.client.isClosed) {
|
|
200
|
+
if (this.#replayMode)
|
|
201
|
+
this.client.send('replay_delete_session', [this.replayId]);
|
|
202
|
+
this.client.send('chart_delete_session', [this.id]);
|
|
203
|
+
}
|
|
204
|
+
this.#dispose(new TradingViewError('INVALID_STATE', 'The chart was deleted'));
|
|
205
|
+
}
|
|
206
|
+
#dispose(error, notify = false) {
|
|
207
|
+
this.#deleted = true;
|
|
208
|
+
this.#replayMode = false;
|
|
209
|
+
this.client.unregisterSession(this.id);
|
|
210
|
+
this.client.unregisterSession(this.replayId);
|
|
211
|
+
this.#rejectReplayRequests(error);
|
|
212
|
+
for (const study of this.#studies.values())
|
|
213
|
+
study.dispose(notify ? error : undefined);
|
|
214
|
+
this.#studies.clear();
|
|
215
|
+
}
|
|
216
|
+
#onClientClose(error, expected) {
|
|
217
|
+
if (this.#deleted)
|
|
218
|
+
return;
|
|
219
|
+
this.#dispose(error, !expected);
|
|
220
|
+
if (!expected)
|
|
221
|
+
this.emit('error', error);
|
|
222
|
+
}
|
|
223
|
+
#assertAlive() {
|
|
224
|
+
if (this.#deleted)
|
|
225
|
+
throw new TradingViewError('INVALID_STATE', 'The chart is deleted');
|
|
226
|
+
}
|
|
227
|
+
#rejectReplayRequests(error) {
|
|
228
|
+
for (const request of this.#replayRequests.values())
|
|
229
|
+
request.reject(error);
|
|
230
|
+
this.#replayRequests.clear();
|
|
231
|
+
}
|
|
232
|
+
/** Bar index → bars back from the most recent loaded bar. */
|
|
233
|
+
#barsBack() {
|
|
234
|
+
const result = new Map();
|
|
235
|
+
[...this.#barIndexes.entries()]
|
|
236
|
+
.sort((a, b) => b[1] - a[1])
|
|
237
|
+
.forEach(([index], position) => result.set(index, position));
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
#onChartPacket(packet) {
|
|
241
|
+
this.client.log('chart', this.id, packet);
|
|
242
|
+
const [, target] = packet.p;
|
|
243
|
+
if (typeof target === 'string' && this.#studies.has(target)) {
|
|
244
|
+
this.#studies.get(target)?.onPacket(packet);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
switch (packet.m) {
|
|
248
|
+
case 'symbol_resolved':
|
|
249
|
+
this.#symbolInfo = { series_id: target, ...packet.p[2] };
|
|
250
|
+
this.emit('symbolLoaded', this.#symbolInfo);
|
|
251
|
+
return;
|
|
252
|
+
case 'timescale_update':
|
|
253
|
+
case 'du': {
|
|
254
|
+
const data = (packet.p[1] ?? {});
|
|
255
|
+
const changes = [];
|
|
256
|
+
for (const key of Object.keys(data)) {
|
|
257
|
+
changes.push(key);
|
|
258
|
+
if (key === '$prices')
|
|
259
|
+
this.#updateCandles(data.$prices);
|
|
260
|
+
else
|
|
261
|
+
this.#studies.get(key)?.onData(data[key]);
|
|
262
|
+
}
|
|
263
|
+
this.emit('update', changes);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
case 'series_loading':
|
|
267
|
+
this.emit('seriesLoading');
|
|
268
|
+
return;
|
|
269
|
+
case 'series_completed': {
|
|
270
|
+
const details = (packet.p[4] ?? {});
|
|
271
|
+
this.emit('seriesCompleted', {
|
|
272
|
+
status: String(packet.p[2] ?? ''),
|
|
273
|
+
dataCompleted: details.data_completed,
|
|
274
|
+
turnaround: String(packet.p[3] ?? ''),
|
|
275
|
+
raw: packet.p,
|
|
276
|
+
});
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
case 'symbol_error':
|
|
280
|
+
this.emit('error', new TradingViewError('SYMBOL_ERROR', `(${String(target)}) Symbol error: ${String(packet.p[2])}`, {
|
|
281
|
+
details: packet.p,
|
|
282
|
+
}));
|
|
283
|
+
return;
|
|
284
|
+
case 'series_error':
|
|
285
|
+
this.emit('error', new TradingViewError('SERIES_ERROR', `Series error: ${String(packet.p[3])}`, {
|
|
286
|
+
details: packet.p,
|
|
287
|
+
}));
|
|
288
|
+
return;
|
|
289
|
+
case 'critical_error':
|
|
290
|
+
this.emit('error', new TradingViewError('CRITICAL_ERROR', `Critical error: ${String(packet.p[1])}`, {
|
|
291
|
+
details: packet.p,
|
|
292
|
+
}));
|
|
293
|
+
return;
|
|
294
|
+
default:
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
#updateCandles(prices) {
|
|
298
|
+
for (const bar of prices?.s ?? []) {
|
|
299
|
+
const [time, open, high, low, close, volume] = bar.v;
|
|
300
|
+
this.#barIndexes.set(bar.i, time);
|
|
301
|
+
this.#candles.set(time, {
|
|
302
|
+
time, open, high, low, close, volume: volume ?? 0,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
#onReplayPacket(packet) {
|
|
307
|
+
this.client.log('replay', this.replayId, packet);
|
|
308
|
+
switch (packet.m) {
|
|
309
|
+
case 'replay_ok': {
|
|
310
|
+
const requestId = String(packet.p[1]);
|
|
311
|
+
this.#replayRequests.get(requestId)?.resolve();
|
|
312
|
+
this.#replayRequests.delete(requestId);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
case 'replay_instance_id':
|
|
316
|
+
this.emit('replayLoaded', String(packet.p[1]));
|
|
317
|
+
return;
|
|
318
|
+
case 'replay_point':
|
|
319
|
+
this.emit('replayPoint', Number(packet.p[1]));
|
|
320
|
+
return;
|
|
321
|
+
case 'replay_resolutions':
|
|
322
|
+
this.emit('replayResolution', String(packet.p[1]), String(packet.p[2]));
|
|
323
|
+
return;
|
|
324
|
+
case 'replay_data_end':
|
|
325
|
+
this.emit('replayEnd');
|
|
326
|
+
return;
|
|
327
|
+
case 'critical_error':
|
|
328
|
+
case 'replay_error': {
|
|
329
|
+
const error = new TradingViewError('CRITICAL_ERROR', `Replay error: ${String(packet.p[1])}`, {
|
|
330
|
+
details: packet.p,
|
|
331
|
+
});
|
|
332
|
+
this.#rejectReplayRequests(error);
|
|
333
|
+
this.emit('error', error);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
default:
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|