openpinescript 0.0.0-stage → 0.1.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.
Files changed (103) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +346 -2
  3. package/dist/cli.mjs +108686 -0
  4. package/dist/index.cjs +108670 -0
  5. package/dist/index.mjs +108624 -0
  6. package/dist/types/index.d.ts +31 -0
  7. package/dist/types/parser/v1/generated/PineV1Lexer.d.ts +66 -0
  8. package/dist/types/parser/v1/generated/PineV1Parser.d.ts +555 -0
  9. package/dist/types/parser/v1/generated/PineV1ParserVisitor.d.ts +318 -0
  10. package/dist/types/parser/v1/index.d.ts +39 -0
  11. package/dist/types/parser/v2/generated/PineV2Lexer.d.ts +67 -0
  12. package/dist/types/parser/v2/generated/PineV2Parser.d.ts +568 -0
  13. package/dist/types/parser/v2/generated/PineV2ParserVisitor.d.ts +325 -0
  14. package/dist/types/parser/v2/index.d.ts +2 -0
  15. package/dist/types/parser/v3/generated/PineV3Lexer.d.ts +67 -0
  16. package/dist/types/parser/v3/generated/PineV3Parser.d.ts +568 -0
  17. package/dist/types/parser/v3/generated/PineV3ParserVisitor.d.ts +325 -0
  18. package/dist/types/parser/v3/index.d.ts +2 -0
  19. package/dist/types/parser/v4/generated/PineV4Lexer.d.ts +86 -0
  20. package/dist/types/parser/v4/generated/PineV4Parser.d.ts +673 -0
  21. package/dist/types/parser/v4/generated/PineV4ParserVisitor.d.ts +367 -0
  22. package/dist/types/parser/v4/index.d.ts +2 -0
  23. package/dist/types/parser/v5/generated/PineV5Lexer.d.ts +95 -0
  24. package/dist/types/parser/v5/generated/PineV5Parser.d.ts +964 -0
  25. package/dist/types/parser/v5/generated/PineV5ParserVisitor.d.ts +521 -0
  26. package/dist/types/parser/v5/index.d.ts +2 -0
  27. package/dist/types/parser/v6/generated/PineV6Lexer.d.ts +98 -0
  28. package/dist/types/parser/v6/generated/PineV6Parser.d.ts +984 -0
  29. package/dist/types/parser/v6/generated/PineV6ParserVisitor.d.ts +528 -0
  30. package/dist/types/parser/v6/index.d.ts +2 -0
  31. package/dist/types/runtime/v1/Series.d.ts +61 -0
  32. package/dist/types/runtime/v1/broker/Broker.d.ts +325 -0
  33. package/dist/types/runtime/v1/broker/index.d.ts +158 -0
  34. package/dist/types/runtime/v1/broker/val.d.ts +28 -0
  35. package/dist/types/runtime/v1/constDefault.d.ts +11 -0
  36. package/dist/types/runtime/v1/context.d.ts +1705 -0
  37. package/dist/types/runtime/v1/footprint.d.ts +32 -0
  38. package/dist/types/runtime/v1/index.d.ts +7 -0
  39. package/dist/types/runtime/v1/language-spec.d.ts +12 -0
  40. package/dist/types/runtime/v1/openTrades.d.ts +12 -0
  41. package/dist/types/runtime/v1/palette.d.ts +18 -0
  42. package/dist/types/runtime/v1/session.d.ts +482 -0
  43. package/dist/types/runtime/v1/sessions.d.ts +31 -0
  44. package/dist/types/runtime/v1/stateClone.d.ts +19 -0
  45. package/dist/types/runtime/v1/stdlib/alerts.d.ts +31 -0
  46. package/dist/types/runtime/v1/stdlib/arrays.d.ts +133 -0
  47. package/dist/types/runtime/v1/stdlib/barmerge.d.ts +27 -0
  48. package/dist/types/runtime/v1/stdlib/barstate.d.ts +79 -0
  49. package/dist/types/runtime/v1/stdlib/cast.d.ts +81 -0
  50. package/dist/types/runtime/v1/stdlib/chart.d.ts +283 -0
  51. package/dist/types/runtime/v1/stdlib/color.d.ts +51 -0
  52. package/dist/types/runtime/v1/stdlib/core.d.ts +162 -0
  53. package/dist/types/runtime/v1/stdlib/dataset.d.ts +36 -0
  54. package/dist/types/runtime/v1/stdlib/display.d.ts +50 -0
  55. package/dist/types/runtime/v1/stdlib/drawings.d.ts +407 -0
  56. package/dist/types/runtime/v1/stdlib/errors.d.ts +21 -0
  57. package/dist/types/runtime/v1/stdlib/font.d.ts +20 -0
  58. package/dist/types/runtime/v1/stdlib/footprint.d.ts +76 -0
  59. package/dist/types/runtime/v1/stdlib/format.d.ts +57 -0
  60. package/dist/types/runtime/v1/stdlib/index.d.ts +71 -0
  61. package/dist/types/runtime/v1/stdlib/input.d.ts +14 -0
  62. package/dist/types/runtime/v1/stdlib/inputs.d.ts +60 -0
  63. package/dist/types/runtime/v1/stdlib/late_members.d.ts +97 -0
  64. package/dist/types/runtime/v1/stdlib/logging.d.ts +31 -0
  65. package/dist/types/runtime/v1/stdlib/maps.d.ts +55 -0
  66. package/dist/types/runtime/v1/stdlib/math.d.ts +71 -0
  67. package/dist/types/runtime/v1/stdlib/matrix.d.ts +201 -0
  68. package/dist/types/runtime/v1/stdlib/metadata.d.ts +14 -0
  69. package/dist/types/runtime/v1/stdlib/mtf.d.ts +15 -0
  70. package/dist/types/runtime/v1/stdlib/renames.d.ts +117 -0
  71. package/dist/types/runtime/v1/stdlib/renames5.d.ts +113 -0
  72. package/dist/types/runtime/v1/stdlib/renames6.d.ts +81 -0
  73. package/dist/types/runtime/v1/stdlib/request.d.ts +207 -0
  74. package/dist/types/runtime/v1/stdlib/scale.d.ts +19 -0
  75. package/dist/types/runtime/v1/stdlib/session.d.ts +113 -0
  76. package/dist/types/runtime/v1/stdlib/sort_field.d.ts +12 -0
  77. package/dist/types/runtime/v1/stdlib/sources.d.ts +24 -0
  78. package/dist/types/runtime/v1/stdlib/str.d.ts +117 -0
  79. package/dist/types/runtime/v1/stdlib/strategy.d.ts +314 -0
  80. package/dist/types/runtime/v1/stdlib/syminfo.d.ts +209 -0
  81. package/dist/types/runtime/v1/stdlib/ta.d.ts +409 -0
  82. package/dist/types/runtime/v1/stdlib/time.d.ts +158 -0
  83. package/dist/types/runtime/v1/stdlib/trades.d.ts +108 -0
  84. package/dist/types/runtime/v1/stdlib/ui.d.ts +196 -0
  85. package/dist/types/runtime/v1/stdlib/v6_members.d.ts +68 -0
  86. package/dist/types/runtime/v1/timeframes.d.ts +139 -0
  87. package/dist/types/runtime/v1/zones.d.ts +30 -0
  88. package/dist/types/transpiler/common/index.d.ts +51 -0
  89. package/dist/types/transpiler/common/libraries.d.ts +29 -0
  90. package/dist/types/transpiler/common/parseErrors.d.ts +38 -0
  91. package/dist/types/transpiler/common/passes/ScopeAnalysis.d.ts +158 -0
  92. package/dist/types/transpiler/common/passes/TypeRules.d.ts +26 -0
  93. package/dist/types/transpiler/common/profiles/index.d.ts +96 -0
  94. package/dist/types/transpiler/common/version.d.ts +40 -0
  95. package/dist/types/transpiler/v1/ToJsVisitor.d.ts +720 -0
  96. package/dist/types/transpiler/v2/ToJsVisitor.d.ts +72 -0
  97. package/dist/types/transpiler/v3/ToJsVisitor.d.ts +87 -0
  98. package/dist/types/transpiler/v4/ToJsVisitor.d.ts +263 -0
  99. package/dist/types/transpiler/v5/ToJsVisitor.d.ts +1101 -0
  100. package/dist/types/transpiler/v6/ToJsVisitor.d.ts +237 -0
  101. package/dist/types/transpiler/v6/rules.d.ts +94 -0
  102. package/dist/types/utils/v2/common.d.ts +4 -0
  103. package/package.json +92 -4
