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.
- package/LICENSE +674 -0
- package/README.md +332 -2
- package/dist/cli.mjs +108691 -0
- package/dist/index.cjs +108670 -0
- package/dist/index.mjs +108624 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/parser/v1/generated/PineV1Lexer.d.ts +66 -0
- package/dist/types/parser/v1/generated/PineV1Parser.d.ts +555 -0
- package/dist/types/parser/v1/generated/PineV1ParserVisitor.d.ts +318 -0
- package/dist/types/parser/v1/index.d.ts +39 -0
- package/dist/types/parser/v2/generated/PineV2Lexer.d.ts +67 -0
- package/dist/types/parser/v2/generated/PineV2Parser.d.ts +568 -0
- package/dist/types/parser/v2/generated/PineV2ParserVisitor.d.ts +325 -0
- package/dist/types/parser/v2/index.d.ts +2 -0
- package/dist/types/parser/v3/generated/PineV3Lexer.d.ts +67 -0
- package/dist/types/parser/v3/generated/PineV3Parser.d.ts +568 -0
- package/dist/types/parser/v3/generated/PineV3ParserVisitor.d.ts +325 -0
- package/dist/types/parser/v3/index.d.ts +2 -0
- package/dist/types/parser/v4/generated/PineV4Lexer.d.ts +86 -0
- package/dist/types/parser/v4/generated/PineV4Parser.d.ts +673 -0
- package/dist/types/parser/v4/generated/PineV4ParserVisitor.d.ts +367 -0
- package/dist/types/parser/v4/index.d.ts +2 -0
- package/dist/types/parser/v5/generated/PineV5Lexer.d.ts +95 -0
- package/dist/types/parser/v5/generated/PineV5Parser.d.ts +964 -0
- package/dist/types/parser/v5/generated/PineV5ParserVisitor.d.ts +521 -0
- package/dist/types/parser/v5/index.d.ts +2 -0
- package/dist/types/parser/v6/generated/PineV6Lexer.d.ts +98 -0
- package/dist/types/parser/v6/generated/PineV6Parser.d.ts +984 -0
- package/dist/types/parser/v6/generated/PineV6ParserVisitor.d.ts +528 -0
- package/dist/types/parser/v6/index.d.ts +2 -0
- package/dist/types/runtime/v1/Series.d.ts +61 -0
- package/dist/types/runtime/v1/broker/Broker.d.ts +325 -0
- package/dist/types/runtime/v1/broker/index.d.ts +158 -0
- package/dist/types/runtime/v1/broker/val.d.ts +28 -0
- package/dist/types/runtime/v1/constDefault.d.ts +11 -0
- package/dist/types/runtime/v1/context.d.ts +1705 -0
- package/dist/types/runtime/v1/footprint.d.ts +32 -0
- package/dist/types/runtime/v1/index.d.ts +7 -0
- package/dist/types/runtime/v1/language-spec.d.ts +12 -0
- package/dist/types/runtime/v1/openTrades.d.ts +12 -0
- package/dist/types/runtime/v1/palette.d.ts +18 -0
- package/dist/types/runtime/v1/session.d.ts +482 -0
- package/dist/types/runtime/v1/sessions.d.ts +31 -0
- package/dist/types/runtime/v1/stateClone.d.ts +19 -0
- package/dist/types/runtime/v1/stdlib/alerts.d.ts +31 -0
- package/dist/types/runtime/v1/stdlib/arrays.d.ts +133 -0
- package/dist/types/runtime/v1/stdlib/barmerge.d.ts +27 -0
- package/dist/types/runtime/v1/stdlib/barstate.d.ts +79 -0
- package/dist/types/runtime/v1/stdlib/cast.d.ts +81 -0
- package/dist/types/runtime/v1/stdlib/chart.d.ts +283 -0
- package/dist/types/runtime/v1/stdlib/color.d.ts +51 -0
- package/dist/types/runtime/v1/stdlib/core.d.ts +162 -0
- package/dist/types/runtime/v1/stdlib/dataset.d.ts +36 -0
- package/dist/types/runtime/v1/stdlib/display.d.ts +50 -0
- package/dist/types/runtime/v1/stdlib/drawings.d.ts +407 -0
- package/dist/types/runtime/v1/stdlib/errors.d.ts +21 -0
- package/dist/types/runtime/v1/stdlib/font.d.ts +20 -0
- package/dist/types/runtime/v1/stdlib/footprint.d.ts +76 -0
- package/dist/types/runtime/v1/stdlib/format.d.ts +57 -0
- package/dist/types/runtime/v1/stdlib/index.d.ts +71 -0
- package/dist/types/runtime/v1/stdlib/input.d.ts +14 -0
- package/dist/types/runtime/v1/stdlib/inputs.d.ts +60 -0
- package/dist/types/runtime/v1/stdlib/late_members.d.ts +97 -0
- package/dist/types/runtime/v1/stdlib/logging.d.ts +31 -0
- package/dist/types/runtime/v1/stdlib/maps.d.ts +55 -0
- package/dist/types/runtime/v1/stdlib/math.d.ts +71 -0
- package/dist/types/runtime/v1/stdlib/matrix.d.ts +201 -0
- package/dist/types/runtime/v1/stdlib/metadata.d.ts +14 -0
- package/dist/types/runtime/v1/stdlib/mtf.d.ts +15 -0
- package/dist/types/runtime/v1/stdlib/renames.d.ts +117 -0
- package/dist/types/runtime/v1/stdlib/renames5.d.ts +113 -0
- package/dist/types/runtime/v1/stdlib/renames6.d.ts +81 -0
- package/dist/types/runtime/v1/stdlib/request.d.ts +207 -0
- package/dist/types/runtime/v1/stdlib/scale.d.ts +19 -0
- package/dist/types/runtime/v1/stdlib/session.d.ts +113 -0
- package/dist/types/runtime/v1/stdlib/sort_field.d.ts +12 -0
- package/dist/types/runtime/v1/stdlib/sources.d.ts +24 -0
- package/dist/types/runtime/v1/stdlib/str.d.ts +117 -0
- package/dist/types/runtime/v1/stdlib/strategy.d.ts +314 -0
- package/dist/types/runtime/v1/stdlib/syminfo.d.ts +209 -0
- package/dist/types/runtime/v1/stdlib/ta.d.ts +409 -0
- package/dist/types/runtime/v1/stdlib/time.d.ts +158 -0
- package/dist/types/runtime/v1/stdlib/trades.d.ts +108 -0
- package/dist/types/runtime/v1/stdlib/ui.d.ts +196 -0
- package/dist/types/runtime/v1/stdlib/v6_members.d.ts +68 -0
- package/dist/types/runtime/v1/timeframes.d.ts +139 -0
- package/dist/types/runtime/v1/zones.d.ts +30 -0
- package/dist/types/transpiler/common/index.d.ts +51 -0
- package/dist/types/transpiler/common/libraries.d.ts +29 -0
- package/dist/types/transpiler/common/parseErrors.d.ts +38 -0
- package/dist/types/transpiler/common/passes/ScopeAnalysis.d.ts +158 -0
- package/dist/types/transpiler/common/passes/TypeRules.d.ts +26 -0
- package/dist/types/transpiler/common/profiles/index.d.ts +96 -0
- package/dist/types/transpiler/common/version.d.ts +40 -0
- package/dist/types/transpiler/v1/ToJsVisitor.d.ts +720 -0
- package/dist/types/transpiler/v2/ToJsVisitor.d.ts +72 -0
- package/dist/types/transpiler/v3/ToJsVisitor.d.ts +87 -0
- package/dist/types/transpiler/v4/ToJsVisitor.d.ts +263 -0
- package/dist/types/transpiler/v5/ToJsVisitor.d.ts +1101 -0
- package/dist/types/transpiler/v6/ToJsVisitor.d.ts +237 -0
- package/dist/types/transpiler/v6/rules.d.ts +94 -0
- package/dist/types/utils/v2/common.d.ts +4 -0
- 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";
|