@pineforge/backtest-mcp 0.9.32 → 0.9.34

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/dist/coverage.js CHANGED
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * PineForge Pine v6 coverage dataset — hand-authored canonical copy.
3
3
  *
4
- * Data sourced from pineforge-engine/docs/coverage.md +
5
- * pine_v6_coverage_detail.md and embedded here as a TS object literal (no
6
- * runtime fetch). This is the single source the MCP coverage tools serve.
4
+ * Data sourced from pineforge-engine docs/coverage.md +
5
+ * docs/pine_v6_coverage_detail.md and pineforge-codegen's README + CHANGELOG, at
6
+ * the release tags named in coverage_version, and embedded here as a TS object
7
+ * literal (no runtime fetch). This is the single source the MCP coverage tools
8
+ * serve. Every status is scoped to what a backtest on this server can do: the
9
+ * server installs no other symbol's bars, no recorded request data and no
10
+ * library sources, so where the engine supports more, the entry says so.
7
11
  *
8
12
  * IMPORTANT: this is a hand-authored canonical copy. Updating it requires a
9
13
  * MANUAL re-sync across BOTH pineforge-backtest-mcp AND pineforge-mcp-public —
@@ -12,11 +16,11 @@
12
16
  * here and mirror the identical change in pineforge-mcp-public.
13
17
  */