@@ -0,0 +1,1705 @@
1
+ import { Series } from "./Series.js";
2
+ import { MarketOrder } from "./stdlib/strategy.js";
3
+ import type { FootprintLevel } from "./footprint.js";
4
+ import { LanguageProfile } from "../../transpiler/common/profiles/index.js";
5
+ export interface Trade {
6
+ id: string;
7
+ entryTime: number;
8
+ entryPrice: number;
9
+ exitTime: number;
10
+ exitPrice: number;
11
+ qty: number;
12
+ pnl: number;
13
+ direction: "long" | "short";
14
+ /**
15
+ * The bars the trade opened and closed on.
16
+ *
17
+ * A time alone cannot be aligned to a chart without searching the bar
18
+ * list for it, and "Avg # Bars in Trades" — a row of TradingView's
19
+ * performance summary — is `exitBarIndex - entryBarIndex` and nothing
20
+ * else. Both are recorded at the fill, where they are known for free.
21
+ */
22
+ entryBarIndex?: number;
23
+ exitBarIndex?: number;
24
+ /**
25
+ * The order id that OPENED the position, which TradingView's list of
26
+ * trades calls the trade's signal. `id` names the closing order, so
27
+ * without this the entry row of every trade reads "Close".
28
+ */
29
+ entryId?: string;
30
+ /**
31
+ * Favourable and adverse excursion, in currency, over the life of the
32
+ * trade — how far the position ran in the script's favour before it
33
+ * closed, and how far against.
34
+ *
35
+ * run-up = (highest price while open - entry price) * qty [long]
36
+ * drawdown = (entry price - lowest price while open) * qty [long]
37
+ *
38
+ * mirrored for a short. TradingView states the price form of both and
39
+ * reports them per trade; the exit price does not enter either one.
40
+ * Both are >= 0, and both are measured against the position's average
41
+ * price so a pyramided add moves them the way it moves the P&L.
42
+ */
43
+ runup?: number;
44
+ drawdown?: number;
45
+ /** Commission charged on the round turn, already deducted from `pnl`. */
46
+ commission?: number;
47
+ /**
48
+ * `strategy.closedtrades.exit_id` and `.exit_comment`, as TradingView
49
+ * names them: the exit's own id, "Close entry(s) order <entry>" for
50
+ * `strategy.close`, "Close position order" for `close_all` and a margin
51
+ * call. Absent where no version records them yet, and the accessors fall
52
+ * back to `id`.
53
+ */
54
+ exitId?: string;
55
+ exitComment?: string;
56
+ }
57
+ /**
58
+ * One row of the equity curve: what the account was worth at a bar's close.
59
+ *
60
+ * Sampled every bar rather than every fill because that is what a chart
61
+ * plots — TradingView's overview draws equity against time, and a curve
62
+ * that only moves when a trade closes is a staircase that hides the open
63
+ * position's swing. `openPL` is the part of `equity` that is not yet
64
+ * realized, which is what makes the difference visible.
65
+ */
66
+ export interface EquityPoint {
67
+ time: number;
68
+ /** Realized cash plus the open position's mark-to-market. */
69
+ equity: number;
70
+ /** Unrealized P&L of the position held at this bar's close; 0 while flat. */
71
+ openPL: number;
72
+ }
73
+ /** One entry still open inside a position. See Position.entries. */
74
+ export interface OpenEntry {
75
+ id: string;
76
+ /** Still open, and as filled. `qty_percent` exits are a share of `opened`. */
77
+ qty: number;
78
+ opened: number;
79
+ price: number;
80
+ bar: number;
81
+ time: number;
82
+ /** Entry commission not yet claimed by a closing trade. */
83
+ commission: number;
84
+ /** Identifies the trade for as long as it lives: the bar and the fill's order count. */
85
+ uid?: string;
86
+ }
87
+ export interface Position {
88
+ size: number;
89
+ avgPrice: number;
90
+ /**
91
+ * The bar the position was OPENED on, the time of that bar, and the order
92
+ * id that opened it.
93
+ *
94
+ * Recorded so `strategy.opentrades.entry_bar_index` and `.entry_id` have
95
+ * something to answer with. Undefined while flat, which is the same fact as
96
+ * `size === 0` and reads as `na` either way.
97
+ *
98
+ * `entryTime` is here because a host has to PLACE this position on a
99
+ * chart, and a bar index cannot be placed: the chart's bar list grows as
100
+ * older bars page in, so index 40 is a different bar after a scroll.
101
+ * `Trade` carries both for the same reason; leaving it off `Position`
102
+ * meant the still-open position could only be drawn on the last bar.
103
+ */
104
+ entryBarIndex?: number;
105
+ entryTime?: number;
106
+ entryId?: string;
107
+ /**
108
+ * Entry-side commission already paid on the position still open, so an
109
+ * exit can charge its own side and claim a share of this rather than
110
+ * recomputing a whole round turn.
111
+ *
112
+ * `commission.cash_per_order` is a flat fee PER ORDER, and it used to be
113
+ * charged twice per closed-trade record. Two partial exits of one entry
114
+ * are three orders and were billed for four; three pyramided entries
115
+ * closed in one go are four orders and were billed for two. The other two
116
+ * commission types scale with quantity, so they came out right by
117
+ * accident and this changes nothing for them.
118
+ */
119
+ entryCommission?: number;
120
+ /**
121
+ * The largest size this position has held since it opened: what a v6
122
+ * `qty_percent` exit is a percentage OF. Half of a 0.731 entry is 0.3655
123
+ * whether or not another exit has already sold part of it.
124
+ */
125
+ openedQty?: number;
126
+ /**
127
+ * The entries that make up the position, oldest first — what TradingView
128
+ * calls its open trades. Kept by every broker and READ only where the
129
+ * broker tracks trades (V6Broker): `strategy.close(id)` and an exit's
130
+ * `from_entry` act on the entries with that id, and a close records one
131
+ * trade per entry it consumes, at that entry's own price.
132
+ */
133
+ entries?: OpenEntry[];
134
+ /**
135
+ * The id ledger, apart from `entries` (the trades, first in first out):
136
+ * what each entry id still owns, and what it opened with. An exit with
137
+ * `from_entry` and `close(id)` draw on an id's quantity, but what they sell
138
+ * comes off the OLDEST trades whatever their id (TradingView's log,
139
+ * harness_strategy_exit_mix and harness_strategy_pyr3 phase d).
140
+ */
141
+ ledger?: Record<string, {
142
+ qty: number;
143
+ opened: number;
144
+ }>;
145
+ }
146
+ /**
147
+ * One line emitted by v5's `log.*` functions.
148
+ *
149
+ * Declared here rather than beside the functions in stdlib/logging.ts so the
150
+ * dependency runs one way: every stdlib module already imports the Context, and
151
+ * having the Context import a stdlib module back would close the cycle.
152
+ */
153
+ export interface LogEntry {
154
+ readonly level: "info" | "warning" | "error";
155
+ readonly message: string;
156
+ /** The bar the line was emitted on, so a log can be aligned to a series. */
157
+ readonly bar: number;
158
+ /** The bar's timestamp, which is what the generated CSV rows are keyed by. */
159
+ readonly time: number;
160
+ }
161
+ interface PlotBase {
162
+ title: string;
163
+ color?: string;
164
+ }
165
+ export interface LinePlot extends PlotBase {
166
+ type: 'line';
167
+ value: number;
168
+ linewidth?: number;
169
+ style?: number;
170
+ }
171
+ export interface OHLCPlot extends PlotBase {
172
+ type: 'candle';
173
+ open: number;
174
+ high: number;
175
+ low: number;
176
+ close: number;
177
+ }
178
+ export interface MarkerPlot extends PlotBase {
179
+ type: 'shape' | 'char' | 'arrow';
180
+ value: number;
181
+ style?: string;
182
+ }
183
+ export interface ColorDirective extends PlotBase {
184
+ type: 'bgcolor' | 'barcolor';
185
+ }
186
+ export type PlotData = LinePlot | OHLCPlot | MarkerPlot | ColorDirective;
187
+ /** Where in the Pine source a runtime error was raised; see `Context.locateError`. */
188
+ export interface PineLocation {
189
+ line: number;
190
+ col: number;
191
+ /** The alias of the imported library the error was raised in, if it was in one. */
192
+ library?: string;
193
+ /** For an error inside a library: the call in the script that led there. */
194
+ caller?: {
195
+ line: number;
196
+ col: number;
197
+ };
198
+ }
199
+ export interface FillData {
200
+ plotId1: string;
201
+ plotId2: string;
202
+ color: string;
203
+ title: string;
204
+ /** `fill(show_last)`: draw only the last N bars. Absent when the script gave none. */
205
+ showLast?: number;
206
+ /** The gradient overload, `fill(p1, p2, top_value, bottom_value, top_color, bottom_color)`. */
207
+ gradient?: true;
208
+ /** `top_color` is drawn at the price `top_value`, `bottom_color` at `bottom_value`; this bar's. */
209
+ top_value?: number;
210
+ bottom_value?: number;
211
+ top_color?: string;
212
+ bottom_color?: string;
213
+ }
214
+ export interface InputDef {
215
+ id: string;
216
+ defval: any;
217
+ title: string;
218
+ type: string;
219
+ }
220
+ /**
221
+ * Script-level metadata declared by the `study()` / `strategy()` directive.
222
+ * `kind` drives mode-specific behavior (e.g. study mode disables the broker).
223
+ */
224
+ /**
225
+ * The directive kinds a script can declare.
226
+ *
227
+ * `indicator` is v5's rename of `study` and nothing else — same parameters,
228
+ * same meaning. Both spellings are kept rather than normalised so a host
229
+ * reading `scriptMeta.kind` sees the keyword the author actually wrote, which
230
+ * is also what every diagnostic quotes back at them.
231
+ *
232
+ * `library` is v5's third kind. A library declares functions for other scripts
233
+ * to import and plots nothing itself, so a host must be able to tell one from
234
+ * an indicator before it tries to render anything.
235
+ */
236
+ export type ScriptKind = 'study' | 'indicator' | 'strategy' | 'library';
237
+ /**
238
+ * What a strategy starts with when `strategy()` does not say.
239
+ *
240
+ * TradingView's own figure, read off its own output: the golden strategy
241
+ * export passes no `initial_capital` and its `strategy.equity` reads exactly
242
+ * 1000000 on every bar before the first fill
243
+ * (conformance/v5/parity/harness_strategy.BTCUSDT.1D.log). The engine used to
244
+ * start at 100000, which put every equity-derived figure — `netprofit_percent`,
245
+ * `grossprofit_percent` — out by a factor of ten.
246
+ */
247
+ export declare const DEFAULT_INITIAL_CAPITAL = 1000000;
248
+ /**
249
+ * ...but only for Pine v5. Read off TradingView with a strategy that passes no
250
+ * `initial_capital`: v2, v3, v4 and v6 start at 100,000, v5 at 1,000,000.
251
+ */
252
+ export declare function defaultInitialCapital(version: number): number;
253
+ export interface ScriptMeta {
254
+ kind: ScriptKind;
255
+ title: string;
256
+ shorttitle: string;
257
+ overlay: boolean;
258
+ precision?: number;
259
+ pyramiding?: number;
260
+ calc_on_every_tick?: boolean;
261
+ calc_on_order_fills?: boolean;
262
+ currency?: string;
263
+ /**
264
+ * The rest of the `strategy()` declaration — the backtest settings a
265
+ * TradingView user reads off the Properties tab and changes without
266
+ * touching the script.
267
+ *
268
+ * Recorded whether or not the broker acts on each one, because a host
269
+ * cannot show a properties panel for arguments it was never told about,
270
+ * and a panel that invents its own defaults will disagree with the
271
+ * script the moment the script sets one.
272
+ */
273
+ initial_capital?: number;
274
+ default_qty_type?: string;
275
+ default_qty_value?: number;
276
+ commission_type?: string;
277
+ commission_value?: number;
278
+ slippage?: number;
279
+ margin_long?: number;
280
+ margin_short?: number;
281
+ process_orders_on_close?: boolean;
282
+ risk_free_rate?: number;
283
+ /**
284
+ * v6, July 2026: run once per tick of every HISTORICAL bar. Recorded for a
285
+ * host to display; this engine is given bars, not their ticks, so it has
286
+ * nothing to run per tick and computes exactly as if this were false.
287
+ * Present only when a v6 script declares it.
288
+ */
289
+ calc_on_every_history_tick?: boolean;
290
+ }
291
+ export interface Drawing {
292
+ id: string;
293
+ kind: 'line' | 'label' | 'box' | 'table' | 'polyline' | 'linefill';
294
+ /** The bar the drawing was created on. */
295
+ bar: number;
296
+ props: Record<string, any>;
297
+ }
298
+ /**
299
+ * One observation of a non-price series — a dividend, a quarterly figure.
300
+ *
301
+ * These are EVENTS, not bars: a value is published on one date and stands until
302
+ * the next, so a request reads as a step function over its samples rather than
303
+ * as one value per bar.
304
+ */
305
+ export interface RequestSample {
306
+ /** UNIX ms. The value applies to this bar and every later one until the next sample. */
307
+ time: number;
308
+ value: number;
309
+ }
310
+ /** A higher-timeframe candle supplied for `security()` evaluation (P1). */
311
+ export interface SecurityCandle {
312
+ time: number;
313
+ open: number;
314
+ high: number;
315
+ low: number;
316
+ close: number;
317
+ volume: number;
318
+ /**
319
+ * When the bar closes, if the host knows it. A stock's hourly and daily bars end
320
+ * where the SESSION ends, not on a clock boundary (the last hourly bar of a
321
+ * 09:30-16:00 session is 15:30-16:00); without it the end is the calendar's.
322
+ * `request.security` closes a bar with the chart bar that ends it, so a host
323
+ * with a session calendar should give this (TradingView's `time_close`).
324
+ */
325
+ closeTime?: number;
326
+ }
327
+ /**
328
+ * The stack of call ids being executed, with the joined key of every depth
329
+ * kept as it moves.
330
+ *
331
+ * A call site's key was rebuilt by joining the whole stack on every call,
332
+ * which was a tenth of a run's time. The keys are interned, so each is one
333
+ * string whose hash is computed once. A built-in that names a frame of its own
334
+ * (`ta.ts` pushes "rsi_gain" and the like) pushes through the same methods.
335
+ */
336
+ declare class CallStack {
337
+ private ids;
338
+ /** `keys[i]` is the first `i + 1` ids joined with "/". */
339
+ readonly keys: string[];
340
+ private intern;
341
+ get length(): number;
342
+ /** The innermost id. */
343
+ get top(): string | undefined;
344
+ /** The whole stack joined with "/", "" when empty. */
345
+ get path(): string;
346
+ /** Returns the new path. */
347
+ push(id: string): string;
348
+ pop(): void;
349
+ join(sep: string): string;
350
+ [Symbol.iterator](): Iterator<string>;
351
+ }
352
+ export declare class Context {
353
+ protected callStack: CallStack;
354
+ /** What a call id says about itself, which never changes: its name and whether it is a user frame. */
355
+ private callIds;
356
+ /**
357
+ * Keyword arguments passed to a built-in whose signature does not name them.
358
+ *
359
+ * `<function>(<argument>)`, deduplicated. Empty is the only acceptable
360
+ * value: a name Pine accepts and this engine does not model is a gap, and a
361
+ * silently dropped argument is the worst shape one can take — the script
362
+ * runs, and does something other than what it says.
363
+ */
364
+ droppedArguments: Set<string>;
365
+ /**
366
+ * The series NAMESPACE of the user-function frame currently executing.
367
+ *
368
+ * Empty at script scope, and `<call id>|` inside a user function — nested
369
+ * one level per frame, so a function called from two places has two
370
+ * namespaces and a function called from inside another has one per outer
371
+ * frame.
372
+ *
373
+ * ── Why this exists ─────────────────────────────────────────────────────
374
+ *
375
+ * "each function call has its own instance of the series variables" is how
376
+ * Pine works, and this engine gave every call site ONE instance:
377
+ *
378
+ * f() =>
379
+ * var c = 0
380
+ * c := c + 1
381
+ * c
382
+ * a = f()
383
+ * b = f()
384
+ *
385
+ * yielded a = 1,3,5 and b = 2,4,6, where TradingView gives 1,2,3 for both.
386
+ * Both calls advanced the same counter. The same held for every function
387
+ * local, not just `var` ones: `t = close * 2` inside a function was one
388
+ * series shared by every caller, so a function's HISTORY (`t[1]`) mixed the
389
+ * call sites together.
390
+ *
391
+ * The emitter already qualifies a local's name by its FUNCTION, which stops
392
+ * two different functions from colliding. This is the other half: two calls
393
+ * of the SAME function are two variables too.
394
+ */
395
+ private seriesScope;
396
+ private seriesScopes;
397
+ /** Which user-function CALL is running: a fresh number per call, 0 at script scope. */
398
+ private frameSeq;
399
+ private frameSeqs;
400
+ private frameSeqCounter;
401
+ /** The series the running user call was handed as arguments: a parameter's history stays bar-indexed. */
402
+ private argSeries;
403
+ private frameArgs;
404
+ /** A series key in the frame that is running — the plain name at script scope. */
405
+ private scoped;
406
+ private states;
407
+ private committedSnapshot;
408
+ vars: Map<string, Series>;
409
+ time: number;
410
+ open: number;
411
+ high: number;
412
+ low: number;
413
+ close: number;
414
+ volume: number;
415
+ currentBarIndex: number;
416
+ /**
417
+ * The chart's timeframe, as Pine spells it: "1", "5", "60", "D", "W", "M".
418
+ *
419
+ * Read by `period` / `interval` in stdlib/chart.ts. A script that branches
420
+ * on the chart timeframe (a very common pattern in session and pivot
421
+ * indicators) computes different numbers depending on this, so a host that
422
+ * runs intraday data MUST set it — the default is the daily bars this
423
+ * repo's sample dataset uses, not a claim about the data supplied.
424
+ */
425
+ resolution: string;
426
+ /** The chart's symbol, as `tickerid` / `ticker` report it. */
427
+ symbol: string;
428
+ /**
429
+ * The instrument's minimum price increment, read by `math.round_to_mintick`.
430
+ *
431
+ * A property of the SYMBOL, so a host that charts anything other than the
432
+ * sample dataset must set it. The default is the two-decimal tick of a
433
+ * typical equity, which is a starting point rather than a claim about the
434
+ * data supplied — the same stance `resolution` takes above.
435
+ */
436
+ mintick: number;
437
+ /**
438
+ * The rest of what a script can ask about its instrument — the `syminfo.*`
439
+ * family added at v4.
440
+ *
441
+ * ── Why these are host-declared, with stated defaults ───────────────────
442
+ *
443
+ * None of it is derivable from OHLCV bars. A CSV of prices does not say
444
+ * which exchange listed them, what currency they are quoted in, or which
445
+ * time zone the session runs in. So each is a field the host sets, and each
446
+ * default below is a CHOICE that a script can act on:
447
+ *
448
+ * `syminfo.type == "crypto"` selects a different pivot window in a large
449
+ * share of published indicators, and `syminfo.currency` appears directly
450
+ * in position-sizing arithmetic.
451
+ *
452
+ * Left unset, a script gets the defaults — a US equity quoted in USD on a
453
+ * one-cent tick — which is exactly what this repo's sample dataset is. That
454
+ * is a starting point, not a claim about whatever data a host supplies.
455
+ *
456
+ * Two are DERIVED rather than stored, because Pine defines them in terms of
457
+ * the ticker id: `syminfo.prefix` and `syminfo.ticker` are the two halves
458
+ * of `EXCHANGE:SYMBOL`. Storing them separately would let them drift out of
459
+ * step with the symbol they describe.
460
+ */
461
+ currency: string;
462
+ basecurrency: string;
463
+ description: string;
464
+ symbolType: string;
465
+ symbolRoot: string;
466
+ timezone: string;
467
+ pointvalue: number;
468
+ sessionSpec: string;
469
+ mincontract: number;
470
+ /** True once the host supplied `mincontract`; only then are order quantities floored to it. */
471
+ mincontractSupplied: boolean;
472
+ isin: string;
473
+ currentContract: string | null;
474
+ /** The live quote, for `bid` / `ask` (v6). Not in an OHLCV bar. */
475
+ bid: number;
476
+ ask: number;
477
+ /**
478
+ * Volume at price, per bar, for `request.footprint()` (v6). Keyed by bar
479
+ * open time. `null` until a host supplies any — see getFootprintLevels.
480
+ */
481
+ private footprints;
482
+ /** Supplies footprint data: for each bar, its volume per price level. */
483
+ provideFootprints(bars: ReadonlyArray<{
484
+ time: number;
485
+ levels: FootprintLevel[];
486
+ }>): void;
487
+ /**
488
+ * This bar's levels, `null` when the host has none for it, and a refusal
489
+ * when the host has supplied no footprints at all — the `request.*` rule:
490
+ * a series this engine cannot fetch is asked for, not silently `na`.
491
+ */
492
+ getFootprintLevels(): FootprintLevel[] | null;
493
+ /** The source location of the one `request.footprint()` call, once seen. */
494
+ private footprintSite;
495
+ /**
496
+ * "Scripts can make only one request.footprint() call. Multiple calls
497
+ * cause a runtime error." A CALL is a place in the source: the innermost
498
+ * call id, which carries its location. Counting executions instead
499
+ * rejected one call inside a `for` loop on the first bar.
500
+ */
501
+ countFootprintRequest(): void;
502
+ /** Declares the current bid and ask. See `bid` in stdlib/v6_members.ts. */
503
+ provideQuote(quote: {
504
+ bid?: number;
505
+ ask?: number;
506
+ }): void;
507
+ /** Declares the instrument's metadata. Every field is optional. */
508
+ provideSymbolInfo(info: {
509
+ symbol?: string;
510
+ mintick?: number;
511
+ currency?: string;
512
+ basecurrency?: string;
513
+ description?: string;
514
+ type?: string;
515
+ root?: string;
516
+ timezone?: string;
517
+ pointvalue?: number;
518
+ session?: string;
519
+ mincontract?: number;
520
+ isin?: string;
521
+ current_contract?: string;
522
+ }): void;
523
+ /**
524
+ * How many bars the host is going to feed, and when the last one opens.
525
+ *
526
+ * ── Why the host has to say ─────────────────────────────────────────────
527
+ *
528
+ * `last_bar_index` and `last_bar_time` (v5, December 2021) are documented
529
+ * as "known at the beginning of the script's calculation" — on bar 1 a
530
+ * script can already ask how far the dataset runs, which is how the common
531
+ * `bar_index == last_bar_index - 1` idiom works at all.
532
+ *
533
+ * This engine streams bars: `setBar` is called one at a time and the
534
+ * Context has no way to know how many more are coming. Only the caller
535
+ * knows, so the caller declares it. `mock_run` reads it off the CSV; the
536
+ * session API takes it from the candle array it was handed.
537
+ *
538
+ * ── When it is not declared ─────────────────────────────────────────────
539
+ *
540
+ * The fallback is the highest bar seen SO FAR, which is correct on the last
541
+ * bar and an underestimate before it. That is a real divergence from
542
+ * TradingView rather than a hidden approximation, and it is why
543
+ * `provideDatasetExtent` exists: a host that can be exact should be.
544
+ */
545
+ datasetSize: number | null;
546
+ datasetLastTime: number | null;
547
+ /** Declare the extent of the run before the first bar. */
548
+ provideDatasetExtent(barCount: number, lastBarTime?: number): void;
549
+ /** Bar index of the last bar in the dataset — `last_bar_index`. */
550
+ get lastBarIndex(): number;
551
+ /**
552
+ * Is the script executing on the dataset's final bar — `barstate.islast`?
553
+ *
554
+ * Derived, not stored. `is_last` is written only by `applyTick`, which is
555
+ * the LIVE path, so on a purely historical run nothing ever set it and
556
+ * `barstate.islast` was false on every bar including the last one. That
557
+ * silently broke the commonest idiom in Pine — `if barstate.islast` is how
558
+ * every dashboard table, summary label and end-of-chart drawing is guarded,
559
+ * and all of them simply never ran.
560
+ *
561
+ * The extent is known: `runHistory` declares it before the first bar. So
562
+ * the answer is the comparison, with the live flag kept as an override for
563
+ * the realtime bar, which is last by definition however long the dataset is.
564
+ */
565
+ get onLastBar(): boolean;
566
+ /**
567
+ * How many bars at the END of the run the host marks REALTIME.
568
+ *
569
+ * ── Why the host has to say ─────────────────────────────────────────────
570
+ *
571
+ * `barstate.islastconfirmedhistory` is "true if the script is executing on
572
+ * the dataset's last bar when the market is closed, or on the bar
573
+ * immediately preceding the realtime bar if the market is open". The second
574
+ * reading needs to be answered ON that preceding bar — one bar before the
575
+ * realtime flag arrives — so it cannot be inferred from `is_realtime`, which
576
+ * the host sets bar by bar.
577
+ *
578
+ * The same shape as `provideDatasetExtent` above, and for the same reason:
579
+ * only the caller knows how its run ends. Zero — no realtime bar, the whole
580
+ * dataset is settled history — is the default, because a host that says
581
+ * nothing is replaying closed bars. Every harness in this repository that
582
+ * models an open market (`is_realtime = is_last`) sets it to 1.
583
+ *
584
+ * Getting this wrong is silent: with a realtime bar unaccounted for, the
585
+ * guard fires on NO bar at all, and the twenty-nine documentation examples
586
+ * that draw their labels and tables under it drew nothing.
587
+ */
588
+ realtimeBars: number;
589
+ /** Bar index of the last bar the script may treat as settled history. */
590
+ get lastConfirmedHistoryIndex(): number;
591
+ /** Opening time of the last bar in the dataset — `last_bar_time`. */
592
+ get lastBarTime(): number;
593
+ /**
594
+ * The chart's background colour — what `chart.bg_color` returns.
595
+ *
596
+ * "chart.bg_color returns the color of the chart's background from the
597
+ * 'Chart settings/Appearance/Background' field."
598
+ *
599
+ * There is no chart here, so the host declares it. The default is
600
+ * TradingView's own light-theme background, `#ffffff`, which is a CHOICE
601
+ * and not a fact about the run — a script that draws its own dark
602
+ * dashboard will get black text out of `chart.fg_color` unless the host
603
+ * says otherwise. That is why the setter exists.
604
+ */
605
+ chartBgColor: string;
606
+ /** Declares the chart's background colour, for `chart.bg_color`. */
607
+ provideChartBackground(color: string): void;
608
+ /**
609
+ * The first and last VISIBLE bar times — `chart.left_visible_bar_time` and
610
+ * `chart.right_visible_bar_time`.
611
+ *
612
+ * "chart.left_visible_bar_time returns the time of the leftmost bar
613
+ * currently visible on the chart."
614
+ *
615
+ * Visibility is a property of a viewport, and this engine has none: it runs
616
+ * every bar it is given. So the visible range defaults to the WHOLE
617
+ * dataset, which is what a script scanning "the bars on screen" would see
618
+ * if the user had zoomed all the way out.
619
+ *
620
+ * Stated rather than hidden, because it changes results: a script that
621
+ * counts bars between the two, or anchors a VWAP to the left edge, gets the
622
+ * full history here and a window on TradingView. A host that knows the
623
+ * viewport should declare it.
624
+ */
625
+ visibleRange: {
626
+ left: number;
627
+ right: number;
628
+ } | null;
629
+ /** Declares the visible bar range, in milliseconds. */
630
+ provideVisibleRange(leftTime: number, rightTime: number): void;
631
+ /** Opening time of the leftmost visible bar. */
632
+ get leftVisibleBarTime(): number;
633
+ /** Opening time of the rightmost visible bar. */
634
+ get rightVisibleBarTime(): number;
635
+ /**
636
+ * Opening time of the FIRST bar seen, or 0 before any bar.
637
+ *
638
+ * Recorded on the first `setBar` rather than derived, because the Context
639
+ * is fed one bar at a time and nothing else remembers where the run began.
640
+ */
641
+ firstBarTime: number;
642
+ /**
643
+ * Lines emitted by `log.info` / `log.warning` / `log.error` (v5).
644
+ *
645
+ * Kept on the Context rather than written to the console because they are
646
+ * OUTPUT: scripts/common/make-log-harness.ts generates harnesses whose entire
647
+ * result is a log line per bar, and a host has to be able to read them back
648
+ * to reproduce locally what TradingView's Pine Logs pane shows.
649
+ */
650
+ logs: LogEntry[];
651
+ /**
652
+ * Wall-clock time reported by `timenow`, in ms.
653
+ *
654
+ * Settable so a backtest or replay is reproducible — reading Date.now()
655
+ * inside the script would make two runs of the same data disagree.
656
+ */
657
+ now: number;
658
+ plots: Map<string, (PlotData | null)[]>;
659
+ fills: Map<string, (FillData | null)[]>;
660
+ /**
661
+ * v4 drawing objects — line, label, box, table — keyed by their generated id.
662
+ *
663
+ * A Map rather than an array so `line.delete(id)` is O(1) and ids stay
664
+ * stable, which is what a host needs to diff one bar's drawings against the
665
+ * next instead of re-rendering everything.
666
+ *
667
+ * These do NOT feed `plots`: a drawing is not a per-bar value, it is an
668
+ * object with a lifetime. Nothing in the golden-parity suite reads them —
669
+ * TradingView's exported chart data does not include drawings either.
670
+ */
671
+ drawings: Map<string, Drawing>;
672
+ private drawingCounter;
673
+ /** Live ids per kind, oldest first, so the cap below can evict in O(1). */
674
+ private drawingOrder;
675
+ /**
676
+ * How many drawings of each kind survive, and when the rest are deleted.
677
+ *
678
+ * The limit is the script's own: `max_lines_count`, `max_labels_count`,
679
+ * `max_boxes_count` and `max_polylines_count` on `indicator()` /
680
+ * `study()` / `strategy()`, each defaulting to 50 (`declareScript` applies
681
+ * them). Linefills are fixed at 50. Tables are absent deliberately: Pine
682
+ * limits them by POSITION (one per slot), not by count, so an eviction rule
683
+ * would be the wrong shape.
684
+ *
685
+ * ── The rule, as TradingView applies it ─────────────────────────────────
686
+ *
687
+ * It does NOT hold the count at `limit`. Drawings pile up past the limit
688
+ * and are then cut back in one go: when creating a drawing makes the live
689
+ * count of that kind exceed `limit + 5`, the oldest are deleted until the
690
+ * count equals `limit`. It happens at the creation itself, so it also
691
+ * happens in the middle of one bar's execution.
692
+ *
693
+ * Measured with conformance/v5/parity/probe_drawing_limits.pine:
694
+ *
695
+ * limit 10, one line per bar the count after each bar goes
696
+ * 1, 2, ... 15, then 10, 11 ... 15, 10, ...
697
+ * limit 10, 30 labels in one the same sawtooth, per label.new
698
+ * execution
699
+ * limit 500, 1003 lines 505 remain, the newest 505
700
+ * (harness_trader_club)
701
+ *
702
+ * Log: conformance/v5/parity/probe_drawing_limits.BTCUSDT.5.csv. Only
703
+ * limits 10 and 500 were measured; the +5 slack is assumed to be constant
704
+ * in between and at the default of 50.
705
+ *
706
+ * Without any cap, `label.new` on every bar of a 6,000-bar chart retains
707
+ * 6,000 records here while TradingView shows about 50, so a script that
708
+ * reads its own drawings back diverges, and memory grows without bound on
709
+ * exactly the scripts most likely to be long-running.
710
+ */
711
+ private drawingLimits;
712
+ /** Registers a drawing and returns its id, evicting the oldest past the cap. */
713
+ newDrawing(kind: Drawing["kind"], props: Record<string, any>): string;
714
+ /**
715
+ * v5 and v6: who goes when a kind passes `limit + 5`.
716
+ *
717
+ * Measured on TradingView (dev-docs/tv-parity/_probes/version-scope/
718
+ * v6-gc-order-1, -2, -3 and v5-gc-order, limit 10; golden test
719
+ * tests/v6/runtime/drawing_eviction_tv.test.ts):
720
+ *
721
+ * - A drawing a `var` / `varip` variable holds RIGHT NOW is never
722
+ * evicted. One held only inside an array is not protected. UDT fields,
723
+ * maps and plain variables were not probed and are treated as not
724
+ * protected.
725
+ * - On a historical bar the oldest unprotected drawings go, whatever bar
726
+ * they were drawn on, until the count is `limit`.
727
+ * - On the realtime bar a pass first removes unprotected drawings from
728
+ * EARLIER bars, oldest first, and stops there, even with the count
729
+ * still above `limit`. Only a pass that finds no such drawing removes
730
+ * the current bar's own, oldest first, down to `limit`.
731
+ *
732
+ * v4 and earlier were not probed and keep the plain oldest-first cut.
733
+ */
734
+ private evictDrawings;
735
+ /** Ids of drawings that a `var` / `varip` variable holds at this moment, in any frame. */
736
+ private varHeldDrawings;
737
+ /**
738
+ * Removes a drawing, keeping the eviction queue in step.
739
+ *
740
+ * Deleting straight out of `drawings` would leave the id in `drawingOrder`,
741
+ * so the next eviction would shift off a dead id, delete nothing, and let
742
+ * the live count sit above the cap indefinitely.
743
+ */
744
+ deleteDrawing(id: any): void;
745
+ /**
746
+ * Reads a field off a user-defined-type instance.
747
+ *
748
+ * Unwraps a Series first: a UDT instance held in a variable is wrapped by
749
+ * `ctx.new_var` exactly as a number is, so `p.x` reaches this with the
750
+ * wrapper rather than the object.
751
+ *
752
+ * A missing field, or a read through `na`, gives `na` rather than throwing.
753
+ * Pine's type checker catches a misspelt field at COMPILE time and this
754
+ * engine does not model field types, so throwing here would turn a
755
+ * legitimate `na` object — the state every UDT variable starts in — into a
756
+ * crash.
757
+ */
758
+ field(target: any, name: string): any;
759
+ /**
760
+ * Writes a field on a user-defined-type instance, and returns the value.
761
+ *
762
+ * Writing through `na` is an ERROR, unlike reading. `p.x := 1` when `p` has
763
+ * never been constructed cannot mean anything, and silently discarding the
764
+ * write would leave a script that looks like it is accumulating state and
765
+ * is not — the failure this engine exists to prevent.
766
+ */
767
+ /**
768
+ * The value a UDT field stores: a series argument's value on this bar.
769
+ *
770
+ * `Item.new(bx, bar_index, t)` with `box bx = box.new(…)` and `float t =
771
+ * high` hands the constructor SERIES -- the variables' wrappers -- and an
772
+ * object that kept the wrapper would read whatever the variable holds on
773
+ * the bar it is READ, not the one it was built on. A `var` array of such
774
+ * objects then saw every element change together: all reported the latest
775
+ * bar's values, and `box.delete(oldest.bx)` deleted the NEWEST box.
776
+ * TradingView stores the value (checked with Pine Logs:
777
+ * dev-docs/tv-parity/_probes/na-drawings/parity-udt-fields.pine), as `setField` already did for writes.
778
+ *
779
+ * Arrays, maps, matrices, drawings and nested objects are values that are
780
+ * references, so unwrapping keeps sharing them exactly as Pine does.
781
+ */
782
+ fieldValue(value: any): any;
783
+ setField(target: any, name: string, value: any): any;
784
+ /**
785
+ * `obj.copy()` — a SHALLOW copy, which is what Pine specifies.
786
+ *
787
+ * Shallow matters: a UDT holding an array shares that array with its copy,
788
+ * and a script relying on the opposite would diverge from TradingView.
789
+ */
790
+ copyObject(target: any): any;
791
+ /**
792
+ * `receiver.name(args)` where `name` is a BUILT-IN method — v5's method-call
793
+ * syntax for the collection and drawing namespaces.
794
+ *
795
+ * `a.push(1)` and `array.push(a, 1)` are the same call in Pine, and the
796
+ * emitter cannot tell them apart statically: it does not track types, so it
797
+ * does not know whether `a` is an array, a matrix or a map. The receiver
798
+ * does know, at run time, so the dispatch happens here.
799
+ *
800
+ * A matrix is the one case a value cannot answer for itself — it is an
801
+ * array of arrays, which is also a valid array — so `matrix.new` marks its
802
+ * result. See `MATRIX_TAG` in runtime/v1/stdlib/matrix.ts.
803
+ */
804
+ /**
805
+ * Which declaration of an overloaded user function or method a call
806
+ * reaches, chosen from the VALUES: the emitted dispatcher's job when the
807
+ * types are not written down where the call is (see
808
+ * V5ToJsVisitor.emitOverloadDispatcher).
809
+ *
810
+ * Candidates are `[fn, min, max, declared parameter types]`. Those whose
811
+ * arity fits are scored by how well each argument's own type matches the
812
+ * declared one — a user-defined object by its `__type`, a drawing by its
813
+ * `__builtin`, an array by its first element, a number as int or float —
814
+ * and the best wins; a tie goes to the first declared.
815
+ */
816
+ pickOverload(name: string, args: any[], candidates: Array<[Function, number, number, Array<string | null>]>): Function | null;
817
+ /** Calls the overload a dispatcher picked, binding keyword arguments by its own parameter names. */
818
+ callPicked(fn: Function, args: any[], name: string): any;
819
+ /** Loop signals thrown across a `switch` arm's function boundary. */
820
+ readonly LOOP_BREAK: Readonly<{
821
+ loopSignal: "break";
822
+ }>;
823
+ readonly LOOP_CONTINUE: Readonly<{
824
+ loopSignal: "continue";
825
+ }>;
826
+ builtinMethod(id: string, receiver: any, name: string, ...args: any[]): any;
827
+ /**
828
+ * Method names only the DRAWING namespaces answer.
829
+ *
830
+ * Derived from the registry rather than listed, so a drawing member added
831
+ * later is covered and a name a collection also spells — `copy`, `get`,
832
+ * `set` — is not. Which of `line` and `box` a name belongs to does not
833
+ * matter here: the call is a no-op either way.
834
+ */
835
+ private static drawingOnly;
836
+ private static drawingOnlyMethods;
837
+ /** Which built-in namespace owns this value's methods, or null. */
838
+ private static receiverNamespace;
839
+ /** Looks a drawing up, or throws — a stale id is a bug, not a no-op. */
840
+ /**
841
+ * Titles of every `enum` member this script declares, keyed by the member's
842
+ * own value — `"OscType.rsi"` → `"Relative Strength Index"`.
843
+ *
844
+ * ── Why a member is a STRING and not an object ──────────────────────────
845
+ *
846
+ * Because `==` has to work, and the emitter emits JavaScript `==`. A script
847
+ * variable holds a Series, so `x == OscType.rsi` compares a Series to the
848
+ * member; JavaScript coerces an object against a PRIMITIVE via `valueOf`
849
+ * and compares two OBJECTS by reference, so a member object would have made
850
+ * every such comparison false — silently, on every bar.
851
+ *
852
+ * A string carries the enum name as a prefix, which keeps the rule the
853
+ * manual states: "Scripts cannot compare members of different enums ... even
854
+ * if the fields have identical names and titles." `A.up` and `B.up` are
855
+ * different strings.
856
+ *
857
+ * The TITLE cannot live in the member for the same reason, so it lives here
858
+ * — read back by `str.tostring`, which the manual calls "the only way to
859
+ * retrieve its title".
860
+ */
861
+ enumTitles: Map<string, string>;
862
+ /** Members of each enum, in declaration order — what a dropdown lists. */
863
+ enumMembers: Map<string, string[]>;
864
+ /**
865
+ * Records an enum declaration and returns its member table.
866
+ *
867
+ * Called once per bar from the emitted script, and idempotent: re-recording
868
+ * the same titles is a no-op, and the table it returns holds the same
869
+ * STRINGS every time, which is what makes `==` work across bars.
870
+ */
871
+ declareEnum(name: string, titles: Record<string, string>): Record<string, string>;
872
+ /** The title of an enum member, or undefined when the value is not one. */
873
+ enumTitle(value: any): string | undefined;
874
+ /**
875
+ * Every live drawing of one kind, oldest first — `polyline.all`,
876
+ * `linefill.all`, `label.all`.
877
+ *
878
+ * Reads the EVICTION QUEUE rather than filtering `drawings`, so the order
879
+ * is the one Pine documents (creation order) and deleted ids are already
880
+ * gone from it.
881
+ */
882
+ drawingIds(kind: Drawing["kind"]): string[];
883
+ /** `na`, in the sense every drawing accessor has to answer to. */
884
+ private static isNa;
885
+ /**
886
+ * A stand-in drawing for an `na` id: writes are discarded, reads are `na`.
887
+ *
888
+ * ── Why an na id is not an error ────────────────────────────────────────
889
+ *
890
+ * Because TradingView's own examples depend on it, on three separate pages.
891
+ *
892
+ * `v5-concepts-lines-and-boxes-02` declares `var line periodLine = na`
893
+ * and calls `line.set_color(periodLine, finalColor)` on the first period
894
+ * change — before anything has been assigned to it. Its OTHER branch is
895
+ * guarded with `else if not na(periodLine)`, so the author knew the value
896
+ * could be absent and did not guard the branch where it always is.
897
+ *
898
+ * `v5-language-loops-17` fills an array with `array.new<line>(20)` — 20
899
+ * `na` lines — and immediately loops `for pivotLine in pivotLines`
900
+ * reading `pivotLine.get_x2()`.
901
+ *
902
+ * `v4-essential-drawings-12` and `-14` are the published Pivot Points
903
+ * indicators: three `var line ... = na` declarations and a block that
904
+ * moves them before the first assignment.
905
+ *
906
+ * All four ran on TradingView, so a setter on an absent drawing does
907
+ * nothing and a getter on one returns `na`. Modelling that here rather than
908
+ * at each of the forty call sites keeps `getDrawing`'s contract — "hand me
909
+ * the record" — and makes a STALE id still an error, which it is.
910
+ *
911
+ * Fresh each time, so a discarded write can never be read back.
912
+ */
913
+ private static naDrawing;
914
+ getDrawing(id: any, fn: string): Drawing;
915
+ is_history: boolean;
916
+ is_realtime: boolean;
917
+ is_new: boolean;
918
+ /** True only during a realtime bar's closing execution (`Session.commit`); see `barstate.isconfirmed`. */
919
+ is_confirmed: boolean;
920
+ is_last: boolean;
921
+ position: Position;
922
+ /** Bar timestamp by bar index — see the note where it is written. */
923
+ barTimes: number[];
924
+ /** The bar a timestamp falls on, or NaN when no bar has carried it. */
925
+ barIndexOfTime(time: number): number;
926
+ cash: number;
927
+ /**
928
+ * Host overrides for the `strategy()` / `indicator()` declaration.
929
+ *
930
+ * TradingView's settings dialog lets a user change a strategy's starting
931
+ * capital or pyramiding without editing the script, and a host needs the
932
+ * same. They are consulted by `declareScript`, which is the only moment
933
+ * they can take effect: `cash` moves with the first fill, so seeding it
934
+ * afterwards would backdate money the broker has already spent.
935
+ *
936
+ * A key absent here means "use whatever the script declared".
937
+ */
938
+ propertyOverrides: Record<string, any>;
939
+ /**
940
+ * What the SCRIPT declared, before any host override.
941
+ *
942
+ * Kept apart so a settings panel can offer "reset to the script's value"
943
+ * — reading it back off the Context afterwards returns the override, which
944
+ * would make the reset button restore the thing it was resetting from.
945
+ */
946
+ declaredProperties: Record<string, any>;
947
+ /**
948
+ * The equity the strategy started with, for `strategy.initial_capital`.
949
+ *
950
+ * Held separately from `cash`, which moves with every fill — the two are
951
+ * equal only until the first trade closes, and the difference between them
952
+ * IS the realised profit.
953
+ *
954
+ * Set from `strategy(initial_capital = ...)` by declareScript().
955
+ */
956
+ initialCapital: number;
957
+ trades: Trade[];
958
+ orders: any[];
959
+ /**
960
+ * The equity curve, one point per CLOSED bar, for strategies only.
961
+ *
962
+ * A study never opens a position, so sampling one would append a flat
963
+ * line of `initial_capital` to every indicator's render model.
964
+ */
965
+ equityCurve: EquityPoint[];
966
+ /**
967
+ * Excursion of the position currently open, in currency, reset when a
968
+ * position is opened from flat and read when it closes. Both are >= 0.
969
+ *
970
+ * Tracked on the Context rather than on the Position because they are
971
+ * measured across bars and the broker only ever sees one: by the time
972
+ * `closeAt` runs, the high the trade reached three bars ago is gone.
973
+ */
974
+ positionRunup: number;
975
+ positionDrawdown: number;
976
+ /** The largest absolute position size held at any point in the run. */
977
+ maxContractsHeld: number;
978
+ /** Commission charged across the whole run, for the performance summary. */
979
+ commissionPaid: number;
980
+ /**
981
+ * The first and last close the strategy saw, for the buy & hold baseline
982
+ * TradingView draws against the equity curve. Recorded here because the
983
+ * render model is built from the Context alone and the candles are the
984
+ * caller's.
985
+ */
986
+ firstClose: number;
987
+ lastClose: number;
988
+ /**
989
+ * Market orders placed on this bar, waiting for the next bar's open.
990
+ *
991
+ * `setBar` drains it; see MarketOrder in ./stdlib/strategy.ts for why a
992
+ * market order does not fill on the bar that places it.
993
+ */
994
+ marketOrders: MarketOrder[];
995
+ /** Orders filled this run: entries and exits. See `Broker.countOrder`. */
996
+ filledOrderCount: number;
997
+ /** The closed trades' profit, summed as they close, in order: `netProfit()` reads it instead of re-adding every trade. */
998
+ netProfitSum: number;
999
+ inputDefs: InputDef[];
1000
+ userInputs: Record<string, any>;
1001
+ private inputCounter;
1002
+ opsv2_na: number;
1003
+ scriptMeta: ScriptMeta | null;
1004
+ __sandbox: any;
1005
+ private securityData;
1006
+ private htfStates;
1007
+ requestedSecurities: Set<string>;
1008
+ /**
1009
+ * The language profile this script runs under. Drives version-dependent
1010
+ * runtime behaviour: banned identifiers (`bar_index` vs `n`) and the
1011
+ * security() lookahead default.
1012
+ *
1013
+ * profiles/ depends only on version.ts and nothing in runtime/, so this
1014
+ * import direction introduces no cycle.
1015
+ */
1016
+ readonly profile: LanguageProfile;
1017
+ constructor(profile?: LanguageProfile);
1018
+ private initBaseSeries;
1019
+ /**
1020
+ * Resets the entire execution environment for a new pass.
1021
+ * Crucially, preserves `inputDefs` and `userInputs`.
1022
+ */
1023
+ reset(): void;
1024
+ /**
1025
+ * Records the script's `study()` / `strategy()` directive metadata.
1026
+ * Called from transpiled code; positional args are mapped to named params
1027
+ * in directive-declaration order, with keyword args (prefix-stripped) taking
1028
+ * precedence. Idempotent across bars.
1029
+ */
1030
+ declareScript(kind: ScriptKind, positional?: any[], kwargs?: Record<string, any>): void;
1031
+ /**
1032
+ * @param meta the input's CONSTRAINTS and grouping — `minval`, `maxval`,
1033
+ * `step`, `options`, `tooltip`, `inline`, `group`, `confirm`,
1034
+ * `display`. Recorded on the definition so a host can render the
1035
+ * control, and the numeric three are ENFORCED on the value below.
1036
+ *
1037
+ * They used to be accepted and thrown away, on the reasoning that a
1038
+ * constraint the engine does not enforce is worse than no constraint.
1039
+ * The reasoning was right and the conclusion was not: the answer is
1040
+ * to enforce it. `minval` appears on seventeen of TradingView's own
1041
+ * examples and `options` restricts an input to a fixed set, and a
1042
+ * user override outside either was being taken at face value.
1043
+ */
1044
+ registerInput(defval: any, title?: string, type?: string, meta?: Record<string, any>): any;
1045
+ /** `registerInput`'s last answer for each input, by its position in the run. */
1046
+ private inputMemo;
1047
+ /** Clamp to `minval`/`maxval` and fall back to the default outside `options`. */
1048
+ private static constrainInput;
1049
+ /**
1050
+ * SMART CALL EXECUTION
1051
+ * Handles:
1052
+ * 1. Registry Lookup (Values vs Functions)
1053
+ * 2. Context Injection
1054
+ * 3. Keyword Argument Resolution (kwargs)
1055
+ */
1056
+ /**
1057
+ * How the CURRENT call was written: which parameters the caller named, and
1058
+ * how many arguments it passed positionally.
1059
+ *
1060
+ * `call` flattens keyword arguments into the positional array below, which
1061
+ * is what a JavaScript function can receive — and that flattening throws
1062
+ * away the one thing an OVERLOADED built-in needs. `line.new` has two
1063
+ * published signatures, the registry holds one, and which slot a value
1064
+ * belongs to depends on whether the caller wrote it as `width = 3` or as
1065
+ * the fifth thing in the list. See `pointTail` in stdlib/drawings.ts.
1066
+ *
1067
+ * Set for the duration of one call and restored afterwards, so a built-in
1068
+ * that calls another does not read its caller's shape.
1069
+ */
1070
+ keywordArguments: ReadonlySet<string>;
1071
+ /** How many arguments the caller passed before any keyword. */
1072
+ positionalArguments: number;
1073
+ /**
1074
+ * Resolve a trailing keyword bundle against a parameter list.
1075
+ *
1076
+ * The same rule the built-in path applies, over names the emitter supplies
1077
+ * rather than names the registry does. A gap is left `undefined` so the
1078
+ * emitted function's own JavaScript default takes over, which is how a
1079
+ * Pine parameter default is expressed.
1080
+ */
1081
+ private bindKeywordArguments;
1082
+ /**
1083
+ * Does `fname` have a parameter called `name` at THIS version?
1084
+ *
1085
+ * The stdlib is one implementation across versions, so a parameter added
1086
+ * for v6 — `active` on every input, `text_formatting` on labels — is on
1087
+ * the shared function. V6_ONLY_PARAMS says which those are, and below v6
1088
+ * they are treated as absent.
1089
+ */
1090
+ private parameterExists;
1091
+ /**
1092
+ * Positional arguments, re-slotted for the signature THIS version has.
1093
+ *
1094
+ * The stdlib keeps one signature per function, the union of every
1095
+ * version's, and two kinds of version difference sit in the middle of one:
1096
+ *
1097
+ * below v6 a parameter v6 ADDED (V6_ONLY_PARAMS). Filled by position
1098
+ * it is cleared, so it does what it did before it existed:
1099
+ * nothing. Keyword use is refused separately, in the binding
1100
+ * below.
1101
+ * at v6 a function whose v6 order differs from the shared one —
1102
+ * v6 removed `transp` and `when` from the middle of several.
1103
+ * Positional arguments are placed BY NAME from V6_POSITIONAL:
1104
+ * v6 `plot(close, "p", c, 2, style, false)` means trackprice =
1105
+ * false, not transp. A name the shared signature lacks is
1106
+ * dropped and recorded, like an unknown keyword.
1107
+ */
1108
+ private positionalForVersion;
1109
+ call(id: string, fn: Function, ...args: any[]): any;
1110
+ /**
1111
+ * Pins a runtime error to the Pine source position of the call it passed
1112
+ * through, so the host can say WHERE the script failed.
1113
+ *
1114
+ * Every call id carries its position -- `array.get@L45:C12`, and
1115
+ * `array.get@L4:C14@L` for a call inside the library imported as `L` -- so
1116
+ * the position costs nothing until something throws: this runs only on
1117
+ * the way out, once per frame, and the first frame an error passes through
1118
+ * is the innermost call, which is the one that raised it. It is a property
1119
+ * of the error rather than text in its message, which other code matches.
1120
+ *
1121
+ * An error inside a library is located at the library's own line, and
1122
+ * the first frame outside it -- the call in the user's own script, the
1123
+ * line an editor can show -- is kept as `caller`.
1124
+ *
1125
+ * Not covered: an error raised in script code that is not inside any call
1126
+ * (nothing the stdlib throws is), which has no position to report.
1127
+ */
1128
+ private locateError;
1129
+ /**
1130
+ * Resolves a built-in by name straight from the registry.
1131
+ *
1132
+ * Pine keeps functions and variables in separate namespaces, so
1133
+ * `sma = sma(close, 10)` is legal and common. The emitted JavaScript has no
1134
+ * such separation: `let opsv2_sma = ...` shadows the sandbox binding, and
1135
+ * the call inside its own initialiser saw the variable (undefined) instead
1136
+ * of the function. The transpiler routes shadowed built-in callees here.
1137
+ */
1138
+ /**
1139
+ * The stdlib entry for `name` AT THIS SCRIPT'S VERSION.
1140
+ *
1141
+ * `REGISTRY` is deliberately the union of every version's view, so a few
1142
+ * names carry two meanings in it and the union's last writer decides which
1143
+ * one survives. `float`, `bool` and `string` are the v1–v3 `input()` type
1144
+ * CONSTANTS and v4's casting FUNCTIONS; `session` is a v1–v3 constant and a
1145
+ * v5 namespace. Reading the union directly handed a v4 script the string
1146
+ * "float" where it had called `float(na)` — "[Context.call] ... The
1147
+ * provided reference is not a function".
1148
+ *
1149
+ * This is the preference `createStdlib` already applies to the sandbox,
1150
+ * applied to the CALL path as well. Falling back to the union keeps a name
1151
+ * the view does not carry resolvable: the transpiler has already decided
1152
+ * what a script may spell, and the runtime is not the place to refuse a
1153
+ * second time.
1154
+ */
1155
+ private entryFor;
1156
+ /** `entryFor`'s answers: the registries are large and the same few names are asked for on every bar. */
1157
+ private entries;
1158
+ builtin(name: string): any;
1159
+ /**
1160
+ * `and` over operands that have ALREADY been evaluated.
1161
+ *
1162
+ * Pine does not short-circuit, so the emitter passes every operand as an
1163
+ * argument and lets JavaScript's own evaluation order do the work. The
1164
+ * body is trivial on purpose: all the meaning is in the call having
1165
+ * happened at all. See visitAnd_expr in transpiler/v1/ToJsVisitor.ts.
1166
+ */
1167
+ allOf(...operands: boolean[]): boolean;
1168
+ /** `or` over operands that have already been evaluated. See `allOf`. */
1169
+ anyOf(...operands: boolean[]): boolean;
1170
+ /**
1171
+ * Per call site: the bar and pass it was last reached on, and whether it
1172
+ * has run at all. One lookup per call where there were three.
1173
+ */
1174
+ private siteReach;
1175
+ /**
1176
+ * The call sites a script is known to reach more than once on a bar. Null
1177
+ * (the default, and always for a forming bar) takes a rollback point at
1178
+ * every stateful site on every bar; a set takes one only at the sites in it,
1179
+ * and `missedRepeat` reports the first site that needed one and had none.
1180
+ *
1181
+ * Performance: only `Session.runHistory` sets it, for the history bars,
1182
+ * which are never re-ticked. A live or forming bar is re-ticked and rolled
1183
+ * back at every site, so it must keep this null: `runHistoryOnce` resets it
1184
+ * before returning, and anything else that runs bars must not set it.
1185
+ * Guarded by tests/v1/runtime/rollback_snapshot_work.test.ts. See
1186
+ * docs/common/ARCHITECTURE.md ("Performance invariants").
1187
+ */
1188
+ repeatingSites: Set<string> | null;
1189
+ /** The site that was reached again on a bar with no rollback point; see `repeatingSites`. */
1190
+ missedRepeat: string | null;
1191
+ /** Counts passes over a bar: `forgetBarProgress` starts a new one. */
1192
+ private barPass;
1193
+ /**
1194
+ * Call sites that own series state, learned the first time one asks for it.
1195
+ *
1196
+ * A site that has run and never asked — `hl2`, `math.abs`, `array.get` —
1197
+ * has nothing to roll back, so it is skipped entirely. Without this every
1198
+ * stateless call took the snapshot path on every bar.
1199
+ */
1200
+ private statefulSites;
1201
+ /**
1202
+ * State keys at or beneath each call site, so a rollback does not have to
1203
+ * scan every state in the run to find the handful it owns.
1204
+ *
1205
+ * A built-in may call another through `ctx.call` — `ta.stoch` delegates to
1206
+ * `ta.highest` — and the nested state hangs below the outer site's key, so
1207
+ * rolling the outer one back has to take the inner ones with it.
1208
+ */
1209
+ private stateKeysBySite;
1210
+ /** State keys already indexed in `stateKeysBySite`. */
1211
+ private indexedStateKeys;
1212
+ /**
1213
+ * Rollback points for the sites in `repeatableCalls`, taken at their first
1214
+ * reach of the bar, and dropped by `forgetBarProgress` when the bar moves
1215
+ * on or is re-run. The per-entry `bar` is still checked, because a host
1216
+ * may drive the stdlib directly and never call `setBar` at all.
1217
+ *
1218
+ * ── One slot per BAR, not one per call ─────────────────────────────────
1219
+ *
1220
+ * A series function's history has one slot per bar. Reaching its call site
1221
+ * again on the same bar OVERWRITES that bar's slot rather than appending a
1222
+ * second one, and the value returned is the one including whatever was
1223
+ * just written. Earlier bars keep whatever their LAST reach wrote.
1224
+ *
1225
+ * That one rule accounts for every loop probe TradingView has answered:
1226
+ *
1227
+ * - Same arguments every reach (the loops export): each reach writes the
1228
+ * same value into the same slot, so all three reads agree and
1229
+ * `loopEma / 3` equals a plain `ta.ema(close, 10)` on all 2,475 bars.
1230
+ * - Different arguments (the loopargs export): each reach overwrites,
1231
+ * and the three reads differ. Replaying this rule against that export
1232
+ * reproduces TradingView on all 2,464 bars it reports, na-parity
1233
+ * included — where "the first reach wins", "the last reach wins" and
1234
+ * "each reach keeps its own history" each miss every single bar.
1235
+ * - Never reached at all: nothing is written, so the history does not
1236
+ * advance and the site goes STALE. `probe_call_inside_if` and
1237
+ * `probe_ternary_both_arms` both confirm that, and it is why this is
1238
+ * not symmetric with the two cases above.
1239
+ *
1240
+ * Overwriting is implemented as rollback-and-rerun, which is the same
1241
+ * thing from the outside and does not require every stateful built-in to
1242
+ * grow a re-entry path of its own.
1243
+ *
1244
+ * Which sites get one, and why not all of them, is at the call site in
1245
+ * `call()`.
1246
+ */
1247
+ private barStateSnapshot;
1248
+ /**
1249
+ * Forgets everything scoped to ONE PASS over the current bar.
1250
+ *
1251
+ * Called at the top of `setBar`, which covers every bar and every re-tick
1252
+ * of a forming one, and again from `restoreSnapshot` so that rolling back
1253
+ * to a baseline restores this too. "One pass" includes the script's own
1254
+ * execution-order counters, not just the broker's bookkeeping.
1255
+ *
1256
+ * The two order guards are the subtle half. They are NOT part of
1257
+ * `captureSnapshot`, so a rollback restored the broker's position and its
1258
+ * pending orders while leaving the guards saying "already done for this
1259
+ * bar". Tick 2 then skipped both the market fills and the pending-order
1260
+ * matching, and the script saw a position that had already been closed.
1261
+ *
1262
+ * `repeatableCalls` deliberately survives: which sites repeat is learned
1263
+ * about the SCRIPT, not about the bar.
1264
+ */
1265
+ private forgetBarProgress;
1266
+ /** Every state entry belonging to `key` or nested beneath it. */
1267
+ private collectCallState;
1268
+ /** Remembers `key`'s state so a later reach on this bar can undo its own. */
1269
+ private snapshotCallState;
1270
+ /** Puts `key` back as it was at the start of the bar. */
1271
+ private restoreCallState;
1272
+ getPersistentState<T>(initFn: () => T): T;
1273
+ /** Declaration sites whose initialiser has already run, keyed by @L:C. */
1274
+ private varInitialised;
1275
+ /** Series declared `varip`, exempt from intra-bar rollback. */
1276
+ private varipSeries;
1277
+ /** Every series declared `var` or `varip`, by scoped name: what drawing eviction treats as a holder. */
1278
+ private varSeries;
1279
+ /**
1280
+ * `var x = expr` — initialise ONCE, then persist across bars.
1281
+ *
1282
+ * The initialiser is passed as a thunk, not a value: `var x = expr` must not
1283
+ * evaluate `expr` after the first bar. Passing the value would evaluate it
1284
+ * every bar and throw the result away, which is invisible for `0.0` and
1285
+ * quite visible for `var count = count + 1` or anything with a side effect.
1286
+ *
1287
+ * Persistence re-stamps the LAST value onto the current bar, read from
1288
+ * `valueOf()` rather than `get(1)`. The two differ exactly when the variable
1289
+ * was last written on an earlier bar than the previous one — a `var` inside
1290
+ * a conditional block — where `get(1)` finds a hole and returns NaN while
1291
+ * `valueOf()` returns the value that is actually still held. Pine persists
1292
+ * the value, so `valueOf()` is the right question.
1293
+ *
1294
+ * Re-stamping every bar is also what makes `:=` work unchanged: it writes
1295
+ * the current bar's slot through `new_var`, and the next bar carries that
1296
+ * forward. No write-back path and no coupling to the assignment emitter.
1297
+ *
1298
+ * @param key the declaration's @L<line>:C<col> site, so two DIFFERENT
1299
+ * `var` declarations do not share an initialised flag.
1300
+ *
1301
+ * ── `var` inside a user function ────────────────────────────────────────
1302
+ *
1303
+ * Every call site gets its OWN `var`, which is what Pine does — "each
1304
+ * function call has its own instance of the series variables". Given
1305
+ *
1306
+ * f() =>
1307
+ * var c = 0
1308
+ * c := c + 1
1309
+ * c
1310
+ * a = f()
1311
+ * b = f()
1312
+ *
1313
+ * both `a` and `b` count 1,2,3.
1314
+ *
1315
+ * This was wrong for a long time, and the note that stood here explained
1316
+ * why keying the initialised flag on the call stack was not enough: the
1317
+ * SERIES NAME was shared too, so per-site flags only redistributed the
1318
+ * error. Both halves are now in place — `seriesScope` gives each frame its
1319
+ * own namespace in `vars`, and this method scopes the flag into the same
1320
+ * namespace — so `key` alone is the declaration SITE and the frame supplies
1321
+ * the instance.
1322
+ *
1323
+ * `varipSeries` is scoped with them, so a `varip x` in one frame and a
1324
+ * `var x` in another are no longer one series and no longer share an
1325
+ * exemption from rollback.
1326
+ */
1327
+ var_def(name: string, key: string, initFn: () => any, ip?: boolean): Series;
1328
+ /**
1329
+ * Pine truthiness, for anything used as a CONDITION.
1330
+ *
1331
+ * ── Why this exists ─────────────────────────────────────────────────────
1332
+ *
1333
+ * `new_var` returns a Series, and in JavaScript EVERY object is truthy. So
1334
+ * a condition stored in a variable was permanently true:
1335
+ *
1336
+ * up = close > open // a Series wrapping true/false
1337
+ * plot(up ? 1 : 0) // 1 on every bar, forever
1338
+ *
1339
+ * Relational operators hid it — `close > open` coerces through valueOf() and
1340
+ * works — but `?:`, `if`, `and`, `or` and `not` use ToBoolean, which does not
1341
+ * consult valueOf(). Naming a condition and reusing it is how nearly every
1342
+ * published script is written, so every branch in them was taken.
1343
+ *
1344
+ * The corpus suite could not see this: a script whose conditions are all
1345
+ * true still runs and still plots finite numbers.
1346
+ *
1347
+ * ── The rule ────────────────────────────────────────────────────────────
1348
+ *
1349
+ * Unwrap first, then apply Pine's own conversion: `na` is false, 0 is false,
1350
+ * any other number is true. v1–v3 coerce numbers to bool freely (`isSunday()
1351
+ * and openPrice ? ...` is published v1), so this must accept numbers rather
1352
+ * than demand a bool.
1353
+ */
1354
+ truthy(v: any): boolean;
1355
+ /**
1356
+ * FACTORY: Creates or Updates a Series Object.
1357
+ */
1358
+ new_var(name: string, val: any): Series;
1359
+ /**
1360
+ * `new_var` for the series behind a history reference on an expression,
1361
+ * `(close + 0)[3]`: bar-indexed, carrying its last value over the bars the
1362
+ * expression was not evaluated on. See `Series.updateCarry`.
1363
+ */
1364
+ new_anon(name: string, val: any): Series;
1365
+ /**
1366
+ * FACTORY (Tuple): Handles assignments like [a, b] = func()
1367
+ */
1368
+ new_vars(names: string[], values: any): Series[];
1369
+ /**
1370
+ * `for … in` — one iteration handle over an array, matrix or map.
1371
+ *
1372
+ * Yields `[key, value]` pairs. What those mean is the collection's business,
1373
+ * and the manual states each one:
1374
+ *
1375
+ * array "item ... holds sequential values or references from the
1376
+ * specified collection_id" and index is "the index ... of the
1377
+ * retrieved item"
1378
+ * matrix the item is a ROW array, and the index is "the row's position
1379
+ * within the matrix"
1380
+ * map "the index contains the key, and item contains the associated
1381
+ * value from each key-value pair"
1382
+ *
1383
+ * A matrix is represented as an array of row arrays, so it needs no branch
1384
+ * of its own here: iterating an array of rows already yields rows.
1385
+ *
1386
+ * ── The size rule is different for maps, so the code is too ──────────────
1387
+ *
1388
+ * "Scripts can modify the sizes of arrays and matrices directly within a
1389
+ * for…in loop's local scope. When a for…in loop changes the size of a
1390
+ * collection during an iteration, the loop's header uses the UPDATED size
1391
+ * to control subsequent iterations."
1392
+ *
1393
+ * That is why the array branch re-reads `.length` every step instead of
1394
+ * capturing it once. A snapshot would run past the end of a shrunk array
1395
+ * and stop short of a grown one.
1396
+ *
1397
+ * "Attempting to add or remove a map's key-value pairs while looping
1398
+ * through it with this structure typically causes a runtime error."
1399
+ *
1400
+ * So the map branch does the opposite: it takes the keys once and then
1401
+ * REFUSES a size change, rather than silently iterating a stale key list.
1402
+ * `arity` is how many loop variables the script wrote, which is what makes
1403
+ * the single-variable form on a map reportable — the manual's second form
1404
+ * is the only one maps accept.
1405
+ */
1406
+ forIn(collection: any, arity: number, at: string): Generator<[any, any]>;
1407
+ /**
1408
+ * Universal History Accessor (Called by transpiled code: [])
1409
+ */
1410
+ get(target: any, offset: number, id: string): number;
1411
+ /**
1412
+ * A user variable declared OUTSIDE the running function: not one of its own
1413
+ * locals (their ids carry the frame's scope) and not a built-in series.
1414
+ */
1415
+ private isOuterUserSeries;
1416
+ /**
1417
+ * `v[n]` on a variable from outside the running function. TradingView keeps
1418
+ * a history of it per CALL SITE, recorded only when that call runs
1419
+ * (Pine Logs, dev-docs/tv-parity/_probes/fn-history): `[n]` is its value at
1420
+ * the n-th previous call of this site, not on the n-th previous bar. Before
1421
+ * that many calls it is na. The probe's first calls (bars 0, 0) log na, and
1422
+ * the next ones read the value the variable had at those first calls.
1423
+ */
1424
+ private callSiteHistory;
1425
+ /** The series registered under `name`, this frame's instance first. */
1426
+ private findSeries;
1427
+ /**
1428
+ * Internal helper for fallback lookups
1429
+ */
1430
+ getSeries(name: string, offset: number): number;
1431
+ setBar(time: number, open: number, high: number, low: number, close: number, volume: number, deferFills?: boolean): void;
1432
+ /** Whether the script asked to be re-run after each order fill. */
1433
+ get calcOnOrderFills(): boolean;
1434
+ /**
1435
+ * Set while a bar is being replayed for `calc_on_order_fills`: how far along
1436
+ * the bar's price path (segment index plus the share of it covered) the
1437
+ * last fill was, and the price there. Null otherwise.
1438
+ */
1439
+ intrabar: {
1440
+ cursor: number;
1441
+ price: number;
1442
+ } | null;
1443
+ /** The most times one bar is re-run after fills; TradingView's own limit is unknown. */
1444
+ private static readonly MAX_FILL_RERUNS;
1445
+ /**
1446
+ * Runs one historical bar of a `calc_on_order_fills` strategy.
1447
+ *
1448
+ * Each fill, in the order it happens along the bar's path, re-runs the
1449
+ * script on the whole bar with `var` and series state put back to the bar's
1450
+ * start (as a realtime tick does; `varip` keeps its value), while the broker
1451
+ * keeps what filled. A market order the re-run places fills at once at the
1452
+ * price of the fill that triggered it, and a limit or stop order it places
1453
+ * can fill later on the same bar. The ordinary run comes last, and its
1454
+ * effects are what the next bar sees. Verified on TradingView's probe
1455
+ * calc-on-fills (dev-docs/tv-parity/_probes/calc-on-fills) and the 12/26-IT
1456
+ * strategy (ENGINE-BUGS.md section 1).
1457
+ */
1458
+ runBarWithFills(time: number, open: number, high: number, low: number, close: number, volume: number, exec: () => void): void;
1459
+ /** The bar `fillMarketOrders` last ran on, so a re-tick does not refill. */
1460
+ private _marketOrdersFilledBar;
1461
+ /**
1462
+ * Registers a pre-built PlotData object for the current bar.
1463
+ * Each ui.ts function constructs the correctly-typed variant.
1464
+ */
1465
+ /**
1466
+ * Strip the runtime wrapper off every field of a plot or fill record.
1467
+ *
1468
+ * `f = plot(...)` compiles to `ctx.new_var(...)`, so the plot id a script
1469
+ * hands to `fill()` arrives as a `Series`, not a string — and a `Series`
1470
+ * holds a back-reference to this Context, which holds the sandbox, which
1471
+ * holds every stdlib function. Storing one put the entire runtime inside
1472
+ * the render model: `structuredClone` threw, so `postMessage` threw, so
1473
+ * every script using `fill()` failed in any host that runs the engine in a
1474
+ * Worker or a backend — which is the architecture the integration guide
1475
+ * documents. The values were all correct; the model just could not leave
1476
+ * the process it was built in.
1477
+ *
1478
+ * The same applies to `plot(close, color = myColorVar)`, where the colour
1479
+ * is a declared variable rather than a literal.
1480
+ */
1481
+ private plainPlotData;
1482
+ registerPlot(data: PlotData): void;
1483
+ registerFill(id1: any, id2: any, options?: {
1484
+ color?: string;
1485
+ title?: string;
1486
+ [k: string]: any;
1487
+ }): void;
1488
+ finalizeBar(): void;
1489
+ /**
1490
+ * Records one equity point and advances the open position's excursion.
1491
+ *
1492
+ * Run at the END of a bar, after the script and the broker have both had
1493
+ * it: an entry placed on this bar is already in `position`, and the high
1494
+ * and low of the bar it filled on count towards its run-up, which is what
1495
+ * TradingView reports.
1496
+ */
1497
+ /**
1498
+ * Mark-to-market on the open position, at a price the caller names.
1499
+ *
1500
+ * One formula, three callers: the equity curve samples it at each bar's
1501
+ * close, `strategy.equity()` answers with it, and the performance report
1502
+ * takes it at the last close. All three had their own copy, so a change
1503
+ * to any of them — deducting the entry commission, say — would have
1504
+ * landed in one place and quietly disagreed with the other two.
1505
+ *
1506
+ * The price is a parameter rather than read from the Context because the
1507
+ * report deliberately uses `lastClose` and the other two use `close`.
1508
+ */
1509
+ openProfitAt(price: number): number;
1510
+ /**
1511
+ * What one unit of the symbol's currency is worth in the strategy's
1512
+ * currency (`strategy(currency = ...)`) on this bar: the latest sample of
1513
+ * the host's `currency_rate` series at or before the bar's time. 1 when the
1514
+ * script asked for no other currency, or the host supplied no series, so
1515
+ * nothing converts and nothing throws.
1516
+ *
1517
+ * TradingView converts each closed trade at the exit bar's rate
1518
+ * (_probes/currency-usd-1btc). Open profit converted at the current bar's
1519
+ * rate, and sizing (percent_of_equity, cash qty) left unconverted, are
1520
+ * unverified against TradingView.
1521
+ */
1522
+ accountRate(): number;
1523
+ private sampleEquity;
1524
+ /**
1525
+ * Applies a real-time tick to the forming (last) bar, per the control
1526
+ * protocol §7. The candle is an ABSOLUTE snapshot of the forming bar, not an
1527
+ * increment. On the first tick of a bar we capture the committed baseline; on
1528
+ * every later tick we roll back to it first, so re-evaluation never
1529
+ * double-counts accumulating state (e.g. `cum`, EMA `prev`, broker orders).
1530
+ *
1531
+ * The caller runs the compiled script after this, then reads provisional
1532
+ * output. Finalize with `commitBar()` when the bar closes.
1533
+ */
1534
+ /**
1535
+ * `barstate.isnew` / `isconfirmed` follow TradingView's realtime bar states, and `Session.commit`
1536
+ * runs the closing execution. Probed on v5 (rt-confirmed-v5) and v6 (rt-semantics-v6,
1537
+ * rt-confirmed-v6); v1-v4 keep the older reading (isnew always true, isconfirmed = not realtime).
1538
+ */
1539
+ get realtimeBarStates(): boolean;
1540
+ applyTick(time: number, open: number, high: number, low: number, close: number, volume: number): void;
1541
+ /**
1542
+ * Commits the forming bar permanently and advances. After this the next
1543
+ * `applyTick` re-captures a fresh baseline for the new forming bar.
1544
+ */
1545
+ commitBar(): void;
1546
+ /** A rollback point for a sub-context (see `request.security_lower_tf`): opaque to the caller. */
1547
+ checkpoint(): unknown;
1548
+ /** Rolls this context back to a `checkpoint()`. */
1549
+ rollbackTo(cp: unknown): void;
1550
+ private captureSnapshot;
1551
+ private restoreSnapshot;
1552
+ private secKey;
1553
+ /** Supplies HTF candles for a (symbol, resolution); called by the session layer. */
1554
+ provideSecurityData(symbol: string, resolution: string, candles: SecurityCandle[]): void;
1555
+ /**
1556
+ * Seeded pseudo-random state, one stream per seed.
1557
+ *
1558
+ * Held on the Context rather than in the stdlib module so that two Contexts
1559
+ * — a backtest and a re-run of it — do not share a stream, and so that
1560
+ * `reset()` restarts the sequence. A seeded run that changed its numbers
1561
+ * because something else had drawn from the generator first would not be
1562
+ * reproducible, which is the only thing a seed is for.
1563
+ */
1564
+ private randomStreams;
1565
+ /**
1566
+ * The next number in the stream for `seed`, in [0, 1).
1567
+ *
1568
+ * mulberry32 — small, fast, and with a period far beyond any chart. NOT
1569
+ * TradingView's generator, which is unpublished; see `math.random`.
1570
+ */
1571
+ seededRandom(seed: number): number;
1572
+ /**
1573
+ * Timestamped samples for `request.financial` / `.dividends` / `.splits` /
1574
+ * `.earnings` / `.quandl`, keyed by kind and by the arguments that select a
1575
+ * series.
1576
+ *
1577
+ * Supplied by the host, like `securityData` above and for the same reason:
1578
+ * this engine has no connection to TradingView, and a request it cannot
1579
+ * answer must be refused rather than resolved to `na`.
1580
+ */
1581
+ private requestData;
1582
+ /**
1583
+ * Supplies one non-price series.
1584
+ *
1585
+ * `key` is what runtime/v1/stdlib/request.ts builds from the call's
1586
+ * arguments — `"NASDAQ:AAPL|TOTAL_REVENUE|FQ"`. Samples are sorted here, so
1587
+ * a host may provide them in any order.
1588
+ */
1589
+ provideRequestData(kind: string, key: string, samples: RequestSample[]): void;
1590
+ getRequestData(kind: string, key: string): RequestSample[] | undefined;
1591
+ /** The chart's own bars, as they arrive. The basis of a derived request. */
1592
+ private chartBars;
1593
+ /**
1594
+ * The history run's whole dataset, set by `Session.runHistory`: only the TIMES
1595
+ * of the bars after the current one are read, to tell where a session or a
1596
+ * higher-timeframe bar ends. No price is ever taken from it.
1597
+ */
1598
+ chartAhead: readonly {
1599
+ time: number;
1600
+ }[] | null;
1601
+ /** Derived higher-timeframe series, keyed by resolution, extended as bars arrive. */
1602
+ private derivedSeries;
1603
+ /** Heikin-Ashi transforms of a request's candles, keyed like `securityData`. */
1604
+ private heikinAshi;
1605
+ /**
1606
+ * Does `symbol` name the chart's OWN instrument?
1607
+ *
1608
+ * `request.security(syminfo.tickerid, …)` is how nearly every published
1609
+ * script asks for a higher timeframe of what it is already looking at, and
1610
+ * `syminfo.tickerid` is the exchange-qualified spelling of the same thing
1611
+ * `syminfo.ticker` gives bare. An empty symbol means the chart's too.
1612
+ */
1613
+ private isChartSymbol;
1614
+ /**
1615
+ * `request.security()` on the chart's own symbol, at the chart's timeframe
1616
+ * or a higher one, answered from the chart's own bars.
1617
+ *
1618
+ * ── Why this is exact and not an approximation ──────────────────────────
1619
+ *
1620
+ * A higher-timeframe bar IS the aggregate of the chart bars inside it: it
1621
+ * opens where the first one opened, closes where the last one closed, spans
1622
+ * their extremes and carries their total volume. That is the relationship
1623
+ * between the two series, not a model of it. TradingView builds its own
1624
+ * weekly candles from the same daily feed.
1625
+ *
1626
+ * Before this, `request.security(syminfo.tickerid, "D", close)` on a daily
1627
+ * chart returned `na` — the most basic call there is, silently empty on
1628
+ * every bar, in twenty-five of TradingView's own examples.
1629
+ *
1630
+ * ── What is still refused ───────────────────────────────────────────────
1631
+ *
1632
+ * A LOWER timeframe, and any other symbol. The manual is explicit about the
1633
+ * first: request.security() on a lower timeframe "will evaluate the
1634
+ * expression from the LTF context", and there is no LTF context to evaluate
1635
+ * it in — the intrabar prices are not in the data. Both keep returning `na`
1636
+ * and recording the request, so a host that HAS the data can supply it.
1637
+ *
1638
+ * ── Causality ───────────────────────────────────────────────────────────
1639
+ *
1640
+ * Only bars the script has already seen are folded. Aggregating the whole
1641
+ * dataset would hand the script the future: the group containing the
1642
+ * current bar would close before its last bar had arrived. `security()`
1643
+ * then applies its own closed-bar rule on top of a series that is already
1644
+ * causal.
1645
+ */
1646
+ private deriveSecurityData;
1647
+ /**
1648
+ * Is this request just the chart's own series, under another name?
1649
+ *
1650
+ * True only for a DERIVED series at the chart's own timeframe — the same
1651
+ * symbol, no host data, and a resolution that folds one chart bar into one
1652
+ * requested bar. Then the requested bar and the chart bar ARE the same bar,
1653
+ * and there is no closed-bar rule to apply: `request.security(
1654
+ * syminfo.tickerid, timeframe.period, close)` is the current close.
1655
+ *
1656
+ * Host-supplied data is never this, even at a matching label. A host may
1657
+ * hand over any series it likes under the name "D", and the conservative
1658
+ * closed-bar rule is the right default for something this engine cannot see
1659
+ * inside.
1660
+ */
1661
+ isOwnChartSeries(symbol: string, resolution: string): boolean;
1662
+ /**
1663
+ * Where a request's history starts: always the first bar. `calc_bars_count`
1664
+ * ("the maximum number of recent historical bars the function can request")
1665
+ * is accepted and has NO observable effect, which TradingView's own log
1666
+ * shows (conformance/v5/parity/harness_security_cbc, BTCUSDT 5m, 5,464 bars):
1667
+ * `request.security("60", bar_index, calc_bars_count = 50)` equals the same
1668
+ * call without it on every row, and `ta.cum(1)` inside it is 6984 at chart
1669
+ * bar 0; `security_lower_tf("1", bar_index, calc_bars_count = 200)` likewise
1670
+ * equals the call without it. 50 and 200 limited nothing.
1671
+ *
1672
+ * (A data-supply fact for hosts, not a broker rule: the hourly context's
1673
+ * history starts long before the chart's first bar, with 6,983 hourly bars
1674
+ * ahead of it there, while the 1-minute lower-timeframe context starts at the
1675
+ * chart's first bar.)
1676
+ */
1677
+ securityFirstBar(_symbol: string, _resolution: string, _calcBarsCount: any, _fname: string): number;
1678
+ getSecurityData(symbol: string, resolution: string, lookaheadOn?: boolean): SecurityCandle[] | undefined;
1679
+ /** Records a (symbol, resolution) the script needs but hasn't been given. */
1680
+ requestSecurity(symbol: string, resolution: string): void;
1681
+ /**
1682
+ * The chart's context, for a sub-context that evaluates a requested
1683
+ * expression; null on the chart's own. `syminfo.main_tickerid` and
1684
+ * `timeframe.main_period` read it.
1685
+ */
1686
+ main: Context | null;
1687
+ /**
1688
+ * A context to evaluate a `request.security` expression in: the requested
1689
+ * timeframe and symbol, and the chart's symbol facts when it is the
1690
+ * chart's symbol — `timeframe.period` inside the request is "5" for a
1691
+ * 5-minute request, as TradingView logs it, where a bare context said "1D".
1692
+ */
1693
+ securitySubContext(symbol: string, resolution: string): Context;
1694
+ /** Stable key for the current call site (used for per-`security()` HTF state). */
1695
+ currentCallKey(): string;
1696
+ /** Per-call-site HTF state, exempt from live-tick snapshot/rollback. */
1697
+ getHtfState<T>(key: string, initFn: () => T): T;
1698
+ /**
1699
+ * Evaluates a deferred `security()` expression for one HTF bar inside a
1700
+ * sub-context, by rebinding the VM's global series (open/high/.../close) and
1701
+ * `ctx` to the sub-context for the duration of the thunk. Restores on exit.
1702
+ */
1703
+ evalSecurityBar(sub: Context, bar: SecurityCandle, thunk: () => any, provisional?: boolean): any;
1704
+ }
1705
+ export {};