openpinescript 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +332 -2
  3. package/dist/cli.mjs +108691 -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,325 @@
1
+ /**
2
+ * The broker for Pine v1 to v5. TradingView's migration guides list no strategy
3
+ * or broker change between those versions: to-pine-version-3 and -4 have none,
4
+ * and to-pine-version-5 lists compile-time checks only (https://www.tradingview.com/pine-script-docs/migration-guides/to-pine-version-3/,
5
+ * .../to-pine-version-4/, .../to-pine-version-5/). It holds every rule
6
+ * TradingView's own logs have shown, and V6Broker extends it, overriding only
7
+ * what the v6 guide documents (https://www.tradingview.com/pine-script-docs/migration-guides/to-pine-version-6/).
8
+ *
9
+ * rule v1–v5 v6 status
10
+ * per-entry closed trades, first in first out yes yes (same) log-verified (pyr0, pyr3: v5 and v6 identical)
11
+ * strategy.close(id) takes its qty off the position FIFO yes yes (same) log-verified (pyr3 phase d)
12
+ * an opposite strategy.order closes the oldest entry yes yes (same) log-verified (pyr0 phase c)
13
+ * process_orders_on_close fills at the placing bar close yes yes (same) log-verified (pooc)
14
+ * close(immediately) fills at the bar close, after script yes yes (same) log-verified (pyr0 g2, g4)
15
+ * strategy.exit once per entry, qty_percent of opened yes yes (same) log-verified (v5 release_notes_04, v6 golden)
16
+ * two ledgers: an id's quantity apart from the trades yes yes (same) log-verified (exit_mix a; pyr3 d). from_entry / close(id) draw on the id, the fill takes the OLDEST trades
17
+ * relative exit levels from each trade's own fill price yes yes (same) log-verified (exit_mix a, e; reentry b: two L fills, each its own target)
18
+ * an exit with no from_entry is one order per entry id yes yes (same) log-verified (exit_mix c, d). Each id has its own qty and its own trail
19
+ * one exit unit per trade, re-armed when the id re-enters yes yes (same) log-verified (reentry a, 5m and 1D: xa fills again for the second A)
20
+ * a bar's exits fill in price-path order, oldest trade yes yes (same) log-verified (reentry c1-c4: limit first when the open is nearer the high, stop first when nearer the low)
21
+ * exit() before its entry exists has no effect that call yes yes (same) log-verified (exit_order d1/d2 on AAPL 5m; reentry a; release_notes_04)
22
+ * trailing stop with offset 0 holds at its activation yes yes (same) log-verified (v5 and v6 release_notes_04: fills at 4285.58, 4286.08); a nonzero offset follows intrabar (v6 harness_v6_strategy), the same rule for all
23
+ * margin_long / margin_short 0, no checks 100, entries blocked, shorts called log-verified for the block (v6 release_notes_04 at 100K capital); margin calls doc-only (MG "Default margin percentage")
24
+ * strategy.exit absolute vs relative pairs absolute wins nearer level wins doc-only (MG "strategy.exit() evaluates parameter pairs")
25
+ * 9000 orders runtime error oldest trimmed doc-only (MG "Excess orders are trimmed")
26
+ * `when` argument accepted compile error doc-only (MG "Removal of when parameter")
27
+ * calc_bars_count (security, security_lower_tf) no effect no effect log-verified (harness_security_cbc, v5): accepted, never limits history
28
+ * entry commission charged at the fill yes yes (same) log-verified on v6 (harness_v6_strategy), applied to all: the guides document no difference
29
+ */
30
+ /**
31
+ * runtime/v1/broker/Broker.ts
32
+ *
33
+ * The broker emulator, as a class so a Pine version can extend it.
34
+ *
35
+ * ── Why a class, when four versions needed no such thing ──────────────────
36
+ *
37
+ * TradingView did not change the broker across v1-v5. The v2->v3, v3->v4 and
38
+ * v4->v5 migration guides document no change to order fills, trade
39
+ * calculation or the emulator: every strategy delta in them is compile-time
40
+ * API shape (`strategy.exit()` must have an effect, named constants
41
+ * mandatory, `long` renamed to `direction`) or a general language rule, and
42
+ * each is already handled in the transpiler or the registry.
43
+ *
44
+ * v6 is the first version where the EMULATOR itself differs. `strategy.exit()`
45
+ * now evaluates its relative and absolute parameters as a pair and takes
46
+ * whichever level is expected to trigger first, where v5 always took the
47
+ * absolute one; and the 9000-order cap trims the oldest orders instead of
48
+ * halting the script. The same orders produce different fills depending on the
49
+ * `//@version` line, which is what a subclass is for.
50
+ *
51
+ * ── State stays on the Context, and that is deliberate ────────────────────
52
+ *
53
+ * `Context` snapshots broker state for live-tick re-evaluation and restores it
54
+ * with `structuredClone` (`RuntimeSnapshot`, ../context.ts). `structuredClone`
55
+ * does not preserve prototypes: cloning an instance would return a plain
56
+ * object with no methods, and the next call on the restored broker would throw
57
+ * — on the live path, which the ordinary suite does not exercise on every run.
58
+ *
59
+ * So this class is BEHAVIOUR and the context keeps the DATA. The accessors
60
+ * below still read `(ctx as any)._pendingExits` and friends, which is exactly
61
+ * what the snapshot already clones by name.
62
+ */
63
+ import type { Context } from "../context.js";
64
+ import { type PendingExit, type ExitUnit, type PendingEntry, type RiskState, type MarketOrder } from "../stdlib/strategy.js";
65
+ export declare class Broker {
66
+ protected readonly ctx: Context;
67
+ constructor(ctx: Context);
68
+ protected getPendingExits(): Map<string, PendingExit>;
69
+ protected getPendingEntries(): Map<string, PendingEntry>;
70
+ getRiskState(): RiskState;
71
+ protected syncRiskState(): RiskState;
72
+ /**
73
+ * The equity `strategy.risk.max_drawdown` measures: open equity before v5, where nothing
74
+ * contradicts it (see V5Broker for what TradingView does).
75
+ */
76
+ protected drawdownEquity(): number;
77
+ protected checkRiskHalt(rs: RiskState): boolean;
78
+ protected gated(when: any): boolean;
79
+ order(id: string, dir: string, qty?: any, limit?: any, stop?: any, oca_name?: string, oca_type?: string, comment?: string, when?: any, alert_message?: string, obeysPyramiding?: boolean): void;
80
+ /**
81
+ * Fills the queued market orders at `price`: the open of the bar after the
82
+ * one that placed them, or with `process_orders_on_close` the close of the
83
+ * bar that did (see `Context.finalizeBar`).
84
+ */
85
+ fillMarketOrders(price?: number): void;
86
+ /** Fills a queued `strategy.close` / `close_all` at `price`. */
87
+ protected fillClose(o: MarketOrder, price: number): void;
88
+ entry(id: string, direction: string, qty?: any, limit?: any, stop?: any, oca_name?: string, oca_type?: string, comment?: string, when?: any, alert_message?: string, overridePrice?: number, viaLimit?: boolean, exempt?: boolean, sizingPrice?: number): boolean;
89
+ /**
90
+ * Whether `pyramiding` holds back an entry in this direction: the position
91
+ * is already open the same way with as many entries as it allows.
92
+ */
93
+ protected pyramidingBlocks(isLong: boolean, exempt: boolean): boolean;
94
+ /**
95
+ * Whether a cash / percent-of-equity size is computed at the price the ORDER names rather
96
+ * than the price it fills at: a market entry at the close of the bar that placed it, a
97
+ * limit entry at its limit price even when it fills at a better open. TradingView v6,
98
+ * AAPL 1D: dev-docs/tv-parity/_probes/sizing-price/run-20261113-043958 (market ps = qc,
99
+ * floor(eq/close[1]); limit ps = ql, floor(eq/limit)). Only v6 has the evidence; V6Broker
100
+ * turns it on, and moving the override to V5Broker (or here) widens it.
101
+ */
102
+ protected sizesAtOrderPrice(): boolean;
103
+ /** `orderPrice` when sizing follows the order's price, else undefined (size at the fill price). */
104
+ protected sizingPriceFor(orderPrice: number | undefined): number | undefined;
105
+ /** The quantity an entry with no `qty` takes at `price`: `strategy.default_entry_qty`. */
106
+ entryQtyAt(price: number): number;
107
+ /**
108
+ * TradingView floors an order's quantity to `syminfo.mincontract`, the
109
+ * script's own variable staying as written. Only when the host supplied
110
+ * the minimum: the context default (1) would floor fractional sizes to 0.
111
+ */
112
+ /**
113
+ * Whether quantities are floored to `syminfo.mincontract`. TradingView evidence: v4
114
+ * (Adaptive Momentum Test, floor to 0.00001), v5 (_probes/v5-sizing) and v6 (_probes/sizing). Overridden in
115
+ * V4Broker (v5, v6 inherit); v2 and v3 were not probed and keep the unfloored size.
116
+ */
117
+ protected floorsToMinContract(): boolean;
118
+ /**
119
+ * Whether a percent_of_equity size leaves room for the entry commission:
120
+ * `equity * pct / (price * (1 + commission%))`. Evidence: v4 (_probes/version-scope/v4-sizing,
121
+ * 30/30 trades), v5 (_probes/v5-sizing) and v6 (_probes/sizing). Overridden in V4Broker (v5, v6 inherit).
122
+ */
123
+ protected sizesWithCommissionRoom(): boolean;
124
+ /**
125
+ * Whether each fill's commission is rounded to 8 decimals. Evidence: v6 only
126
+ * (_probes/sizing-precision); v4-sizing checks position sizes, not commission values.
127
+ */
128
+ protected roundsCommissionTo8(): boolean;
129
+ protected floorToMinContract(qty: number): number;
130
+ protected defaultQty(price: number): number;
131
+ close(id: string, when?: any, comment?: string, qty?: any, qty_percent?: any, alert_message?: string, immediately?: any): void;
132
+ close_all(comment?: string, when?: any, alert_message?: string, immediately?: any): void;
133
+ /**
134
+ * Queues a close for the next bar's open -- see MarketOrder -- or, with
135
+ * `immediately`, fills it now. strategy.close_all: "immediately (series
136
+ * bool) If true, the closing order will be executed on the tick where it
137
+ * has been placed, ignoring the strategy parameters that restrict the
138
+ * order execution to the open of the next bar." On a historical bar the
139
+ * tick that places it is the bar's close.
140
+ */
141
+ protected placeClose(order: MarketOrder, immediately: any): void;
142
+ closeAt(comment: string, price: number, exitId?: string, exitComment?: string): void;
143
+ cancel(id: string, when?: any): void;
144
+ cancel_all(): void;
145
+ exit(id: string, from_entry?: string, qty?: any, qty_percent?: any, profit?: any, limit?: any, loss?: any, stop?: any, trail_price?: any, trail_points?: any, trail_offset?: any, oca_name?: string, comment?: string, when?: any, alert_message?: string, comment_profit?: string, comment_loss?: string, comment_trailing?: string): void;
146
+ /**
147
+ * May an entry of `qty` at `price` be placed? A seam for V6Broker's margin
148
+ * rule; every version before it admits every entry, as it always has.
149
+ */
150
+ protected admitsEntry(_isLong: boolean, _qty: number, _price: number): boolean;
151
+ /**
152
+ * `strategy.netprofit`: the closed trades' profit, which already carries
153
+ * each trade's commission. Summed as the trades close (`recordTrade`), in
154
+ * the order a re-sum would add them, and rolled back with the snapshot.
155
+ */
156
+ netProfit(): number;
157
+ /**
158
+ * What `strategy.netprofit` reports: the closed trades' profit less the
159
+ * open position's entry commission, charged when the entry FILLS, not when
160
+ * the trade closes. TradingView's v6 log reads -12.0041896 the bar a 0.731
161
+ * entry at 82108 fills, at 0.02% (conformance/v6/golden/harness_v6_strategy).
162
+ * The migration guides document no difference between versions, so every
163
+ * version has it. Only the accessor: `avg_trade` and `netprofit_percent`
164
+ * are defined over closed trades, and stay on `netProfit`.
165
+ */
166
+ reportedNetProfit(): number;
167
+ /**
168
+ * The margin requirement for a side, as a ratio: 25% margin is 0.25, and
169
+ * 0 when the script sets none. Enforced only by V6Broker; read by
170
+ * `strategy.margin_liquidation_price` too, so the two cannot disagree.
171
+ */
172
+ marginRatio(isLong: boolean): number;
173
+ /**
174
+ * Fills at the bar's close what is due there once the script has run: every
175
+ * queued market order under `process_orders_on_close`, otherwise the
176
+ * closes placed with `immediately`.
177
+ */
178
+ fillAtClose(): void;
179
+ /**
180
+ * Whether the strategy halts at its 9000th order (v4, v5) instead of
181
+ * trimming the oldest (v6, which overrides this). Doc-only; see the table at the top of this file.
182
+ */
183
+ protected haltsAtOrderLimit(): boolean;
184
+ /** One more order filled: past 9000 a pre-v6 strategy stops with a runtime error. */
185
+ protected countOrder(): void;
186
+ /** Whether market orders fill at the close of the bar that placed them. */
187
+ fillsOnClose(): boolean;
188
+ /**
189
+ * One exit level, given as an absolute PRICE and/or a RELATIVE distance
190
+ * (already converted to a price). Either may be NaN.
191
+ *
192
+ * v1–v5: "When both absolute (limit, stop) and relative (profit, loss)
193
+ * parameters are given, the absolute ones always win" — the v6 migration
194
+ * guide, describing v5. `favourable` and `isLong` are unused here; they are
195
+ * what V6Broker needs to choose between the two instead.
196
+ */
197
+ protected pairLevel(absolute: number, relative: number, _isLong: boolean, _favourable: boolean): number;
198
+ protected applyEntryOCA(entries: Map<string, PendingEntry>, filledOrder: PendingEntry, price: number): void;
199
+ protected applyExitOCA(exits: Map<string, PendingExit>, filledExit: PendingExit): void;
200
+ protected commissionFor(price: number, qty: number): number;
201
+ protected slip(price: number, buying: boolean): number;
202
+ protected recordTrade(t: {
203
+ id: string;
204
+ entryPrice: number;
205
+ exitPrice: number;
206
+ qty: number;
207
+ pnl: number;
208
+ direction: "long" | "short";
209
+ exitId?: string;
210
+ exitComment?: string;
211
+ /** The entry this trade closes, where the broker tracks entries. */
212
+ entry?: {
213
+ id: string;
214
+ bar: number;
215
+ time: number;
216
+ commission: number;
217
+ };
218
+ /** A further entry closed by the same order: a flat per-order fee was paid by the first. */
219
+ sameOrder?: boolean;
220
+ }): void;
221
+ protected entryTimeOf(): number;
222
+ protected toTick(price: number): number;
223
+ /**
224
+ * A price already on the tick, with the float noise `round(p / tick) * tick`
225
+ * leaves (92.85 comes out 92.85000000000001) written off. The entry's own
226
+ * fill price is what a closed trade reports, so it has to read as the price.
227
+ */
228
+ protected exactTick(price: number): number;
229
+ protected levelToTick(level: number, above: boolean): number;
230
+ protected fillPrice(level: number, open: number, worseIsHigher: boolean): number;
231
+ protected livesYet(placedBar: number): boolean;
232
+ /**
233
+ * One step of `calc_on_order_fills`: fills the FIRST pending entry or exit
234
+ * the rest of the bar's path reaches, and moves the cursor there. False
235
+ * when nothing more is reached. Market orders are not handled here (they
236
+ * fill at the cursor's price, see `Context.runBarWithFills`).
237
+ */
238
+ processIntrabarStep(): boolean;
239
+ private pickIntrabar;
240
+ processPendingOrders(high: number, low: number): void;
241
+ /**
242
+ * The risk checks that run before any order is matched: true when one of
243
+ * them halted the strategy (and so ends the bar's order processing).
244
+ */
245
+ protected riskGate(rs: any, high: number, low: number): boolean;
246
+ /** Matches the pending entries and exits against the whole bar. */
247
+ protected processBarOrders(rs: any, high: number, low: number): void;
248
+ /** Fills one exit unit at the level `hit` says the bar reached. */
249
+ protected fillExitHit(rs: any, exits: Map<string, PendingExit>, exitId: string, pendingExit: PendingExit, unit: ExitUnit, hit: {
250
+ price: number;
251
+ kind: string;
252
+ }, isLong: boolean): void;
253
+ /**
254
+ * Where the bar first reaches an exit's limit, stop or trailing stop,
255
+ * walking the bar the way TradingView's broker emulator does: "if the
256
+ * high is closer to the open than the low, open → high → low → close,
257
+ * otherwise open → low → high → close". Used where the broker tracks
258
+ * trades (every version).
259
+ *
260
+ * The order matters for a trailing stop more than for anything else. A
261
+ * bar that opens at its high and falls through the activation level arms
262
+ * the trail at the LOW; the high came before, so it cannot stop the
263
+ * position out. Testing the bar's high against a trail armed by its low
264
+ * exited a short TradingView kept (bar 220 of
265
+ * conformance/v6/golden/harness_v6_strategy).
266
+ *
267
+ * Prices are mirrored for a short, so "up" is always the favourable side:
268
+ * the limit above, the stops below. The trail's best price is carried
269
+ * across bars in `trail_peak`, in real prices.
270
+ *
271
+ * Performance: `processPendingOrders` calls this once per exit, not once per
272
+ * unit, for an exit with no relative levels (`profit`, `loss`) and no trail
273
+ * (`trail_price`, `trail_points`), and gives every unit the one answer. That
274
+ * holds only while, for such an exit, this reads nothing from `st` or `basis`
275
+ * and leaves `st.trail_peak` as it found it (it writes back what it read,
276
+ * NaN or the unit's own peak, which is no change). If this ever reads the
277
+ * unit or `basis` there, update `levelsFixed` in `processPendingOrders`
278
+ * together with it. Guarded by tests/v1/runtime/broker_exit_sharing.test.ts.
279
+ */
280
+ protected walkExit(ex: PendingExit, st: {
281
+ trail_peak: number;
282
+ }, basis: number, isLong: boolean, open: number, high: number, low: number, close: number, path?: number[]): {
283
+ hit: {
284
+ price: number;
285
+ kind: "limit" | "stop" | "trail";
286
+ } | null;
287
+ at: number;
288
+ };
289
+ /**
290
+ * Whether the exit already has a unit for trade `uid`. Performance: a scan
291
+ * from the newest, where the live trade's unit is, instead of
292
+ * `units.some` over the thousands a reversing strategy leaves behind.
293
+ */
294
+ protected hasUnit(units: ExitUnit[], uid: string | undefined): boolean;
295
+ /**
296
+ * How much the exit unit `unit` may sell: its trade's share (`qty`, else
297
+ * `qty_percent` of what the trade opened, else all of it), less what the
298
+ * unfilled units BEFORE it (exits in placement order, units in trade order)
299
+ * have reserved on the same entry id, and never more than the id still owns.
300
+ */
301
+ protected reservedQty(exits: Map<string, PendingExit>, unit: ExitUnit): number;
302
+ /** What the entry id still owns on the ledger; see `Position.ledger`. */
303
+ protected openQty(id: string): number;
304
+ /**
305
+ * The price a relative level is measured from for an exit scoped to entry
306
+ * `id`: the fill price of the oldest open trade with that id
307
+ * (harness_strategy_exit_mix: `profit = 1` from_entry "B" exits at B's fill
308
+ * plus one tick, not at the average). Several trades under one id are not
309
+ * covered by the log; the oldest is a choice. With none open, the average.
310
+ */
311
+ protected basisPrice(id: string): number;
312
+ /**
313
+ * Closes `qty` of the position at `price` (already on the tick and
314
+ * slipped), from the entries named `only` or else oldest first, and books
315
+ * ONE TRADE PER ENTRY it consumes, at that entry's own price and with that
316
+ * entry's own commission — TradingView's list of trades. What is left
317
+ * re-averages: the position's price is the remaining entries'.
318
+ */
319
+ protected closeEntries(qty: number, price: number, only: string | undefined, names: {
320
+ id: string;
321
+ exitId?: string;
322
+ exitComment?: string;
323
+ ledger?: string;
324
+ }): void;
325
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * runtime/v1/broker/index.ts
3
+ *
4
+ * One broker class per Pine version, each extending the one below it — the
5
+ * same chain the transpilers use (`V2ToJsVisitor extends V1ToJsVisitor`, and
6
+ * so on) and the same chain the version runtimes express as re-exports.
7
+ *
8
+ * ── Why four of these are empty ───────────────────────────────────────────
9
+ *
10
+ * Because TradingView changed nothing. The v2→v3, v3→v4 and v4→v5 migration
11
+ * guides document no change to order fills, trade calculation or the broker
12
+ * emulator; every strategy delta in them is compile-time API shape or a
13
+ * general language rule, and all of those are already implemented in the
14
+ * transpiler and the registry.
15
+ *
16
+ * An empty subclass is the same kind of statement `runtime/v3/index.ts` makes
17
+ * by being one re-export line: it records that the question was asked and the
18
+ * answer was "nothing", and it gives the next version-specific behaviour an
19
+ * obvious place to land. Deleting them would save five lines and lose that.
20
+ */
21
+ import type { Context } from "../context.js";
22
+ import { Broker } from "./Broker.js";
23
+ /**
24
+ * The documented margin-call size, in contracts, or 0 when no call is due.
25
+ * Pure, so the Strategies page's worked example can be checked against it
26
+ * digit for digit. `cash` is Initial Capital + Net Profit; `size` is signed.
27
+ */
28
+ export declare function marginCallSize(cash: number, size: number, avgPrice: number, price: number, ratio: number, minContract?: number): number;
29
+ /**
30
+ * v1 to v5 share ONE broker, `Broker`. TradingView's own migration guides list
31
+ * no fill, trade or emulator change between them:
32
+ * - https://www.tradingview.com/pine-script-docs/migration-guides/to-pine-version-3/
33
+ * no strategy or broker changes (the `security` lookahead default flips,
34
+ * which is profile data);
35
+ * - https://www.tradingview.com/pine-script-docs/migration-guides/to-pine-version-4/
36
+ * renamed built-ins and `na` typing only;
37
+ * - https://www.tradingview.com/pine-script-docs/migration-guides/to-pine-version-5/
38
+ * compile-time checks only: a `strategy.exit()` must use profit, limit,
39
+ * loss, stop or a trail pair; `strategy.long` cannot be passed to `when`;
40
+ * `long` became `direction`.
41
+ * So these classes are empty on purpose: they record that the question was
42
+ * asked and the answer was "nothing", and give a version-specific difference an
43
+ * obvious place to land.
44
+ */
45
+ export declare class V2Broker extends Broker {
46
+ }
47
+ export declare class V3Broker extends V2Broker {
48
+ }
49
+ export declare class V4Broker extends V3Broker {
50
+ protected floorsToMinContract(): boolean;
51
+ protected sizesWithCommissionRoom(): boolean;
52
+ }
53
+ export declare class V5Broker extends V4Broker {
54
+ /**
55
+ * `strategy.risk.max_drawdown` on TradingView (v5, BINANCE:BTCUSDT 1h; dev-docs/tv-parity/
56
+ * _probes/risk-drawdown/run-20261113-044227): the drawdown is measured on REALIZED equity
57
+ * (cash: initial capital plus closed trades), not open equity.
58
+ * A long held through a 3.1% open-equity drawdown (bar close 2026-01-18 23:00) was not closed
59
+ * until its scheduled exit, and after the closed-trade drawdown passed 3% (trade 44) the
60
+ * strategy never traded again. v6 inherits it (its broker takes v5's rules); no v1-v4 evidence.
61
+ */
62
+ protected drawdownEquity(): number;
63
+ }
64
+ /**
65
+ * v6 — the first version whose broker differs.
66
+ *
67
+ * MG §"strategy.exit() evaluates parameter pairs": "When both absolute (limit,
68
+ * stop) and relative (profit, loss) parameters are given, the absolute ones
69
+ * always win" in v5; in v6 "both levels are evaluated, and the one the price
70
+ * hits first is used". Price reaches whichever level is NEARER the entry first,
71
+ * so that is the one kept:
72
+ *
73
+ * long short
74
+ * take-profit lower higher (favourable side)
75
+ * stop-loss higher lower (adverse side)
76
+ * trail start lower higher (favourable side)
77
+ *
78
+ * The guide's own example is the sharpest check: `profit = 0, loss = 0`
79
+ * beside 2×ATR `limit`/`stop` exits immediately at the entry price in v6,
80
+ * because a zero distance is nearer than any ATR.
81
+ *
82
+ * It also enforces MARGIN, which no earlier broker does — see the section
83
+ * below.
84
+ *
85
+ * The guide's remaining strategy change, trimming beyond 9000 trades, is NOT
86
+ * here: it changes what the per-trade accessors report, not what the broker
87
+ * does, so it is profile data read by runtime/v1/stdlib/trades.ts.
88
+ */
89
+ export declare class V6Broker extends V5Broker {
90
+ /**
91
+ * v6's pyr0, pyr3 and pooc logs are row for row those of v5, so it takes
92
+ * the same rules. Only the trailing stop differs: v6's own golden
93
+ * (harness_v6_strategy) has it follow the bar's highs intrabar.
94
+ */
95
+ /**
96
+ * MG §"Excess orders are trimmed": past 9000 orders "v5 raises a runtime
97
+ * error and halts" and v6 trims the oldest and continues
98
+ * (https://www.tradingview.com/pine-script-docs/migration-guides/to-pine-version-6/).
99
+ */
100
+ protected haltsAtOrderLimit(): boolean;
101
+ protected floorsToMinContract(): boolean;
102
+ protected sizesWithCommissionRoom(): boolean;
103
+ protected roundsCommissionTo8(): boolean;
104
+ /** Market and limit entries are sized at their order price (see `Broker.sizesAtOrderPrice`). */
105
+ protected sizesAtOrderPrice(): boolean;
106
+ /**
107
+ * "Entries exceeding available funds are blocked." An entry needs
108
+ * `qty * price * marginRatio` of available funds: equity, less the margin
109
+ * a same-direction position already ties up. An opposite position is about
110
+ * to be closed by this entry, so it ties up nothing.
111
+ */
112
+ protected admitsEntry(isLong: boolean, qty: number, price: number): boolean;
113
+ processPendingOrders(high: number, low: number): void;
114
+ /**
115
+ * The margin call, transcribed from the Strategies page ("Margin"):
116
+ *
117
+ * Money Spent = Quantity * Entry Price
118
+ * MVS = Position Size * Current Price
119
+ * Open Profit = MVS - Money Spent (× -1 if short)
120
+ * Equity = Initial Capital + Net Profit + Open Profit
121
+ * Margin = MVS * Margin Ratio
122
+ * Available Funds = Equity - Margin
123
+ * Loss = Available Funds / Margin Ratio
124
+ * Cover Amount = TRUNCATE(Loss / Current Price)
125
+ * Margin Call Size = Cover Amount * 4
126
+ *
127
+ * "if a strategy's available funds fall below the required margin" — that
128
+ * is, Available Funds < 0 — the emulator "immediately liquidates" that size.
129
+ *
130
+ * ── Not documented, so chosen and stated ─────────────────────────────
131
+ *
132
+ * Current Price the bar's ADVERSE extreme — low for a long, high for a
133
+ * short. The page never says which price; this is the
134
+ * rule this engine's own `max_intraday_loss` check uses,
135
+ * and the one that catches a call TradingView's intrabar
136
+ * emulator would also see.
137
+ * Fill at that price, as a market order (so slippage applies),
138
+ * recorded under the id "Margin call".
139
+
140
+ * These three want a TradingView-captured fixture before they are trusted
141
+ * for numbers; the formula itself is the documented one and reproduces the
142
+ * page's worked example exactly (see tests/v6/runtime/margin.test.ts).
143
+ */
144
+ protected checkMarginCall(high: number, low: number): void;
145
+ /** Closes `qty` of the position at `price`, under the id "Margin call". */
146
+ protected liquidate(qty: number, price: number): void;
147
+ protected pairLevel(absolute: number, relative: number, isLong: boolean, favourable: boolean): number;
148
+ }
149
+ export { Broker } from "./Broker.js";
150
+ /**
151
+ * This context's broker, built once and kept on the context.
152
+ *
153
+ * Memoised per context rather than per version, because the instance holds a
154
+ * reference to its own context. It holds no trading state — that stays on the
155
+ * context so the live-tick snapshot can keep cloning it (see Broker.ts) — so
156
+ * rebuilding one would be harmless, just wasteful.
157
+ */
158
+ export declare function brokerFor(ctx: Context): Broker;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Unwraps a Series (or anything with a valueOf) to a primitive number.
3
+ *
4
+ * Here rather than in stdlib/strategy.ts, where it used to live as a private
5
+ * helper: the broker needs it too, and EVERY export of a stdlib module is
6
+ * published as a Pine built-in by scripts/common/generate-metadata.ts. Exported
7
+ * from there, it became `strategy.val(x)` — callable from a script.
8
+ */
9
+ export declare function val(x: any): number;
10
+ /**
11
+ * Unwraps a Series (or any wrapper with a valueOf) to a primitive string; `na`
12
+ * is the empty string.
13
+ *
14
+ * Every id, comment and alert message the broker records goes through here
15
+ * first. A `string` variable reaches a strategy call as its Series, which holds
16
+ * the whole runtime (`ctx.__sandbox`); stored as is, it made the result
17
+ * impossible to `postMessage`.
18
+ */
19
+ export declare function text(x: any): string;
20
+ /**
21
+ * A strategy call's direction as the broker spells it.
22
+ *
23
+ * v1-v4 name the argument `long` and it is a bool: "true for buy, false for
24
+ * sell". From v5 it is `direction`, which is `strategy.long` or
25
+ * `strategy.short` -- here the strings "long" and "short". Both reach the same
26
+ * parameter, so a bool is mapped and anything else is passed through as text.
27
+ */
28
+ export declare function directionOf(x: any): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Passed by the emitter as `str.tostring`'s second argument, in place of a
3
+ * format, when the call has no format and its value is a compile-time CONSTANT
4
+ * number. TradingView prints a constant and a series differently (see
5
+ * `numberToString` in ./stdlib/str.ts), and that is a property of the source,
6
+ * not of the value.
7
+ *
8
+ * Its own file rather than an export of str.ts, because the stdlib registry
9
+ * generator turns every export of a stdlib module into a built-in.
10
+ */
11
+ export declare const CONST_DEFAULT = "\0const";