@mathieuc/tradingview 3.5.2 → 4.0.0-beta.1

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.
Files changed (120) hide show
  1. package/README.md +112 -51
  2. package/dist/chart/chart-session.d.ts +114 -0
  3. package/dist/chart/chart-session.js +339 -0
  4. package/dist/chart/graphics.d.ts +129 -0
  5. package/dist/chart/graphics.js +153 -0
  6. package/dist/chart/index.d.ts +11 -0
  7. package/dist/chart/index.js +6 -0
  8. package/dist/chart/strategy.d.ts +113 -0
  9. package/dist/chart/strategy.js +78 -0
  10. package/dist/chart/study.d.ts +60 -0
  11. package/dist/chart/study.js +179 -0
  12. package/dist/chart/timeframes.d.ts +7 -0
  13. package/dist/chart/timeframes.js +22 -0
  14. package/dist/chart/types.d.ts +105 -0
  15. package/dist/chart/types.js +9 -0
  16. package/dist/client/client.d.ts +89 -0
  17. package/dist/client/client.js +254 -0
  18. package/dist/client/index.d.ts +4 -0
  19. package/dist/client/index.js +2 -0
  20. package/dist/client/transport.d.ts +27 -0
  21. package/dist/client/transport.js +38 -0
  22. package/dist/data/candles.d.ts +40 -0
  23. package/dist/data/candles.js +101 -0
  24. package/dist/data/history.d.ts +61 -0
  25. package/dist/data/history.js +101 -0
  26. package/dist/data/index.d.ts +30 -0
  27. package/dist/data/index.js +16 -0
  28. package/dist/data/indicators.d.ts +54 -0
  29. package/dist/data/indicators.js +139 -0
  30. package/dist/data/operation.d.ts +71 -0
  31. package/dist/data/operation.js +205 -0
  32. package/dist/data/provider.d.ts +23 -0
  33. package/dist/data/provider.js +11 -0
  34. package/dist/data/quotes.d.ts +33 -0
  35. package/dist/data/quotes.js +117 -0
  36. package/dist/data/symbols.d.ts +9 -0
  37. package/dist/data/symbols.js +19 -0
  38. package/dist/errors.d.ts +53 -0
  39. package/dist/errors.js +24 -0
  40. package/dist/events.d.ts +36 -0
  41. package/dist/events.js +70 -0
  42. package/dist/http/account.d.ts +47 -0
  43. package/dist/http/account.js +127 -0
  44. package/dist/http/index.d.ts +11 -0
  45. package/dist/http/index.js +5 -0
  46. package/dist/http/indicators.d.ts +37 -0
  47. package/dist/http/indicators.js +144 -0
  48. package/dist/http/layouts.d.ts +36 -0
  49. package/dist/http/layouts.js +24 -0
  50. package/dist/http/market.d.ts +45 -0
  51. package/dist/http/market.js +59 -0
  52. package/dist/http/pine-permissions.d.ts +29 -0
  53. package/dist/http/pine-permissions.js +56 -0
  54. package/dist/http/request.d.ts +44 -0
  55. package/dist/http/request.js +83 -0
  56. package/dist/index.d.ts +21 -0
  57. package/dist/index.js +19 -0
  58. package/dist/indicators/builtin-indicator.d.ts +17 -0
  59. package/dist/indicators/builtin-indicator.js +75 -0
  60. package/dist/indicators/index.d.ts +8 -0
  61. package/dist/indicators/index.js +2 -0
  62. package/dist/indicators/pine-indicator.d.ts +63 -0
  63. package/dist/indicators/pine-indicator.js +101 -0
  64. package/dist/protocol/compression.d.ts +12 -0
  65. package/dist/protocol/compression.js +83 -0
  66. package/dist/protocol/framing.d.ts +59 -0
  67. package/dist/protocol/framing.js +68 -0
  68. package/dist/protocol/ids.d.ts +2 -0
  69. package/dist/protocol/ids.js +10 -0
  70. package/dist/protocol/index.d.ts +4 -0
  71. package/dist/protocol/index.js +3 -0
  72. package/dist/quote/fields.d.ts +35 -0
  73. package/dist/quote/fields.js +21 -0
  74. package/dist/quote/index.d.ts +4 -0
  75. package/dist/quote/index.js +2 -0
  76. package/dist/quote/quote-session.d.ts +73 -0
  77. package/dist/quote/quote-session.js +205 -0
  78. package/docs/README.es.md +58 -0
  79. package/docs/README.fr.md +58 -0
  80. package/docs/README.pt.md +58 -0
  81. package/docs/data-api.md +285 -0
  82. package/docs/low-level-api.md +213 -0
  83. package/docs/migration-v4.md +128 -0
  84. package/docs/release-4.0.0-beta.1.md +39 -0
  85. package/docs/v4-coverage.md +213 -0
  86. package/package.json +53 -20
  87. package/.env.sample +0 -2
  88. package/.eslintrc.js +0 -28
  89. package/.gitattributes +0 -2
  90. package/docs/DOCS.md +0 -3
  91. package/examples/AllPrivateIndicators.js +0 -44
  92. package/examples/BuiltInIndicator.js +0 -51
  93. package/examples/CustomChartType.js +0 -123
  94. package/examples/CustomTimeframe.js +0 -31
  95. package/examples/Errors.js +0 -155
  96. package/examples/FakeReplayMode.js +0 -40
  97. package/examples/FromToData.js +0 -35
  98. package/examples/GetDrawings.js +0 -27
  99. package/examples/GraphicIndicator.js +0 -42
  100. package/examples/MultipleSyncFetch.js +0 -44
  101. package/examples/PinePermManage.js +0 -67
  102. package/examples/ReplayMode.js +0 -103
  103. package/examples/Search.js +0 -14
  104. package/examples/SimpleChart.js +0 -63
  105. package/examples/UserLogin.js +0 -16
  106. package/main.js +0 -11
  107. package/src/chart/graphicParser.js +0 -308
  108. package/src/chart/session.js +0 -553
  109. package/src/chart/study.js +0 -435
  110. package/src/classes/BuiltInIndicator.js +0 -137
  111. package/src/classes/PineIndicator.js +0 -132
  112. package/src/classes/PinePermManager.js +0 -159
  113. package/src/client.js +0 -298
  114. package/src/miscRequests.js +0 -609
  115. package/src/protocol.js +0 -60
  116. package/src/quote/market.js +0 -132
  117. package/src/quote/session.js +0 -125
  118. package/src/types.js +0 -36
  119. package/src/utils.js +0 -20
  120. package/vite.config.js +0 -7
