|
PineForge v0.12.2-8-g8df08f2
Deterministic PineScript v6 backtest runtime — C ABI reference
|
A PineForge backtest is a four-step pipeline. Each step maps to one function in <pineforge/pineforge.h>.
The argument is reserved for future use; pass NULL today. Each handle owns its own state machine and is not thread-safe — one handle per worker thread.
Override Pine input.*() values, strategy(...) declaration params, or runtime knobs before calling run_backtest. Calls made after a run are accepted but only take effect on subsequent runs.
Full list of recognised override keys is on the Configuration page.
Or for the common case of "auto-detect timeframe, no magnifier":
Use the stream lifecycle when the data source changes but the strategy instance must not. strategy_stream_begin() executes the confirmed OHLCV warmup once. Every later operation advances the same broker, equity, pending orders, Pine variables, TA objects, request.security() evaluators, and any partially formed higher-timeframe candle.
The default strategy cadence remains close-only. Resting broker orders are checked on every normalized trade, so stop/limit fills use the observed source path and a market order from the preceding close fills on the first subsequent trade. strategy_stream_push_ticks() is the batch equivalent for replay and reduces FFI overhead without changing tick semantics.
The warmup's last bar must be confirmed. Begin normalized trades at or after the next input-bar open. Call strategy_stream_advance_time() at confirmed boundaries; it closes elapsed bars, creates zero-volume carry-forward bars for quiet in-session intervals, and skips configured out-of-session intervals. Normally end with finalize_partial_input_bar = 0 to avoid treating an open bar as confirmed.
See Historical to realtime streaming for the complete tick validation rules, a contiguous-replay example, and the runnable Python tutorial.
The runtime fills r in place — the pf_report_t struct itself is caller-owned (typically stack-allocated), but the arrays it points to (trades, security_diag, trace, trace_names) are heap-allocated inside the runtime.
See Report schema for every field. Quick summary:
Both calls are mandatory and idempotent. Order matters: free the report first, then the handle — the report's trace_names strings are owned by the live handle.
strategy_free() while r.trace_names is still in use leaves dangling pointers — the trace name string table lives on the strategy, not the report.Calling run_backtest or run_backtest_full starts a new backtest and resets per-run broker/report state. The strategy_stream_* lifecycle is the explicit way to preserve and continue state across historical and realtime sources.
For parameter sweeps, walk-forward windows, or any A/B comparison, create a fresh handle per run:
This is the canonical sweep loop. See tutorial/run_advanced.py for the Python equivalent and Parameter sweep for the annotated walkthrough.
The one-shot run_backtest* calls return through strategy_get_last_error. Stream lifecycle calls additionally return 0 on success and -1 on failure. In both cases, read strategy_get_last_error for the detailed message.
strategy_create returns NULL. Always check.n == 0) is valid — the report is filled with zero counts and an empty trade list.