|
PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
|
PineForge exposes two distinct multi-timeframe surfaces. They look similar from Pine but resolve through completely different runtime paths, and they have different rules:
| Surface | Pine call | Direction | Bars source |
|---|---|---|---|
| Upward (HTF) | request.security(sym, "60", expr) | target TF coarser than input | aggregated from input bars |
| Downward (LTF) | request.security_lower_tf(sym, "1", expr) | target TF finer than input | synthesized from each input bar's OHLC path |
The downward path is where PineForge diverges from TradingView. TV downloads a separate finer-resolution feed when you switch to a lower timeframe; PF does not. The input feed's resolution is the upper bound on what request.security_lower_tf can target — beyond that, no data exists. Sub-bars are synthesized on demand by walking the input bar's OHLC path. See Bar magnifier for the same idea applied to fill resolution.
For the upward path and the chart-aggregation rules, see also Timeframes.
Every backtest run takes two timeframe strings on run_backtest_full(...):
The runtime resolves them in order:
input_tf — what the bar feed actually is. If empty, the runtime computes the median delta between consecutive pf_bar_t::timestamp values and snaps to a canonical Pine TF.script_tf — what the strategy script believes it's running on. If empty, defaults to the resolved input_tf.script_tf > input_tf, the runtime aggregates input bars up to script-TF parents before each on_bar dispatch. The two timeframes are concatenated by an aggregator (ratio or calendar — TimeframeAggregator, src/timeframe.cpp).The report exposes the resolved values and the ratio:
| Report field | Meaning |
|---|---|
input_tf_seconds | Resolved input TF (after auto-detect). |
script_tf_seconds | Resolved script TF (after defaulting). |
script_tf_ratio | script_tf_seconds / input_tf_seconds. |
needs_aggregation | 1 when ratio > 1, 0 otherwise. |
The full runnable harness — three tables walking the script_tf sweep, the input_tf/script_tf pair matrix, and the lower-TF synthesis ratio — is in tutorial/run_mtf.py. See the Worked example section below.
The lower-TF builtin returns an array of one value per synthesized sub-bar. The codegen pattern in generated.cpp is:
register_security_eval, register_security_lower_tf_eval, security_lower_tf_sub_bar_index and security_series_slot_is_new are the Pine source host's (source::PineStrategyHost, src/source/pine_security_eval.cpp). The kernel underneath registers a plain aggregating evaluator and keeps the feed store; every request.security rule on this page — lookahead and gaps publication, Heikin-Ashi, the lower-timeframe emulation, the diagnostics below — is source-host state kept per sec_id beside it.
When the run begins, the source host validates each registered lower-TF site against the run's evaluator input timeframe (validate_security_timeframes):
lookahead and gaps must be off (TV does not expose them on this builtin and PF refuses to fake them).Violations raise at run-time with a precise diagnostic, e.g.:
For each input bar, the engine generates input_tf / target_tf synthetic sub-bars by walking the input bar's OHLC endpoints (the same path-sampling primitive the bar magnifier uses, see src/engine_lower_tf.cpp). Each synthetic sub-bar gets 1/ratio of the input bar's volume.
Per-run sub-bar counts surface in the report's security_feeds_total field. With a 15m chart and a "1" lower-TF target, expect:
tutorial/mtf/ ships two strategies side-by-side:
| File | Demonstrates |
|---|---|
tutorial/mtf/strategy_htf.pine + generated_htf.cpp | request.security (HTF SMA filter on a 15m chart). HTF is an input — sweepable without rebuild. |
tutorial/mtf/strategy_ltf.pine + generated_ltf.cpp | request.security_lower_tf("1", close) — intra-bar 1m range as an entry signal. |
tutorial/run_mtf.py | Three tables: script_tf sweep, input_tf/script_tf pair matrix, lower-TF synthesis ratio. |
Build:
Expected (excerpt):
Numbers depend on the OHLCV snapshot.