PineForge v0.1.2-15-g0247b7f
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
Strategy lifecycle

A PineForge backtest is a four-step pipeline. Each step maps to one function in <pineforge/pineforge.h>.

strategy_create() → configure() → run_backtest_full() → read pf_report_t
│ │ │ │
▼ ▼ ▼ ▼
pf_strategy_t set_input / fills pf_report_t report_free()
set_override strategy_free()

1. Allocate a strategy handle

if (!s) {
/* allocation failure — out of memory */
return -1;
}
pf_strategy_t strategy_create(const char *params_json)
Allocate a new strategy instance.
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:194

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.

2. Configure (optional)

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.

strategy_set_input (s, "Length", "21");
strategy_set_input (s, "Use Trend Filter","true");
strategy_set_override (s, "initial_capital", "100000");
strategy_set_override (s, "commission_value","0.04");
strategy_set_trace_enabled(s, 1); /* enable @pf-trace capture */
void strategy_set_override(pf_strategy_t s, const char *key, const char *value)
Override a strategy(...) declaration parameter.
void strategy_set_input(pf_strategy_t s, const char *key, const char *value)
Override a Pine input.
void strategy_set_trace_enabled(pf_strategy_t s, int on)
Toggle per-bar trace recording.

Full list of recognised override keys is on the Configuration page.

3. Push bars and run

pf_bar_t *bars = load_my_ohlcv(&n);
pf_report_t r = {0};
run_backtest_full(s, bars, n,
/* input_tf */ "15",
/* script_tf */ "15",
/* magnifier */ 1, /* samples */ 4,
&r);
void run_backtest_full(pf_strategy_t s, pf_bar_t *bars, int n, const char *input_tf, const char *script_tf, int bar_magnifier, int magnifier_samples, pf_magnifier_distribution_t magnifier_dist, pf_report_t *out)
Run a backtest with explicit timeframe and magnifier configuration.
@ PF_MAGNIFIER_ENDPOINTS
Default — exact O,H,L,C points plus uniform fill between.
Definition pineforge.h:87
Single OHLCV bar pushed into the engine.
Definition pineforge.h:95
Backtest report filled by run_backtest / run_backtest_full.
Definition pineforge.h:153

Or for the common case of "auto-detect timeframe, no magnifier":

run_backtest(s, bars, n, &r);
void run_backtest(pf_strategy_t s, pf_bar_t *bars, int n, pf_report_t *out)
Run a backtest with auto-detected timeframe and no bar magnifier.

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.

4. Consume the report

See Report schema for every field. Quick summary:

printf("Trades: %d Net PnL: %.2f\n", r.trades_len, r.net_profit);
for (int i = 0; i < r.trades_len; ++i) {
pf_trade_t t = r.trades[i];
printf(" [%c] entry %.4f -> exit %.4f pnl %.2f\n",
t.is_long ? 'L' : 'S',
}
double net_profit
Sum of all closed-trade PnL.
Definition pineforge.h:158
int trades_len
Length of trades.
Definition pineforge.h:157
pf_trade_t * trades
Heap array of closed trades.
Definition pineforge.h:156
Closed-trade record returned in pf_report_t::trades.
Definition pineforge.h:107
double exit_price
Exit fill price (incl.
Definition pineforge.h:111
double pnl
Net realized PnL in account currency.
Definition pineforge.h:112
int is_long
1 if long, 0 if short.
Definition pineforge.h:114
double entry_price
Entry fill price (incl.
Definition pineforge.h:110

5. Free everything

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.

report_free(&r); /* releases trades / security_diag / trace arrays */
strategy_free(s); /* releases the handle */
void strategy_free(pf_strategy_t s)
Release a strategy handle previously returned by strategy_create.
void report_free(pf_report_t *report)
Free heap arrays attached to a filled report.
Warning
Calling 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.

One handle per run

A pf_strategy_t carries closed-trade history, equity curve, and position state. Calling run_backtest twice on the same handle accumulates trades from both runs into the second report — the state machine never rewinds.

For parameter sweeps, walk-forward windows, or any A/B comparison, create a fresh handle per run:

for (int len = 10; len <= 30; len += 2) {
char buf[16]; snprintf(buf, sizeof buf, "%d", len);
strategy_set_input(s, "Length", buf);
pf_report_t r = {0};
run_backtest(s, bars, n, &r);
printf("len=%d pnl=%.2f\n", len, r.net_profit);
}

This is the canonical sweep loop. See tutorial/run_advanced.py for the Python equivalent and Parameter sweep for the annotated walkthrough.

Errors and partial state

The C ABI does not return error codes. Instead:

  • Allocation failure in strategy_create returns NULL. Always check.
  • Bad input timeframe falls back to auto-detection.
  • Empty bar feed (n == 0) is valid — the report is filled with zero counts and an empty trade list.
  • Internal runtime errors (e.g. invalid request.security symbol) emit a pine_runtime_error log line and either skip the offending evaluation or terminate the strategy bar — never the host process.

If you need finer-grained error reporting, attach a logger via the runtime's internal log hook (closed-source-only API; not part of the public ABI).