@mathieuc/tradingview 4.0.0-beta.0 → 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.
package/README.md CHANGED
@@ -25,7 +25,7 @@ console.log(candles.at(-1)); // { time, open, high, low, close, volume }
25
25
 
26
26
  Prefer to start without code? The setup paths below work too.
27
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.
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
29
 
30
30
  ## Get started
31
31
 
@@ -4,8 +4,8 @@ export { Study } from './study.js';
4
4
  export type { StudyChange, StudyEvents, StudyValue } from './study.js';
5
5
  export { applyGraphicsCommands, parseGraphics, } from './graphics.js';
6
6
  export type { BoxStyleValue, ExtendValue, GraphicBox, GraphicHorizHist, GraphicHorizLine, GraphicLabel, GraphicLine, GraphicPoint, GraphicPolygon, GraphicTable, GraphicsData, HAlignValue, LabelStyleValue, LineStyleValue, RawGraphics, SizeValue, TableCell, TablePositionValue, TextWrapValue, VAlignValue, YLocValue, } from './graphics.js';
7
- export { mergeStrategyReport, parseTrades } from './strategy.js';
8
- export type { FromTo, PerformanceReport, RelAbsValue, StrategyReport, StrategyReportChange, TradeReport, } from './strategy.js';
7
+ export { mergeStrategyReport, parseTrades, summarizeStrategyReport } from './strategy.js';
8
+ export type { FromTo, PerformanceReport, RelAbsValue, StrategyReport, StrategyReportChange, StrategySummary, TradeReport, } from './strategy.js';
9
9
  export { timeframeSeconds } from './timeframes.js';
10
10
  export { CHART_TYPE_STUDIES } from './types.js';
11
11
  export type { Adjustment, Candle, ChartType, ChartTypeInputs, MarketSymbol, Subsession, SymbolInfo, Timeframe, Timezone, TradingSession, } from './types.js';
@@ -1,6 +1,6 @@
1
1
  export { ChartSession } from './chart-session.js';
2
2
  export { Study } from './study.js';
3
3
  export { applyGraphicsCommands, parseGraphics, } from './graphics.js';
4
- export { mergeStrategyReport, parseTrades } from './strategy.js';
4
+ export { mergeStrategyReport, parseTrades, summarizeStrategyReport } from './strategy.js';
5
5
  export { timeframeSeconds } from './timeframes.js';
6
6
  export { CHART_TYPE_STUDIES } from './types.js';
@@ -68,7 +68,7 @@ export interface StrategyReport {
68
68
  };
69
69
  [key: string]: unknown;
70
70
  };
71
- /** Trades, most recent first. */
71
+ /** Trade records, most recent first; may include open positions with an exit valuation. */
72
72
  trades: TradeReport[];