package/README.md CHANGED
@@ -1,76 +1,137 @@
1
- # TradingView API [![GitHub stars](https://img.shields.io/github/stars/Mathieu2301/TradingView-API.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/Mathieu2301/TradingView-API/stargazers/)
1
+ # TradingView-API
2
2
 
3
- [![Tests](https://github.com/Mathieu2301/TradingView-API/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/Mathieu2301/TradingView-API/actions/workflows/tests.yml)
4
- [![CodeFactor](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/badge/main)](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/overview/main)
5
- [![GitHub latest commit](https://img.shields.io/github/last-commit/Mathieu2301/TradingView-API)](https://GitHub.com/Mathieu2301/TradingView-API/commit/)
6
- [![Npm package yearly downloads](https://badgen.net/npm/dt/@mathieuc/tradingview)](https://npmjs.com/@mathieuc/tradingview)
7
- [![Minimum node.js version](https://badgen.net/npm/node/@mathieuc/tradingview)](https://npmjs.com/@mathieuc/tradingview)
8
- [![Npm package version](https://badgen.net/npm/v/@mathieuc/tradingview)](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 realtime market prices and indicator values from Tradingview !
5
+ [Get started](#get-started) · [Explore examples](examples) · [Read the data API guide](docs/data-api.md)
11
6
 
12
- ## 🟢 Need help with your project?
7
+ **Language:** English · [Français](docs/README.fr.md) · [Español](docs/README.es.md) · [Português](docs/README.pt.md)
13
8
 
14
- 🚀 Click [here](https://forms.gle/qPp5RKo8L55C5oJE7) for personalized assistance on your project.
9
+ [![Tests](https://github.com/Mathieu2301/TradingView-API/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/Mathieu2301/TradingView-API/actions/workflows/tests.yml) [![npm](https://badgen.net/npm/v/@mathieuc/tradingview)](https://www.npmjs.com/package/@mathieuc/tradingview) [![Stars](https://img.shields.io/github/stars/Mathieu2301/TradingView-API?style=social)](https://github.com/Mathieu2301/TradingView-API)
15
10
 
16
- ## 🔵 Telegram group
11
+ ![Recorded demonstration: a candle request returns 40 daily BTC/USDT candles, visualized as a line chart](assets/readme-demo.gif)
17
12
 
18
- 👉 To get help, exchange tips, find collaborators, developers, missions, etc...
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
- Join the Telegram group of the TradingView-API Community: [t.me/tradingview_api](https://t.me/tradingview_api)
15
+ ### One request, real data
21
16
 
22
- ## Features
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
- - [x] Premium features
25
- - [x] Automatically backtest many strategies and try many settings in a very little time
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
- ## Possibilities
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). 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
- - Trading bot
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
- ## Installation
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
- Stable version:
68
+ ```ts
69
+ import {
70
+ getCandles, watchCandles, getQuote, getIndicatorData, searchMarkets,
71
+ } from '@mathieuc/tradingview/data';
53
72
 
54
- ```ruby
55
- npm i @mathieuc/tradingview
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
- Last version:
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
- ```ruby
61
- npm i github:Mathieu2301/TradingView-API
119
+ // When your application is finished:
120
+ await client.close();
62
121
  ```
63
122
 
64
- ## Examples
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
- You can find all the examples and snippets in `./examples` folder.
125
+ ## Accounts and limits
67
126
 
68
- ## Before opening an issue
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
- Please look at examples and previously resolved issues before opening a new one. I can't help everyone (especially for questions that are not library related but JavaScript related). Thank you for your understanding.
71
- ___
129
+ ## Project links
72
130
 
73
- ## Problems
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
- If you have errors in console or unwanted behavior,
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
+ }