PineForge v0.12.2-8-g8df08f2
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
pineforge.h
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 *
4 * pineforge.h — public C ABI for the PineForge runtime.
5 *
6 * This header is the single source of truth for the harness ↔ compiled-
7 * strategy boundary. Every PineForge-generated .so exports a fixed set
8 * of C symbols declared below; the Python harness (validate_detailed_
9 * report.py) and any C/C++/FFI consumer of compiled strategies links
10 * against this contract.
11 *
12 * STABILITY GUARANTEE
13 * ───────────────────
14 * Within the same PINEFORGE_VERSION_MAJOR, this header's POD struct
15 * layouts and `extern "C"` symbol signatures are append-only. Fields
16 * are never reordered, removed, or retyped; new fields may only be
17 * appended at the end of structs. New functions may be added; existing
18 * functions are not removed or signature-changed.
19 *
20 * Across major versions all bets are off. Bump
21 * PINEFORGE_VERSION_MAJOR when breaking the ABI.
22 *
23 * SCOPE — WHAT THIS HEADER COVERS
24 * ───────────────────────────────
25 * ✓ Lifecycle of a compiled strategy (create / destroy)
26 * ✓ Running a backtest (auto-detect or fully configured)
27 * ✓ Per-strategy configuration (inputs, overrides, magnifier, trace)
28 * ✓ The shape of the report returned to the harness
29 *
30 * SCOPE — WHAT THIS HEADER DOES NOT COVER (BY DESIGN)
31 * ───────────────────────────────────────────────────
32 * ✗ The contract between codegen-emitted strategy code and the runtime
33 * internals (TA classes, math, series, strategy commands). That
34 * contract stays C++ — codegen and runtime ship together and are
35 * versioned in lockstep within the closed transpiler.
36 * ✗ Source-compiling strategies. Use the closed transpiler binary.
37 *
38 * The C++ headers under `<pineforge/engine.hpp>` etc. are *internal*
39 * implementation surface — not part of this stability guarantee.
40 */
41
42#ifndef PINEFORGE_H
43#define PINEFORGE_H
44
45#include <stdint.h>
46#include <stddef.h>
47
48/* ── Version ─────────────────────────────────────────────────────── */
49
50/* Macros (PINEFORGE_VERSION_MAJOR / _MINOR / _PATCH / _STRING / _FULL,
51 * PINEFORGE_GIT_SHA) live in the generated <pineforge/version.h>. */
52#include <pineforge/version.h>
53
54/* ── Visibility ──────────────────────────────────────────────────── */
55
56#if defined(_WIN32) || defined(__CYGWIN__)
57 #if defined(PINEFORGE_BUILD_SHARED)
58 #define PF_API __declspec(dllexport)
59 #else
60 #define PF_API __declspec(dllimport)
61 #endif
62#elif defined(__GNUC__) || defined(__clang__)
63 #define PF_API __attribute__((visibility("default")))
64#else
65 #define PF_API
66#endif
67
68/** Monotonic ABI version of pf_report_t / pf_trade_t layout. Bumped
69 * whenever a caller-visible struct grows. Consumers MUST verify
70 * pf_abi_version() == PF_ABI_VERSION before calling run_backtest.
71 * pf_report_t is caller-allocated: growth causes silent stack corruption
72 * in old callers that under-size the struct. pf_trade_t is runtime-
73 * allocated: growth causes array-stride misindexing in old readers that
74 * iterate the trades array with the stale sizeof. Value 2 = first
75 * versioned layout (metrics + equity curve); .so files predating this
76 * macro have no pf_abi_version symbol — treat dlsym failure as
77 * version 1. */
78#define PF_ABI_VERSION 2
79
80#ifdef __cplusplus
81extern "C" {
82#endif
83
84/** @defgroup pf_types Types
85 * @brief POD types and enums passed across the C ABI.
86 * @{
87 */
88
89/** Bar-magnifier sub-bar sampling distribution.
90 *
91 * Selects how intra-bar synthetic ticks are placed when the bar
92 * magnifier is enabled in #run_backtest_full. Layout-compatible with the
93 * internal C++ `pineforge::MagnifierDistribution` enum class — a
94 * `static_assert` in `c_abi.cpp` guarantees the integer values match. */
95typedef enum pf_magnifier_distribution_e {
96 PF_MAGNIFIER_UNIFORM = 0, /**< Uniform spacing across the parent bar. */
97 PF_MAGNIFIER_COSINE = 1, /**< Cosine-tapered density. */
98 PF_MAGNIFIER_TRIANGLE = 2, /**< Triangle-tapered density. */
99 PF_MAGNIFIER_ENDPOINTS = 3, /**< Default — exact O,H,L,C points plus uniform fill between. */
100 PF_MAGNIFIER_FRONT_LOADED = 4, /**< Sample density biased toward bar open. */
101 PF_MAGNIFIER_BACK_LOADED = 5 /**< Sample density biased toward bar close. */
103
104/** Single OHLCV bar pushed into the engine.
105 *
106 * Layout-compatible with the internal C++ `pineforge::Bar` struct. */
107typedef struct pf_bar_s {
108 double open; /**< Open price. */
109 double high; /**< High price. */
110 double low; /**< Low price. */
111 double close; /**< Close price. */
112 double volume; /**< Bar volume. */
113 int64_t timestamp; /**< Bar open time, Unix milliseconds. */
114} pf_bar_t;
115
116/** One provider-neutral realtime executed-trade update.
117 *
118 * `sequence` is optional: pass 0 when the normalized source has no stable
119 * ordering key. Non-zero values must increase strictly within a stream.
120 * `quantity` is expressed in the configured symbol's volume units and is
121 * accumulated into the input bar's volume. The source adapter owns all
122 * provider-specific fields and normalization. */
123typedef struct pf_trade_tick_s {
124 int64_t timestamp; /**< Source event time, Unix milliseconds. */
125 uint64_t sequence; /**< Normalized per-stream sequence, or 0. */
126 double price; /**< Executed trade price (> 0). */
127 double quantity; /**< Traded quantity in symbol volume units (>= 0). */
129
130/** Closed-trade record returned in pf_report_t::trades.
131 *
132 * Layout-compatible with internal `pineforge::TradeC`. */
133typedef struct pf_trade_s {
134 int64_t entry_time; /**< Entry fill time (Unix ms). */
135 int64_t exit_time; /**< Exit fill time (Unix ms). */
136 double entry_price; /**< Entry fill price (incl. slippage). */
137 double exit_price; /**< Exit fill price (incl. slippage). */
138 double pnl; /**< Net realized PnL in account currency (commission-inclusive). */
139 double pnl_pct; /**< Net return-on-cost in percent: pnl (NET of commission) /
140 * entry cost (entry_price * qty * pointvalue) * 100. This is
141 * TradingView's "Net P&L %" convention, arbitrated 2026-06-12
142 * against a real TV export (trade #258 short: 102.44 USD on a
143 * 2276.66 entry => 4.50%). Degenerates to the old gross
144 * (exit/entry-1)*100 form for longs with zero commission;
145 * the previous short form (entry/exit-1)*100 was wrong on
146 * large moves. Sign always matches pnl. */
147 int is_long; /**< 1 if long, 0 if short. */
148 double max_runup; /**< Peak favorable price travel during the trade ($/unit qty). */
149 double max_drawdown; /**< Peak adverse price travel during the trade ($/unit qty). */
150 double qty; /**< Filled quantity. */
151 double commission; /**< Entry+exit commission actually deducted from pnl
152 * (account currency). pnl is already net of this. */
153 int32_t entry_bar_index;/**< Script-bar index of the entry fill (0-based). */
154 int32_t exit_bar_index; /**< Script-bar index of the exit fill (0-based). */
155} pf_trade_t;
156
157/** Trade-level statistics block — computed once each for all / long / short.
158 *
159 * Loss-side fields (`gross_loss`, `avg_loss`, `largest_loss`) are
160 * **positive magnitudes** (absolute values of the underlying negative PnL). */
161typedef struct pf_trade_stats_s {
162 int32_t num_trades; /**< Closed trades in this block (all / long-only / short-only). */
163 int32_t num_wins; /**< Trades with pnl > 0. */
164 int32_t num_losses; /**< Trades with pnl < 0. */
165 int32_t num_even; /**< Trades with pnl == 0.0 exactly; breaks both win and loss
166 * streaks; excluded from win/loss averages.
167 * Invariant: num_trades == num_wins + num_losses + num_even. */
168 double percent_profitable; /**< 100 * num_wins / num_trades, in PERCENT (0-100).
169 * NaN when num_trades == 0. */
170 double net_profit; /**< Sum of pnl (account currency, net of commission). */
171 double net_profit_pct; /**< net_profit as a percent of initial capital (0-100 scale).
172 * NaN when initial capital <= 0. */
173 double gross_profit; /**< Sum of winning pnl. */
174 double gross_profit_pct; /**< gross_profit as a percent of initial capital (0-100 scale).
175 * NaN when initial capital <= 0. */
176 double gross_loss; /**< Sum of |losing pnl| — POSITIVE magnitude (TV display convention). */
177 double gross_loss_pct; /**< gross_loss as a percent of initial capital (0-100 scale).
178 * NaN when initial capital <= 0. */
179 double profit_factor; /**< gross_profit / gross_loss. NaN when gross_loss == 0. */
180 double avg_trade; /**< net_profit / num_trades. NaN when num_trades == 0. */
181 double avg_trade_pct; /**< Mean of per-trade pnl_pct over all trades.
182 * NaN when num_trades == 0. */
183 double avg_win; /**< gross_profit / num_wins. NaN when num_wins == 0. */
184 double avg_win_pct; /**< Mean of per-trade pnl_pct over winning trades.
185 * NaN when num_wins == 0. */
186 double avg_loss; /**< gross_loss / num_losses (positive magnitude).
187 * NaN when num_losses == 0. */
188 double avg_loss_pct; /**< Mean of the NEGATED pnl_pct of the losing trades. Since
189 * pnl_pct is net return-on-cost (sign matches pnl), this is
190 * a genuinely POSITIVE magnitude. Basis = pf_trade_t::pnl_pct.
191 * NaN when num_losses == 0. */
192 double ratio_avg_win_avg_loss; /**< avg_win / avg_loss. NaN unless both sides non-empty. */
193 double largest_win; /**< Single largest pnl among winning trades.
194 * NaN when num_wins == 0. */
195 double largest_win_pct; /**< Maximum pnl_pct over winning trades — an INDEPENDENT
196 * maximum, not the pct of the largest-USD win (TV convention,
197 * validated 2026-06-12 vs TV export).
198 * NaN when num_wins == 0. */
199 double largest_loss; /**< Single largest |pnl| among losing trades (positive magnitude).
200 * NaN when num_losses == 0. */
201 double largest_loss_pct; /**< Maximum of -pnl_pct over losing trades (positive magnitude) —
202 * an INDEPENDENT maximum, not the pct of the largest-USD loss
203 * (TV convention, validated 2026-06-12 vs TV export: All
204 * "Largest loss %" came from a different trade than the
205 * largest USD loss). NaN when num_losses == 0. */
206 double commission_paid; /**< Sum of pf_trade_t::commission in the block. */
207 double expectancy; /**< (num_wins/num_trades)*avg_win - (num_losses/num_trades)*avg_loss,
208 * account currency per trade. NaN when num_trades == 0. */
209 int32_t max_consecutive_wins; /**< Longest winning run; even trades reset both streaks. */
210 int32_t max_consecutive_losses; /**< Longest losing run; even trades reset both streaks. */
211 double avg_bars_in_trade; /**< Mean of (exit_bar_index - entry_bar_index + 1) in SCRIPT
212 * bars, over all trades — inclusive of the entry bar (TV
213 * convention, validated 2026-06-12).
214 * NaN when num_trades == 0. */
215 double avg_bars_in_wins; /**< Mean bar duration of winning trades, inclusive of the entry
216 * bar (TV convention, validated 2026-06-12).
217 * NaN when num_wins == 0. */
218 double avg_bars_in_losses; /**< Mean bar duration of losing trades, inclusive of the entry
219 * bar (TV convention, validated 2026-06-12).
220 * NaN when num_losses == 0. */
222
223/** Equity-curve-derived statistics (all-trades only, like TV). */
224typedef struct pf_equity_stats_s {
225 double max_equity_drawdown; /**< Peak-to-trough equity drop, positive currency magnitude. */
226 double max_equity_drawdown_pct; /**< max_equity_drawdown relative to the peak in effect
227 * (PERCENT 0-100). */
228 double max_equity_runup; /**< Trough-to-peak rise where the trough resets on each new
229 * equity peak (mirrors the engine's intra-run extremes). */
230 double max_equity_runup_pct; /**< max_equity_runup relative to that trough (PERCENT 0-100). */
231 double buy_hold_return; /**< initial_capital * (last_close/first_open - 1), currency.
232 * NaN when first chart open is non-finite or <= 0. */
233 double buy_hold_return_pct; /**< buy_hold_return as PERCENT.
234 * NaN when first chart open is non-finite or <= 0. */
235 double sharpe_tv; /**< Month-end-resampled equity simple returns (chart timezone,
236 * open-time bucketing), risk-free 2%/yr (2/12 per month),
237 * annualized by sqrt(12). Uses sample (N-1) stddev.
238 * NaN with <2 monthly returns or zero deviation. */
239 double sortino_tv; /**< Same resampling as sharpe_tv; uses population downside
240 * deviation vs the monthly risk-free.
241 * NaN with <2 monthly returns or zero deviation. */
242 double sharpe_bar; /**< Per-script-bar returns, annualized by observed bar density
243 * (bars per year = (len-1)/calendar span), NOT a fixed
244 * calendar formula. Uses sample (N-1) stddev.
245 * NaN with <2 returns or zero deviation. */
246 double sortino_bar; /**< Same construction as sharpe_bar over per-bar returns;
247 * uses population downside deviation.
248 * NaN with <2 returns or zero deviation. */
249 double cagr; /**< PERCENT per year: 100*((final_equity/initial_capital)^(1/years)-1).
250 * NaN when span <= 0 or either side <= 0. */
251 double calmar; /**< cagr / max_equity_drawdown_pct — BOTH IN PERCENT, so the
252 * ratio is dimensionless. NaN when drawdown is 0. */
253 double recovery_factor; /**< net_profit / max_equity_drawdown (currency / currency).
254 * NaN when drawdown is 0. */
255 double time_in_market_pct; /**< PERCENT (0-100) of script bars with an open position
256 * at bar close. */
257 double open_pl; /**< Mark-to-market open profit at the final bar. */
259
260/** Composite metrics container: trade stats (all / long / short) +
261 * equity-curve stats. */
266
267/** Single per-script-bar equity point.
268 *
269 * `time_ms` is the script-bar **open** timestamp (Unix ms).
270 * `equity` = `initial_capital` + `net_profit` + `open_profit` at bar close. */
271typedef struct pf_equity_point_s {
272 int64_t time_ms; /**< Script-bar OPEN timestamp (Unix ms). */
273 double equity; /**< initial_capital + net_profit + open_profit. */
274 double open_profit; /**< Mark-to-market open P&L at bar close. */
276
277/** Per-`request.security()` site diagnostic counters.
278 *
279 * Layout-compatible with internal `pineforge::SecurityDiagC`. */
280typedef struct pf_security_diag_s {
281 int sec_id; /**< Stable id for the request.security site. */
282 int64_t feed_count; /**< Higher-TF feed bars consumed. */
283 int64_t complete_count; /**< Evaluations on completed parent bars. */
284 int64_t partial_count; /**< Evaluations on still-forming parent bars. */
286
287/** Single per-bar trace entry.
288 *
289 * Emitted when the source script contains `// @pf-trace name=expr`
290 * pragmas and tracing is enabled via #strategy_set_trace_enabled.
291 * Layout-compatible with internal `pineforge::TraceEntryC`. */
292typedef struct pf_trace_entry_s {
293 int64_t timestamp; /**< Bar timestamp (Unix ms). */
294 int32_t bar_index; /**< Zero-based bar index. */
295 int32_t name_id; /**< Index into pf_report_t::trace_names. */
296 double value; /**< Traced expression value on this bar. */
298
299/** Backtest report filled by #run_backtest / #run_backtest_full.
300 *
301 * Layout-compatible with internal `pineforge::ReportC`.
302 *
303 * ### Ownership and lifetime
304 * The struct itself is caller-owned (typically stack). The embedded
305 * arrays (`trades`, `security_diag`, `trace`, `trace_names`,
306 * `equity_curve`) are heap-allocated by the runtime; the caller must
307 * invoke #report_free exactly once on each filled report.
308 * `trace_names` string pointers remain owned by the strategy handle
309 * until #strategy_free. */
310
311typedef struct pf_report_s {
312 /* Trades */
313 int total_trades; /**< Closed-trade count (== trades_len). */
314 pf_trade_t* trades; /**< Heap array of closed trades. */
315 int trades_len; /**< Length of #trades. */
316 double net_profit; /**< Sum of all closed-trade PnL. */
317
318 /* Bar processing counts */
319 int64_t input_bars_processed; /**< Source-feed bars consumed. */
320 int64_t script_bars_processed; /**< Script-timeframe bars evaluated. */
321
322 /* Security diagnostics */
323 int64_t security_feeds_total; /**< Total higher-TF feed bars across all security sites. */
324 int64_t security_complete_total; /**< Total complete-bar evals across all security sites. */
325 int64_t security_partial_total; /**< Total partial-bar evals across all security sites. */
326
327 /* Bar magnifier diagnostics */
328 int64_t magnifier_sub_bars_total; /**< Sub-bars synthesized by the magnifier. */
329 int64_t magnifier_sample_ticks_total; /**< Sample ticks visited by the magnifier. */
330
331 /* Timeframe metadata */
332 int input_tf_seconds; /**< Detected/configured input timeframe (seconds). */
333 int script_tf_seconds; /**< Script timeframe (seconds). */
334 int script_tf_ratio; /**< script_tf_seconds / input_tf_seconds. */
335 int needs_aggregation; /**< 1 if input → script TF aggregation was performed. */
336 int bar_magnifier_enabled; /**< 1 if magnifier was active for this run. */
337
338 /* Per-security feed/eval counters */
339 pf_security_diag_t* security_diag; /**< One entry per request.security site. */
340 int security_diag_len; /**< Length of #security_diag. */
341
342 /* Per-bar trace records */
343 pf_trace_entry_t* trace; /**< Per-bar trace records (empty unless tracing enabled). */
344 int trace_len; /**< Length of #trace. */
345 const char** trace_names; /**< Names indexed by pf_trace_entry_t::name_id. */
346 int trace_names_len; /**< Length of #trace_names. */
347
348 /* Computed trading metrics. Trade-based blocks reported for all /
349 * long-only / short-only; equity-based stats are all-trades only.
350 * Loss-side fields are positive magnitudes. Undefined values are NaN
351 * (see per-field docs). */
353 /* Per-script-bar equity curve. time_ms is the script-bar OPEN
354 * timestamp; equity = initial_capital + net_profit + open_profit at
355 * bar close. Heap-allocated; freed by report_free. len ==
356 * script_bars_processed, EXCEPT after a mid-run error (check
357 * strategy_get_last_error): an exception can truncate the curve, and
358 * metrics then describe the truncated prefix. NOTE int64_t length
359 * (ctypes: c_int64). */
363
364/** @} */ /* end of pf_types */
365
366/** Opaque handle to a compiled strategy instance. */
367typedef void* pf_strategy_t;
368
369/* ───────────────────────────────────────────────────────────────────
370 * STRATEGY .SO EXPORTS — implemented per compiled strategy
371 * ───────────────────────────────────────────────────────────────────
372 *
373 * Each .so emitted by the codegen exports the following symbols. The
374 * runtime library itself does NOT define them — they are per-strategy
375 * implementations generated by the transpiler.
376 *
377 * Note on naming: these are the legacy unprefixed names retained for
378 * backward compatibility with the existing harness. Future major
379 * versions may introduce `pf_`-prefixed equivalents and deprecate the
380 * unprefixed forms.
381 */
382
383/** @defgroup pf_lifecycle Strategy lifecycle
384 * @brief Create, run, and destroy a compiled strategy instance.
385 * @{
386 *
387 * NOTE: Per-strategy symbols (strategy_create, run_backtest, etc.) are
388 * emitted by the codegen with internal C++ types (ReportC, Bar) that are
389 * layout-compatible but type-distinct from the public C PODs below.
390 * Guard with PINEFORGE_NO_STRATEGY_DECLS so engine.hpp can include this
391 * header for its POD types without conflicting with per-strategy TU
392 * definitions.
393 */
394
395#ifndef PINEFORGE_NO_STRATEGY_DECLS
396
397/** Allocate a new strategy instance.
398 *
399 * @param params_json Currently ignored; pass `NULL`.
400 * @return Strategy handle, or `NULL` on allocation failure.
401 *
402 * Caller owns the returned handle and must release it via #strategy_free. */
403PF_API pf_strategy_t strategy_create(const char* params_json);
404
405/** Release a strategy handle previously returned by #strategy_create.
406 *
407 * Safe to call with `NULL`. Invalidates any `pf_report_t::trace_names`
408 * pointers obtained from this strategy. */
410
411/** Run a backtest with auto-detected timeframe and no bar magnifier.
412 *
413 * @param s Strategy handle from #strategy_create.
414 * @param bars Non-NULL pointer to OHLCV bars (length @p n).
415 * @param n Bar count (>= 0).
416 * @param out Non-NULL output report. Fields are populated with heap
417 * allocations the caller must release via #report_free. */
419 pf_bar_t* bars,
420 int n,
421 pf_report_t* out);
422
423/** Run a backtest with explicit timeframe and magnifier configuration.
424 *
425 * @param s Strategy handle.
426 * @param bars Bar feed.
427 * @param n Bar count.
428 * @param input_tf Input timeframe ("1", "5", "15", "60", "1D", ...).
429 * Empty string → auto-detect from bar timestamps.
430 * @param script_tf Script timeframe. Empty string → defaults to @p input_tf.
431 * @param bar_magnifier Boolean (0 / non-zero) — enable bar magnifier.
432 * @param magnifier_samples Sub-bar samples per parent bar (typical: 4).
433 * @param magnifier_dist Sampling distribution (see #pf_magnifier_distribution_t).
434 * @param out Output report. Free with #report_free. */
436 pf_bar_t* bars,
437 int n,
438 const char* input_tf,
439 const char* script_tf,
440 int bar_magnifier,
441 int magnifier_samples,
442 pf_magnifier_distribution_t magnifier_dist,
443 pf_report_t* out);
444
445/** Free heap arrays attached to a filled report.
446 *
447 * Idempotent. Safe to call with `NULL` or an already-freed report.
448 * The `pf_report_t` struct itself is caller-owned. */
450
451/** @} */ /* end of pf_lifecycle */
452
453/** @defgroup pf_config Per-strategy configuration
454 * @brief Override @c input.*() values, `strategy(...)` params, and runtime knobs.
455 * @{
456 */
457
458/** Override a Pine @c input.*() value before the next run.
459 *
460 * @param s Strategy handle.
461 * @param key The input's title (or fallback identifier).
462 * @param value Serialized value — numbers as decimal strings,
463 * booleans as `"true"` / `"false"`.
464 *
465 * Calls after #run_backtest are accepted but only take effect on
466 * subsequent runs. */
468 const char* key,
469 const char* value);
470
471/** Override a `strategy(...)` declaration parameter.
472 *
473 * Recognised @p key values: `initial_capital`, `commission_value`,
474 * `default_qty_value`, `pyramiding`, `slippage`,
475 * `process_orders_on_close`, `close_entries_rule`, `default_qty_type`,
476 * `commission_type`. */
478 const char* key,
479 const char* value);
480
481/** Toggle volume-weighted bar-magnifier sampling.
482 *
483 * Has no effect unless the bar magnifier is enabled in
484 * #run_backtest_full. */
486 int on);
487
488#endif /* PINEFORGE_NO_STRATEGY_DECLS */
489
490/* ───────────────────────────────────────────────────────────────────
491 * RUNTIME LIBRARY EXPORTS — implemented in libpineforge
492 * ─────────────────────────────────────────────────────────────────── */
493
494/** Toggle per-bar trace recording. Default off (zero-cost when off).
495 *
496 * Enables capture for `// @pf-trace name=expr` pragmas already compiled
497 * into the strategy `.so`. Trace records appear in pf_report_t::trace. */
499
500/** Set the earliest Unix-ms timestamp at which strategy order commands
501 * may fire.
502 *
503 * Earlier bars still execute user code and warm TA/series state, but
504 * `strategy.entry/order/exit/close` commands are ignored. */
506
507/** @} */ /* end of pf_config */
508
509/** @addtogroup pf_lifecycle
510 * @{
511 */
512
513/** Return the physical entry incarnation for one closed-trade row.
514 *
515 * Partial-close/FIFO fragments emitted from the same physical entry share
516 * this value. Distinct broker entry objects receive distinct monotonically
517 * increasing values even when Pine reuses the same user-visible entry ID.
518 * The value is scoped to one strategy run and is intended as report
519 * provenance, not as a stable cross-run identifier.
520 *
521 * @param s Strategy handle whose most recent run filled a report.
522 * @param trade_index Zero-based closed-trade index in that report.
523 * @return Non-zero physical-entry identity, or 0 for an invalid index or a
524 * legacy/synthetic trade without PendingOrder provenance. */
526 pf_strategy_t s, int trade_index);
527
528/** @} */ /* end of pf_lifecycle */
529
530/** @defgroup pf_streaming Historical to realtime streaming
531 * @brief Warm on confirmed OHLCV and continue the same strategy instance on
532 * normalized ordered trades from any data source.
533 * @{
534 */
535
536/** Warm a strategy with confirmed OHLCV, then switch the same instance to a
537 * realtime trade stream without resetting position, equity, pending orders,
538 * Pine variables, TA state, request.security state, or timeframe aggregation.
539 *
540 * The warmup must contain at least one complete fixed-duration input bar.
541 * Normalized ticks start at or after the next input bar's open. This
542 * lifecycle uses close-only strategy calculation (the Pine strategy default)
543 * while resting broker orders are evaluated on every normalized trade.
544 *
545 * @return 0 on success, -1 on failure. Inspect #strategy_get_last_error. */
547 const pf_bar_t* warmup_bars,
548 int n_warmup,
549 const char* input_tf,
550 const char* script_tf);
551
552/** Push one normalized realtime trade. Returns 0 on success, -1 on failure. */
554 const pf_trade_tick_t* tick);
555
556/** Push an ordered batch of realtime trades. Semantically identical to
557 * repeated #strategy_stream_push_tick calls, with lower FFI overhead. */
559 const pf_trade_tick_t* ticks,
560 int n);
561
562/** Advance the stream clock and close every input bar whose end is <= the
563 * supplied time. Quiet in-session intervals become zero-volume carry-forward
564 * bars; intervals outside the configured syminfo session are skipped. */
566
567/** End a realtime stream. When @p finalize_partial_input_bar is non-zero, the
568 * currently forming input bar is dispatched before ending; normally callers
569 * should first advance to a confirmed boundary and pass zero here. */
570PF_API int strategy_stream_end(pf_strategy_t s, int finalize_partial_input_bar);
571
572/** Snapshot the cumulative warmup + realtime report. The embedded arrays are
573 * caller-owned after return and must be released with #report_free. */
575
576/** @} */ /* end of pf_streaming */
577
578/** @addtogroup pf_config
579 * @{
580 */
581
582/** Set the strategy's chart timezone (IANA / POSIX TZ string).
583 *
584 * Pine builtins ``hour``, ``minute``, ``second``, ``dayofmonth``,
585 * ``dayofweek``, ``month``, ``year`` and ``weekofyear`` return the
586 * wall-clock for the chart's timezone — TV exports trade rows in chart
587 * TZ too. Engine bars are stored as Unix-ms (UTC), so without this
588 * override these builtins return UTC and silently diverge from TV by N
589 * hours when the chart is on a non-UTC zone (Asia/Taipei = UTC+8 is the
590 * validator default).
591 *
592 * Pass `NULL`, `""`, `"UTC"` or `"Etc/UTC"` for the legacy UTC
593 * behaviour (cheap, mutex-free). Any other value names a TZ resolved by
594 * the system tzdata; the per-bar decomposition then runs under a
595 * process-global mutex so multi-threaded harnesses don't corrupt each
596 * other's wall time.
597 *
598 * Should be called before #run_backtest / #run_backtest_full. Persists
599 * across runs on the same strategy handle until overridden. */
601
602/** Plumb the symbol's exchange timezone (IANA string) into syminfo. Feeds
603 * ``session.ismarket`` / ``time(session)`` predicates. Defaults to "UTC"
604 * (crypto). Distinct from #strategy_set_chart_timezone — the chart TZ
605 * drives wall-clock builtins and intraday-cap day rollover; this drives
606 * session membership. `NULL` is ignored. Call before #run_backtest*. */
608
609/** Set the symbol's session string (e.g. "0930-1600:23456", default
610 * "24x7"). Feeds ``session.ismarket`` / ``time(session)``. `NULL`
611 * ignored. Call before #run_backtest*. */
613
614/** Set the instrument tick size (``syminfo.mintick``, default 0.01). Drives the
615 * directional stop-entry snap and ``slippage = N*mintick`` economics. Set
616 * per-instrument (e.g. 0.25 for ES, 0.00001 for FX). Non-positive ignored.
617 * Call before #run_backtest*. */
619
620/** Set the instrument point value (``syminfo.pointvalue``, default 1.0) — the
621 * $-per-point-per-contract multiplier applied to every money path: realized
622 * PnL and MFE/MAE, open profit / mark-to-market equity (and the drawdown /
623 * runup extremes), percent-of-equity and cash position sizing, percent
624 * commission notionals, and the margin admission check. Set per-instrument
625 * (e.g. 50 for ES). Non-positive ignored. Call before #run_backtest*. */
627
628/** Inject a fundamental/exchange metadata value by Pine member name
629 * (e.g. "shares_outstanding_total", "target_price_average"). These have
630 * no OHLCV source; reads of un-injected members return na. Call before
631 * #run_backtest*. */
633 double value);
634
635/** Install a timestamped quote-to-account currency conversion curve.
636 *
637 * Each value is account-currency units per one unit of the symbol's quote
638 * currency and becomes active, inclusively, at the corresponding Unix-ms
639 * timestamp. The latest active value carries forward; broker events before
640 * the first point use the scalar `account_currency_fx` metadata fallback.
641 * Installing a curve also selects the converted account-currency broker
642 * ledger, including during that pre-first fallback interval; it is not
643 * equivalent to a same-currency run merely because a rate happens to be 1.
644 * Arrays are copied. Timestamps must be strictly increasing and rates
645 * positive and finite. Pass `n == 0` to clear the curve and restore scalar
646 * behavior. Timestamped curves currently support ordinary historical runs.
647 * Broker-open rate changes on margin-call-enabled carried positions are
648 * TV-pinned for 1x longs; carried shorts and leveraged positions fail closed
649 * at the crossing. Streaming, calc-on-order-fills, and bar-magnifier runs
650 * also fail closed.
651 *
652 * @return 0 on success, -1 for a null strategy or invalid arrays. */
654 pf_strategy_t s, const int64_t* effective_from_ms,
655 const double* account_per_quote, int n);
656
657/** Returns the error message captured by the most recent #run_backtest /
658 * #run_backtest_full call on this strategy.
659 *
660 * Returns an empty string when the run completed normally, or `NULL`
661 * only when `s` itself is `NULL`. The pointer is owned by the engine
662 * and remains valid until the next #run_backtest* call (which clears
663 * the captured error before it begins).
664 *
665 * The runtime catches every `std::exception` derivative inside the
666 * engine's run loop so the C ABI never unwinds a C++ exception across
667 * the `extern "C"` boundary. Consumers must check this after every
668 * run to surface engine-rejected configurations such as a script
669 * timeframe finer than the input timeframe, a `request.security`
670 * timeframe below the chart timeframe without a supported lower-TF
671 * emulation, or a missing input timeframe when securities are
672 * registered. */
674
675/** @} */ /* end of pf_config */
676
677/** @defgroup pf_version Version query
678 * @brief Runtime version metadata.
679 * @{
680 */
681
682/** Runtime version descriptor returned by #pf_version_get. */
683typedef struct pf_version_s {
684 int major; /**< Major version. */
685 int minor; /**< Minor version. */
686 int patch; /**< Patch version. */
687 const char* commit_sha; /**< Short git commit SHA, or `""` if unknown. */
689
690/** @return Linked runtime version. */
692
693/** @return Monotonic ABI version (see #PF_ABI_VERSION). */
695
696/** Full git-derived version descriptor.
697 *
698 * Returns `"MAJOR.MINOR.PATCH[-N-gSHA[-dirty]]"` for git checkouts, or
699 * plain `"MAJOR.MINOR.PATCH"` for tarball builds. The pointer is to a
700 * static string with program lifetime; do not free. */
701PF_API const char* pf_version_string(void);
702
703/** @} */ /* end of pf_version */
704
705#ifdef __cplusplus
706} /* extern "C" */
707#endif
708
709#endif /* PINEFORGE_H */
void strategy_set_syminfo_timezone(pf_strategy_t s, const char *tz)
Plumb the symbol's exchange timezone (IANA string) into syminfo.
void strategy_set_syminfo_mintick(pf_strategy_t s, double mintick)
Set the instrument tick size (syminfo.mintick, default 0.01).
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.
const char * strategy_get_last_error(pf_strategy_t s)
Returns the error message captured by the most recent run_backtest / run_backtest_full call on this s...
void strategy_set_syminfo_session(pf_strategy_t s, const char *session)
Set the symbol's session string (e.g.
void strategy_set_syminfo_pointvalue(pf_strategy_t s, double pointvalue)
Set the instrument point value (syminfo.pointvalue, default 1.0) — the $-per-point-per-contract multi...
void strategy_set_chart_timezone(pf_strategy_t s, const char *tz)
Set the strategy's chart timezone (IANA / POSIX TZ string).
void strategy_set_magnifier_volume_weighted(pf_strategy_t s, int on)
Toggle volume-weighted bar-magnifier sampling.
void strategy_set_trade_start_time(pf_strategy_t s, int64_t timestamp_ms)
Set the earliest Unix-ms timestamp at which strategy order commands may fire.
void strategy_set_syminfo_metadata(pf_strategy_t s, const char *key, double value)
Inject a fundamental/exchange metadata value by Pine member name (e.g.
void strategy_set_trace_enabled(pf_strategy_t s, int on)
Toggle per-bar trace recording.
int strategy_set_account_currency_fx_series(pf_strategy_t s, const int64_t *effective_from_ms, const double *account_per_quote, int n)
Install a timestamped quote-to-account currency conversion curve.
uint64_t strategy_closed_trade_entry_incarnation(pf_strategy_t s, int trade_index)
Return the physical entry incarnation for one closed-trade row.
pf_strategy_t strategy_create(const char *params_json)
Allocate a new strategy instance.
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.
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.
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.
int strategy_stream_begin(pf_strategy_t s, const pf_bar_t *warmup_bars, int n_warmup, const char *input_tf, const char *script_tf)
Warm a strategy with confirmed OHLCV, then switch the same instance to a realtime trade stream withou...
int strategy_stream_push_tick(pf_strategy_t s, const pf_trade_tick_t *tick)
Push one normalized realtime trade.
int strategy_stream_push_ticks(pf_strategy_t s, const pf_trade_tick_t *ticks, int n)
Push an ordered batch of realtime trades.
int strategy_stream_advance_time(pf_strategy_t s, int64_t timestamp_ms)
Advance the stream clock and close every input bar whose end is <= the supplied time.
int strategy_stream_end(pf_strategy_t s, int finalize_partial_input_bar)
End a realtime stream.
int strategy_stream_fill_report(pf_strategy_t s, pf_report_t *out)
Snapshot the cumulative warmup + realtime report.
pf_magnifier_distribution_t
Bar-magnifier sub-bar sampling distribution.
Definition pineforge.h:95
@ PF_MAGNIFIER_FRONT_LOADED
Sample density biased toward bar open.
Definition pineforge.h:100
@ PF_MAGNIFIER_COSINE
Cosine-tapered density.
Definition pineforge.h:97
@ PF_MAGNIFIER_ENDPOINTS
Default — exact O,H,L,C points plus uniform fill between.
Definition pineforge.h:99
@ PF_MAGNIFIER_BACK_LOADED
Sample density biased toward bar close.
Definition pineforge.h:101
@ PF_MAGNIFIER_TRIANGLE
Triangle-tapered density.
Definition pineforge.h:98
@ PF_MAGNIFIER_UNIFORM
Uniform spacing across the parent bar.
Definition pineforge.h:96
int pf_abi_version(void)
pf_version_t pf_version_get(void)
const char * pf_version_string(void)
Full git-derived version descriptor.
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:367
#define PF_API
Definition pineforge.h:65
Single OHLCV bar pushed into the engine.
Definition pineforge.h:107
double volume
Bar volume.
Definition pineforge.h:112
double high
High price.
Definition pineforge.h:109
double low
Low price.
Definition pineforge.h:110
double close
Close price.
Definition pineforge.h:111
double open
Open price.
Definition pineforge.h:108
int64_t timestamp
Bar open time, Unix milliseconds.
Definition pineforge.h:113
Single per-script-bar equity point.
Definition pineforge.h:271
double open_profit
Mark-to-market open P&L at bar close.
Definition pineforge.h:274
double equity
initial_capital + net_profit + open_profit.
Definition pineforge.h:273
int64_t time_ms
Script-bar OPEN timestamp (Unix ms).
Definition pineforge.h:272
Equity-curve-derived statistics (all-trades only, like TV).
Definition pineforge.h:224
double sharpe_tv
Month-end-resampled equity simple returns (chart timezone, open-time bucketing), risk-free 2%/yr (2/1...
Definition pineforge.h:235
double time_in_market_pct
PERCENT (0-100) of script bars with an open position at bar close.
Definition pineforge.h:255
double max_equity_drawdown_pct
max_equity_drawdown relative to the peak in effect (PERCENT 0-100).
Definition pineforge.h:226
double max_equity_drawdown
Peak-to-trough equity drop, positive currency magnitude.
Definition pineforge.h:225
double max_equity_runup_pct
max_equity_runup relative to that trough (PERCENT 0-100).
Definition pineforge.h:230
double buy_hold_return
initial_capital * (last_close/first_open - 1), currency.
Definition pineforge.h:231
double open_pl
Mark-to-market open profit at the final bar.
Definition pineforge.h:257
double max_equity_runup
Trough-to-peak rise where the trough resets on each new equity peak (mirrors the engine's intra-run e...
Definition pineforge.h:228
double sortino_tv
Same resampling as sharpe_tv; uses population downside deviation vs the monthly risk-free.
Definition pineforge.h:239
double cagr
PERCENT per year: 100*((final_equity/initial_capital)^(1/years)-1).
Definition pineforge.h:249
double recovery_factor
net_profit / max_equity_drawdown (currency / currency).
Definition pineforge.h:253
double calmar
cagr / max_equity_drawdown_pct — BOTH IN PERCENT, so the ratio is dimensionless.
Definition pineforge.h:251
double buy_hold_return_pct
buy_hold_return as PERCENT.
Definition pineforge.h:233
double sharpe_bar
Per-script-bar returns, annualized by observed bar density (bars per year = (len-1)/calendar span),...
Definition pineforge.h:242
double sortino_bar
Same construction as sharpe_bar over per-bar returns; uses population downside deviation.
Definition pineforge.h:246
Composite metrics container: trade stats (all / long / short) + equity-curve stats.
Definition pineforge.h:262
pf_trade_stats_t all
Definition pineforge.h:263
pf_equity_stats_t equity
Definition pineforge.h:264
pf_trade_stats_t longs
Definition pineforge.h:263
pf_trade_stats_t shorts
Definition pineforge.h:263
Backtest report filled by run_backtest / run_backtest_full.
Definition pineforge.h:311
int input_tf_seconds
Detected/configured input timeframe (seconds).
Definition pineforge.h:332
int security_diag_len
Length of security_diag.
Definition pineforge.h:340
int64_t security_feeds_total
Total higher-TF feed bars across all security sites.
Definition pineforge.h:323
int bar_magnifier_enabled
1 if magnifier was active for this run.
Definition pineforge.h:336
pf_trace_entry_t * trace
Per-bar trace records (empty unless tracing enabled).
Definition pineforge.h:343
int trace_names_len
Length of trace_names.
Definition pineforge.h:346
int64_t input_bars_processed
Source-feed bars consumed.
Definition pineforge.h:319
int script_tf_seconds
Script timeframe (seconds).
Definition pineforge.h:333
double net_profit
Sum of all closed-trade PnL.
Definition pineforge.h:316
int64_t equity_curve_len
Definition pineforge.h:361
int trades_len
Length of trades.
Definition pineforge.h:315
pf_metrics_t metrics
Definition pineforge.h:352
int trace_len
Length of trace.
Definition pineforge.h:344
int64_t script_bars_processed
Script-timeframe bars evaluated.
Definition pineforge.h:320
int64_t magnifier_sample_ticks_total
Sample ticks visited by the magnifier.
Definition pineforge.h:329
int total_trades
Closed-trade count (== trades_len).
Definition pineforge.h:313
int64_t security_partial_total
Total partial-bar evals across all security sites.
Definition pineforge.h:325
pf_security_diag_t * security_diag
One entry per request.security site.
Definition pineforge.h:339
int64_t magnifier_sub_bars_total
Sub-bars synthesized by the magnifier.
Definition pineforge.h:328
const char ** trace_names
Names indexed by pf_trace_entry_t::name_id.
Definition pineforge.h:345
pf_equity_point_t * equity_curve
Definition pineforge.h:360
int64_t security_complete_total
Total complete-bar evals across all security sites.
Definition pineforge.h:324
int script_tf_ratio
script_tf_seconds / input_tf_seconds.
Definition pineforge.h:334
int needs_aggregation
1 if input → script TF aggregation was performed.
Definition pineforge.h:335
pf_trade_t * trades
Heap array of closed trades.
Definition pineforge.h:314
Per-request.security() site diagnostic counters.
Definition pineforge.h:280
int sec_id
Stable id for the request.security site.
Definition pineforge.h:281
int64_t feed_count
Higher-TF feed bars consumed.
Definition pineforge.h:282
int64_t complete_count
Evaluations on completed parent bars.
Definition pineforge.h:283
int64_t partial_count
Evaluations on still-forming parent bars.
Definition pineforge.h:284
Single per-bar trace entry.
Definition pineforge.h:292
double value
Traced expression value on this bar.
Definition pineforge.h:296
int64_t timestamp
Bar timestamp (Unix ms).
Definition pineforge.h:293
int32_t name_id
Index into pf_report_t::trace_names.
Definition pineforge.h:295
int32_t bar_index
Zero-based bar index.
Definition pineforge.h:294
Trade-level statistics block — computed once each for all / long / short.
Definition pineforge.h:161
double avg_win_pct
Mean of per-trade pnl_pct over winning trades.
Definition pineforge.h:184
double avg_win
gross_profit / num_wins.
Definition pineforge.h:183
double net_profit_pct
net_profit as a percent of initial capital (0-100 scale).
Definition pineforge.h:171
double largest_loss_pct
Maximum of -pnl_pct over losing trades (positive magnitude) — an INDEPENDENT maximum,...
Definition pineforge.h:201
int32_t num_losses
Trades with pnl < 0.
Definition pineforge.h:164
double gross_profit
Sum of winning pnl.
Definition pineforge.h:173
int32_t max_consecutive_losses
Longest losing run; even trades reset both streaks.
Definition pineforge.h:210
double avg_loss_pct
Mean of the NEGATED pnl_pct of the losing trades.
Definition pineforge.h:188
double commission_paid
Sum of pf_trade_t::commission in the block.
Definition pineforge.h:206
double percent_profitable
100 * num_wins / num_trades, in PERCENT (0-100).
Definition pineforge.h:168
double largest_win_pct
Maximum pnl_pct over winning trades — an INDEPENDENT maximum, not the pct of the largest-USD win (TV ...
Definition pineforge.h:195
double gross_loss
Sum of |losing pnl| — POSITIVE magnitude (TV display convention).
Definition pineforge.h:176
double gross_profit_pct
gross_profit as a percent of initial capital (0-100 scale).
Definition pineforge.h:174
double largest_loss
Single largest |pnl| among losing trades (positive magnitude).
Definition pineforge.h:199
double avg_bars_in_wins
Mean bar duration of winning trades, inclusive of the entry bar (TV convention, validated 2026-06-12)...
Definition pineforge.h:215
double avg_bars_in_losses
Mean bar duration of losing trades, inclusive of the entry bar (TV convention, validated 2026-06-12).
Definition pineforge.h:218
double profit_factor
gross_profit / gross_loss.
Definition pineforge.h:179
double avg_loss
gross_loss / num_losses (positive magnitude).
Definition pineforge.h:186
double largest_win
Single largest pnl among winning trades.
Definition pineforge.h:193
double avg_bars_in_trade
Mean of (exit_bar_index - entry_bar_index + 1) in SCRIPT bars, over all trades — inclusive of the ent...
Definition pineforge.h:211
double gross_loss_pct
gross_loss as a percent of initial capital (0-100 scale).
Definition pineforge.h:177
int32_t max_consecutive_wins
Longest winning run; even trades reset both streaks.
Definition pineforge.h:209
double expectancy
(num_wins/num_trades)*avg_win - (num_losses/num_trades)*avg_loss, account currency per trade.
Definition pineforge.h:207
double net_profit
Sum of pnl (account currency, net of commission).
Definition pineforge.h:170
int32_t num_trades
Closed trades in this block (all / long-only / short-only).
Definition pineforge.h:162
double avg_trade_pct
Mean of per-trade pnl_pct over all trades.
Definition pineforge.h:181
double ratio_avg_win_avg_loss
avg_win / avg_loss.
Definition pineforge.h:192
double avg_trade
net_profit / num_trades.
Definition pineforge.h:180
int32_t num_even
Trades with pnl == 0.0 exactly; breaks both win and loss streaks; excluded from win/loss averages.
Definition pineforge.h:165
int32_t num_wins
Trades with pnl > 0.
Definition pineforge.h:163
Closed-trade record returned in pf_report_t::trades.
Definition pineforge.h:133
int32_t exit_bar_index
Script-bar index of the exit fill (0-based).
Definition pineforge.h:154
double pnl_pct
Net return-on-cost in percent: pnl (NET of commission) / entry cost (entry_price * qty * pointvalue) ...
Definition pineforge.h:139
double exit_price
Exit fill price (incl.
Definition pineforge.h:137
int32_t entry_bar_index
Script-bar index of the entry fill (0-based).
Definition pineforge.h:153
double commission
Entry+exit commission actually deducted from pnl (account currency).
Definition pineforge.h:151
double pnl
Net realized PnL in account currency (commission-inclusive).
Definition pineforge.h:138
int is_long
1 if long, 0 if short.
Definition pineforge.h:147
double max_drawdown
Peak adverse price travel during the trade ($/unit qty).
Definition pineforge.h:149
double qty
Filled quantity.
Definition pineforge.h:150
double max_runup
Peak favorable price travel during the trade ($/unit qty).
Definition pineforge.h:148
int64_t entry_time
Entry fill time (Unix ms).
Definition pineforge.h:134
double entry_price
Entry fill price (incl.
Definition pineforge.h:136
int64_t exit_time
Exit fill time (Unix ms).
Definition pineforge.h:135
One provider-neutral realtime executed-trade update.
Definition pineforge.h:123
double quantity
Traded quantity in symbol volume units (>= 0).
Definition pineforge.h:127
uint64_t sequence
Normalized per-stream sequence, or 0.
Definition pineforge.h:125
int64_t timestamp
Source event time, Unix milliseconds.
Definition pineforge.h:124
double price
Executed trade price (> 0).
Definition pineforge.h:126
Runtime version descriptor returned by pf_version_get.
Definition pineforge.h:683
int patch
Patch version.
Definition pineforge.h:686
int minor
Minor version.
Definition pineforge.h:685
int major
Major version.
Definition pineforge.h:684
const char * commit_sha
Short git commit SHA, or "" if unknown.
Definition pineforge.h:687