73
73
  history: {
74
74
  buyHold?: number[];
@@ -98,3 +98,16 @@ export type StrategyReportChange = 'report.currency' | 'report.settings' | 'repo
98
98
  export declare function parseTrades(trades: any[]): TradeReport[];
99
99
  /** Merges a raw report into `target`; returns the changed parts. */
100
100
  export declare function mergeStrategyReport(target: StrategyReport, report: any): StrategyReportChange[];
101
+ /** Aggregate counts are authoritative; an exit valuation does not prove closure. */
102
+ export interface StrategySummary {
103
+ tradeRecordCount: number;
104
+ closedTradeCount?: number;
105
+ openTradeCount?: number;
106
+ closedNetProfit?: number;
107
+ openPnL?: number;
108
+ /** Available only when both closed and open PnL are present. */
109
+ totalPnL?: number;
110
+ currency?: string;
111
+ }
112
+ /** Summarizes reported aggregates without inferring missing values or trade status. */
113
+ export declare function summarizeStrategyReport(report: StrategyReport): StrategySummary;
@@ -1,5 +1,9 @@
1
+ import { TradingViewError } from '../errors.js';
1
2
  /** Converts raw trades (oldest first) into readable trades (most recent first). */
2
3
  export function parseTrades(trades) {
4
+ if (!Array.isArray(trades) || trades.some((trade) => !trade || typeof trade !== 'object' || Array.isArray(trade))) {
5
+ throw new TradingViewError('PARSE_ERROR', 'Strategy trades must be an array of records');
6
+ }
3
7
  return [...trades].reverse().map((t) => ({
4
8
  entry: {
5
9
  name: t.e?.c,
@@ -20,6 +24,8 @@ export function mergeStrategyReport(target, report) {
20
24
  const changes = [];
21
25
  if (!report || typeof report !== 'object')
22
26
  return changes;
27
+ // Parse before mutating the target so a malformed trade list cannot leave a partial merge.
28
+ const trades = report.trades === undefined ? undefined : parseTrades(report.trades);
23
29
  if (report.currency) {
24
30
  target.currency = report.currency;
25
31
  changes.push('report.currency');
@@ -32,20 +38,41 @@ export function mergeStrategyReport(target, report) {
32
38
  target.performance = report.performance;
33
39
  changes.push('report.perf');
34
40
  }
35
- if (report.trades) {
36
- target.trades = parseTrades(report.trades);
41
+ if (trades !== undefined) {
42
+ target.trades = trades;
37
43
  changes.push('report.trades');
38
44
  }
39
- if (report.equity) {
40
- target.history = {
41
- buyHold: report.buyHold,
42
- buyHoldPercent: report.buyHoldPercent,
43
- drawDown: report.drawDown,
44
- drawDownPercent: report.drawDownPercent,
45
- equity: report.equity,
46
- equityPercent: report.equityPercent,
47
- };
48
- changes.push('report.history');
45
+ const historyKeys = [
46
+ 'buyHold', 'buyHoldPercent', 'drawDown', 'drawDownPercent', 'equity', 'equityPercent',
47
+ ];
48
+ let historyChanged = false;
49
+ for (const key of historyKeys) {
50
+ if (Array.isArray(report[key])) {
51
+ target.history[key] = report[key];
52
+ historyChanged = true;
53
+ }
49
54
  }
55
+ if (historyChanged)
56
+ changes.push('report.history');
50
57
  return changes;
51
58
  }
59
+ /** Summarizes reported aggregates without inferring missing values or trade status. */
60
+ export function summarizeStrategyReport(report) {
61
+ const finite = (value) => typeof value === 'number' && Number.isFinite(value) ? value : undefined;
62
+ const count = (value) => {
63
+ const n = finite(value);
64
+ return n !== undefined && Number.isInteger(n) && n >= 0 ? n : undefined;
65
+ };
66
+ const closedNetProfit = finite(report.performance.all?.netProfit);
67
+ const openPnL = finite(report.performance.openPL);
68
+ return {
69
+ currency: report.currency,
70
+ tradeRecordCount: report.trades.length,
71
+ closedTradeCount: count(report.performance.all?.totalTrades),
72
+ openTradeCount: count(report.performance.all?.totalOpenTrades),
73
+ closedNetProfit,
74
+ openPnL,
75
+ totalPnL: closedNetProfit !== undefined && openPnL !== undefined
76
+ ? finite(closedNetProfit + openPnL) : undefined,
77
+ };
78
+ }
@@ -161,8 +161,16 @@ export class Study extends Emitter {
161
161
  }));
162
162
  }
163
163
  }
164
- if (parsed?.data?.report)
165
- changes.push(...mergeStrategyReport(this.#strategyReport, parsed.data.report));
164
+ if (parsed?.data?.report) {
165
+ try {
166
+ changes.push(...mergeStrategyReport(this.#strategyReport, parsed.data.report));
167
+ }
168
+ catch (error) {
169
+ this.emit('error', new TradingViewError('PARSE_ERROR', 'Unable to parse strategy report', {
170
+ cause: error,
171
+ }));
172
+ }
173
+ }
166
174
  }
167
175
  if (Array.isArray(ns?.indexes))
168
176
  this.#graphicIndexes = ns.indexes;
@@ -26,3 +26,5 @@ export type { QuoteData, QuoteField } from '../quote/fields.js';
26
26
  export type { StudyValue } from '../chart/study.js';
27
27
  export type { GraphicsData } from '../chart/graphics.js';
28
28
  export type { StrategyReport } from '../chart/strategy.js';
29
+ export { summarizeStrategyReport } from '../chart/strategy.js';
30
+ export type { StrategySummary } from '../chart/strategy.js';
@@ -13,3 +13,4 @@ export { DEFAULT_TIMEOUT_MS } from './operation.js';
13
13
  // Plain HTTP lookups are already one-shot; they are part of the data API too.
14
14
  export { getTechnicalAnalysis, searchIndicators, searchMarkets, } from '../http/index.js';
15
15
  export { TradingViewError } from '../errors.js';
16
+ export { summarizeStrategyReport } from '../chart/strategy.js';
@@ -109,13 +109,13 @@ export async function getUser(credentials, options = {}) {
109
109
  if (!credentials?.session)
110
110
  throw new TradingViewError('INVALID_ARGUMENT', 'A session cookie is required');
111
111
  const maxRedirects = options.maxRedirects ?? 5;
112
- let location = trustedAccountLocation(options.location ?? 'https://www.tradingview.com/chart/');
112
+ let location = trustedAccountLocation(options.location ?? 'https://www.tradingview.com/');
113
113
  for (let redirects = 0;; redirects += 1) {
114
- const { status, text, headers } = await request(location, { credentials, redirect: 'manual' }, options);
114
+ const { text, headers } = await request(location, { credentials, redirect: 'manual' }, options);
115
115
  if (text.includes('auth_token'))
116
116
  return parseUserPage(text, credentials);
117
117
  const next = headers.get('location');
118
- const resolved = status >= 300 && status < 400 && next ? new URL(next, location).toString() : undefined;
118
+ const resolved = next ? new URL(next, location).toString() : undefined;
119
119
  if (!resolved || resolved === location) {
120
120
  throw new TradingViewError('AUTH_ERROR', 'Wrong or expired sessionid/signature');
121
121
  }
package/docs/data-api.md CHANGED
@@ -152,6 +152,62 @@ Without an account, TradingView refuses Pine studies ("maximum number of studies
152
152
 
153
153
  `watchIndicator(query, { onData, onError })` streams the same result on every study update; `watcher.latest` holds the last one.
154
154
 
155
+ ### Strategy totals and open positions
156
+
157
+ ```ts
158
+ import { summarizeStrategyReport } from '@mathieuc/tradingview/data';
159
+
160
+ const summary = summarizeStrategyReport(result.strategyReport);
161
+ // tradeRecordCount, closedTradeCount, openTradeCount,
162
+ // closedNetProfit, openPnL, totalPnL, currency
163
+ ```
164
+
165
+ Trade records can include open positions with a current exit valuation. An `exit`
166
+ object does **not** establish that a trade is closed. The summary uses reported
167
+ `performance.all.totalTrades` and `totalOpenTrades`, never the record count, for
168
+ closed/open counts. It does not assign a closed/open status to individual records.
169
+ Counts may differ from the available records in a partial report.
170
+
171
+ `totalPnL` is closed net profit plus open PnL, in the report currency. Missing or
172
+ non-finite values remain `undefined`; a missing open PnL is not assumed to be zero.
173
+ Raw percentage/fraction fields are not rescaled. History series, including buy &
174
+ hold, are retained independently even when no equity series is provided. Updates
175
+ replace supplied arrays and retain omitted series; an empty array clears a series.
176
+
177
+ Malformed trade lists (a non-array or non-object records) raise `PARSE_ERROR`
178
+ before any part of that report is applied. Studies emit this error for both plain
179
+ and compressed reports. Omitted trade lists retain previous records; `[]` clears them.
180
+ This is structural validation, not full validation of every trade field.
181
+
182
+ These offline checks validate report decoding and normalization, not fresh-client
183
+ access, Replay playback, export entitlement or Deep Backtesting availability.
184
+
185
+ In a Basic-account UI check on October 3, 2026, both strategy trade CSV export and
186
+ strategy report XLSX export opened an upgrade prompt recommending Essential.
187
+ No successful export was verified. These are TradingView UI entitlements, not
188
+ library export methods or a guarantee about other accounts. Deep Backtesting is
189
+ separate and was blocked by a Premium upgrade prompt in that session.
190
+
191
+ ### Complete strategy report example
192
+
193
+ See [strategy-report.js](../examples/strategy-report.js) for a bounded, executable
194
+ public-strategy → trade-records → PnL-summary workflow. Run it from a repository
195
+ checkout after building:
196
+
197
+ ```bash
198
+ node --env-file=.env examples/strategy-report.js
199
+ ```
200
+
201
+ It uses the account cookies from `.env`, runs a public Supertrend strategy and
202
+ closes its connection automatically. It places no orders. Trade records are
203
+ most recent first and can include open positions with current exit valuations;
204
+ use the reported aggregate counts rather than inferring closure from an exit.
205
+ Missing summary fields remain `undefined`, not zero. `count` selects returned
206
+ candles, not a guaranteed exact strategy backtest window. Basic was sufficient
207
+ for the tested symbol/timeframe; UI exports and Deep Backtesting have separate
208
+ subscription requirements.
209
+
210
+
155
211
  ## Search and technical analysis
156
212
 
157
213
  ```ts
@@ -0,0 +1,39 @@
1
+ # 4.0.0-beta.1 release candidate
2
+
3
+ Status: prepared for review; not published by this change.
4
+ Target npm dist-tag: **beta**. Keep **latest** on 3.5.2.
5
+
6
+ ## Changes since beta.0
7
+
8
+ - Preserve buy-and-hold history even when a report has no equity history (#331).
9
+ - Export `summarizeStrategyReport`: separate record counts, closed/open trade
10
+ counts, closed net profit, open PnL and total PnL; retain unknown fields as
11
+ undefined (#331).
12
+ - Reject structurally malformed trade lists before modifying a report; report
13
+ parse errors consistently for plain and compressed envelopes (#332).
14
+ - Add a runnable public-strategy/report/PnL example, with bounded execution,
15
+ credential checks and explicit failure status.
16
+ - Document Basic Replay playback evidence and separate paid UI export and
17
+ Deep Backtesting limits from library report access.
18
+
19
+ ## Validation
20
+
21
+ - Deterministic Node/Bun suites, typecheck, lint, build and packed-consumer smoke
22
+ checks are required before publication.
23
+ - Basic live probe: daily BINANCE:BTCEUR Replay load, three step acknowledgments,
24
+ automatic bar advancement, start and stop acknowledgments.
25
+ - Strategy example executed against Basic; report aggregates and history returned.
26
+ - Private compressed strategy capture validated in the preceding fixes.
27
+ - No claim of universal Replay entitlement, UI export success or Deep Backtesting.
28
+
29
+ ## Publication gate
30
+
31
+ Publication requires the maintainer's explicit go-ahead. After approval, use the
32
+ reviewed commit with a clean checkout, rerun package checks, and publish the
33
+ inspected tarball using `npm publish <tarball> --tag beta`. Verify the resulting
34
+ version and dist-tags. Do not use the default `latest` tag. No tag, GitHub release,
35
+ or npm publication is created by preparing this candidate.
36
+
37
+ For v3 migration and breaking changes inherited from beta.0, see
38
+ [migration-v4.md](migration-v4.md). This candidate adds no new intended breaking
39
+ change relative to beta.0.
@@ -1,13 +1,13 @@
1
1
  # v4 coverage matrix
2
2
 
3
- [Migration guide](migration-v4.md) · [Data API](data-api.md) · [Low-level API](low-level-api.md) · [Reliability evidence](v4-reliability.md)
3
+ [Migration guide](migration-v4.md) · [Data API](data-api.md) · [Low-level API](low-level-api.md)
4
4
 
5
5
  This matrix lists every capability of v3 (`main.js`, `src/`, examples, tests) and of the `agent.ts` preview, with its v4 replacement and the evidence that it works.
6
6
 
7
7
  **Evidence columns**
8
8
 
9
- - **Unit**: deterministic test in `tests/unit/` (`npm test` with Vitest on Node, `npm run test:bun` with Bun's runner; 109 tests, both green). Websocket tests use a scripted fake server (`tests/helpers/fake-server.ts`) or packets captured from TradingView (`tests/fixtures/live-session.json`); HTTP tests use a mocked `fetch`.
10
- - **Live**: result of `npm run test:live` (`tests/live/`) and examples against TradingView on **2 October 2026**: 17 anonymous tests locally (including connection recovery) and 22 tests (including five authenticated) in the [manual GitHub Actions run](https://github.com/Mathieu2301/TradingView-API/actions/runs/37078178729):
9
+ - **Unit**: deterministic test in `tests/unit/` (`npm test` with Vitest on Node, `npm run test:bun` with Bun's runner; 115 tests, both green). Websocket tests use a scripted fake server (`tests/helpers/fake-server.ts`) or packets captured from TradingView (`tests/fixtures/live-session.json`); HTTP tests use a mocked `fetch`.
10
+ - **Live**: result of `npm run test:live` (`tests/live/`) and examples against TradingView on **2 October 2026**: 16 anonymous tests locally and 21 tests (including five authenticated) in the [manual GitHub Actions run](https://github.com/Mathieu2301/TradingView-API/actions/runs/37075296707):
11
11
  - ✅ verified live anonymously;
12
12
  - 🔒 path or variant not exercised live (often requires a specific account asset); deterministic tests only;
13
13
  - ➖ not applicable (no network involved).
@@ -31,7 +31,7 @@ Test names are abbreviated as `file › test`.
31
31
  | --- | --- | --- | --- |
32
32
  | `protocol.parseWSPacket` (frame split, heartbeat as number, invalid JSON warning) | `protocol.decodeFrames` (length-based, UTF-16 lengths, typed frames, lenient fallback, invalid frames reported) | `protocol › decodes several frames...`, `keeps payloads containing frame markers intact`, `reports invalid JSON...`, `falls back to splitting malformed input`, `decodes every message of a captured live session` | ✅ (fixture captured live; UTF-16 lengths checked on Japanese/Korean descriptions) |
33
33
  | `protocol.formatWSPacket` | `protocol.encodeFrame`, `encodePacket`, `encodeHeartbeat` | `protocol › encodes packets with UTF-16 lengths` | ✅ |
34
- | `protocol.parseCompressed` (ZIP via jszip, base64 normalisation, zlib/raw/gzip fallbacks) | `protocol.decodeCompressed` (built-in ZIP reader: stored, deflated, data descriptors, empty entry names; zlib, raw deflate, gzip, plain JSON) and `normaliseBase64`, `readFirstZipEntry` | `protocol › compressed payloads › *` (ZIP fixtures generated independently with Python `zipfile`) | 🔒 live compressed strategy payload not captured; plain report verified |
34
+ | `protocol.parseCompressed` (ZIP via jszip, base64 normalisation, zlib/raw/gzip fallbacks) | `protocol.decodeCompressed` (built-in ZIP reader: stored, deflated, data descriptors, empty entry names; zlib, raw deflate, gzip, plain JSON) and `normaliseBase64`, `readFirstZipEntry` | `protocol › compressed payloads › *` (ZIP fixtures generated independently with Python `zipfile`) | ✅ private compressed browser capture replayed through compiled parser (3 October 2026); plain report verified |
35
35
  | `utils.genSessionID` | `protocol.createSessionId` (crypto-random) | `protocol › ids` | ➖ |
36
36
  | `utils.genAuthCookies` | Internal cookie builder used by every HTTP call | `http › * with credentials` (cookie headers asserted) | ➖ |
37
37
  | Websocket URL `wss://<server>.tradingview.com/socket.io/websocket?from=chart&type=chart`, Origin and browser headers | Same, in `TradingViewClient`; transport is pluggable (`transport` option, default `ws`) | `client › connects with browser-like headers...` | ✅ Node and Bun (Bun needed the Origin header fix) |
@@ -89,7 +89,7 @@ Test names are abbreviated as `file › test`.
89
89
  | v3 capability | v4 | Unit evidence | Live |
90
90
  | --- | --- | --- | --- |
91
91
  | `setMarket(symbol, { replay })` (replay session, add series, reset) | Same option | `chart › runs replay mode...` | ✅ |
92
- | `replayStep(n)`, `replayStart(interval)`, `replayStop()` resolved by `replay_ok` | Same; reject with `INVALID_STATE` outside replay and on delete | `chart › runs replay mode...`, `rejects pending replay requests...` | ✅ step (`sessions › replays history step by step`); ➖ start/stop live |
92
+ | `replayStep(n)`, `replayStart(interval)`, `replayStop()` resolved by `replay_ok` | Same; reject with `INVALID_STATE` outside replay and on delete | `chart › runs replay mode...`, `rejects pending replay requests...` | ✅ step (`sessions › replays history step by step`); ✅ start/stop acknowledgments and advancing bars on Basic (3 October 2026) |
93
93
  | `onReplayLoaded`, `onReplayPoint`, `onReplayResolution`, `onReplayEnd` | `replayLoaded`, `replayPoint`, `replayResolution`, `replayEnd` events | `chart › runs replay mode...` | ✅ |
94
94
  | Replay `critical_error` | `CRITICAL_ERROR` on the chart; pending replay requests rejected | (code path shared with chart errors) | ➖ |
95
95
 
@@ -102,7 +102,7 @@ Test names are abbreviated as `file › test`.
102
102
  | `study.periods` with plot names, `plot_N` fallback for unnamed/duplicate plots | `study.values` (oldest first) | `study › creates a Pine study, names plots...`, `chart › parses a captured live chart session` | ✅ (built-in volume rows) |
103
103
  | `study.graphic` (labels, lines, boxes, tables + cells, polygons, horizLines, horizHists, raw) and `graphicsCmds` erase/create | `study.graphics` (`cells` array, `raw` object) | `study › reads graphics commands...`, `applies erase commands` | ✅ horizontal histograms (volume profile); 🔒 Pine drawings |
104
104
  | Bars-back translation of graphic X indexes | Same | `study › reads graphics commands...` | ✅ |
105
- | `study.strategyReport` (plain `data.report` and compressed `dataCompressed`; trades, performance, history, currency, settings) | Same | `study › decodes plain and compressed strategy reports`, `reports undecodable strategy reports as PARSE_ERROR` | ✅ Supertrend strategy report with account; compressed variant unit-tested |
105
+ | `study.strategyReport` (plain `data.report` and compressed `dataCompressed`; trades, performance, history, currency, settings) | Same | `study › decodes plain and compressed strategy reports`, `reports undecodable strategy reports as PARSE_ERROR` | ✅ Supertrend strategy report with account; compressed variant also validated using a private browser capture |
106
106
  | `study.setIndicator()` (`modify_study`) | Same | `study › modifies and removes a study` | 🔒 |
107
107
  | `study.remove()` | Same (idempotent) | same | ✅ |
108
108
  | `onReady`, `onUpdate`, `onError`, `study_error` | `ready`, `loading`, `update`, `error` (`STUDY_ERROR`, message formatted with server context) | `study › formats study errors with their context` | ✅ (anonymous Pine refusal reported as `STUDY_ERROR`) |
@@ -189,4 +189,25 @@ These v3 behaviours were changed on purpose; none removes a capability.
189
189
 
190
190
  ## Not verified live for this release
191
191
 
192
- The 22-test manual live workflow verified authenticated connection, account lookup, a public Pine RSI, a Supertrend strategy report, the private-indicators listing endpoint, and recent historical `to` with an account. It did **not** verify password login, actual private/invite-only scripts, compressed strategy reports against a live response, older history beyond server limits, second-based/custom timeframes, the `prodata` server, owned layouts/drawings, or Pine permission changes. Those paths have deterministic tests but need the corresponding account assets to verify live. Run `SESSION=... SIGNATURE=... npm run test:live` to repeat the authenticated subset.
192
+ The 21-test manual live workflow verified authenticated connection, account lookup, a public Pine RSI, a Supertrend strategy report, the private-indicators listing endpoint, and recent historical `to` with an account. It did **not** verify password login, actual private/invite-only scripts, older history beyond server limits, second-based/custom timeframes, the `prodata` server, owned layouts/drawings, or Pine permission changes. Those paths have deterministic tests but need the corresponding account assets to verify live. Run `SESSION=... SIGNATURE=... npm run test:live` to repeat the authenticated subset.
193
+
194
+ ## Basic account follow-up — 3 October 2026
195
+
196
+ A fresh authenticated library client loaded `BINANCE:BTCEUR` daily Replay at a
197
+ reference 30 days before the probe. Loading returned five bars; three step
198
+ requests were acknowledged; automatic playback produced advancing bar timestamps;
199
+ start and stop were both acknowledged. The client was closed after the bounded
200
+ probe. This verifies the library protocol for that symbol/timeframe/reference,
201
+ not intraday Replay, every exchange, unlimited history, or simulated order entry.
202
+ The existing browser chart and its settings were not changed.
203
+
204
+ `examples/strategy-report.js` was executed with the same Basic account against
205
+ the public Supertrend strategy on `BINANCE:BTCEUR`, timeframe `60`, count `300`.
206
+ It returned trade records, closed/open aggregate counts and PnL, and buy-and-hold
207
+ history. No private payloads or credentials are included in this repository.
208
+ The strategy may compute over more history than the requested candle count;
209
+ `count` is not an exact backtest-window guarantee.
210
+
211
+ UI CSV and XLSX exports remain plan-blocked (Essential offered), and Deep
212
+ Backtesting remains plan-blocked (Premium offered). The library report example
213
+ is not an implementation of those UI exports and does not enable Deep Backtesting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathieuc/tradingview",
3
- "version": "4.0.0-beta.0",
3
+ "version": "4.0.0-beta.1",
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",
@@ -34,9 +34,7 @@
34
34
  "test:live": "vitest run --config vitest.live.config.ts",
35
35
  "smoke": "node scripts/smoke.mjs",
36
36
  "check": "npm run typecheck && npm run lint && npm test && npm run build && npm run smoke",
37
- "prepack": "npm run build",
38
- "probe:endurance": "npm run build && node scripts/endurance.mjs",
39
- "probe:parity": "npm run build && node scripts/parity.mjs"
37
+ "prepack": "npm run build"
40
38
  },
41
39
  "repository": {
42
40
  "type": "git",
@@ -1,33 +0,0 @@
1
- # V4 reliability evidence
2
-
3
- This page tracks reproducible release checks beyond the [capability matrix](v4-coverage.md). It is evidence for a beta, not a claim that every TradingView account feature works.
4
-
5
- ## V3/V4 candle parity
6
-
7
- On 2 October 2026, the published `@mathieuc/tradingview@3.5.2` and V4 `main` (`95b6d0f`) loaded the same `BINANCE:BTCEUR` market. For each of `D` and `60`, the probe compared 19 closed candles out of 20 requested. All 38 matched on timestamp and OHLC. Volumes matched after applying V3's two-decimal rounding; V4 intentionally preserves the server precision. The still-open candle was excluded because it can change between sequential requests.
8
-
9
- Repeat without adding V3 to V4's dependencies:
10
-
11
- ```sh
12
- npm ci
13
- npm install --prefix /tmp/tradingview-v3-baseline --no-save --no-package-lock @mathieuc/tradingview@3.5.2
14
- TV_V3_ENTRY=/tmp/tradingview-v3-baseline/node_modules/@mathieuc/tradingview/main.js npm run probe:parity
15
- ```
16
-
17
- `probe:parity` exits nonzero on a mismatched closed candle. It needs live network access and is not part of deterministic CI.
18
-
19
- ## Live connection lifecycle
20
-
21
- `tests/live/recovery.test.ts` starts candle and quote watchers on one client, closes the connection while both are active, verifies both stop and report `DISCONNECTED`, then fetches candles with a new client. This is a controlled socket close, not proof of recovery from every network failure. The library does not reconnect a closed `TradingViewClient` automatically; consumers create a new client.
22
-
23
- `npm run probe:endurance -- --minutes=120 --cycle-seconds=600` streams one-minute BTCUSDT candles and quotes for two hours, rotates the connection every ten minutes, and emits JSON lines with startup counts, updates, heartbeats and errors. It exits nonzero on a startup failure, an early watcher close or missing data. It never reads or logs account credentials. A one-minute smoke run on 2 October 2026 completed three cycles with 16 candle updates, 18 quote updates, five heartbeats and zero errors. The two-hour result should only be recorded after the run ends. The probe also accepts `--symbol`, `--timeframe` and `--chart-type` for issue-specific checks. For example, issue #236 can be investigated with `--symbol=OANDA:EURUSD --chart-type=HeikinAshi`, though a closed forex market cannot prove that quote updates remain live.
24
-
25
- ## Account redirect regression
26
-
27
- V4 now starts the account lookup at `/chart/` and follows `Location` only on HTTP 3xx responses. A deterministic test covers HTTP 200 with a misleading `Location` header. This ports the relevant guard from legacy PR #322; it does not claim to solve CAPTCHA/WAF challenges. The authenticated manual CI run passed all 22 live tests after this change.
28
-
29
- ## Remaining beta limitations
30
-
31
- - The 17 anonymous live tests and five authenticated tests from the [coverage matrix](v4-coverage.md) exercise short-lived calls. The authenticated subset requires `SESSION` and `SIGNATURE`; all five tests passed in the manual GitHub Actions run on 2 October 2026.
32
- - `prodata`, private/invite-only scripts, owned layouts and drawings, Pine permission changes, password login and a live compressed strategy report still require suitable account assets. Deterministic tests cover their known packet shapes but are not live proof.
33
- - The scheduled/manual live CI job is currently non-blocking. A red live run must be reviewed before a stable V4 release.