|
PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
|
End-to-end backtest you can reproduce from a fresh clone in under a minute. Source: tutorial/.
| Step | What you learn |
|---|---|
| 1 | Build the runtime and the committed, pre-transpiled generated.cpp into strategy.so. |
| 2 | Load strategy.so from Python via ctypes. |
| 3 | Push an OHLCV feed and call run_backtest_full. |
| 4 | Read every interesting field of pf_report_t. |
| 5 | Free the report and the handle, in order. |
Re-running with other parameters, sweeping a grid and running strategies in parallel are the example pages at the end.
By the end you'll have the canonical patterns for ad-hoc backtests, parameter sweeps, walk-forward windows, and live diagnostic capture.
Requires cmake, g++, and python3.
Configures CMake (first time only), builds tutorial/macd/strategy.so, then runs the harness. The output below is the recorded engine v1.0.0 / codegen 1.0.0 result on the frozen feed; it has not been re-measured for 1.4.0:
Numbers depend on the OHLCV snapshot — refresh with python3 tutorial/data/fetch_btcusdt.py to get current Binance bars.
Mount the strategy + OHLCV into the release hub's image, ghcr.io/pineforge-4pass/pineforge-release (a released runtime plus a pinned pineforge-codegen, named by its engine<E>-codegen<C> tag; this repository publishes no image of its own); get a JSON report on stdout.
The image transpiles the .pine with its own codegen and runs it on its own engine, so it gives the numbers of the engine release it carries; the hub's 1.0.0 image (engine v1.0.0, codegen 1.0.0) gives Path A's numbers above. To build the image yourself, use pineforge-release's docker/Dockerfile, which vendors this tree's docker/ harness; this repository ships no Dockerfile.
The full harness is ~200 lines. Here's the dataflow, end to end.
Skipped here — see FFI from Python for the ctypes mirror of the report structs. The harness defines BarC, TradeC, and ReportC exactly matching pf_bar_t, pf_trade_t, pf_report_t.
(BarC * n)() allocates a contiguous block — the runtime walks it as pf_bar_t[] directly, no copying.
argtypes. Without them, Python passes an int as a 32-bit C int — an int64_t argument such as strategy_stream_advance_time's timestamp loses half its bits.Order matters — see Lifecycle § Free everything.
The pages below pick up where this tutorial leaves off — each one is a self-contained, runnable example targeting a specific use case.
| Example | What it shows |
|---|---|
| Pure C example | Same MACD run, no Python. End-to-end C code with gcc build. |
| Historical to realtime streaming | Warm the same MACD instance on OHLCV, then continue it on ordered trade ticks. |
| Parameter sweep in Python | Re-run one .so with a 2-D MACD grid. No recompile per run. |
| Multi-strategy harness | Load N .so files, run them in parallel against the same feed. |
| Magnifier on vs off | A/B comparison showing how intra-bar fills change the trade list. |
| Calling from Rust | Idiomatic safe Rust wrapper around the C ABI. |
pf_report_t