14
18
  export const COVERAGE = {
15
- coverage_version: "2026-06-04 / 0fccede",
19
+ coverage_version: "engine v1.0.1 + codegen 1.0.1 (2026-10-02)",
16
20
  legend: {
17
- supported: "The Pine feature has a dedicated runtime class or function in libpineforge.a that fully implements its behavior end-to-end.",
18
- partial: "The feature is implemented in the runtime but with documented gaps or restrictions (e.g. some sub-features, configuration paths, or variants are missing or rejected).",
19
- unsupported: "The feature is not implemented anywhere in PineForge; it is parsed-and-skipped or rejected, producing no runtime behavior (e.g. drawing, plotting, and live alerts).",
21
+ supported: "Works end-to-end in a backtest on this server: libpineforge.a implements it, with the code PineForge's transpiler emits for it.",
22
+ partial: "Works with a documented gap or restriction: some variants, argument types or data are refused or missing, or the value only approximates TradingView's. The entry names the gap. Where the engine supports more than this server can supply (another symbol's bars, recorded request data, library sources), the entry says so.",
23
+ unsupported: "A backtest on this server cannot use it: PineForge's transpiler refuses it, the generated C++ does not compile, the run stops where its value is read (data this server cannot supply), or it is accepted with no effect (plots, tables, alerts and visual setters).",
20
24
  via_transpiler: "The feature has no dedicated runtime module, but PineForge's PineScript-to-C++ transpiler emits it inline against the C++ standard library or generated structs, so it still works end-to-end.",
21
25
  },
22
26
  topics: [
@@ -24,8 +28,8 @@ export const COVERAGE = {
24
28
  id: "engine_lifecycle",
25
29
  title: "Engine / strategy lifecycle",
26
30
  status: "supported",
27
- summary: "BacktestEngine with three run(...) overloads, per-bar on_bar hook, and ReportC/SecurityDiagC reporting are fully implemented in the runtime.",
28
- detail: "The runtime owns the full strategy lifecycle. BacktestEngine is an abstract base; the consumer compiler emits a strategy subclass that derives from it and implements on_bar(const Bar&). Three run(...) overloads are exposed: a bare (bars, n) form, a TF-aware form (input_tf/script_tf + magnifier args), and a full form that also injects a SymInfo, the input map, and a StrategyOverrides struct (NaN/-1 mean leave-default). The TF-aware overload auto-detects input_tf via detect_timeframe when empty and defaults script_tf to input_tf.\n\nStrategyOverrides only carries a fixed set of fields (initial_capital, commission_value, default_qty_value, pyramiding, slippage, commission_type, default_qty_type, process_orders_on_close, close_entries_rule); anything else (currency, margin, risk thresholds) must be set by the generated subclass — there is no runtime entry point. Per-input overrides go through set_input/clear_inputs before run(...); magnifier density via set_magnifier_volume_weighted.\n\nLifecycle reporting is via fill_report(ReportC*) which populates closed-trade summaries (TradeC[]), bar counters, magnifier work counters, TF/aggregation diagnostics, and per-security SecurityDiagC. The public C ABI exposes exactly 10 symbols: strategy_create/strategy_free, run_backtest, run_backtest_full, report_free, plus input/override/magnifier/trace setters and pf_version_get. indicator() is parse-and-skip (strategy-only engine); library() is unsupported. Note on barstate: the runtime DOES track barstate.islast (the barstate_islast_ flag is true on the genuine last bar of the batch run) — what is unsupported is live/realtime barstate semantics (barstate.isrealtime, barstate.islastconfirmedhistory are always false in batch).",
31
+ summary: "BacktestEngine runs a strategy over its bars through one-shot run(...) overloads, the per-bar on_bar hook and cumulative reporting; calc_on_order_fills is modelled. This server runs one-shot backtests of strategy() scripts.",
32
+ detail: "The generated strategy derives from source::PineStrategyHost (a NativeStrategyHost, itself a BacktestEngine) and implements on_bar(const Bar&). Three run(...) overloads are exposed: a bare (bars, n) form, a TF-aware form (input_tf/script_tf + magnifier args), and a full form that also injects a SymInfo, the input map and a StrategyOverrides struct (NaN/-1 mean leave-default). The TF-aware overload auto-detects input_tf via detect_timeframe when empty and defaults script_tf to input_tf. The engine also has a continuous historical-to-realtime stream lifecycle (strategy_stream_*); this server runs one-shot backtests only.\n\nStrategyOverrides carries a fixed set of fields: initial_capital, commission_value, default_qty_value, pyramiding, slippage, commission_type, default_qty_type, process_orders_on_close, calc_on_order_fills and close_entries_rule. Anything else (currency, margin, risk limits) is set by the generated strategy; there is no runtime entry point for it. Per-input overrides go through set_input/clear_inputs before run(...); magnifier density via set_magnifier_volume_weighted. fill_report(ReportC*) fills closed trades, bar counters, magnifier work counters, TF/aggregation diagnostics and per-security SecurityDiagC. The public header pineforge.h has 71 PF_API declarations: 62 runtime implementations and nine per-strategy generated exports.\n\ncalc_on_order_fills is modelled: the Pine adapter recalculates the script after each fill, with TradingView's script-state rollback. calc_on_every_tick is not modelled (codegen does not read it). varip is accepted: a historical bar executes once, so a varip keeps its value like var, and it is left out of the calc_on_order_fills rollback.\n\nindicator() scripts are refused: PineForge runs strategies only. A library(...) script and export in a strategy are refused. codegen inlines an imported library when it is given the library's source (transpile(..., libraries={...}) or a requests manifest); this server passes no library sources, so an import is refused, unless its alias is ta, math or str and it names only that namespace's built-ins, which is a no-op.",
29
33
  supported: [
30
34
  "BacktestEngine",
31
35
  "run / run_backtest / run_backtest_full",
@@ -36,21 +40,22 @@ export const COVERAGE = {
36
40
  "fill_report (ReportC / SecurityDiagC)",
37
41
  "strategy_create / strategy_free / report_free",
38
42
  "detect_timeframe",
39
- "barstate.islast (barstate_islast_; true on last batch bar)",
43
+ "calc_on_order_fills (modelled: the script recalculates after each fill, with TradingView's rollback)",
44
+ "varip (accepted: on historical bars it keeps its value like var; left out of the calc_on_order_fills rollback)",
40
45
  ],
41
46
  unsupported: [
42
- "indicator() (parse-and-skip; strategy-only engine)",
43
- "library() / import / export (library system not implemented)",
44
- "calc_on_every_tick / calc_on_order_fills (no live feed)",
45
- "barstate.isrealtime / barstate.islastconfirmedhistory realtime semantics (always false in batch)",
47
+ "indicator() (refused: PineForge runs strategies only)",
48
+ "library() / export (refused: PineForge transpiles strategies only)",
49
+ "import (refused on this server, which passes codegen no library sources; an import aliased ta, math or str that names only that namespace's built-ins is a no-op)",
50
+ "calc_on_every_tick (accepted, not modelled: codegen does not read it)",
46
51
  ],
47
52
  },
48
53
  {
49
54
  id: "strategy_orders",
50
55
  title: "Strategy orders",
51
56
  status: "supported",
52
- summary: "All strategy order commands (entry/order/exit/close/close_all/cancel/cancel_all) are runtime-implemented with OHLC-path fills, OCA, pyramiding, slippage, commissions, margin gates, trailing stops, and TV deferred-flip carry.",
53
- detail: "The runtime owns strategy_entry, strategy_order, strategy_exit, strategy_close, strategy_close_all, strategy_cancel, and strategy_cancel_all. Pending orders resolve on every process_pending_orders(bar) call along a 4-waypoint OHLC path (O->H->L->C or O->L->H->C depending on open proximity to high vs low), handling stop/limit priority, gap fills, opposing-stop arbitration, OCA siblings, and trail levels. slippage_ (ticks) and syminfo_mintick_ round all fills; stop entries use directional mintick snapping (long stops up, short stops down) to match TradingView.\n\nPriced strategy.entry orders track TradingView's deferred-flip carry rule: an opposite priced entry placed while a position is open, firing later from flat after a close, opens qty + carried_position_qty; source order within a single on_bar matters. strategy_exit reserves a slice of the open position (partial exits one-shot per live position) and accepts profit/loss/limit/stop/trail_* params, but does not itself enforce that at least one is set. strategy_close does FIFO close by entry id (or all when empty), honouring close_entries_rule_any_ for ANY-mode partial close, with an immediately flag to bypass pending resolution.\n\nSizing uses QtyType {FIXED, PERCENT_OF_EQUITY, CASH} and default_qty_value_; commission uses CommissionType {PERCENT, CASH_PER_ORDER, CASH_PER_CONTRACT}. Margin checks use margin_long_/margin_short_ percentages from the subclass; if implied required capital exceeds equity the fill is silently rejected (matching TV). All strategy.* order constants (oca.*, commission.*, fixed/cash/percent_of_equity, long/short, default_entry_qty) are runtime-backed. strategy.convert_to_account/symbol are transpiler identity (no FX conversion).",
57
+ summary: "All strategy order commands (entry/order/exit/close/close_all/cancel/cancel_all) with OHLC-path fills, OCA, pyramiding, slippage, commissions, TradingView's margin admission and margin calls, partial and FIFO-vs-ANY closes, trailing stops and TV deferred-flip carry.",
58
+ detail: "The generated strategy calls the order methods of source::PineStrategyHost: strategy_entry, strategy_order, strategy_exit, strategy_close, strategy_close_all, strategy_cancel and strategy_cancel_all. Native resting requests resolve at each native driver decision point along a 4-waypoint OHLC path (O->H->L->C or O->L->H->C, by the open's proximity to the high or the low): stop/limit priority, gap fills, opposing-stop arbitration, OCA siblings and trail levels. Slippage (ticks) and syminfo.mintick round all fills; stop entries snap to the tick in their direction (long stops up, short stops down), as on TradingView.\n\nPriced strategy.entry orders follow TradingView's deferred-flip carry: an opposite priced entry placed while a position is open, firing later from flat after a close, opens qty + the carried position qty; source order within one on_bar matters. strategy.exit reserves a slice of the open position (partial exits with the same id are one-shot per position) and takes profit/loss/limit/stop/trail_* params; the runtime does not itself require one of them. strategy.close closes FIFO by entry id (or all when empty), honours close_entries_rule ANY for partial closes, and immediately bypasses pending-order resolution.\n\nSizing uses default_qty_type (fixed, percent_of_equity, cash) and default_qty_value. A Pine v6 script that omits initial_capital, default_qty_type or default_qty_value runs with TradingView's defaults (100000, strategy.percent_of_equity, 100), which codegen 1.0.0 and later declare in the generated constructor. Commission is percent, cash per order or cash per contract. Margin uses margin_long/margin_short percentages (100 = no leverage): the Pine adapter admits an opening by TradingView's money rule and books TradingView's margin calls through a maintenance-only margin model. strategy.convert_to_account and strategy.convert_to_symbol have no runtime feed: the transpiler treats them as identity (no FX adjustment). strategy.default_entry_qty transpiles, but codegen 1.0.1's C++ for it does not compile.",
54
59
  supported: [
55
60
  "strategy.entry",
56
61
  "strategy.order",
@@ -58,71 +63,73 @@ export const COVERAGE = {
58
63
  "strategy.close",
59
64
  "strategy.close_all",
60
65
  "strategy.cancel / strategy.cancel_all",
61
- "strategy.default_entry_qty",
62
66
  "strategy.oca.* / strategy.commission.*",
63
67
  "strategy.long / strategy.short",
68
+ "strategy.fixed / strategy.cash / strategy.percent_of_equity",
64
69
  "QtyType / CommissionType",
65
- "slippage, pyramiding, margin_long_/margin_short_ gates",
70
+ "slippage / pyramiding / margin_long / margin_short / process_orders_on_close / close_entries_rule (strategy declaration arguments)",
71
+ ],
72
+ via_transpiler: [
73
+ "strategy.convert_to_account / strategy.convert_to_symbol (emitted as identity: the value comes back unconverted, no FX adjustment)",
66
74
  ],
67
75
  unsupported: [
68
- "strategy.convert_to_account / strategy.convert_to_symbol (transpiler identity, no FX adjustment)",
69
- "strategy.exit requirement that >=1 price param is set (policy lives outside runtime)",
76
+ "strategy.default_entry_qty (transpiles, but codegen 1.0.1's C++ for it does not compile)",
70
77
  ],
71
78
  },
72
79
  {
73
80
  id: "strategy_state",
74
81
  title: "Strategy state / accessors",
75
82
  status: "supported",
76
- summary: "Position/equity/drawdown/runup tracking, win/loss counts, and the full closed- and open-trade accessor sets are wired directly on BacktestEngine.",
77
- detail: "The runtime tracks position state, equity/drawdown/runup, win/loss counts, and an intraday fill counter. strategy.closedtrades.* accessors are defined inline: profit, profit_percent, commission, entry/exit_bar_index, entry/exit_comment, entry/exit_id, entry/exit_price, entry/exit_time, size, max_runup(_percent), max_drawdown(_percent). strategy.opentrades.* mirrors the closed set minus the four exit_* fields. Pine v6 has no closedtrades/opentrades.direction(...) accessor — direction is encoded in the sign of size (positive=long, negative=short) — and the support checker rejects any direction(...) call.\n\nAggregate state methods on the engine: net_profit/gross_profit/gross_loss (and _percent), avg_trade/avg_winning_trade/avg_losing_trade (and _percent), count_wintrades/count_losstrades, current_equity, open_profit(price), open_trades_capital_held, and signed_position_size. Strategy state variables like strategy.equity, strategy.netprofit, strategy.position_size, strategy.position_avg_price, max_drawdown/max_runup, max_contracts_held_*, and eventrades are all runtime-backed. margin_liquidation_price() always returns na<double>().\n\nBar metadata helpers decompose current_bar_.timestamp (UTC) into year/month/dayofmonth/hour/minute/second/dayofweek/weekofyear with scalar accessors. barstate flags map onto three engine flags (is_first_tick_, is_last_tick_, barstate_islast_) with documented batch-mode approximations (islast always false, ishistory always true, isrealtime always false).",
83
+ summary: "Position, equity, drawdown and run-up tracking, win/loss counts, the closed- and open-trade accessors, strategy.margin_liquidation_price, and barstate.* flags with their backtest values.",
84
+ detail: "strategy.closedtrades.* accessors: profit, profit_percent, commission, entry/exit_bar_index, entry/exit_comment, entry/exit_id, entry/exit_price, entry/exit_time, size, max_runup(_percent), max_drawdown(_percent). strategy.opentrades.* mirrors the closed set minus the four exit_* fields. Pine v6 has no closedtrades/opentrades.direction(...) accessor: direction is the sign of size (positive long, negative short), and the support checker rejects a direction(...) call.\n\nAggregate state on the engine: net_profit/gross_profit/gross_loss (and _percent), avg_trade/avg_winning_trade/avg_losing_trade (and _percent), count_wintrades/count_losstrades, current_equity, open_profit(price), open_trades_capital_held and signed_position_size. strategy.equity, netprofit, position_size, position_avg_price, max_drawdown/max_runup, max_contracts_held_* and eventrades read that state. strategy.margin_liquidation_price reads the kernel's liquidation price in TradingView's tick spelling (PineStrategyHost::margin_liquidation_price).\n\nbarstate in a backtest, where every bar is historical: isfirst is bar_index == 0; islast is true on the run's final bar; ishistory is always true; isrealtime is always false; isnew follows the first tick of a script bar and isconfirmed its last; islastconfirmedhistory is true on the run's final bar, and codegen warns that it approximates.",
78
85
  supported: [
79
- "strategy.closedtrades.* / strategy.opentrades.* accessors",
80
- "strategy.equity / netprofit / grossprofit / grossloss",
81
- "strategy.position_size / position_avg_price",
82
- "strategy.max_drawdown / max_runup (_percent)",
83
- "strategy.wintrades / losstrades / eventrades",
84
- "strategy.max_contracts_held_*",
86
+ "strategy.closedtrades.* / strategy.opentrades.* (accessors; direction is the sign of size)",
87
+ "strategy.equity / strategy.netprofit / strategy.grossprofit / strategy.grossloss",
88
+ "strategy.position_size / strategy.position_avg_price",
89
+ "strategy.max_drawdown / strategy.max_runup / strategy.max_drawdown_percent / strategy.max_runup_percent",
90
+ "strategy.wintrades / strategy.losstrades / strategy.eventrades",
91
+ "strategy.max_contracts_held_all / strategy.max_contracts_held_long / strategy.max_contracts_held_short",
92
+ "strategy.margin_liquidation_price (the kernel's liquidation price in TradingView's tick spelling)",
85
93
  "current_equity / open_profit / signed_position_size",
94
+ "barstate.isfirst / barstate.islast / barstate.ishistory / barstate.isnew / barstate.isconfirmed (backtest values: see detail)",
95
+ ],
96
+ partial: [
97
+ "barstate.isrealtime (always false: every bar is historical in a backtest; codegen warns that it approximates)",
98
+ "barstate.islastconfirmedhistory (true on the run's final bar; codegen warns that it approximates)",
86
99
  ],
87
100
  unsupported: [
88
- "strategy.closedtrades.direction / strategy.opentrades.direction (no such Pine v6 accessor; rejected by support checker — use sign of size)",
89
- "strategy.margin_liquidation_price (always returns na)",
90
- "barstate.islast/isrealtime/islastconfirmedhistory realtime semantics (batch-mode placeholders)",
101
+ "strategy.closedtrades.direction / strategy.opentrades.direction (no such Pine v6 accessor; the support checker rejects it: use the sign of size)",
91
102
  ],
92
103
  },
93
104
  {
94
105
  id: "strategy_risk",
95
106
  title: "Strategy risk",
96
- status: "partial",
97
- summary: "Runtime fields cover position-size cap, drawdown cap (abs/%), intraday-loss cap (abs/%), consecutive losing days, intraday filled-order cap, and direction allow-list — but none are exposed via StrategyOverrides.",
98
- detail: "BacktestEngine tracks risk fields and gates entries through check_risk_allow_entry(is_long) and update_risk_state(). The covered strategy.risk.* surface: allow_entry_in (risk_direction_: BOTH/LONG_ONLY/SHORT_ONLY), max_position_size (blocks new entries when position_qty_ >= cap), max_drawdown (+_is_pct_, halts on peak-to-trough drawdown crossing abs $ or % of peak equity), max_intraday_loss (+_is_pct_, halts on running intraday P&L; day boundary uses month/day-of-month, not session), max_cons_loss_days (halts after N consecutive losing days), and max_intraday_filled_orders (skips fills past the per-day cap; counter resets on new day-of-year).\n\nRisk halt is one-way: once risk_halted_ is set, no new entries are accepted for the rest of the run. The reason this topic is partial rather than supported: the summary table marks Strategy risk as 'Partial', and critically none of these risk fields are exposed via StrategyOverrides — its fixed override set excludes risk thresholds, so they must all be set directly by the generated subclass, with no runtime/ABI entry point for configuring them. The strategy.direction.* constants map to RiskDirection enum values.",
107
+ status: "supported",
108
+ summary: "All six strategy.risk.* limits are enforced: allow_entry_in, max_position_size, max_drawdown, max_intraday_loss, max_cons_loss_days and max_intraday_filled_orders. The script's own calls set them; none is a StrategyOverrides key.",
109
+ detail: "The generated strategy declares the six limits through PineStrategyHost::set_pine_risk_* and the Pine adapter enforces them (PineExecutionAdapter::update_risk_state). allow_entry_in blocks entries against the allowed direction (strategy.direction.all/long/short). max_position_size blocks new entries when the position quantity reaches the cap. max_drawdown halts the strategy when peak-to-trough drawdown crosses the cap (absolute, or % of peak equity). max_intraday_loss halts it when the running intraday P&L crosses the cap; the day boundary uses month and day of month, not the session. max_cons_loss_days halts it after N consecutive losing days. max_intraday_filled_orders is a latch-till-day-rollover fill cap: the cap-triggering fill emits TradingView's synthetic cap-close, then every further fill (and order placement) on that chart day is dropped.\n\nThe drawdown and consecutive-loss-day halts are one-way: once either latches, no new entries are accepted for the rest of the run. None of these limits is a StrategyOverrides key, so a backtest cannot override them from outside the script.",
99
110
  supported: [
100
111
  "strategy.risk.allow_entry_in",
101
112
  "strategy.risk.max_position_size",
102
113
  "strategy.risk.max_drawdown",
103
114
  "strategy.risk.max_intraday_loss",
104
115
  "strategy.risk.max_cons_loss_days",
105
- "strategy.risk.max_intraday_filled_orders",
106
- "strategy.direction.all/long/short (RiskDirection enum)",
107
- ],
108
- unsupported: [
109
- "No StrategyOverrides / C-ABI entry point for any risk field (must be set in generated subclass)",
110
- "Risk thresholds not configurable at runtime via strategy_set_override",
116
+ "strategy.risk.max_intraday_filled_orders (latch-till-day-rollover cap-close)",
117
+ "strategy.direction.all / strategy.direction.long / strategy.direction.short",
111
118
  ],
119
+ unsupported: [],
112
120
  },
113
121
  {
114
122
  id: "inputs",
115
123
  title: "Inputs",
116
124
  status: "supported",
117
- summary: "All input.* kinds work via a string injection map plus typed getters; only UI metadata is dropped.",
118
- detail: "Inputs are stored as a std::unordered_map<std::string,std::string> on BacktestEngine. Generated code reads them through typed getters (get_input_double / _int / _bool / _string) that fall back to the Pine default on a missing key or parse failure. get_input_bool accepts \"true\"/\"1\" and \"false\"/\"0\" (anything else returns the default); the numeric getters route through std::stod / std::stoi with try/catch. The runtime is agnostic about the input *kind* — every input.* variant (float, int, bool, string, source, color, timeframe, enum, price, session, symbol, text_area, time) is presented as a string and the call-site getter decides the parse. The C ABI exposes strategy_set_input to override a value before run(...); internally this is set_input(key, value) / clear_inputs() on BacktestEngine. All 14 input.* functions are classified Runtime in the detail audit. UI metadata (group, inline, tooltip, display, confirm, options, min/max/step) has no runtime backing and is the consumer's problem.",
125
+ summary: "Every input.* kind is read: values arrive as strings through an override map and typed getters, and input.source overrides resolve native source names. input.source() needs a native chart series as its default; UI metadata has no runtime backing.",
126
+ detail: "Inputs are stored as a std::unordered_map<std::string,std::string> on the engine. Generated code reads them through typed getters (get_input_double / _int / _int64 / _bool / _string, and get_input_source) that fall back to the Pine default on a missing key or a parse failure. get_input_bool accepts \"true\"/\"1\" and \"false\"/\"0\" (anything else returns the default); the numeric getters route through std::stod / std::stoi / std::stoll with try/catch. get_input_int64 backs 64-bit payloads such as input.color (packed ARGB). get_input_source backs input.source overrides: it resolves a native source name (open, high, low, close, volume, hl2, hlc3, ohlc4, hlcc4) to the engine's source series and falls back to the codegen default when the key is absent or the override is not a native name. The runtime does not care about the input kind: every input.* value is a string and the getter at the call site decides the parse. The C ABI's strategy_set_input overrides a value before run(...). input.source() must default to a native chart series (open, high, low, close, volume, hl2, hlc3, ohlc4, hlcc4); codegen refuses any other default. UI metadata (group, inline, tooltip, display, confirm, options, min/max/step) has no runtime backing.",
119
127
  supported: [
120
128
  "input()",
121
129
  "input.float()",
122
130
  "input.int()",
123
131
  "input.bool()",
124
132
  "input.string()",
125
- "input.source()",
126
133
  "input.color()",
127
134
  "input.timeframe()",
128
135
  "input.enum()",
@@ -133,26 +140,25 @@ export const COVERAGE = {
133
140
  "input.time()",
134
141
  "get_input_double",
135
142
  "get_input_int",
143
+ "get_input_int64",
136
144
  "get_input_bool",
137
145
  "get_input_string",
146
+ "get_input_source",
138
147
  "strategy_set_input",
139
148
  ],
149
+ partial: [
150
+ "input.source() (its default must be open, high, low, close, volume, hl2, hlc3, ohlc4 or hlcc4; any other default is refused)",
151
+ ],
140
152
  unsupported: [
141
- "group",
142
- "inline",
143
- "tooltip",
144
- "display",
145
- "confirm",
146
- "options",
147
- "min/max/step (UI metadata, no runtime backing)",
153
+ "group / inline / tooltip / display / confirm / options / min / max / step (input UI metadata: no runtime backing)",
148
154
  ],
149
155
  },
150
156
  {
151
157
  id: "ta",
152
158
  title: "ta.*",
153
159
  status: "supported",
154
- summary: "59 ta.* functions + 8 volume ta.* series variables backed by stateful runtime classes, plus a free pivot_point_levels(); the ta.vwap 3-tuple bands form is now runtime-backed (Sprint B), with only pivot anchor/developing params left to the compiler.",
155
- detail: "ta.hpp (split across ta_moving_averages/oscillators/volatility_trend/extremes_volume/misc.cpp) implements 59 official Pine v6 ta.* functions plus 8 volume ta.* series variables as stateful classes, each exposing compute(...) (advance state) and recompute(...) (re-run on the same bar without disturbing permanent history, used by the magnifier and security intrabar paths). The consumer compiler allocates one instance per call site. Coverage spans moving averages (sma, ema, rma, wma, hma, vwma, alma, swma), oscillators/momentum (rsi, stoch, cci, mfi, mom, roc, cmo, tsi, wpr, cog, tr, atr, rci), bands/widths (bb, kc, bbw, kcw), trend/pivots (supertrend, dmi, sar, pivothigh, pivotlow), cross/state machines (crossover, crossunder, cross, rising, falling, barssince, valuewhen, change), windowed stats (stdev, variance, median, mode, range, dev, highest, lowest, percentrank, correlation, linreg), volume series variables (obv, accdist, nvi, pvi, pvt, wad, wvad, iii) and ta.vwap. ta.vwap now has both overloads: the single-value VWAP class and the 3-tuple VWAPBands class (VWAPBandsResult{vwap,upper,lower}, running variance via cum_pv_sq_) added in Sprint B for ta.vwap(src, anchor, stdev_mult).",
160
+ summary: "59 ta.* functions and 8 ta.* series variables backed by stateful runtime classes, plus ta.pivot_point_levels with anchor and developing, ta.vwap with any anchor and its 3-tuple bands form, and series lengths for ta.highest/lowest/highestbars/lowestbars.",
161
+ detail: "ta.hpp (split across ta_moving_averages/oscillators/volatility_trend/extremes_volume/misc.cpp) implements the official Pine v6 ta.* functions and series variables as stateful classes, each exposing compute(...) (advance state) and recompute(...) (re-run on the same bar without disturbing permanent history, used by the magnifier and security paths). The transpiler allocates one instance per call site. Coverage spans moving averages (sma, ema, rma, wma, hma, vwma, alma with floor, swma), oscillators/momentum (rsi, stoch, cci, mfi, mom, roc, cmo, tsi, wpr, cog, rci, tr, atr), bands/widths (bb, kc and kcw with useTrueRange, bbw), trend/pivots (supertrend, dmi, sar, pivothigh, pivotlow), cross/state machines (crossover, crossunder, cross, change, rising, falling, barssince, valuewhen), windowed stats (stdev, variance, dev, median, mode, range, highest, lowest, highestbars, lowestbars, percentrank, percentile_nearest_rank, percentile_linear_interpolation, correlation, linreg), cumulative/chart extremes (cum, max, min), the volume series variables (obv, accdist, nvi, pvi, pvt, wad, wvad, iii) and ta.vwap.\n\nta.vwap restarts on the session day by default; any other anchor uses AnchoredVWAP, and the 3-tuple [vwap, upper, lower] = ta.vwap(src, anchor, stdev_mult) form is backed by VWAPBands / AnchoredVWAPBands. ta.pivot_point_levels(type, anchor, developing) is the stateful PivotPointLevels; Woodie with developing = true stops the run (Woodie has no developing levels). The parenthesized call form of a series variable, such as ta.obv(), is refused: it is not a Pine v6 function. ta.change takes a numeric source in the runtime; the transpiler casts a bool source to 0/1. ta.tr(handle_na) follows Pine v6's first-bar split.\n\nA length that is neither a constant nor an input: a simple length, fixed for the run, builds the indicator on the call site's first execution; a series length of ta.highest, ta.lowest, ta.highestbars or ta.lowestbars re-windows every call; ta.supertrend keeps its first execution's factor; a length of 0, a negative length or na stops the run. A series length of any other ta.* is refused.",
156
162
  supported: [
157
163
  "ta.sma",
158
164
  "ta.ema",
@@ -160,114 +166,109 @@ export const COVERAGE = {
160
166
  "ta.atr",
161
167
  "ta.macd",
162
168
  "ta.bb",
163
- "ta.kc",
169
+ "ta.kc / ta.kcw (useTrueRange)",
170
+ "ta.alma (floor)",
164
171
  "ta.supertrend",
165
172
  "ta.dmi",
166
173
  "ta.sar",
167
174
  "ta.stoch",
168
175
  "ta.linreg",
169
- "ta.vwap",
170
- "ta.vwap 3-tuple bands (src, anchor, stdev_mult)",
171
- "ta.obv",
172
- "ta.accdist",
176
+ "ta.vwap (session-day anchor by default, any anchor, and the 3-tuple bands form)",
177
+ "ta.obv / ta.accdist / ta.nvi / ta.pvi / ta.pvt / ta.wad / ta.wvad / ta.iii (series variables; the call form with parentheses is refused: not a Pine v6 function)",
173
178
  "ta.tr",
179
+ "ta.change (a bool source is cast to 0/1 by the transpiler)",
180
+ "ta.highest / ta.lowest / ta.highestbars / ta.lowestbars (a series length re-windows every call)",
181
+ "ta.cum / ta.max / ta.min",
174
182
  "ta.pivothigh",
175
183
  "ta.pivotlow",
184
+ "ta.pivot_point_levels (anchor and developing; Woodie with developing = true stops the run)",
176
185
  "pivot_point_levels()",
177
186
  ],
178
- unsupported: [
179
- "ta.obv() and other parenthesized series-variable call forms — rejected by support checker (not Pine v6 functions)",
180
- "ta.change with bool source — runtime is numeric-only, compiler casts bool to 0.0/1.0",
181
- "Woodie pivot anchor/developing params (period open not received by free fn; close-based fallback used)",
182
- "strategy.*trades.direction(...) accessors (no such Pine v6 accessor; rejected)",
183
- ],
187
+ unsupported: [],
184
188
  },
185
189
  {
186
190
  id: "math",
187
191
  title: "math.*",
188
192
  status: "via_transpiler",
189
- summary: "Runtime backs only pine_random and rolling math::Sum; every other math.* (abs, sqrt, trig, min/max, round, constants) is emitted inline by the transpiler.",
190
- detail: "math.hpp/math.cpp own exactly two pieces: pine_random(...), a deterministic SplitMix64-style mixer that is stable across platforms/runs but is NOT TradingView's PRNG (math.random maps here, with no byte-for-byte TV parity); and math::Sum(length), a rolling-window sum backing math.sum(source, length) where NaN inputs short-circuit to NaN. math.round_to_mintick also maps to BacktestEngine::round_to_mintick. Everything else in the math namespace is the consumer compiler's responsibility — the transpiler emits it inline against <cmath> or simple expressions: math.abs/sqrt/pow/exp/log/log10/ceil/floor/round/sin/cos/tan/asin/acos/atan, math.min/max/avg/sign/todegrees/toradians, and the constants math.pi (M_PI), math.e (M_E), math.phi (inline literal), math.rphi (inline literal). So most scalar math.* works end-to-end despite having no dedicated runtime module.",
193
+ summary: "The runtime backs math.random (deterministic, not TradingView's generator), the rolling math.sum and math.round_to_mintick; PineForge's transpiler emits every other math.* inline, so they work end-to-end.",
194
+ detail: "math.hpp/math.cpp own two pieces. pine_random(lo, call_site, hi, seed, bar_index) is a deterministic SplitMix64-style mixer, stable across platforms and runs but not TradingView's PRNG: math.random maps here, so its values differ from TradingView's (TradingView-exact PRNG parity is out of scope by design). math::Sum(length) backs math.sum(source, length): na sources are ignored, the output stays na until length non-na values exist, then holds the sum of the last length non-na values, including on na-input bars. math.round_to_mintick maps to BacktestEngine::round_to_mintick. Everything else in the math namespace is emitted inline by PineForge's transpiler against <cmath> or simple expressions: math.abs/sqrt/pow/exp/log/log10/ceil/floor/round/sign/avg/min/max/todegrees/toradians, the trig functions, and the constants math.pi, math.e, math.phi and math.rphi.",
191
195
  supported: [
192
- "math.random (pine_random, SplitMix64, not TV-exact)",
193
- "math.sum (math::Sum class)",
196
+ "math.sum (math::Sum: na inputs are skipped)",
194
197
  "math.round_to_mintick (BacktestEngine::round_to_mintick)",
195
- "math.abs (transpiler)",
196
- "math.sqrt (transpiler)",
197
- "math.pow (transpiler)",
198
- "math.min/math.max (transpiler)",
199
- "math.sin/cos/tan (transpiler)",
200
- "math.pi/math.e/math.phi/math.rphi (transpiler constants)",
201
198
  ],
202
- unsupported: [
203
- "TradingView-exact PRNG parity for math.random (out of scope by design — determinism preferred over TV byte-parity)",
199
+ partial: [
200
+ "math.random (deterministic and reproducible, but not TradingView's generator: its values differ from TradingView's)",
201
+ ],
202
+ via_transpiler: [
203
+ "math.abs / math.sqrt / math.pow / math.exp / math.log / math.log10 / math.ceil / math.floor / math.round / math.sign / math.avg / math.todegrees / math.toradians",
204
+ "math.min / math.max",
205
+ "math.sin / math.cos / math.tan / math.asin / math.acos / math.atan",
206
+ "math.pi / math.e / math.phi / math.rphi (constants)",
204
207
  ],
208
+ unsupported: [],
205
209
  },
206
210
  {
207
211
  id: "str",
208
212
  title: "str.*",
209
- status: "partial",
210
- summary: "Five str.* helpers have dedicated runtime backing; the rest (length, contains, replace, lower/upper, tonumber, etc.) are emitted inline by the transpiler.",
211
- detail: "str_utils.hpp/str_utils.cpp own five helpers: pine_str_format ({N} placeholder substitution, all occurrences replaced), pine_str_format_time (Pine tokens yyyy/MM/dd/HH/mm/ss mapped to strftime; UTC via gmtime_r, otherwise swaps TZ under pine_tz::ScopedTimezone with localtime_r), pine_str_match (first capture group or full match; empty on no match/regex error), pine_str_split (vector<string>; empty separator yields {source}), and pine_str_tostring (NaN renders \"NaN\"; modes mintick/percent/volume, default falls back to std::to_string). pine_enum_str_at (in engine.hpp) backs str.tostring(<enum_member>) with index clamping.\n\nThe topic is partial at the runtime layer because all other string operations have no runtime API — the transpiler emits them inline against std::string: str.length, str.contains, str.replace/replace_all, str.lower/upper, str.tonumber, str.substring, str.startswith/endswith, str.pos, str.repeat, str.trim. Those still run end-to-end; they just aren't runtime modules.",
213
+ status: "supported",
214
+ summary: "The runtime backs str.format, str.format_time, str.match, str.split and str.tostring; PineForge's transpiler emits the other str.* functions inline against std::string, so they work end-to-end.",
215
+ detail: "str_utils.hpp/str_utils.cpp own the runtime helpers. pine_str_format and str_format_values implement MessageFormat: {N} and {N,number,<style>} placeholders (integer, percent, currency or a decimal pattern), text between single quotes is literal, and a placeholder with no such argument is kept as written; a number argument in {N} renders as #,###.###. pine_str_format_time maps Pine tokens (yyyy/MM/dd/HH/mm/ss) to strftime: empty/\"UTC\"/\"Etc/UTC\" use gmtime_r, any other zone swaps TZ under tz_util::ScopedTimezone and uses localtime_r. pine_str_match returns the first capture group, else the full match, and an empty string on no match or a regex error. pine_str_split returns a vector<string>; an empty separator yields {source}. pine_str_tostring renders the value's shortest round-trip decimal digits, rounded half-up; NaN, Infinity and -Infinity print as such; modes are the default (up to ten fraction digits), \"percent\", \"volume\" (K/M/B/T), \"mintick\", or a decimal pattern (#.##, #.00, #,###, #.##%). str.tostring(<enum member>) uses pine_enum_str_at (source/pine_policy_support.hpp), which clamps the index.\n\nEvery other string operation (str.length, str.contains, str.replace/replace_all, str.lower/upper, str.tonumber, str.substring, str.startswith/endswith, str.pos, str.repeat, str.trim) has no runtime API: PineForge's transpiler emits it inline against std::string, and it runs end-to-end. codegen warns where str.repeat's result can be na, which the engine cannot yet represent exactly. str.tostring of an array is not lowered, and its C++ does not compile (codegen 1.0.1).",
212
216
  supported: [
213
- "str.format (pine_str_format)",
214
- "str.format_time (pine_str_format_time)",
215
- "str.match (pine_str_match)",
216
- "str.split (pine_str_split)",
217
- "str.tostring (pine_str_tostring)",
218
- "str.tostring(enum) via pine_enum_str_at",
217
+ "str.format (MessageFormat placeholders)",
218
+ "str.format_time",
219
+ "str.match",
220
+ "str.split",
219
221
  ],
220
- unsupported: [
221
- "str.length",
222
- "str.contains",
223
- "str.replace / str.replace_all",
224
- "str.lower / str.upper",
225
- "str.tonumber",
226
- "str.substring",
227
- "str.startswith / str.endswith (no runtime API — emitted inline by transpiler)",
222
+ partial: [
223
+ "str.tostring (default, percent, volume and mintick formats, decimal patterns, enum members; an array argument is not lowered and its C++ does not compile)",
228
224
  ],
225
+ via_transpiler: [
226
+ "str.length / str.contains / str.replace / str.replace_all / str.lower / str.upper / str.tonumber",
227
+ "str.substring / str.startswith / str.endswith / str.pos / str.repeat / str.trim (emitted inline against std::string)",
228
+ ],
229
+ unsupported: [],
229
230
  },
230
231
  {
231
232
  id: "request_security",
232
233
  title: "request.security()",
233
234
  status: "partial",
234
- summary: "Dedicated runtime owns same-symbol MTF security: state machine, higher-TF ratio/calendar aggregation, lookahead/gaps, and lower-TF emulation; cross-symbol and external request.* are rejected.",
235
- detail: "coverage.md marks request.security() as Partial (line 44): libpineforge.a owns the security state machine (SecurityEvalState, coverage.md line 460), ratio/calendar aggregation via TimeframeAggregator, lookahead/gaps semantics, lower-TF emulation, and per-security diagnostics. The generated subclass drives it through configure_security_evaluators()/register_security_eval(...), evaluate_security(...), and clear_security(...) (lines 480-482). Higher-TF requests route bars through the aggregator: on a complete bar eval_complete_count++; on a partial bar under lookahead_on eval_partial_count++ with is_complete=false; on a partial bar under gaps_on clear_security (lines 492-495). The detail doc (line 657) classifies request.security() itself as Runtime (same-symbol MTF + higher-TF aggregation).\n\nrequest.security_lower_tf() is also Runtime-supported (detail line 658) via synthesize_lower_tf_bars, returning an earliest-to-latest array (coverage.md lines 507-510), but only for same-symbol intraday lower TFs that satisfy supports_lower_tf_emulation: both input and requested are fixed intraday minute strings (no D/W/M/S suffix), requested<input, and input_seconds % requested_seconds == 0 (lines 499-501). Lower-TF emulation rejects lookahead_on/gaps_on (ensure_supported_lower_tf_emulation_flags throws; it is lookahead_off/gaps_off only, lines 503-505). Only numeric and bool element arrays are supported; tuple/UDT/color/string element arrays are rejected by the transpiler (lines 510-512).\n\nThe 'Partial' status reflects that cross-symbol securities are not modelled (only same-symbol) and the external request.* variants are rejected at transpile. coverage.md's narrative (lines 769-776) names eight: request.financial, request.dividends, request.earnings, request.splits, request.currency_rate, request.economic, request.seed, request.quandl. Note: the detail doc table (line 655) additionally lists request.footprint() as Unsupported, so the full external-rejection set in that table is nine, not eight; the entry's eight-item list omits footprint (a completeness gap, but footprint is correctly not claimed as supported). barmerge.gaps_on/off and barmerge.lookahead_on/off are Runtime-backed flags on SecurityEvalState (detail lines 315-318).",
235
+ summary: "On the chart's own symbol (syminfo.tickerid), request.security and request.security_lower_tf run here: higher-timeframe aggregation, lookahead and gaps, and lower-timeframe emulation. On another symbol, request.security is supported by the engine, which reads that symbol's own bars installed before the run; this server cannot supply other symbols' bars, so such a request whose value can reach a trade stops the run. A symbol written as a string counts as another symbol.",
236
+ detail: "Chart symbol (syminfo.tickerid or syminfo.ticker): the runtime owns the security state machine (SecurityEvalState), ratio/calendar aggregation (TimeframeAggregator), lookahead/gaps semantics, lower-timeframe emulation and per-security diagnostics. A higher-timeframe request routes the chart's bars through the aggregator: a complete bar evaluates with is_complete=true; a partial bar evaluates under lookahead_on, clears under gaps_on, and is otherwise held until it completes. codegen warns that lookahead_on exposes the completed higher-timeframe value from the bucket's first chart bar.\n\nrequest.security_lower_tf emulates intrabars from each chart bar when both timeframes are fixed intraday minute strings (no D/W/M/S suffix), the requested one is finer and it divides the chart's evenly; the array runs earliest to latest within the chart bar. Its elements may be float, int or bool; tuple, UDT, color and string element types are refused. Emulation is lookahead_off/gaps_off only. A run fails when a request exists but the chart timeframe is unknown, when a lower timeframe cannot be emulated, or when a request.security timeframe is finer than a chart fed only its own bars.\n\nAnother symbol: supported by the engine; this server cannot supply other symbols' bars. In engine v1.0.1 a site of another symbol reads that symbol's own bars, which the host installs through the engine's C API before the run (strategy_set_symbol_feed / _feed_column / strategy_set_symbol_facts) from a feed a requests manifest pins; codegen 1.0.0 and later lower such a site onto it. This server installs no other symbol's bars, and codegen treats a symbol written as a string (\"BINANCE:BTCUSDT\") as another symbol even when it names the chart's own market. So a request on another symbol whose value can reach a trade transpiles, then stops the run where its value is read (\"... no data is pinned for this request, and its value was read\"); it never reads the chart's bars in its place. One whose value reaches only plots, alerts, tables or logs lowers to na with a warning, and trades are unaffected.\n\nThe other request.* calls: request.financial, request.earnings, request.dividends and request.splits read per-bar series that a requests manifest records, and request.footprint inside request.security reads a pinned feed's delta column. This server installs neither, so a value of theirs that can reach a trade stops the run where it is read, and one that reaches only plots, alerts, tables or logs reads na. request.economic, request.currency_rate, request.seed and request.quandl are refused at transpile.",
237
+ partial: [
238
+ "request.security (supported by the engine; this server cannot supply other symbols' bars: on syminfo.tickerid it runs on the chart's bars; on another symbol, including one written as a string, a value that can reach a trade stops the run, while one that reaches only plots, alerts, tables or logs reads na)",
239
+ "request.security_lower_tf (chart's symbol, fixed intraday minute timeframes that divide the chart's; float, int or bool elements: tuple, UDT, color and string elements are refused)",
240
+ ],
236
241
  supported: [
237
- "request.security",
238
- "request.security_lower_tf",
239
- "barmerge.gaps_on",
240
- "barmerge.gaps_off",
242
+ "barmerge.gaps_on / barmerge.gaps_off",
241
243
  "barmerge.lookahead_off",
242
- "barmerge.lookahead_on",
244
+ "barmerge.lookahead_on (codegen warns that it exposes the completed higher-timeframe value from the bucket's first chart bar)",
245
+ "syminfo.tickerid / syminfo.ticker (the chart's own symbol in a request)",
246
+ "ticker.heikinashi (the chart's own symbol only: Heikin-Ashi candles inside a same-symbol request.security)",
243
247
  ],
248
+ via_transpiler: ["ticker.inherit / ticker.standard (pass the symbol through)"],
244
249
  unsupported: [
245
- "request.financial",
246
- "request.dividends",
247
- "request.earnings",
248
- "request.splits",
249
- "request.currency_rate",
250
- "request.economic",
251
- "request.seed",
252
- "request.quandl",
253
- "request.footprint",
254
- "barmerge.lookahead_on (for lower-TF emulation)",
255
- "cross-symbol request.security",
250
+ "request.financial / request.earnings / request.dividends / request.splits (PineForge reads them from recorded per-bar series; this server installs none, so a value that can reach a trade stops the run, and one that reaches only plots, alerts, tables or logs reads na)",
251
+ "request.footprint (reads a pinned feed's delta column; this server installs none, so a value that can reach a trade stops the run)",
252
+ "request.economic / request.currency_rate (refused at transpile)",
253
+ "request.seed (refused at transpile: TradingView seeds have no PineForge equivalent)",
254
+ "request.quandl (refused at transpile: deprecated upstream)",
255
+ "ticker.new / ticker.modify (refused: no cross-symbol ticker construction)",
256
+ "ticker.renko / ticker.kagi / ticker.linebreak / ticker.pointfigure (refused: the engine does not build these chart types)",
256
257
  ],
257
258
  },
258
259
  {
259
260
  id: "bar_magnifier",
260
261
  title: "Bar magnifier",
261
262
  status: "supported",
262
- summary: "Dedicated magnifier runtime samples the intrabar OHLC price path with six distribution modes plus optional volume-weighted sample density.",
263
- detail: "coverage.md lists Bar magnifier as Supported: OHLC-path sampling with 6 distribution modes plus optional volume-weighted sample density, implemented in magnifier.hpp/magnifier.cpp. The MagnifierDistribution enum exposes UNIFORM (equal arc-length spacing), COSINE (Chebyshev-like endpoint density), TRIANGLE (segment-midpoint density), ENDPOINTS (default; always exact O,H,L,C with uniform fill), FRONT_LOADED (density near O), and BACK_LOADED (density near C).\n\nsample_price_path(bar, n, dist) emits at least 2 points, always exactly O first and C last, with the middle leg O->H->L->C when open is closer to high else O->L->H->C (ties low-first). sample_price_path_volume_weighted(...) scales the sample count by bar.volume/mean_volume clamped to [min,max] (default 2..64); BacktestEngine::run_magnified_bar precomputes per-bar mean volume, and the toggle is set_magnifier_volume_weighted(bool) (C ABI: strategy_set_magnifier_volume_weighted).\n\nInside run_magnified_bar the engine threads every sub-bar, calls feed_security_eval_state once per sub-bar so security state machines see the fine bars, and forces is_first_tick_ true on the last sample of the last sub-bar so on_bar advances series history exactly once per script bar. The magnifier is configured via the TF-aware run(...) overloads (bar_magnifier, magnifier_samples, magnifier_dist) and run_backtest_full.",
263
+ summary: "With a feed finer than the chart, the magnifier walks TradingView's own intrabars (a 15-minute chart walks 2-minute bars); with the chart's own bars only, it samples each bar's OHLC path with six distribution modes and optional volume-weighted density.",
264
+ detail: "With a feed finer than the chart, the bar magnifier walks TradingView's own intrabars at TradingView's intrabar timeframe (a 15-minute chart walks 2-minute bars), each owned by the chart bar holding its last minute (source/magnifier_intrabars.hpp). With the chart's own bars only, it samples each bar's OHLC path (magnifier.hpp/magnifier.cpp). MagnifierDistribution has six modes: UNIFORM (equal arc-length spacing), COSINE (Chebyshev-like endpoint density), TRIANGLE (segment-midpoint density), ENDPOINTS (default; always exact O, H, L, C with uniform fill), FRONT_LOADED (density near O) and BACK_LOADED (density near C).\n\nsample_price_path(bar, n, dist) emits at least 2 points, O first and C last, with the middle leg O->H->L->C when the open is closer to the high, else O->L->H->C (ties low-first). sample_price_path_volume_weighted(...) scales the sample count by bar.volume / mean volume, clamped to [min, max] (default 2..64); the toggle is set_magnifier_volume_weighted(bool) (C ABI: strategy_set_magnifier_volume_weighted).\n\nThe kernel matches orders over every sub-bar of a magnified script bar; the Pine host runs the script once, at the terminal sub-bar, so on_bar advances series history once per script bar. The magnifier is configured through the TF-aware run(...) overloads and run_backtest_full. A script that declares use_bar_magnifier = true exports strategy_declares_bar_magnifier().",
264
265
  supported: [
265
266
  "MagnifierDistribution (UNIFORM, COSINE, TRIANGLE, ENDPOINTS, FRONT_LOADED, BACK_LOADED)",
266
267
  "sample_price_path",
267
268
  "sample_price_path_volume_weighted",
268
269
  "set_magnifier_volume_weighted",
269
- "run_magnified_bar",
270
270
  "strategy_set_magnifier_volume_weighted",
271
+ "strategy_declares_bar_magnifier",
271
272
  ],
272
273
  unsupported: [],
273
274
  },
@@ -275,31 +276,28 @@ export const COVERAGE = {
275
276
  id: "time_session_timezone",
276
277
  title: "Time / session / timezone",
277
278
  status: "supported",
278
- summary: "Dedicated session_time runtime implements pine_time/pine_time_close with session filtering plus session.* state and a mutex-guarded ScopedTimezone for thread-safe TZ formatting.",
279
- detail: "coverage.md lists Time/session/timezone as Supported: pine_time / pine_time_close with session filtering and a mutex-guarded pine_tz::ScopedTimezone, in session_time.hpp/session_time.cpp (timezone.cpp internal, not in the public include path). pine_time(bar_ms, tf, session, tz, chart_tf) and pine_time_close(...) return Unix milliseconds, or na<int64_t>() when the bar is outside the requested session (matching TradingView's filtered-session semantics); they parse session strings and convert timezones internally. The detail doc classifies time and time_close as Runtime in both var forms (current_bar_.timestamp / pine_time_close) and 1-arg fn forms (pine_time / pine_time_close), along with the 1-arg date/time functions hour(), minute(), second(), dayofmonth(), dayofweek(), month(), weekofyear(), year() (all Runtime).\n\nThe session.* variables are Runtime-backed (Sprint A): session.ismarket, session.ispremarket (0400-RTH_open local), session.ispostmarket (RTH_close-2000 local), and session.isfirstbar/islastbar with per-bar lookahead in engine_run.cpp. session.isfirstbar_regular/islastbar_regular are Runtime but aliased to the non-regular forms because the engine carries a single session string and cannot distinguish RTH vs ETH (documented limitation). time_tradingday is also Runtime (Sprint G1; derives session-day open in syminfo_.timezone with DST-edge fallback). session.regular/session.extended are emitted as string constants by the transpiler.\n\npine_tz::ScopedTimezone(tz) is RAII: it grabs a process-wide mutex, swaps the TZ env var (saving/restoring the prior value), and releases on destruction. This is the only reason pine_str_format_time and the session helpers are thread-safe in a multi-strategy harness. Empty / 'UTC' / 'Etc/UTC' use gmtime_r; other zones swap TZ under ScopedTimezone and use localtime_r. timenow is unsupported (no live clock; always na in batch mode).",
279
+ summary: "pine_time / pine_time_close with session filtering and timezone conversion, session.* flags from the engine's session calendar, and a mutex-guarded tz_util::ScopedTimezone.",
280
+ detail: "pine_time(bar_ms, tf, session, tz, chart_tf) and pine_time_close(...) return Unix milliseconds, or na when no bar of tf built on the requested session holds the bar (TradingView's semantics for filtered sessions). A session argument builds its own bars in its timezone (the explicit one, else syminfo.timezone): a D bar runs from a session day's first window open to its last close, a W or M bar from the first session day of its week or month to the next one's, and an intraday bar opens at each window's open. time() / time_close() with a nonzero bars_back or timeframe_bars_back read another bar's time (codegen 1.0.0).\n\nsession.ismarket, session.ispremarket and session.ispostmarket read the engine's session calendar and in-session facts, and session.isfirstbar / islastbar and their _regular forms read the engine's session-day members (codegen 1.0.0). Inside a request.security payload they keep time-of-day predicates and warn; session.<flag>[k] reads the flag's history. tz_util::ScopedTimezone(tz) is RAII: it holds a process-wide mutex and swaps TZ, so pine_str_format_time and the session helpers are safe in a multi-strategy harness.\n\ntimenow compiles with a warning that it diverges from TradingView: a backtest has no wall clock, and codegen 1.0.1 reads the current bar's time for it.",
280
281
  supported: [
281
- "pine_time / time",
282
- "pine_time_close / time_close",
283
- "session.ismarket",
284
- "session.ispremarket",
285
- "session.ispostmarket",
286
- "session.isfirstbar",
287
- "session.islastbar",
282
+ "time / pine_time",
283
+ "time_close / pine_time_close",
284
+ "session.ismarket / session.ispremarket / session.ispostmarket",
285
+ "session.isfirstbar / session.islastbar / session.isfirstbar_regular / session.islastbar_regular",
288
286
  "time_tradingday",
289
- "pine_tz::ScopedTimezone",
290
- "hour/minute/dayofweek/year (1-arg forms)",
287
+ "hour / minute / second / dayofmonth / dayofweek / month / year / weekofyear",
288
+ "tz_util::ScopedTimezone",
291
289
  ],
292
- unsupported: [
293
- "timenow",
294
- "session.isfirstbar_regular vs isfirstbar (Runtime but aliased, no RTH/ETH distinction)",
290
+ partial: [
291
+ "timenow (compiles with a warning: a backtest has no wall clock, so it reads the current bar's time)",
295
292
  ],
293
+ unsupported: [],
296
294
  },
297
295
  {
298
296
  id: "timeframe_parsing",
299
297
  title: "Timeframe parsing",
300
298
  status: "supported",
301
- summary: "Dedicated timeframe runtime parses TF strings, computes ratios, detects calendar/TF boundaries, auto-detects TF, and aggregates via TimeframeAggregator (passthrough/ratio/calendar).",
302
- detail: "coverage.md lists Timeframe parsing as Supported: tf_to_seconds, tf_ratio, tf_change, detect_timeframe, calendar boundary detection, and TimeframeAggregator (passthrough/ratio/calendar) in timeframe.hpp/timeframe.cpp. tf_to_seconds(tf) covers minute strings ('1','5','60','240',...), day strings ('D','1D'->86400) and week strings ('W','1W'->604800); month ('M','1M') returns -1 to flag calendar mode. Inline predicates tf_multiplier and tf_is_intraday/_daily/_weekly/_monthly/_seconds back the timeframe.* variables.\n\ntf_ratio(input_tf, target_tf) returns >1 for ratio aggregation, 1 for same TF, -1 for calendar (month), and -2 when target is finer than input. detect_timeframe(bars, n, max_samples=100) infers a TV-style TF string from median timestamp deltas (fallback '1' on insufficient/irregular data). tf_change(prev_ms,curr_ms,tf) and crosses_boundary(prev_ms,curr_ms,period) provide TF/calendar boundary detection. TimeframeAggregator runs in PASSTHROUGH (default ctor), RATIO (ctor (int ratio) -> every ratio input bars produce one output bar), and CALENDAR (ctor (target_tf,input_tf) -> day/week/month boundaries) modes, with feed(bar) returning AggregatedBar{Bar bar; bool is_complete; int sub_bar_count;}.\n\nThe detail doc marks timeframe.change() (tf_change), timeframe.in_seconds() (tf_to_seconds), timeframe.period, .multiplier, .main_period, and the timeframe.is* predicates as Runtime. timeframe.from_seconds() is Transpiler-emitted (inverse of tf_to_seconds), and timeframe.isticks is a constant false via the transpiler since the engine has no tick-TF support.",
299
+ summary: "The timeframe runtime parses TF strings, computes ratios, detects calendar/TF boundaries, auto-detects the input TF, and aggregates via TimeframeAggregator (passthrough/ratio/calendar).",
300
+ detail: "timeframe.hpp/timeframe.cpp: tf_to_seconds(tf) covers minute strings ('1','5','60','240',...), day strings ('D','1D' -> 86400) and week strings ('W','1W' -> 604800); month ('M','1M') returns -1 to flag calendar mode. tf_multiplier and tf_is_intraday/_daily/_weekly/_monthly/_seconds back the timeframe.* variables.\n\ntf_ratio(input_tf, target_tf) returns >1 for ratio aggregation, 1 for the same TF, -1 for calendar (month) and -2 when the target is finer than the input. detect_timeframe(bars, n, max_samples=100) infers a TV-style TF string from median timestamp deltas (fallback '1' on insufficient or irregular data). tf_change(prev_ms, curr_ms, tf) and crosses_boundary(prev_ms, curr_ms, period) detect TF/calendar boundaries. TimeframeAggregator runs in PASSTHROUGH, RATIO (every ratio input bars make one output bar) and CALENDAR (day/week/month boundaries) modes; feed(bar) returns AggregatedBar{bar, is_complete, sub_bar_count}.\n\ntimeframe.change(), timeframe.in_seconds(), timeframe.period, timeframe.multiplier, timeframe.main_period and the timeframe.is* predicates work. timeframe.isticks is false (the engine has no tick timeframe). timeframe.from_seconds() is refused: codegen 1.0.1 reports that it is not implemented yet.",
303
301
  supported: [
304
302
  "tf_to_seconds / timeframe.in_seconds",
305
303
  "tf_ratio",
@@ -309,19 +307,17 @@ export const COVERAGE = {
309
307
  "timeframe.period",
310
308
  "timeframe.multiplier",
311
309
  "timeframe.main_period",
312
- "timeframe.isintraday/isdaily/isweekly/ismonthly/isseconds",
313
- ],
314
- unsupported: [
315
- "timeframe.isticks (constant false; no tick TF)",
316
- "timeframe.from_seconds (transpiler-emitted, not a runtime module)",
310
+ "timeframe.isintraday / timeframe.isdaily / timeframe.isweekly / timeframe.ismonthly / timeframe.isseconds",
311
+ "timeframe.isticks (always false: the engine has no tick timeframe)",
317
312
  ],
313
+ unsupported: ["timeframe.from_seconds (refused: codegen 1.0.1 reports that it is not implemented yet)"],
318
314
  },
319
315
  {
320
316
  id: "numeric_matrices",
321
317
  title: "Numeric matrices",
322
318
  status: "supported",
323
- summary: "PineMatrix wraps Eigen::MatrixXd with a full member surface (construction, access, transforms, linear algebra, predicates); element type fixed to double.",
324
- detail: "The runtime owns `PineMatrix` (header `matrix.hpp`, impl `matrix.cpp`), an Eigen-backed double matrix. Construction is via static `new_(rows, cols, init_val=0)`. Access/structure ops cover `get/set/fill/row/col/rows/columns`, `add_row/add_col/remove_row/remove_col/swap_rows/swap_columns`, plus transforms `copy/submatrix/reshape/reverse/transpose/sort(column, ascending)/concat`.\n\nNumeric capability is the distinguishing feature: aggregation (`avg/min/max/mode/sum`), arithmetic (`diff/mult/pow`), full linear algebra (`det/inv/pinv/rank/trace/eigenvalues/eigenvectors`), `kron`, `elements_count`, and predicates (`is_square/is_identity/is_diagonal/is_antidiagonal/is_symmetric/is_antisymmetric/is_triangular/is_stochastic/is_binary/is_zero`). The per-identifier audit classifies all 44 `matrix.*` functions as Runtime, with numeric methods (det, inv, pinv, eigenvalues, eigenvectors) float-only. `order.ascending`/`order.descending` are Runtime constants used by `matrix.sort`.\n\nThe element type is fixed to double; non-double element matrices fall to the separate typed-matrix template (PineGenericMatrix), which covers int/bool/string/color/UDT element types but with structural ops only. UDT-typed numeric matrices are not runtime-supported.",
319
+ summary: "PineMatrix wraps Eigen::MatrixXd with a full member surface (construction, access, transforms, linear algebra, predicates); the element type is double. Since codegen 1.0.1 a matrix's history (m[1]) is readable.",
320
+ detail: "The runtime owns PineMatrix (matrix.hpp, matrix.cpp), an Eigen-backed double matrix. Construction is the static new_(rows, cols, init_val=0). Access and structure: get/set/fill/row/col/rows/columns, add_row/add_col/remove_row/remove_col/swap_rows/swap_columns; transforms copy/submatrix/reshape/reverse/transpose/sort(column, ascending)/concat. Aggregation avg/min/max/mode/sum, arithmetic diff/mult/pow, linear algebra det/inv/pinv/rank/trace/eigenvalues/eigenvectors, kron, elements_count, and the predicates is_square/is_identity/is_diagonal/is_antidiagonal/is_symmetric/is_antisymmetric/is_triangular/is_stochastic/is_binary/is_zero. order.ascending/order.descending are runtime constants used by matrix.sort.\n\nThe element type is fixed to double; other element types use PineGenericMatrix<T> (see typed_matrices). Since codegen 1.0.1, m[k] of a matrix variable (top-level or a block's local) is a read-only copy of the matrix as the variable left it k executions back: (m[1]).get(0, 0) and matrix.copy(m[1]) work; a change to the copy stops the run with RE10051, and a method on it before the variable has a history with RE10053. The history of a function's matrix parameter or local is not kept. As in 1.0.0, matrix.sum(m1, m2) and other matrix results used without a declared type do not compile.",
325
321
  supported: [
326
322
  "matrix.new",
327
323
  "matrix.det",
@@ -329,103 +325,81 @@ export const COVERAGE = {
329
325
  "matrix.pinv",
330
326
  "matrix.eigenvalues",
331
327
  "matrix.eigenvectors",
328
+ "matrix.rank",
329
+ "matrix.trace",
332
330
  "matrix.kron",
333
331
  "matrix.transpose",
334
332
  "matrix.sort",
333
+ "matrix.copy",
335
334
  "order.ascending",
336
335
  "order.descending",
337
336
  ],
338
- unsupported: ["UDT-element numeric matrices (numeric methods stay double-only)"],
337
+ unsupported: [],
339
338
  },
340
339
  {
341
340
  id: "typed_matrices",
342
341
  title: "Typed matrices",
343
342
  status: "supported",
344
- summary: "PineGenericMatrix<T> header-only template gives structural matrix ops for int/bool/string/color/UDT element types; no numeric methods.",
345
- detail: "`PineGenericMatrix<T>` (header-only, `include/pineforge/generic_matrix.hpp`) is a template over `std::vector<std::vector<T>>` (with T=bool specialized to `vector<vector<char>>`) covering non-double element types: int, bool, string, color, and UDT. The per-identifier audit (detail doc line 615) shows `matrix.new<type>()` dispatching `<float>` to `PineMatrix` and all other types to `PineGenericMatrix<T>`.\n\nUDT element types are genuinely runtime-supported: the source template instantiates over arbitrary structs, and `tests/test_generic_matrix_udt.cpp` exercises a `Pivot` UDT through new/get/set/add_row/row/submatrix/transpose/reshape plus strong-exception-guarantee paths. Note the coverage.md sentence 'UDT-typed matrices are not runtime-supported' belongs to the `PineMatrix` (Eigen double) section — it means the double-only `PineMatrix` cannot hold UDTs, not that `PineGenericMatrix<UDT>` is absent.\n\nThe support is structural only: add_row/remove_row/reshape/transpose and the other shape/access operations apply, but numeric methods (det, inv, pinv, eigenvalues, etc.) remain exclusively on `PineMatrix` (double). So a string/color/UDT matrix can be built, indexed, reshaped, and transposed, but cannot be inverted or have eigenvalues computed. (sort is also restricted: int/bool/string only on the primary template, unsupported for bool's specialization.)",
343
+ summary: "PineGenericMatrix<T> header-only template gives structural matrix ops for int/bool/string/color/UDT element types; numeric methods stay on the double PineMatrix.",
344
+ detail: "PineGenericMatrix<T> (header-only, include/pineforge/generic_matrix.hpp) is a template over std::vector<std::vector<T>> (T=bool specialized to vector<vector<char>>) for non-double element types: int, bool, string, color and UDT. matrix.new<float>() is a PineMatrix; every other element type is a PineGenericMatrix<T>.\n\nUDT element types work: the template instantiates over arbitrary structs, and the engine tests a UDT matrix (tests/test_generic_matrix_udt.cpp). Only the double PineMatrix cannot hold UDTs.\n\nThe support is structural: add_row/remove_row/reshape/transpose and the other shape and access operations apply, but the numeric methods (det, inv, pinv, rank, trace, eigenvalues, eigenvectors) exist only on PineMatrix. A string/color/UDT matrix can be built, indexed, reshaped and transposed, but not inverted. sort works on int and string matrices, not bool.",
346
345
  supported: [
347
346
  "matrix.new<int>",
348
347
  "matrix.new<bool>",
349
348
  "matrix.new<string>",
350
349
  "matrix.new<color>",
351
350
  "matrix.new<UDT>",
352
- "add_row",
353
- "remove_row",
354
- "reshape",
355
- "transpose",
351
+ "matrix.add_row / matrix.remove_row / matrix.reshape / matrix.transpose (structural ops)",
356
352
  ],
357
353
  unsupported: [
358
- "det",
359
- "inv",
360
- "pinv",
361
- "rank",
362
- "trace",
363
- "eigenvalues",
364
- "eigenvectors (all numeric methods are PineMatrix/double-only)",
354
+ "matrix.det / matrix.inv / matrix.pinv / matrix.rank / matrix.trace / matrix.eigenvalues / matrix.eigenvectors (on an int, bool, string, color or UDT matrix: numeric methods are PineMatrix/double-only)",
365
355
  ],
366
356
  },
367
357
  {
368
358
  id: "series_history",
369
359
  title: "Series history",
370
360
  status: "supported",
371
- summary: "Series<T> header-only ring buffer implements Pine [k] history indexing with push/update/current, max_len default 500, out-of-range returns na<T>().",
372
- detail: "`Series<T>` (header-only, `series.hpp`) is a generic deque/ring buffer that implements Pine's `[k]` history semantics. `push(value)` records a new bar (newest at front); `update(value)` overwrites the current bar (used for magnifier intrabar); `operator[](k)` returns 0=current, k>=1 = k bars ago; `current()`, `size()`, and `clear()` round it out.\n\n`max_len` defaults to 500. Out-of-range or negative offsets return `na<T>()`. In the magnifier path the engine forces `is_first_tick_` true on the last sample of the last sub-bar so generated `on_bar(...)` advances series history exactly once per script bar. The audit classifies the `series` type keyword as Runtime, backed by this ring buffer.",
361
+ summary: "Series<T> is a ring buffer with Pine [k] semantics (max_len default 500, out-of-range reads na). Since codegen 1.0.1 the history operator also reads objects, drawings, arrays and matrices.",
362
+ detail: "Series<T> (header-only, series.hpp) implements Pine's [k] history: push(value) records a new bar (newest at the front), update(value) overwrites the current bar (magnifier intrabar), operator[](k) returns 0 = current, k >= 1 = k bars ago, plus current(), size() and clear(). max_len defaults to 500; an out-of-range or negative offset reads na<T>(), not an error. In the magnifier path the Pine host runs the script once per script bar, so history advances exactly once per script bar.\n\nSince codegen 1.0.1: obj[k] of a user-defined object or a drawing is the reference the variable held k bars back (na before the first), and (obj[k]).field reads that object as it is now, as on TradingView; a[k] of an array or matrix is a read-only copy of the collection k executions back. TradingView's own refusals apply: a field or method straight after the history operator (c[1].v, a[1].size(); write (c[1]).v), the history of a field, and an array's history used as a number, condition, string or element. See arrays_maps_udts and drawing_plotting_alerts for the limits.",
373
363
  supported: [
374
364
  "series",
375
- "Series<T>::push",
376
- "Series<T>::update",
377
- "operator[k] history indexing",
378
- "current",
379
- "size",
380
- "clear",
381
- ],
382
- unsupported: ["out-of-range/negative [k] returns na (not an error)"],
365
+ "Series<T>::push / Series<T>::update / Series<T>::current",
366
+ "series[k] (0 = current bar, k >= 1 = k bars ago; out of range reads na; max_len 500)",
367
+ ],
368
+ unsupported: [],
383
369
  },
384
370
  {
385
371
  id: "color",
386
372
  title: "Color",
387
373
  status: "supported",
388
- summary: "pine_color holds 17 named ARGB constants plus new_color, r, g, b, t helpers; no charting/drawing types.",
389
- detail: "The runtime owns color via `color.hpp` (header-only): 17 named ARGB constants in `pine_color::*` (aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, orange, purple, red, silver, teal, white, yellow). Helpers: `new_color(c, transp)` clears alpha and packs `(100 - transp) * 2.55` into the high byte; `r(c)/g(c)/b(c)` return channel bytes; `t(c)` recovers transparency (0-100) from the alpha byte.\n\nIn the per-identifier audit, `color.new()`, `color.r/g/b/t()`, and `color()` are Runtime; `color.rgb()` is Transpiler-emitted (inline ARGB assembly) and `color.from_gradient()` is Unknown (no runtime gradient function). There are no charting or drawing types in the runtime.",
374
+ summary: "pine_color holds 17 named ARGB constants plus new_color, r, g, b and t helpers; color.rgb is emitted inline, and color.from_gradient is accepted but returns a default color.",
375
+ detail: "color.hpp (header-only): 17 named ARGB constants in pine_color::* (aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, orange, purple, red, silver, teal, white, yellow). new_color(c, transp) sets the alpha byte to the whole number nearest 255 x (100 - transp) / 100, clamped to 0..255 (an na transparency is fully transparent); r(c)/g(c)/b(c) return the channel bytes; t(c) recovers the transparency (0..100). A fractional input or series transparency rounds to the nearest alpha byte as TradingView's does; a fractional constant transparency also rounds, where TradingView truncates (10.5 reads back 11 where TradingView reads 10). color.rgb is emitted inline by the transpiler, and color.new / color.rgb bind a keyword transparency like a positional one.\n\ncolor.from_gradient is accepted with a warning: it evaluates its arguments and returns a default color, not the gradient. Drawing objects are data in drawing.hpp; the runtime has no charting or rendering types.",
390
376
  supported: [
391
- "color (type)",
377
+ "color (type; a bare color cast is cosmetic: a warning and a default color)",
392
378
  "color.new",
393
- "color.r",
394
- "color.g",
395
- "color.b",
396
- "color.t",
397
- "17 color.* named constants",
379
+ "color.r / color.g / color.b / color.t",
380
+ "color.* (17 named constants)",
398
381
  ],
382
+ via_transpiler: ["color.rgb (emitted inline)"],
399
383
  unsupported: [
400
- "color.from_gradient (no runtime gradient)",
401
- "drawing/charting color types",
384
+ "color.from_gradient (accepted with a warning: it evaluates its arguments and returns a default color, not the gradient)",
402
385
  ],
403
386
  },
404
387
  {
405
388
  id: "na",
406
389
  title: "`na` / `is_na`",
407
390
  status: "supported",
408
- summary: "na.hpp provides generic na<T>() generators and is_na(...) checks for double (NaN), int/int64 (INT_MIN), and bool (false).",
409
- detail: "The runtime owns `na` via `na.hpp` (header-only). `na<T>()` generates the sentinel per type: double -> NaN, int/int64_t -> INT_MIN, bool -> false. `is_na(double)` uses `std::isnan`; an integer overload checks `== INT_MIN`. These sentinels are used throughout the runtime (e.g. out-of-range `Series[k]`, absent pivot levels, `margin_liquidation_price()`, na-accepted syminfo fields).\n\nIn the per-identifier audit, both the `na` variable/fn and the `na()` function form map to `na<T>()` in `na.hpp` (Runtime). Related helpers `nz()` (`is_na(x) ? 0.0 : x`) and `fixnan()` (`is_na(x) ? prev : x`) are Transpiler-emitted inline rather than runtime classes.",
410
- supported: [
411
- "na",
412
- "na()",
413
- "is_na",
414
- "na<double>() NaN",
415
- "na<int>()/na<int64_t>() INT_MIN",
416
- "na<bool>() false",
417
- ],
418
- unsupported: [
419
- "nz() (transpiler-inlined, not a runtime module)",
420
- "fixnan() (transpiler-inlined)",
421
- ],
391
+ summary: "na<T>() and is_na(...) for double (NaN), int (INT_MIN), int64 (INT64_MIN) and bool (false), plus null-ID detection for maps; nz() and fixnan() are emitted inline.",
392
+ detail: "na.hpp (header-only): na<T>() gives the sentinel per type: double -> NaN, int -> INT_MIN, int64_t -> INT64_MIN, bool -> false. is_na(double) uses std::isnan; the integer overload compares with the type's minimum; map.hpp adds a null-ID overload for PineMap<K,V>. These sentinels are used throughout the runtime (out-of-range Series[k], absent pivot levels, na syminfo fields).\n\nnz() and fixnan() are emitted inline by the transpiler; fixnan evaluates its argument once (codegen 1.0.0). Since codegen 1.0.0, na follows Pine's rules wherever the generated C++ converts a value: it is false in if, ?:, and/or and boolean parameters, and an integer conversion of na stays na.",
393
+ supported: ["na", "na()", "is_na", "na<double>() (NaN)", "na<int>() / na<int64_t>() (INT_MIN / INT64_MIN)", "na<bool>() (false)"],
394
+ via_transpiler: ["nz (emitted inline)", "fixnan (emitted inline; evaluates its argument once)"],
395
+ unsupported: [],
422
396
  },
423
397
  {
424
398
  id: "logging_errors",
425
399
  title: "Logging / runtime errors",
426
400
  status: "supported",
427
- summary: "Dedicated header-only log.hpp runtime fully backs log.info/warning/error and runtime.error (which throws std::runtime_error).",
428
- detail: "The coverage.md summary table lists Logging / runtime errors as Supported, owned by libpineforge.a. log.hpp exposes four inline functions: pine_log_info, pine_log_warning, pine_log_error (each writing to stderr with [INFO]/[WARN]/[ERROR] prefixes) and pine_runtime_error, which throws std::runtime_error. The detail doc maps the Pine identifiers log.info() -> pine_log_info(), log.warning() -> pine_log_warning(), log.error() -> pine_log_error(), and runtime.error() -> pine_runtime_error(), all marked Runtime.\n\nThe runtime additionally raises std::runtime_error itself from validate_security_timeframes, feed_security_eval_state (lower-TF synthesis failure), and ensure_supported_lower_tf_emulation_flags. The one logging-adjacent gap is the @strategy_alert_message annotation, which is parse-and-skip (alert template, no runtime), but that belongs to the alert surface rather than the log namespace.",
401
+ summary: "Header-only log.hpp backs log.info/warning/error and runtime.error (which throws std::runtime_error and fails the run).",
402
+ detail: "log.hpp exposes four inline functions: pine_log_info, pine_log_warning, pine_log_error (each writing to stderr with an [INFO]/[WARN]/[ERROR] prefix) and pine_runtime_error, which throws std::runtime_error. log.info() -> pine_log_info(), log.warning() -> pine_log_warning(), log.error() -> pine_log_error(), runtime.error() -> pine_runtime_error(); formatted log.* calls are lowered (codegen 1.0.0).\n\nThe runtime also raises std::runtime_error itself from validate_security_timeframes, feed_security_eval_state (lower-TF synthesis failure) and ensure_supported_lower_tf_emulation_flags; strategy_get_last_error carries the text of a failed run. The @strategy_alert_message annotation is parse-and-skip (alert template, no runtime), which belongs to the alert surface.",
429
403
  supported: [
430
404
  "log.info",
431
405
  "log.warning",
@@ -436,65 +410,61 @@ export const COVERAGE = {
436
410
  "pine_log_error",
437
411
  "pine_runtime_error",
438
412
  ],
439
- unsupported: [
440
- "@strategy_alert_message (parse-and-skip; alert template, not part of log namespace)",
413
+ unsupported: ["@strategy_alert_message (parse-and-skip; alert template, not part of the log namespace)"],
414
+ },
415
+ {
416
+ id: "maps",
417
+ title: "Maps",
418
+ status: "partial",
419
+ summary: "map.hpp's PineMap<K,V> backs Pine maps with string keys and primitive values: insertion order, alias/copy/null semantics and the 50,000-pair limit. Other key or value types, map history and nested map-bearing matrices are refused.",
420
+ detail: "map.hpp provides ordered PineMap<K,V> handles: map-ID aliasing, map.copy() container separation, insertion-ordered keys and values, typed missing results, null IDs, Pine-aware primitive keys and the 50,000-pair limit, with primitive-only rollback snapshots. The transpiler emits this runtime for its supported subset, string keys and primitive values, including typed and inferred na, function and UDT parameter and return propagation, once-only receiver evaluation and pair iteration. It refuses the rest instead of emitting incorrect C++: a key type other than string (\"map keys must be string in PineForge's supported map subset\"), a non-primitive value (\"map values must be primitive in PineForge's supported map subset\"), map-bearing history, nested map-bearing matrices and ambiguous specializations.",
421
+ partial: [
422
+ "map (type: string keys and primitive values only)",
423
+ "map.new / map.put / map.get / map.contains / map.remove / map.keys / map.values / map.size / map.clear / map.copy / map.put_all (string keys and primitive values only; other key or value types are refused)",
441
424
  ],
425
+ supported: [],
426
+ unsupported: [],
442
427
  },
443
428
  {
444
429
  id: "arrays_maps_udts",
445
- title: "Arrays / maps / UDTs",
430
+ title: "Arrays / UDTs",
446
431
  status: "via_transpiler",
447
- summary: "No runtime array/map/UDT module exists; PineForge's transpiler emits them inline as std::vector, std::unordered_map, and generated C++ structs, so they work end-to-end.",
448
- detail: "coverage.md flags this category as 'No runtime module (Pine surface still supported via consumer compiler)'. The runtime ships no array.hpp / map.hpp / UDT module; its only generic value containers are Series<T> (history), PineMatrix (numeric matrices), and PineGenericMatrix<T> (typed matrices). Pine arrays/maps/UDTs themselves still work in PineForge because the transpiler emits array<T> as std::vector<T>, map<K,V> as std::unordered_map<K,V>, and UDTs (including nested fields and array<UDT>) as plain C++ structs.\n\nThe detail doc confirms this: the array type, map type, all 54 array.* functions, and all 11 map.* functions are tagged Transpiler (no runtime module). The type and method keywords are Transpiler-handled for UDT struct/method generation. array.sort/sort_indices use std::sort; array.from is supported. order.ascending/order.descending are Runtime (used by array.sort and PineMatrix::sort).\n\nGaps to note: drawing-type arrays (array.new_label/line/box/table, etc.) are parsed but the underlying drawing ops are skipped, since there is no drawing runtime. request.security_lower_tf returns arrays of numeric/bool elements only; tuple, UDT, color, and string element arrays are rejected by the transpiler.",
449
- supported: [
432
+ summary: "No runtime array or UDT module: PineForge's transpiler emits arrays as std::vector and user-defined types as C++ structs, so they work end-to-end, including (since codegen 1.0.1) the history of arrays and objects. Maps are their own topic (maps).",
433
+ detail: "The runtime ships no array or UDT module; the transpiler emits array<T> as std::vector<T> and UDTs (including nested fields and array<UDT>) as plain C++ structs, and handles the type and method keywords. array.sort/sort_indices use std::sort; array.from is supported; order.ascending/order.descending are runtime constants. Every array.slice warns that PineForge copies the slice where TradingView aliases it. array.new_color and array.new_table are refused. Arrays of lines, boxes, labels and linefills hold drawings, which are data (see drawing_plotting_alerts).\n\nHistory (codegen 1.0.1): a[k] of an array variable, top-level or a block's local, is a read-only copy of the array as the variable left it k executions back; built-ins read it, na() tests it, and a for...in loop over a[1] iterates the array the variable holds now, as on TradingView. A change to the copy stops the run with RE10051, and a method on it before the variable has a history with RE10052. obj[k] of a UDT variable is the reference it held k bars back, and (obj[k]).field reads that object as it is now. Not kept: the history of a function's array parameter or local, of a call's result or of a selection (TradingView keeps one per call), and a typed method's receiver at a call site that skips bars counts calls instead of chart bars. The history of an array of drawings does not compile, and a variable bound to a[1] before the variable has a history holds an empty array where TradingView's is na. Refused with TradingView's codes: a field or method straight after the history operator (c[1].v, a[1].size(); write (a[1]).size()), the history of a field (CE10290), and an array's history used as a number, condition, string or element (CE10123 and related codes).",
434
+ supported: ["order.ascending / order.descending (runtime constants)"],
435
+ partial: ["array.slice (PineForge copies the slice where TradingView aliases it, so writes through either array can diverge; codegen warns)"],
436
+ via_transpiler: [
450
437
  "array (type)",
451
- "array.new",
452
- "array.from",
453
- "array.sort",
454
- "array.sort_indices",
455
- "map (type)",
456
- "map.* (11 fns)",
457
- "type (UDT struct gen)",
458
- "method (UDT method gen)",
459
- "order.ascending",
460
- "order.descending",
461
- "matrix (PineMatrix / PineGenericMatrix runtime)",
462
- ],
463
- unsupported: [
464
- "array.new_label/line/box/table/linefill (drawing-type arrays: container works, drawing ops skipped)",
465
- "request.security_lower_tf tuple/UDT/color/string element arrays (transpiler-rejected)",
438
+ "array.new / array.from / array.push / array.get / array.set / array.size / array.sort / array.sort_indices",
439
+ "array.* (array functions are emitted inline)",
440
+ "type (UDT struct generation)",
441
+ "method (UDT method generation)",
466
442
  ],
443
+ unsupported: ["array.new_color / array.new_table (refused: not implemented)"],
467
444
  },
468
445
  {
469
446
  id: "drawing_plotting_alerts",
470
447
  title: "Drawing / plotting / alerts",
471
- status: "unsupported",
472
- summary: "No charting/drawing/alert runtime exists; the transpiler parses-and-skips these so strategies still compile and run, but no visual side-effects or live alerts are emitted.",
473
- detail: "coverage.md lists Drawing / plotting / alerts as 'No runtime module' and states explicitly that no charting/drawing/alert types exist in the runtime; PineForge's transpiler parses-and-skips them so the strategy still compiles and runs, but no visual side-effects are produced. This is by design: PineForge is an offline backtesting engine, not a renderer, so visual/charting/alert APIs are out of scope regardless of consumer.\n\nThe detail doc tags the whole surface parse-and-skip: plot, plotshape, plotchar, plotcandle, plotbar, plotarrow (compile, no visual output); fill, hline, bgcolor, barcolor; the box, label, line, linefill, polyline, table drawing types and all their methods (e.g. label.* 20 fns, line.* 20 fns, box.* 27 fns); their .all collection variables; and the drawing/plotting style constants (label.style_* 22, line.style_* 6, plot.style_* 12, shape.*, location.*, extend.*, size.*, position.*, xloc.*, yloc.*). chart.point and its methods are parse-and-skip chart geometry.\n\nAlerts are the same: alert() and alertcondition() are parse-and-skip (parsed, no live emission); alert.freq_all/freq_once_per_bar/freq_once_per_bar_close constants and the @strategy_alert_message annotation are parse-and-skip. The forward-looking note rates plotting primitives as 'Feasible' via a future report-as-data path, but live alert() is 'Out of scope structurally' because PineForge produces no realtime stream. indicator() is also parse-and-skip since the engine is strategy-only.",
474
- supported: [],
448
+ status: "partial",
449
+ summary: "Drawings are data: line, box, label and linefill objects and chart.point keep their geometry, which strategy logic can read back (since codegen 1.0.1 also through their history). Nothing is drawn: visual setters, plots, tables, polylines and alerts are accepted and have no effect.",
450
+ detail: "drawing.hpp keeps line/box/label/linefill handles in per-type arenas and chart.point as a value type: geometry as data, no rendering. Visual fields (color, style, width and the like) are dropped at lowering, and a visual setter such as line.set_color is accepted with a warning and does nothing. A drawing op on an na handle halts the run, as on TradingView; a deleted or collected drawing reads as na, and drawings are collected as TradingView collects them. The arenas are part of the script state that calc_on_order_fills checkpoints and rolls back. Since codegen 1.0.1, b[k] of a drawing variable is the reference it held k bars back, (b[1]).get_top() reads that box as it is now, and == / != compare two lines or two labels by identity. Refused: b[1].get_top() without the parentheses (CE10010), the history of a drawing inside a request.security expression, and the history of a chart.point variable whose fields the script changes. In codegen 1.0.1 the C++ for line.all, box.all and label.all does not compile.\n\nplot, plotshape, plotchar, plotcandle, plotbar, plotarrow, fill, hline, bgcolor, barcolor, table.*, polyline.*, alert(...) and alertcondition(...) are accepted with a warning and have no effect on a backtest: the engine renders nothing and emits no alert events. A request whose value reaches only these sinks lowers to na.",
451
+ supported: [
452
+ "line.new / line.get_x1 / line.get_x2 / line.get_y1 / line.get_y2 / line.get_price / line.set_x1 / line.set_x2 / line.set_y1 / line.set_y2 / line.set_xy1 / line.set_xy2 / line.copy / line.delete (geometry kept as data the strategy reads back; nothing is drawn)",
453
+ "box.new / box.get_top / box.get_bottom / box.get_left / box.get_right / box.set_top / box.set_bottom / box.set_left / box.set_right / box.set_lefttop / box.set_rightbottom / box.copy / box.delete (geometry as data)",
454
+ "label.new / label.get_x / label.get_y / label.get_text / label.set_x / label.set_y / label.set_xy / label.set_text / label.copy / label.delete (geometry and text as data)",
455
+ "linefill.new / linefill.get_line1 / linefill.get_line2 / linefill.delete (data)",
456
+ "chart.point.new / chart.point.from_index / chart.point.from_time / chart.point.copy (a value type)",
457
+ ],
475
458
  unsupported: [
476
- "plot",
477
- "plotshape",
478
- "plotchar",
479
- "plotcandle",
480
- "plotbar",
481
- "plotarrow",
482
- "fill",
483
- "hline",
484
- "bgcolor",
485
- "barcolor",
486
- "label.new",
487
- "line.new",
488
- "box.new",
489
- "table.*",
490
- "polyline.*",
491
- "linefill.*",
492
- "chart.point.*",
493
- "alert",
494
- "alertcondition",
495
- "alert.freq_* constants",
459
+ "line.all / box.all / label.all (codegen 1.0.1's C++ for them does not compile)",
460
+ "line.set_color / line.set_style / line.set_width / line.set_extend / box.set_bgcolor / box.set_border_color / box.set_border_width / label.set_color / label.set_style / label.set_textcolor / label.set_size / linefill.set_color (visual setters: accepted with a warning, no effect)",
461
+ "plot / plotshape / plotchar / plotcandle / plotbar / plotarrow (accepted with a warning, no effect)",
462
+ "fill / hline / bgcolor / barcolor (accepted with a warning, no effect)",
463
+ "table / table.* (accepted with a warning, no effect)",
464
+ "polyline / polyline.* (accepted with a warning, no effect)",
465
+ "alert / alertcondition (accepted with a warning: no alert events are emitted)",
466
+ "alert.freq_all / alert.freq_once_per_bar / alert.freq_once_per_bar_close",
496
467
  "@strategy_alert_message",
497
- "indicator",
498
468
  ],
499
469
  },
500
470
  ],
@@ -504,6 +474,7 @@ export const COVERAGE = {
504
474
  "str.": "str",
505
475
  "request.": "request_security",
506
476
  "barmerge.": "request_security",
477
+ "ticker.": "request_security",
507
478
  "strategy.": "strategy_orders",
508
479
  "strategy.risk.": "strategy_risk",
509
480
  "strategy.direction.": "strategy_risk",
@@ -512,7 +483,7 @@ export const COVERAGE = {
512
483
  "input.": "inputs",
513
484
  "matrix.": "numeric_matrices",
514
485
  "array.": "arrays_maps_udts",
515
- "map.": "arrays_maps_udts",
486
+ "map.": "maps",
516
487
  "color.": "color",
517
488
  "timeframe.": "timeframe_parsing",
518
489
  "session.": "time_session_timezone",
@@ -548,9 +519,9 @@ export const COVERAGE = {
548
519
  table: "drawing_plotting_alerts",
549
520
  polyline: "drawing_plotting_alerts",
550
521
  linefill: "drawing_plotting_alerts",
551
- indicator: "drawing_plotting_alerts",
522
+ indicator: "engine_lifecycle",
552
523
  array: "arrays_maps_udts",
553
- map: "arrays_maps_udts",
524
+ map: "maps",
554
525
  matrix: "numeric_matrices",
555
526
  series: "series_history",
556
527
  color: "color",
@@ -576,6 +547,7 @@ export const COVERAGE = {
576
547
  library: "engine_lifecycle",
577
548
  import: "engine_lifecycle",
578
549
  export: "engine_lifecycle",
550
+ varip: "engine_lifecycle",
579
551
  },
580
552
  };
581
553
  /**
@@ -614,22 +586,23 @@ export function coverageTopic(id) {
614
586
  * Resolve an arbitrary Pine identifier / namespace to a coverage status.
615
587
  *
616
588
  * Resolution order:
617
- * 1. Exact identifier match in any topic's supported[] / unsupported[]:
618
- * a hit in unsupported[] => "unsupported"; a hit in supported[] => that
619
- * topic's own status (so a feature listed under a `partial` topic reports
620
- * `partial`, etc.).
589
+ * 1. Exact identifier match in any topic's unsupported[] / partial[] /
590
+ * via_transpiler[] / supported[] list (checked in that order): the list
591
+ * names the status, and the note quotes the matching entry.
592
+ * 1b. A "namespace.*" entry (e.g. "table.*") covers every identifier under
593
+ * that namespace, with the same list order; exact matches win over it.
621
594
  * 2. Longest matching namespace prefix in prefix_map => that topic's status.
622
595
  * 3. Exact key in alias_map => that topic's status.
623
596
  * 4. Otherwise { status: "not_found" }.
624
597
  */
625
- const FEATURE_IDENT = /^[A-Za-z_][A-Za-z0-9_.]*\*?$/;
598
+ const FEATURE_IDENT = /^@?[A-Za-z_][A-Za-z0-9_.]*\*?$/;
626
599
  /**
627
600
  * Identifier tokens inside a supported[]/unsupported[] entry, ignoring
628
601
  * parenthetical notes and compound "a / b / c" lists. So
629
602
  * "strategy.cancel / strategy.cancel_all" yields both ids, and
630
603
  * "barmerge.lookahead_on (for lower-TF emulation)" yields just the id.
631
604
  */
632
- function entryIdentifiers(entry) {
605
+ export function entryIdentifiers(entry) {
633
606
  return entry
634
607
  .replace(/\([^)]*\)/g, " ")
635
608
  .split(/[\s/,]+/)
@@ -640,26 +613,39 @@ function entryIdentifiers(entry) {
640
613
  function normalizeFeatureQuery(q) {
641
614
  return q.replace(/\(\)$/, "").trim();
642
615
  }
616
+ /** A topic's per-feature lists, in resolution order; each list's name is the status it reports. */
617
+ const ENTRY_LISTS = ["unsupported", "partial", "via_transpiler", "supported"];
643
618
  export function checkPineFeature(feature) {
644
619
  const query = feature;
645
620
  const q = normalizeFeatureQuery(query);
646
- // (1) Exact identifier match in supported[] / unsupported[].
621
+ // (1) Exact identifier match in unsupported[] / partial[] / via_transpiler[] / supported[].
647
622
  for (const t of COVERAGE.topics) {
648
- if (t.unsupported.some((e) => entryIdentifiers(e).includes(q))) {
649
- return {
650
- query,
651
- status: "unsupported",
652
- topic: t.id,
653
- note: `'${query}' is listed as unsupported under topic '${t.id}' (${t.title}).`,
654
- };
623
+ for (const status of ENTRY_LISTS) {
624
+ const entry = (t[status] ?? []).find((e) => entryIdentifiers(e).includes(q));
625
+ if (entry !== undefined) {
626
+ return {
627
+ query,
628
+ status,
629
+ topic: t.id,
630
+ note: `'${query}' is listed as ${status} under topic '${t.id}' (${t.title}): ${entry}`,
631
+ };
632
+ }
655
633
  }
656
- if (t.supported.some((e) => entryIdentifiers(e).includes(q))) {
657
- return {
658
- query,
659
- status: t.status,
660
- topic: t.id,
661
- note: `'${query}' is listed as supported under topic '${t.id}' (${t.title}), whose overall status is '${t.status}'.`,
662
- };
634
+ }
635
+ // (1b) A "namespace.*" entry covers every identifier under that namespace.
636
+ for (const t of COVERAGE.topics) {
637
+ for (const status of ENTRY_LISTS) {
638
+ for (const entry of t[status] ?? []) {
639
+ const wildcard = entryIdentifiers(entry).find((id) => id.endsWith(".*") && q.startsWith(id.slice(0, -1)));
640
+ if (wildcard !== undefined) {
641
+ return {
642
+ query,
643
+ status,
644
+ topic: t.id,
645
+ note: `'${query}' falls under '${wildcard}', listed as ${status} under topic '${t.id}' (${t.title}): ${entry}`,
646
+ };
647
+ }
648
+ }
663
649
  }
664
650
  }
665
651
  // (2) Longest namespace prefix in prefix_map.
@@ -684,7 +670,9 @@ export function checkPineFeature(feature) {
684
670
  };
685
671
  }
686
672
  // (3) Exact alias_map key.
687
- const aliasTopicId = COVERAGE.alias_map[query];
673
+ const aliasTopicId = Object.prototype.hasOwnProperty.call(COVERAGE.alias_map, query)
674
+ ? COVERAGE.alias_map[query]
675
+ : undefined;
688
676
  if (aliasTopicId !== undefined) {
689
677
  const t = COVERAGE.topics.find((x) => x.id === aliasTopicId);
690
678
  const status = t ? t.status : "not_found";