PineForge unreleased
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/* Public C words with enum types must retain their published width even when
49 * a consumer enables -fshort-enums. The C99 fallback keeps the strict-C99
50 * header build valid; the two newer languages get a native static assertion. */
51#if defined(__cplusplus)
52#define PF_STATIC_ASSERT(condition) static_assert((condition), #condition)
53#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
54#define PF_STATIC_ASSERT(condition) _Static_assert((condition), #condition)
55#else
56#define PF_STATIC_ASSERT_JOIN_(a, b) a##b
57#define PF_STATIC_ASSERT_JOIN(a, b) PF_STATIC_ASSERT_JOIN_(a, b)
58#define PF_STATIC_ASSERT(condition) \
59 typedef char PF_STATIC_ASSERT_JOIN(pf_static_assertion_, __LINE__)[(condition) ? 1 : -1]
60#endif
61
62/* ── Version ─────────────────────────────────────────────────────── */
63/* Macros (PINEFORGE_VERSION_MAJOR / _MINOR / _PATCH / _STRING / _FULL,
64 * PINEFORGE_GIT_SHA) live in the generated <pineforge/version.h>. */
65#include <pineforge/version.h>
66
67/* pf_pending_order_v1_t / pf_field_desc_t -- the generated, C-compatible POD
68 * mirror of the engine's resting-order record (ABI v4, task 7). Regenerate
69 * with scripts/gen_pending_order_mirror.py; never edit by hand. */
71
72/* ── Visibility ──────────────────────────────────────────────────── */
73
74#if defined(_WIN32) || defined(__CYGWIN__)
75 #if defined(PINEFORGE_BUILD_SHARED)
76 #define PF_API __declspec(dllexport)
77 #else
78 #define PF_API __declspec(dllimport)
79 #endif
80#elif defined(__GNUC__) || defined(__clang__)
81 #define PF_API __attribute__((visibility("default")))
82#else
83 #define PF_API
84#endif
85
86/** Monotonic strategy-module compatibility version. Bumped for layout growth
87 * or a required module rebuild. Consumers MUST verify
88 * pf_abi_version() == PF_ABI_VERSION before calling run_backtest.
89 * pf_report_t is caller-allocated: growth silently corrupts an undersized buffer.
90 * pf_trade_t is runtime-allocated: growth changes the array stride.
91 * Value 2 introduced metrics and the equity curve. Modules predating this
92 * macro have no pf_abi_version symbol; treat dlsym failure as version 1.
93 * Value 3 appended pf_trade_t::open_at_end (the range-end close
94 * flag); a v2 reader would misindex every row after the first.
95 * Value 4 appended live-runtime accessors and the per-bar broker-state
96 * hash array to pf_report_t.
97 * Value 5 requires a 1.8.0 module rebuild for the native order layouts,
98 * without growing the caller-allocated C report/trade layouts. */
99#define PF_ABI_VERSION 5
100
101/** Feature probe for the opt-in split chart/request.security feed boundary.
102 * When defined, #strategy_set_aux_security_feed is available. */
103#define PINEFORGE_HAS_AUX_SECURITY_FEED_V1 1
104
105/** Feature probe for native higher-timeframe request.security feeds.
106 * When defined, #strategy_set_native_security_feed is available. */
107#define PINEFORGE_HAS_NATIVE_SECURITY_FEED_V1 1
108
109/** Feature probe for immutable native-run account-currency FX curves.
110 * When defined, #strategy_configure_native_fx_curve_v1 is available. */
111#define PINEFORGE_HAS_NATIVE_FX_CURVE_V1 1
112
113#ifdef __cplusplus
114extern "C" {
115#endif
116
117/** @defgroup pf_types Types
118 * @brief POD types and enums passed across the C ABI.
119 * @{
120 */
121
122/** Bar-magnifier sub-bar sampling distribution.
123 *
124 * Selects how intra-bar synthetic ticks are placed when the bar
125 * magnifier is enabled in #run_backtest_full. Layout-compatible with the
126 * internal C++ `pineforge::MagnifierDistribution` enum class — a
127 * `static_assert` in `c_abi.cpp` guarantees the integer values match. */
128typedef enum pf_magnifier_distribution_e {
129 PF_MAGNIFIER_UNIFORM = 0, /**< Uniform spacing across the parent bar. */
130 PF_MAGNIFIER_COSINE = 1, /**< Cosine-tapered density. */
131 PF_MAGNIFIER_TRIANGLE = 2, /**< Triangle-tapered density. */
132 PF_MAGNIFIER_ENDPOINTS = 3, /**< Default — exact O,H,L,C points plus uniform fill between. */
133 PF_MAGNIFIER_FRONT_LOADED = 4, /**< Sample density biased toward bar open. */
134 PF_MAGNIFIER_BACK_LOADED = 5 /**< Sample density biased toward bar close. */
136
137/** Single OHLCV bar pushed into the engine.
138 *
139 * Layout-compatible with the internal C++ `pineforge::Bar` struct. */
140typedef struct pf_bar_s {
141 double open; /**< Open price. */
142 double high; /**< High price. */
143 double low; /**< Low price. */
144 double close; /**< Close price. */
145 double volume; /**< Bar volume. */
146 int64_t timestamp; /**< Bar open time, Unix milliseconds. */
147} pf_bar_t;
148
149/** One provider-neutral realtime executed-trade update.
150 *
151 * `sequence` is optional: pass 0 when the normalized source has no stable
152 * ordering key. Non-zero values must increase strictly within a stream.
153 * `quantity` is expressed in the configured symbol's volume units and is
154 * accumulated into the input bar's volume. The source adapter owns all
155 * provider-specific fields and normalization. */
156typedef struct pf_trade_tick_s {
157 int64_t timestamp; /**< Source event time, Unix milliseconds. */
158 uint64_t sequence; /**< Normalized per-stream sequence, or 0. */
159 double price; /**< Executed trade price (> 0). */
160 double quantity; /**< Traded quantity in symbol volume units (>= 0). */
162
163/** Closed-trade record returned in pf_report_t::trades.
164 *
165 * Layout-compatible with internal `pineforge::TradeC`. */
166typedef struct pf_trade_s {
167 int64_t entry_time; /**< Entry fill time (Unix ms). */
168 int64_t exit_time; /**< Exit fill time (Unix ms). */
169 double entry_price; /**< Entry fill price (incl. slippage). */
170 double exit_price; /**< Exit fill price (incl. slippage). */
171 double pnl; /**< Net realized PnL in account currency (commission-inclusive). */
172 double pnl_pct; /**< Net return-on-cost in percent: pnl (NET of commission) /
173 * entry cost (entry_price * qty * pointvalue) * 100. This is
174 * TradingView's "Net P&L %" convention, arbitrated 2026-06-12
175 * against a real TV export (trade #258 short: 102.44 USD on a
176 * 2276.66 entry => 4.50%). Degenerates to the old gross
177 * (exit/entry-1)*100 form for longs with zero commission;
178 * the previous short form (entry/exit-1)*100 was wrong on
179 * large moves. Sign always matches pnl. */
180 int is_long; /**< 1 if long, 0 if short. */
181 double max_runup; /**< Peak favorable whole-trade excursion, account currency, net of entry fees. */
182 double max_drawdown; /**< Peak adverse whole-trade excursion, account currency, net of entry fees. */
183 double qty; /**< Filled quantity. */
184 double commission; /**< Entry+exit commission actually deducted from pnl
185 * (account currency). pnl is already net of this. */
186 int32_t entry_bar_index;/**< Script-bar index of the entry fill (0-based). */
187 int32_t exit_bar_index; /**< Script-bar index of the exit fill (0-based). */
188 int32_t open_at_end; /**< 1 when this row is the RANGE-END close of a position
189 * that was still open after the final bar; 0 for an
190 * exit the script or a bracket produced. TradingView's
191 * deep-backtest report does not leave the last position
192 * open: it reports it as a closed trade whose exit leg is
193 * the range's last bar at that bar's CLOSE, with an
194 * empty exit Signal, and counts it in closedTrades
195 * (orb-lite on NYSE:F 1D: Entry short 2026-03-16 @ 11.82,
196 * Exit 2026-04-30 @ 12.08 = the last close,
197 * closedTrades:1). The engine emulates that row
198 * (operator decision 2026-09-02): exit_time is the last
199 * script bar's label, exit_price its mintick-rounded
200 * close with no slippage, commission per the strategy's
201 * rules, pnl/pnl_pct/excursions as for any close.
202 * Appended in ABI v3. */
203} pf_trade_t;
204
205/** Trade-level statistics block — computed once each for all / long / short.
206 *
207 * Loss-side fields (`gross_loss`, `avg_loss`, `largest_loss`) are
208 * **positive magnitudes** (absolute values of the underlying negative PnL). */
209typedef struct pf_trade_stats_s {
210 int32_t num_trades; /**< Closed trades in this block (all / long-only / short-only). */
211 int32_t num_wins; /**< Trades with pnl > 0. */
212 int32_t num_losses; /**< Trades with pnl < 0. */
213 int32_t num_even; /**< Trades with pnl == 0.0 exactly; breaks both win and loss
214 * streaks; excluded from win/loss averages.
215 * Invariant: num_trades == num_wins + num_losses + num_even. */
216 double percent_profitable; /**< 100 * num_wins / num_trades, in PERCENT (0-100).
217 * NaN when num_trades == 0. */
218 double net_profit; /**< Sum of pnl (account currency, net of commission). */
219 double net_profit_pct; /**< net_profit as a percent of initial capital (0-100 scale).
220 * NaN when initial capital <= 0. */
221 double gross_profit; /**< Sum of winning pnl. */
222 double gross_profit_pct; /**< gross_profit as a percent of initial capital (0-100 scale).
223 * NaN when initial capital <= 0. */
224 double gross_loss; /**< Sum of |losing pnl| — POSITIVE magnitude (TV display convention). */
225 double gross_loss_pct; /**< gross_loss as a percent of initial capital (0-100 scale).
226 * NaN when initial capital <= 0. */
227 double profit_factor; /**< gross_profit / gross_loss. NaN when gross_loss == 0. */
228 double avg_trade; /**< net_profit / num_trades. NaN when num_trades == 0. */
229 double avg_trade_pct; /**< Mean of per-trade pnl_pct over all trades.
230 * NaN when num_trades == 0. */
231 double avg_win; /**< gross_profit / num_wins. NaN when num_wins == 0. */
232 double avg_win_pct; /**< Mean of per-trade pnl_pct over winning trades.
233 * NaN when num_wins == 0. */
234 double avg_loss; /**< gross_loss / num_losses (positive magnitude).
235 * NaN when num_losses == 0. */
236 double avg_loss_pct; /**< Mean of the NEGATED pnl_pct of the losing trades. Since
237 * pnl_pct is net return-on-cost (sign matches pnl), this is
238 * a genuinely POSITIVE magnitude. Basis = pf_trade_t::pnl_pct.
239 * NaN when num_losses == 0. */
240 double ratio_avg_win_avg_loss; /**< avg_win / avg_loss. NaN unless both sides non-empty. */
241 double largest_win; /**< Single largest pnl among winning trades.
242 * NaN when num_wins == 0. */
243 double largest_win_pct; /**< Maximum pnl_pct over winning trades — an INDEPENDENT
244 * maximum, not the pct of the largest-USD win (TV convention,
245 * validated 2026-06-12 vs TV export).
246 * NaN when num_wins == 0. */
247 double largest_loss; /**< Single largest |pnl| among losing trades (positive magnitude).
248 * NaN when num_losses == 0. */
249 double largest_loss_pct; /**< Maximum of -pnl_pct over losing trades (positive magnitude) —
250 * an INDEPENDENT maximum, not the pct of the largest-USD loss
251 * (TV convention, validated 2026-06-12 vs TV export: All
252 * "Largest loss %" came from a different trade than the
253 * largest USD loss). NaN when num_losses == 0. */
254 double commission_paid; /**< Sum of pf_trade_t::commission in the block. */
255 double expectancy; /**< (num_wins/num_trades)*avg_win - (num_losses/num_trades)*avg_loss,
256 * account currency per trade. NaN when num_trades == 0. */
257 int32_t max_consecutive_wins; /**< Longest winning run; even trades reset both streaks. */
258 int32_t max_consecutive_losses; /**< Longest losing run; even trades reset both streaks. */
259 double avg_bars_in_trade; /**< Mean of (exit_bar_index - entry_bar_index + 1) in SCRIPT
260 * bars, over all trades — inclusive of the entry bar (TV
261 * convention, validated 2026-06-12).
262 * NaN when num_trades == 0. */
263 double avg_bars_in_wins; /**< Mean bar duration of winning trades, inclusive of the entry
264 * bar (TV convention, validated 2026-06-12).
265 * NaN when num_wins == 0. */
266 double avg_bars_in_losses; /**< Mean bar duration of losing trades, inclusive of the entry
267 * bar (TV convention, validated 2026-06-12).
268 * NaN when num_losses == 0. */
270
271/** Equity-curve-derived statistics (all-trades only, like TV). */
272typedef struct pf_equity_stats_s {
273 double max_equity_drawdown; /**< Peak-to-trough equity drop, positive currency magnitude. */
274 double max_equity_drawdown_pct; /**< max_equity_drawdown relative to the peak in effect
275 * (PERCENT 0-100). */
276 double max_equity_runup; /**< Trough-to-peak rise where the trough resets on each new
277 * equity peak (mirrors the engine's intra-run extremes). */
278 double max_equity_runup_pct; /**< max_equity_runup relative to that trough (PERCENT 0-100). */
279 double buy_hold_return; /**< initial_capital * (last_close/first_open - 1), currency.
280 * NaN when first chart open is non-finite or <= 0. */
281 double buy_hold_return_pct; /**< buy_hold_return as PERCENT.
282 * NaN when first chart open is non-finite or <= 0. */
283 /** Month-end-resampled equity simple returns (chart timezone, open-time
284 * bucketing), risk-free 2%/yr (2/12 per month), annualized by sqrt(12).
285 * Uses sample (N-1) stddev. NaN with <2 monthly returns or zero deviation.
286 *
287 * Its pre-1.0 spelling `sharpe_tv` named this same double at this same
288 * offset (48) and was removed for 1.0; the serialized report key is still
289 * `sharpe_tv` (report-schema name). */
291 /** Same resampling as sharpe_monthly; uses population downside deviation
292 * vs the monthly risk-free. NaN with <2 monthly returns or zero deviation.
293 *
294 * Its pre-1.0 spelling `sortino_tv` (offset 56) was removed the same way;
295 * the serialized report key is still `sortino_tv`. */
297 double sharpe_bar; /**< Per-script-bar returns, annualized by observed bar density
298 * (bars per year = (len-1)/calendar span), NOT a fixed
299 * calendar formula. Uses sample (N-1) stddev.
300 * NaN with <2 returns or zero deviation. */
301 double sortino_bar; /**< Same construction as sharpe_bar over per-bar returns;
302 * uses population downside deviation.
303 * NaN with <2 returns or zero deviation. */
304 double cagr; /**< PERCENT per year: 100*((final_equity/initial_capital)^(1/years)-1).
305 * NaN when span <= 0 or either side <= 0. */
306 double calmar; /**< cagr / max_equity_drawdown_pct — BOTH IN PERCENT, so the
307 * ratio is dimensionless. NaN when drawdown is 0. */
308 double recovery_factor; /**< net_profit / max_equity_drawdown (currency / currency).
309 * NaN when drawdown is 0. */
310 double time_in_market_pct; /**< PERCENT (0-100) of script bars with an open position
311 * at bar close. */
312 double open_pl; /**< Mark-to-market open profit at the final bar. */
314
315/** Composite metrics container: trade stats (all / long / short) +
316 * equity-curve stats. */
321
322/** Single per-script-bar equity point.
323 *
324 * `time_ms` is the script-bar **open** timestamp (Unix ms).
325 * `equity` = `initial_capital` + `net_profit` + `open_profit` at bar close. */
326typedef struct pf_equity_point_s {
327 int64_t time_ms; /**< Script-bar OPEN timestamp (Unix ms). */
328 double equity; /**< initial_capital + net_profit + open_profit. */
329 double open_profit; /**< Mark-to-market open P&L at bar close. */
331
332/** Per-`request.security()` site diagnostic counters.
333 *
334 * Layout-compatible with internal `pineforge::SecurityDiagC`. */
335typedef struct pf_security_diag_s {
336 int sec_id; /**< Stable id for the request.security site. */
337 int64_t feed_count; /**< Higher-TF feed bars consumed. */
338 int64_t complete_count; /**< Evaluations on completed parent bars. */
339 int64_t partial_count; /**< Evaluations on still-forming parent bars. */
341
342/** Single per-bar trace entry.
343 *
344 * Emitted when the source script contains `// @pf-trace name=expr`
345 * pragmas and tracing is enabled via #strategy_set_trace_enabled.
346 * Layout-compatible with internal `pineforge::TraceEntryC`. */
347typedef struct pf_trace_entry_s {
348 int64_t timestamp; /**< Bar timestamp (Unix ms). */
349 int32_t bar_index; /**< Zero-based bar index. */
350 int32_t name_id; /**< Index into pf_report_t::trace_names. */
351 double value; /**< Traced expression value on this bar. */
353
354/** Backtest report filled by #run_backtest / #run_backtest_full.
355 *
356 * Layout-compatible with internal `pineforge::ReportC`.
357 *
358 * ### Ownership and lifetime
359 * The struct itself is caller-owned (typically stack). The embedded
360 * arrays (`trades`, `security_diag`, `trace`, `trace_names`,
361 * `equity_curve`, `broker_state_hash`) are heap-allocated by the
362 * runtime; the caller frees each filled report exactly once, with #report_free
363 * or, on the kernel archive, #strategy_native_report_free_v1. `trace_names`
364 * strings stay owned by the strategy handle until #strategy_free. */
365
366typedef struct pf_report_s {
367 /* Trades */
368 int total_trades; /**< Logical closed-trade count, including
369 * the range-end close of a position still open
370 * after the final bar. Equals trades_len on the
371 * owning-array route; with a sink trades_len is 0.
372 * The opt-in report envelope declares capture
373 * separately in <pineforge/report_mode.h>. */
374 pf_trade_t* trades; /**< Heap array of closed trades; script-driven exits
375 * first, then the range-end rows (open_at_end=1). */
376 int trades_len; /**< Resident length of #trades, not the logical total. */
377 double net_profit; /**< Sum of all closed-trade PnL. */
378
379 /* Bar processing counts */
380 int64_t input_bars_processed; /**< Source-feed bars consumed. */
381 int64_t script_bars_processed; /**< Script-timeframe bars evaluated. */
382
383 /* Security diagnostics */
384 int64_t security_feeds_total; /**< Total higher-TF feed bars across all security sites. */
385 int64_t security_complete_total; /**< Total complete-bar evals across all security sites. */
386 int64_t security_partial_total; /**< Total partial-bar evals across all security sites. */
387
388 /* Bar magnifier diagnostics */
389 int64_t magnifier_sub_bars_total; /**< Sub-bars synthesized by the magnifier. */
390 int64_t magnifier_sample_ticks_total; /**< Sample ticks visited by the magnifier. */
391
392 /* Timeframe metadata */
393 int input_tf_seconds; /**< Detected/configured input timeframe (seconds). */
394 int script_tf_seconds; /**< Script timeframe (seconds). */
395 int script_tf_ratio; /**< script_tf_seconds / input_tf_seconds. */
396 int needs_aggregation; /**< 1 if input → script TF aggregation was performed. */
397 int bar_magnifier_enabled; /**< 1 if magnifier was active for this run. */
398
399 /* Per-security feed/eval counters */
400 pf_security_diag_t* security_diag; /**< One entry per request.security site. */
401 int security_diag_len; /**< Length of #security_diag. */
402
403 /* Per-bar trace records */
404 pf_trace_entry_t* trace; /**< Per-bar trace records (empty unless tracing enabled). */
405 int trace_len; /**< Length of #trace. */
406 const char** trace_names; /**< Names indexed by pf_trace_entry_t::name_id. */
407 int trace_names_len; /**< Length of #trace_names. */
408
409 /* Computed trading metrics. Trade-based blocks reported for all /
410 * long-only / short-only; equity-based stats are all-trades only.
411 * Loss-side fields are positive magnitudes. Undefined values are NaN
412 * (see per-field docs). */
414 /* Per-script-bar equity curve (bare host: KernelRecorded); time_ms is the script-bar OPEN
415 * timestamp; equity = initial_capital + net_profit + open_profit at
416 * bar close. Free via #report_free or #strategy_native_report_free_v1.
417 * On a completed KernelRecorded run len == script_bars_processed; a
418 * Failed or Aborted run can expose a shorter prefix (check
419 * strategy_get_last_error), and metrics describe that prefix. NOTE int64_t
420 * length (ctypes: c_int64). With a report sink: NULL / 0 length.
421 * The opt-in <pineforge/report_mode.h> envelope exposes logical point
422 * count and capture flags separately; this field is resident length. */
425 /* Per-script-bar broker-state hash when recording is on and report
426 * points exist (KernelRecorded for a bare native host); freed by
427 * #report_free or #strategy_native_report_free_v1. NULL / 0-length when off or no points were recorded.
428 * On a completed run, len == script_bars_processed and the last row
429 * equals #strategy_broker_state_hash after the run for Pine and bare
430 * native hosts alike: each point latches the continuation, and the
431 * scalar folds the last latch. A Failed or Aborted run can have a
432 * shorter recorded prefix; its last row need not equal the scalar.
433 * ABI v4. With a report sink: NULL / 0 length. */
437
438/** @} */ /* end of pf_types */
439
440/** Opaque handle to a compiled strategy instance. */
441typedef void* pf_strategy_t;
442
443/** @defgroup pf_lifecycle Strategy lifecycle
444 * @brief Create, run, and destroy a compiled strategy instance.
445 * @{
446 *
447 * NOTE: Per-strategy symbols (strategy_create, run_backtest, etc.) are
448 * emitted by the codegen with internal C++ types (ReportC, Bar) that are
449 * layout-compatible but type-distinct from the public C PODs below.
450 * Guard with PINEFORGE_NO_STRATEGY_DECLS so engine.hpp can include this
451 * header for its POD types without conflicting with per-strategy TU
452 * definitions.
453 */
454
455/** What #strategy_execution_contract answers for a valid handle. */
456typedef enum pf_execution_contract_e {
457 PF_EXECUTION_CONTRACT_LEGACY = 1, /**< The retired legacy execution path. */
458 PF_EXECUTION_CONTRACT_NATIVE_MARKET_V1 = 2 /**< The native market kernel; every
459 * handle this runtime creates. */
461
462/** Native execution contract query: a #pf_execution_contract_t, or -1 for a
463 * NULL handle. Absence of this symbol on a known ABI4/stream-v1 library
464 * means legacy. */
466
467/** The bits of #pf_native_run_spec_v1::optional_mask: each names the one
468 * optional field it makes meaningful. A bit outside this set is refused. */
469typedef enum pf_native_spec_optional_e {
470 PF_NATIVE_SPEC_OPTIONAL_QUANTITY_GRID = 1u << 0, /**< `quantity_grid`. */
471 PF_NATIVE_SPEC_OPTIONAL_MAX_ABS_UNITS = 1u << 1, /**< `max_abs_units`. */
472 PF_NATIVE_SPEC_OPTIONAL_INITIAL_MARGIN_FRACTION = 1u << 2, /**< `initial_margin_fraction`. */
473 PF_NATIVE_SPEC_OPTIONAL_MAX_OPEN_LOTS = 1u << 3 /**< `max_open_lots`. */
475
476/** Versioned native run specification. All string pointers are non-null.
477 * session may be empty (all-day literal). session_key, timeframes, timezone
478 * and tickerid are nonempty. `optional_mask` carries
479 * #pf_native_spec_optional_t bits.
480 *
481 * Its three enum-valued words are named in <pineforge/native_c_api.h>:
482 * `fee_kind` is a #pf_native_fee_kind_t, `close_execution` a
483 * #pf_native_close_execution_t and `allowed_open_directions` a
484 * #pf_native_open_directions_t (zero-filled, that word admits no opening at
485 * all). #strategy_configure_native_v1 hands the whole value to the kernel,
486 * which answers an invalid spec with -1 and a Failed native handle
487 * (PF_NATIVE_FAILURE_INVALID_SPECIFICATION). A Ready handle still fails by
488 * the kernel's Contract rule. A Running handle is refused at the C boundary
489 * without changing its native state, including from a callback. A Completed
490 * handle or one Failed by a cooperative abort may be configured for another
491 * run outside callbacks; the kernel judges reuse constraints (an aborted
492 * handle is reused with the same session key and a higher run number), and a
493 * handle whose configure call failed remains Failed. A Completed
494 * host's `on_hash_extension` callback cannot reconfigure it during a read-only hash query.
495 * This call has no typed out-parameters. The
496 * extended path validates before configuring: #strategy_configure_native_ext_v1
497 * keeps the handle Unconfigured on a validation refusal, and
498 * #strategy_configure_native_ext_result_v1 additionally writes the exact
499 * #pf_native_spec_error_t / #pf_native_spec_field_t pair. */
512
513/** Apply a native v1 specification. Returns 0 on success, -1 on failure.
514 * Legacy handles refuse without native state mutation. */
516
517/** Immutable timestamped account-currency FX curve for a native run.
518 * The two arrays have exactly @c n elements and are copied by the runtime. */
519typedef struct pf_native_fx_curve_v1 {
520 uint32_t struct_size;
521 uint32_t n;
522 const int64_t* effective_from_ms;
523 const double* account_per_quote;
525
526/** Why a native FX curve was refused — the C twin of
527 * `pineforge::NativeFxCurveError`. The typed additive route writes this word
528 * beside the offending array index. */
538
539/** Stage an immutable account-currency FX curve on a Ready native handle.
540 *
541 * Timestamps must be strictly increasing and rates finite and positive.
542 * Pass @c n == 0 to clear the staged curve; the scalar @c account_fx remains
543 * the pre-first fallback. Curves are applied when the native run begins.
544 * Legacy handles and non-Ready native handles refuse without mutation.
545 *
546 * @return 0 only when staging is applied; -1 for invalid input, an invalid
547 * handle, a non-native or non-host native handle, or a non-Ready host. The
548 * typed additive #strategy_configure_native_fx_curve_ext_v1 writes the
549 * #pf_native_fx_curve_error_t and offending index when the kernel judged the
550 * curve. */
552 pf_strategy_t s, const pf_native_fx_curve_v1* curve);
553
554/** Typed additive spelling of #strategy_configure_native_fx_curve_v1. On a
555 * kernel validation refusal, writes the #pf_native_fx_curve_error_t and the
556 * offending element index (length mismatch uses index 0). C-layer argument,
557 * handle and layout refusals leave the out-parameters untouched. */
559 pf_strategy_t s, const pf_native_fx_curve_v1* curve,
560 pf_native_fx_curve_error_t* error, uint64_t* index);
561
562/* ───────────────────────────────────────────────────────────────────
563 * STRATEGY .SO EXPORTS — implemented per compiled strategy
564 * ───────────────────────────────────────────────────────────────────
565 *
566 * Each .so emitted by the codegen exports the following symbols. The
567 * runtime library itself does NOT define them — they are per-strategy
568 * implementations generated by the transpiler.
569 *
570 * Note on naming: these are the legacy unprefixed names retained for
571 * backward compatibility with the existing harness. Future major
572 * versions may introduce `pf_`-prefixed equivalents and deprecate the
573 * unprefixed forms.
574 */
575#ifndef PINEFORGE_NO_STRATEGY_DECLS
576
577/** Allocate a new strategy instance.
578 *
579 * @param params_json Currently ignored; pass `NULL`.
580 * @return Strategy handle, or `NULL` on allocation failure.
581 *
582 * Caller owns the returned handle and must release it via #strategy_free. */
583PF_API pf_strategy_t strategy_create(const char* params_json);
584
585/** Release a strategy handle previously returned by #strategy_create.
586 *
587 * Safe to call with `NULL`. Invalidates any `pf_report_t::trace_names`
588 * pointers obtained from this strategy. */
590
591/** Run a backtest with auto-detected timeframe and no bar magnifier.
592 *
593 * @param s Strategy handle from #strategy_create.
594 * @param bars Non-NULL pointer to OHLCV bars (length @p n).
595 * @param n Bar count (>= 0).
596 * @param out Non-NULL output report. Fields are populated with heap
597 * allocations the caller must release via #report_free. */
599 pf_bar_t* bars,
600 int n,
601 pf_report_t* out);
602
603/** Run a backtest with explicit timeframe and magnifier configuration.
604 *
605 * @param s Strategy handle.
606 * @param bars Bar feed.
607 * @param n Bar count.
608 * @param input_tf Input timeframe ("1", "5", "15", "60", "1D", ...).
609 * Empty string → auto-detect from bar timestamps.
610 * @param script_tf Script timeframe. Empty string → defaults to @p input_tf.
611 * @param bar_magnifier Boolean (0 / non-zero) — enable bar magnifier.
612 * @param magnifier_samples Sub-bar samples per parent bar (typical: 4).
613 * @param magnifier_dist Sampling distribution (see #pf_magnifier_distribution_t).
614 * @param out Output report. Free with #report_free. */
616 pf_bar_t* bars,
617 int n,
618 const char* input_tf,
619 const char* script_tf,
620 int bar_magnifier,
621 int magnifier_samples,
622 pf_magnifier_distribution_t magnifier_dist,
623 pf_report_t* out);
624
625/** Free heap arrays attached to a filled report.
626 *
627 * Idempotent. Safe to call with `NULL` or an already-freed report.
628 * The `pf_report_t` struct itself is caller-owned. */
630
631/** @} */ /* end of pf_lifecycle */
632
633/** @defgroup pf_config Per-strategy configuration
634 * @brief Override @c input.*() values, `strategy(...)` params, and runtime knobs.
635 * @{
636 */
637
638/** Override a Pine @c input.*() value before the next run.
639 *
640 * @param s Strategy handle.
641 * @param key The input's title (or fallback identifier).
642 * @param value Serialized value — numbers as decimal strings,
643 * booleans as `"true"` / `"false"`.
644 *
645 * Calls after #run_backtest are accepted but only take effect on
646 * subsequent runs. */
648 const char* key,
649 const char* value);
650
651/** Override a `strategy(...)` declaration parameter.
652 *
653 * Recognised @p key values: `initial_capital`, `commission_value`,
654 * `default_qty_value`, `pyramiding`, `slippage`,
655 * `process_orders_on_close`, `close_entries_rule`, `default_qty_type`,
656 * `commission_type`. */
658 const char* key,
659 const char* value);
660
661/** Toggle volume-weighted bar-magnifier sampling.
662 *
663 * Has no effect unless the bar magnifier is enabled in
664 * #run_backtest_full. */
666 int on);
667
668/** Report the script's `strategy(..., use_bar_magnifier = true)` declaration.
669 *
670 * Exported only by a strategy whose declaration is that literal; every
671 * other `.so` lacks the symbol. TradingView backtests such a script on its
672 * bar magnifier, so a host runs it on a finer input with the magnifier on
673 * (#run_backtest_full).
674 *
675 * @return 1. */
677
678#endif /* PINEFORGE_NO_STRATEGY_DECLS */
679
680/* ───────────────────────────────────────────────────────────────────
681 * RUNTIME LIBRARY EXPORTS — implemented in libpineforge
682 * ─────────────────────────────────────────────────────────────────── */
683/** Toggle per-bar trace recording. Default off (zero-cost when off).
684 *
685 * Enables capture for `// @pf-trace name=expr` pragmas already compiled
686 * into the strategy `.so`. Trace records appear in pf_report_t::trace. */
688
689/** Set the earliest Unix-ms timestamp at which strategy order commands
690 * may fire.
691 *
692 * Earlier bars still execute user code and warm TA/series state, but
693 * `strategy.entry/order/exit/close` commands are ignored. */
695
696/** @} */ /* end of pf_config */
697
698/** @addtogroup pf_lifecycle
699 * @{
700 */
701
702/** Read process-wide scratch-arena diagnostics, outside canonical report bytes.
703 * Writes the peak bytes of simultaneously claimed chunks since attachment
704 * and the exact supplied backing size (including any unallocatable tail).
705 * Returns 0; with no arena attached both values are 0. Returns -1 with both
706 * outputs unchanged if either pointer is NULL, the pointers alias, or the
707 * snapshot cannot be read. Never throws or changes a strategy's last error.
708 * Query after report finalization and before arena detach. The counters
709 * belong to the attachment, not a strategy or repeat; detach resets them.
710 * No pf_report_t, sink receipt, trailer or broker-state hash layout changes. */
711PF_API int pf_report_scratch_arena_stats(uint64_t* high_water_bytes,
712 uint64_t* arena_bytes);
713
714/** Read the handle's admitted-run command-cache and trade-ledger diagnostics.
715 * resident_bytes is the current sum of live command-cache control/slabs and
716 * the ledger's fixed workspace, allocated directory and decoded-entry pages.
717 * Each allocation owner contributes once; borrowed shared-cache storage does
718 * not add another charge. Arena chunks and backing-file extents are separate.
719 * allocation_bypass_count counts optional command-slab and ledger-directory/
720 * page allocation failures, once per sticky bypass, including released owners.
721 * Both outputs are 0 before the first admitted run; admission resets them.
722 * Returns 0, or -1 with both outputs unchanged for a NULL handle/output or
723 * aliased outputs. Never allocates, throws or changes last_error.
724 * Query on the invoking thread after report finalization and before freeing
725 * the strategy. No existing report, receipt or diagnostics layout changes. */
726PF_API int pf_report_cache_stats(pf_strategy_t s, uint64_t* resident_bytes,
727 uint64_t* allocation_bypass_count);
728
729/** Admit a jailed handle only with an attached scratch arena.
730 * Attach the inherited environment arena, if configured (fixed fd 5:
731 * PF_SCRATCH_ARENA_FD=5, positive PF_SCRATCH_ARENA_BYTES);
732 * validate its backing and mapping configuration before execution.
733 * On Linux, require a sealed memfd whose seal mask contains
734 * F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL. F_SEAL_EXEC and other non-write
735 * seals may be added (e.g. MFD_NOEXEC_SEAL). F_SEAL_WRITE or
736 * F_SEAL_FUTURE_WRITE is refused at attachment with
737 * report_sink_failed/arena_backing. These requirements hold regardless of
738 * PF_SCRATCH_ARENA_JAILED; off Linux, admit any attached arena.
739 * Returns 0 accepted; -1 for a NULL handle or an unavailable/invalid arena.
740 * Arena failures set the handle's typed report_sink_failed/arena_backing
741 * failure. Never creates a backing file or falls back to TMPDIR.
742 * Existing unjailed callers do not need to call this opt-in admission.
743 * The first successful attach sets SIGXFSZ to SIG_IGN process-wide; the prior disposition is not restored.
744 * Same fd/size reattaches are no-ops. Hosts relying on their SIGXFSZ handler
745 * or default termination must account for this before attaching an arena. */
747
749/** Read bounded sink provenance without reading the sink or replaying rows.
750 * Include report_sink.h for the descriptor; initialize struct_size and
751 * version=1. Returns 0 filled; -1 with *out unchanged for a bad handle,
752 * size, version or NULL output. Completion follows the sink trailer.
753 * Queries never execute callbacks, advance a cursor or change last_error. */
756
757/** Install the invocation's cooperative SIGTERM handler. The handler only
758 * stores a volatile sig_atomic_t flag; batch runs observe it at completed
759 * input-bar boundaries, independently of #strategy_request_abort.
760 * Opt-in, process-wide, for one serialized child invocation. Call on the
761 * invoking thread with SIGTERM blocked, then unblock it; pending SIGTERM is
762 * preserved. Returns 0, or -1 on signal-setup failure. Repeated installation
763 * preserves an already requested stop. This opt-in installs only the abort
764 * handler for SIGTERM; it does not change any ABI layout. */
766
767/** Read the invocation stop flag: 0 or 1. No allocation or state mutation. */
769
770/** Restore the disposition saved by #pf_run_abort_install_sigterm_v1.
771 * Call with SIGTERM blocked, after native cleanup and failure publication.
772 * Returns 0 (also when uninstalled), or -1 on signal-setup failure. */
774
775/** Return the physical entry incarnation for one closed-trade row.
776 *
777 * Partial-close/FIFO fragments emitted from the same physical entry share
778 * this value. Distinct broker entry objects receive distinct monotonically
779 * increasing values even when Pine reuses the same user-visible entry ID.
780 * The value is scoped to one strategy run and is intended as report
781 * provenance, not as a stable cross-run identifier.
782 *
783 * @param s Strategy handle whose most recent run filled a report.
784 * @param trade_index Zero-based row index into that report's `trades`
785 * array — the script's closed trades followed by the
786 * range-end rows (`open_at_end`, ABI v3), which carry
787 * the incarnation of the lot they mark like any other
788 * close.
789 * @return Non-zero physical-entry identity, or 0 for an invalid index or a
790 * legacy/synthetic trade without request record provenance. */
792 pf_strategy_t s, int trade_index);
793
794/** @} */ /* end of pf_lifecycle */
795
796/** @defgroup pf_streaming Historical to realtime streaming
797 * @brief Warm on confirmed OHLCV and continue the same strategy instance on
798 * normalized ordered trades from any data source.
799 * @{
800 */
801
802/** Warm a strategy with confirmed OHLCV, then switch the same instance to a
803 * realtime trade stream without resetting position, equity, pending orders,
804 * Pine variables, TA state, request.security state, or timeframe aggregation.
805 *
806 * The warmup must contain at least one complete fixed-duration input bar.
807 * Normalized ticks start at or after the next input bar's open. This
808 * lifecycle uses close-only strategy calculation (the Pine strategy default)
809 * while resting broker orders are evaluated on every normalized trade.
810 *
811 * A generated Pine strategy using this entry point rejects calc_on_order_fills,
812 * historical probe/tail overrides, timestamped FX and auxiliary/native
813 * security feeds. A C native host can also use this entry point and accepts
814 * the native FX curve, auxiliary feeds and calculation trigger where its
815 * run specification permits them.
816 * @return 0 on success, -1 on failure. Inspect #strategy_get_last_error. */
818 const pf_bar_t* warmup_bars,
819 int n_warmup,
820 const char* input_tf,
821 const char* script_tf);
822
823/** Native live extension version (1). Additive to ABI v4; callers must probe
824 * this symbol before using the confirmed-bar/action API in older modules. */
826
827/** One physical emulator lot action. is_long describes the position side:
828 * buy = is_entry == is_long. A reversal is exits followed by an entry.
829 * price/time are emulator reference values, not external broker fills.
830 * Strings are borrowed until the next stream mutation or queue clear. */
831typedef struct pf_stream_order_action {
832 uint64_t sequence;
834 int32_t bar_index;
835 int32_t is_entry;
836 int32_t is_long;
837 double quantity;
838 double price;
839 const char* order_id;
840 const char* comment;
843
844/** Consume one confirmed input-timeframe bar. Its script-timeframe aggregate
845 * uses the existing batch OHLC fill kernel when complete. Close-only strategy
846 * calculation; no bar magnifier or synthetic trade ticks. The first tick/bar
847 * locks the feed mode. Off-grid/out-of-order data, invalid OHLCV and missing
848 * in-session bars fail. Calendar-closed intervals may be skipped. No padding.
849 * Returns 0/-1. On ANY input-processing failure discard/recover the instance:
850 * the operation is not transactional and can have advanced native state. */
852
853/** Queued physical fills since the last clear, in execution order. Historical
854 * warmup and report-only range-end rows never enqueue. Sequence starts at 1
855 * after warmup and is not reset by clear. Returns -1 on a null handle. */
857/** Copy one action; return 0 on success, -1 on invalid handle/index/output. */
860/** Clear observed events after the caller durably journals them. */
862/** Versioned deterministic fingerprint of observable broker/stream state.
863 * Excludes the consumable queue and arbitrary private strategy members.
864 * This is a replay check, not a complete state snapshot or cryptographic hash.
865 * Fresh replay must use deterministic strategy code, the same pinned engine
866 * build and configuration. The fingerprint's recipe belongs to the script
867 * ABI epoch: from 1.0.0 it is fixed for every 1.x engine
868 * (engine_script_run_v19), and a new recipe is a new epoch; a value from a
869 * build before 1.0.0 is not comparable. Returns 0 for NULL.
870 *
871 * A refused read also returns 0, so a 0 alone does not tell it from an
872 * answer: a handle that carries selected-window provenance refuses this
873 * query once its run has sealed, and #strategy_state_query_status_v1 names
874 * that refusal. The recorded broker-state rows
875 * (#strategy_set_broker_state_hash_recording) are not values of this token:
876 * it has no recorded counterpart, so a refused read leaves no witness. */
878
879/** Push one normalized realtime trade. Returns 0 on success, -1 on failure. */
881 const pf_trade_tick_t* tick);
882
883/** Push an ordered batch of realtime trades. Semantically identical to
884 * repeated #strategy_stream_push_tick calls, with lower FFI overhead. */
886 const pf_trade_tick_t* ticks,
887 int n);
888
889/** Advance the stream clock and close every input bar whose end is <= the
890 * supplied time. Quiet in-session intervals become zero-volume carry-forward
891 * bars; intervals outside the configured syminfo session are skipped. */
893
894/** End a realtime stream. When @p finalize_partial_input_bar is non-zero, the
895 * currently forming input bar is dispatched before ending; normally callers
896 * should first advance to a confirmed boundary and pass zero here. */
897PF_API int strategy_stream_end(pf_strategy_t s, int finalize_partial_input_bar);
898
899/** Snapshot the cumulative warmup + realtime report. The embedded arrays are
900 * caller-owned after return and must be released with #report_free, or with
901 * #strategy_native_report_free_v1 by a host that links the kernel archive,
902 * which has no #report_free. */
904
905/** @} */ /* end of pf_streaming */
906
907/** @defgroup pf_live Live-runtime surface (ABI v4)
908 * Default-off flags and read-only accessors used by pineforge-live. None of
909 * them changes a historical run unless enabled. @{ */
910
911#define PINEFORGE_HAS_SCRATCH_TABLES_V1 1
912
913#define PINEFORGE_HAS_CACHE_POLICY_V1 1
914
915/** Append-only cache-kind vocabulary. Zero is reserved and is rejected as
916 * an unknown kind. Values are contiguous, starting at one; new kinds take
917 * the next unused value and existing values are never renumbered.
918 * A kind whose cache a later release deletes remains accepted as a
919 * documented retired no-op, never an unknown-kind error. */
920typedef uint32_t pf_cache_kind_t;
921#define PF_CACHE_KIND_LEDGER_CACHE 1u
922#define PF_CACHE_KIND_TRADE_PAGES 2u
923#define PF_CACHE_KIND_DEPENDENCY_PAGES 3u
924#define PF_CACHE_KIND_EXIT_PAGES 4u
925#define PF_CACHE_KIND_BOOK_PAGES 5u
926#define PF_CACHE_KIND_RETAINED_SEQUENCE_PAGES 6u
927#define PF_CACHE_KIND_COHORT_ID_PAGES 7u
928#define PF_CACHE_KIND_EXIT_RANK_PAGES 8u
929#define PF_CACHE_KIND_ERASED_MINIMA_PAGES 9u
930#define PF_CACHE_KIND_CONSUMED_CYCLE_PAGES 10u
931#define PF_CACHE_KIND_POSITION_TAIL_PAGES 11u
932#define PF_CACHE_KIND_SUCCESSOR_ROOT_PAGES 12u
933#define PF_CACHE_KIND_RECEIPT_TAIL_PAGES 13u
934#define PF_CACHE_KIND_CHAIN_SEGMENT_PAGES 14u
935#define PF_CACHE_KIND_EQUITY_CURVE_PAGES 15u
936#define PF_CACHE_KIND_TA_HISTORY_PAGES 16u
937#define PF_CACHE_KIND_FEED_CALENDAR_PAGES 17u
938#define PF_CACHE_KIND_RECORDED_SERIES_PAGES 18u
939#define PF_CACHE_KIND_PROJECTION_EXIT_PAGES 19u
940
941/** One optional resident cache size. Ordinary C alignment applies: kind is
942 * uint32_t and capacity_bytes is uint64_t, with no packed wire layout.
943 * Every capacity is in bytes. For PF_CACHE_KIND_LEDGER_CACHE, the engine
944 * selects the largest row count R with
945 * R * TradeLedger::kLedgerEntryBytes + ceil(R / 256) * sizeof(void*) <= capacity_bytes
946 * (312 bytes per decoded entry and 8 bytes per page pointer on LP64).
947 * The default is 20,449,280 bytes for 65,536 rows on LP64.
948 * Zero selects the existing zero-row
949 * bypass minimum. A nonzero capacity converting to fewer than one row fails
950 * with setting_rejected, input="cache_policy", reason="value_below_minimum";
951 * above the existing 1,048,576-row ceiling it fails with the same input and
952 * reason="value_above_maximum". No clamping or sub-row bypass occurs.
953 * The ledger keeps private pages; its allowance includes their page directory.
954 * Allocated pointers and pages are reported once in cache resident bytes.
955 * Optional directory/page allocation failure permanently bypasses further
956 * cache allocation for that ledger, preserving backing reads and writes.
957 * Other kinds include optional line metadata in their byte allowances and
958 * bypass when below one line. Reads still use backing at zero capacity.
959 * reserved must be zero; a nonzero word is a typed setting_rejected error. */
965
966/** Versioned resident cache policy. Set struct_size = sizeof(pf_cache_policy_v1)
967 * and version = 1; both reserved words and every entry's reserved word must
968 * be zero. Nonzero reserved words fail with setting_rejected,
969 * input="cache_policy", reason="unparseable_value".
970 * The caller owns this descriptor and its entries; the setter copies their
971 * values before returning and retains neither pointer.
972 * entry_count is a uint64_t count; entries may be NULL only when it is zero.
973 * Each successful replacement starts from internal defaults, then applies
974 * the entries. Omitted kinds use defaults; duplicate and unknown kinds fail.
975 * No entry limits logical records, retained history or backing extent. */
983
984/** Copy cache settings before run admission; NULL restores internal defaults.
985 * A run freezes its settings before its first store begin/append. Calls in
986 * begin callbacks, during execution or during an active stream fail without
987 * changing either configuration or the admitted run. An idle call between
988 * runs affects the next run, including stream warmup, and never resizes the
989 * preceding run's retained stores or borrowed views.
990 * Returns 0 on success and -1 on invalid descriptor, nonzero reserved word,
991 * duplicate/unknown kind, ledger capacity outside its setting range,
992 * unrepresentable size or busy/frozen handle. A valid handle records the
993 * typed setting_rejected configuration error through
994 * #strategy_get_last_error_code and #strategy_get_last_error_args.
995 * Optional cache allocation failure bypasses caching; it is not admission
996 * failure. This policy does not change scratch-table maxima or arena shares. */
998 const pf_cache_policy_v1* policy);
999
1000/** Storage policy for the nine historical scratch-table regions. */
1005
1006/** Version 1 scratch-table admission maxima, in keys/rows and key bytes.
1007 * Set struct_size = sizeof(pf_scratch_tables_v1), version = 1, reserved = 0.
1008 * K_q/B_q are reserved zero; q's two region descriptors remain empty.
1009 * Each other K/B pair includes all distinct ever-stored keys, including erased
1010 * keys. U/B_U cover all cohort IDs, V/B_V all exit-rank IDs (V <= 27), and R
1011 * all rooted successors, including internally generated replacements.
1012 * Counts and UTF-8/binary key-byte totals are admitted maxima supplied by
1013 * the host, not estimates inferred from the number of bars.
1014 * Bounded mode reserves one fixed slab before run preparation; exceeding
1015 * the manifest fails the run with scratch_capacity_exceeded. The layout
1016 * and every offset must fit uint64_t, size_t and off_t. Allocation failure
1017 * remains out_of_memory. This policy is independent of the report sink.
1018 * In bounded mode, all eleven maxima zero select the P0 policy at each
1019 * begin: finite batches use that run's input-bar count, and streams without
1020 * a declared lifetime use the fixed stream tier (warmup is not lifetime).
1021 * Any nonzero maximum makes the entire manifest explicit, zeros included.
1022 * The default is an admission policy, not a measured workload bound. */
1023typedef struct pf_scratch_tables_v1 {
1024 uint32_t struct_size;
1025 uint32_t version;
1026 uint32_t backend;
1027 uint32_t reserved;
1028 uint64_t K_q;
1029 uint64_t B_q;
1030 uint64_t K_e;
1031 uint64_t B_e;
1032 uint64_t K_c;
1033 uint64_t B_c;
1034 uint64_t U;
1035 uint64_t B_U;
1036 uint64_t V;
1037 uint64_t B_V;
1038 uint64_t R;
1040
1041/** Copy scratch-table configuration while idle; NULL restores disk defaults.
1042 * Never retains a pointer into the caller's descriptor. Returns 0 on
1043 * success, -1 for a null handle or invalid size/version/backend/reserved/V,
1044 * -2 during a stream, and -3 during a batch run (including begin callbacks).
1045 * A refusal preserves the installed configuration and active run ownership.
1046 * Finite streams must supply maxima for their complete admitted lifetime.
1047 * The embedding host sets RLIMIT_FSIZE and owns disk-scratch SIGXFSZ policy.
1048 * This setter does not attach an arena; see #strategy_require_report_scratch_arena_v1 for the SIGXFSZ side effect of arena attachment. */
1050 const pf_scratch_tables_v1* config);
1051
1052/** Request cooperative abort of the run in progress (see c_abi.cpp). */
1054/** 0 = completed, 1 = NOT_COMPLETED (aborted), -1 = @p s is NULL. */
1056/** Live-runtime tail semantics (spec §3.1): the LAST bar of the array fed to
1057 * every subsequent run() is a still-forming bar, not the chart's rightmost
1058 * historical bar. This is persistent configuration, not a one-shot flag --
1059 * it stays in effect until a caller passes @p on == 0, so a handle reused
1060 * for a later plain historical replay must be explicitly turned back off.
1061 * Effects when @p on is non-zero:
1062 * 1. `barstate.islast` is false for that bar.
1063 * 2. `session.islastbar` is computed from the bucket calendar (no next
1064 * bar to peek at, so it evaluates whether the next bucket -- this
1065 * bar's timestamp plus one script-TF step -- falls out of session).
1066 * 3. `bar_index` stays put; `last_bar_index` is frozen at the horizon
1067 * bar (`horizon_bars - 1`), when @p horizon_bars > 0. `last_bar_time`
1068 * is exact when the horizon bar is in the script-bar array, one
1069 * script-TF step per missing bar past the array's last bar
1070 * otherwise; under aggregation (input_tf < script_tf) it is
1071 * extrapolated from the first bar instead, and the aggregation-path
1072 * caveat below applies.
1073 * 4. The range-end synthetic close row/trade is skipped (no
1074 * `open_at_end` row); the final equity point keeps `open_profit`.
1075 * 5. Interior bars (every bar before the last) are unaffected, except
1076 * effect 2 under aggregation (below).
1077 * Scope: the Pine source host's scheduler applies these where it publishes
1078 * a script bar to the generated script. Effects 1, 3 and 4 hold on every
1079 * path. Effect 2 is its session-predicate step, which runs for every script
1080 * bar of a run whose timeframe is known -- a timeframe-aware overload, or
1081 * `run(bars, n)` with at least two bars, whose timeframe it detects from
1082 * them; with fewer, `session.ismarket`/`session.islastbar` keep their
1083 * reset-state `false` whatever this flag says. The bucket-calendar
1084 * lookahead stands in for the next bar wherever the scheduler holds none:
1085 * with input_tf == script_tf that is the array's last bar alone, but under
1086 * aggregation (input_tf < script_tf) the scheduler holds no next script
1087 * bar at all, so the lookahead decides `session.islastbar` on every bar of
1088 * the run. Callers must feed an input_tf == script_tf array, matching
1089 * #strategy_set_probe_suppress_tail_logic's scope note.
1090 * `strategy_stream_begin` refuses a handle with this flag set.
1091 * Default off (@p on == 0): every historical run stays byte-identical to
1092 * before this flag existed. The mode belongs to the Pine source host; on
1093 * a host that models no still-forming tail bar (a bare kernel host) the
1094 * call is accepted and inert, as it always was there. */
1095PF_API void strategy_set_realtime_tail(pf_strategy_t s, int on, int horizon_bars);
1096/** Live probe tail suppression (spec §3.2): the LAST script bar of the array
1097 * fed to every subsequent run() is matched but not calculated. The kernel
1098 * matches that bar like any other, so resting stop/limit orders settle
1099 * against it; the Pine source host's scheduler then publishes it through
1100 * its suppressed-tail step instead of the generated script -- source
1101 * history and `bar_index` advance, the fills already matched on the bar
1102 * are settled, the report point is marked, `on_bar` is not invoked and no
1103 * range-end row is written. The host's own bar-close policy is not the
1104 * script and still runs on that bar -- its risk-state update and its
1105 * TradingView margin-call checkpoint -- so a margin call due against the
1106 * forming bar is booked against it.
1107 * Apart from what that policy books, the run's last-bar fills are exactly
1108 * the settled book's fills against the forming bar, and the post-run
1109 * pending-order book is the book in force during that bar. This is
1110 * persistent configuration, like #strategy_set_realtime_tail, and
1111 * independent of it -- do not assume the two flags are coupled; set each
1112 * explicitly.
1113 * Scope (ABI v4 / live v1): honoured on every batch path -- the
1114 * single-timeframe and the timeframe-aware overloads, with or without the
1115 * bar magnifier. Under aggregation (input_tf < script_tf) the suppressed
1116 * bar is the run's last COMPLETED script bar, and a trailing partial
1117 * bucket is never calculated at all. Under `calc_on_order_fills` only the
1118 * bar's close calculation is suppressed: a fill on the forming bar still
1119 * triggers the fill recalculation, which runs the script on it.
1120 * `calc_on_order_fills` and the bar magnifier stay gated in live v1 and a
1121 * probe must not enable them; callers must feed an input_tf == script_tf
1122 * array.
1123 * `strategy_stream_begin` refuses a handle with this flag set: clear it
1124 * (on = 0) first.
1125 * Default off (@p on == 0): every historical run stays byte-identical to
1126 * before this flag existed. Like #strategy_set_realtime_tail the mode
1127 * belongs to the Pine source host; on a host that models no still-forming
1128 * tail bar the call is accepted and inert, as it always was there. */
1130/** Force this run's intrabar path order (ABI v4 live-runtime surface): the
1131 * leg order every modeled OHLC path the engine walks uses -- stop/limit
1132 * fill priority, exit trail walking, dual-entry-stop arbitration,
1133 * bar-magnifier sub-bar sampling, and the excursion columns a source host
1134 * reports on a closed row. Values:
1135 * - `0` AUTO (default): the unchanged TV-emulator rule -- the leg nearer
1136 * `open` (by `|high-open|` vs `|open-low|`) goes first.
1137 * - `1` HIGH_FIRST: force `O -> H -> L -> C` regardless of the bar's own
1138 * shape.
1139 * - `2` LOW_FIRST: force `O -> L -> H -> C` regardless of the bar's own
1140 * shape.
1141 * The values are #pf_native_path_order_t's (<pineforge/native_c_api.h>):
1142 * #PF_NATIVE_PATH_ORDER_AUTO, #PF_NATIVE_PATH_ORDER_HIGH_FIRST and
1143 * #PF_NATIVE_PATH_ORDER_LOW_FIRST. Any other @p mode is clamped to AUTO:
1144 * this setter answers nothing, so it cannot refuse one.
1145 * A live probe runs the SAME forming bar under BOTH forced orders and emits
1146 * only the fills that agree between the two -- a fill that depends on which
1147 * leg TradingView's own (unobservable, still-forming) bar will resolve to
1148 * is path-dependent and must be suppressed rather than guessed.
1149 * This is persistent configuration, like #strategy_set_realtime_tail -- it
1150 * stays in effect until a caller passes @p mode == 0, so a handle reused
1151 * for a later plain historical replay must be explicitly set back to AUTO.
1152 * Scope: the mode is carried by the RUN, in `NativeRunSpec::path_order`. A
1153 * native-bound source host projects this setting into that field at begin
1154 * -- at run() and at #strategy_stream_begin alike -- and a bare native host
1155 * declares the field itself; every modeled path the native driver walks
1156 * then resolves its leg order from that field. So the setting takes effect
1157 * at the next begin rather than mid-run, and a stream is NOT exempt: a
1158 * confirmed bar pushed with #strategy_stream_push_bar is sealed on the same
1159 * forced waypoint sequence a run() walks (measured on a 99/101 bracket and
1160 * an AUTO-low-first touch bar: both routes exit at 99.00 under AUTO and at
1161 * 101.00 under HIGH_FIRST). What a forced order cannot reach is an OBSERVED
1162 * TICK (#strategy_stream_push_tick): one price is not a modeled path and
1163 * has no legs to order, so the same tape fills identically under all three
1164 * modes; the forming bar those ticks build is ordered only when it seals,
1165 * on the waypoint sequence above.
1166 * Default AUTO (@p mode == 0): every historical run stays byte-identical to
1167 * before this flag existed. */
1169/** The dual-entry-stop arbitration decided on the LAST bar the most recent
1170 * run() dispatched: a flat position resting exactly one long stop-only
1171 * ENTRY and one short stop-only ENTRY, both touched on that bar.
1172 * Values mirror
1173 * `internal::DualEntryStopPathWinner`'s enumerator order:
1174 * - `0` None -- no such pair was arbitrated on that bar (not flat, no
1175 * matching pair, or neither/only one side touched).
1176 * - `1` LongFirst -- the long stop's first-touch position on the intrabar
1177 * path came first (or the two tied, which the engine always resolves
1178 * in the long leg's favour).
1179 * - `2` ShortFirst -- the short stop's first-touch position came first.
1180 * - `-1` -- @p s is NULL.
1181 * This is a per-BAR snapshot, not a live read of the engine's per-pass
1182 * working state: it is written once, at the arbitration itself, and then
1183 * holds for the rest of that bar even though the working state goes back
1184 * to None the moment the winning side fills (position no longer flat) or
1185 * its stop-entry admission is declined -- neither of which undoes the fact
1186 * that TradingView's broker emulator arbitrated a real pair that bar. A
1187 * caller therefore gets the right answer whether it reads this after a
1188 * `strategy_set_probe_suppress_tail_logic` forming-bar probe (a single
1189 * native request-matching pass) or after an ordinary
1190 * `process_orders_on_close` run with no tail suppression (two passes, the
1191 * winner already filled by the second).
1192 * A live probe reads this after a forming-bar run to see which side the
1193 * engine's own broker-emulator tie-break picked, without having to re-run
1194 * and infer it from which of the two possible fills came back.
1195 * Only the standard (non-`calc_on_order_fills`) dispatch path updates this
1196 * value; it is a silent no-op under the COOF scheduler, mirroring
1197 * #strategy_set_probe_suppress_tail_logic's dispatch-path-scope caveat. */
1199/** Toggle per-report-point broker-state hash recording (spec §3.4, ABI v4).
1200 *
1201 * When @p on is non-zero, every subsequent run() appends
1202 * #strategy_broker_state_hash's value to pf_report_t::broker_state_hash
1203 * after each report point (KernelRecorded for bare hosts); completed runs
1204 * match pf_report_t::script_bars_processed, failed ones may not. Cleared (recorded array
1205 * emptied, not the flag itself) at the start of every run(); the flag is
1206 * persistent configuration, like #strategy_set_realtime_tail, and stays
1207 * set until a caller passes @p on == 0.
1208 * Also covers #strategy_stream_begin's warmup run() and every script bar
1209 * dispatched afterward by the realtime tick stream, so
1210 * #strategy_stream_fill_report's cumulative report satisfies the same
1211 * len == script_bars_processed invariant. Set this BEFORE
1212 * #strategy_stream_begin to also record the warmup leg -- reset_run_state()
1213 * (which stream_begin's internal run() invokes) empties the recorded
1214 * array, not the flag, but a flag flipped on only after stream_begin
1215 * returns misses the warmup bars already dispatched.
1216 * Default off (@p on == 0): pf_report_t::broker_state_hash is NULL /
1217 * 0-length and every historical run stays byte-identical to before this
1218 * flag existed.
1219 *
1220 * What a row is, and what it is for. A row is the run's CONTINUATION
1221 * IDENTITY at that bar, not its trade outcome: it folds the broker state and,
1222 * ahead of it, the state a resume would continue from -- the driving mode
1223 * (batch run, stream warmup, stream realtime) included, on purpose. So the
1224 * array is a replay check WITHIN one driving mode and deliberately not across
1225 * modes: two runs driven the same way over the same bars record the same
1226 * rows, and a run driven the same way that ended at bar k recorded, as its
1227 * last row, the row the longer run recorded after bar k; but a run(), a
1228 * #strategy_stream_begin with one warmup bar and one with every bar as warmup
1229 * record DIFFERENT rows from index 0 over the same bars booking the same
1230 * trades. Only the len == script_bars_processed identity above holds across
1231 * drivings. What pins that a stream books what a batch books is the outcome
1232 * itself -- the trades, the position and the equity -- and not this array. */
1234/** Return the broker-state hash of the FINAL state after the most recent
1235 * run() (see #strategy_set_broker_state_hash_recording's doc and
1236 * pf_report_t::broker_state_hash for the per-bar recording; this accessor
1237 * works whether or not recording was enabled). Compare runs of one
1238 * configuration: the recipe belongs to the script ABI epoch, fixed from 1.0.0
1239 * for every 1.x engine (engine_script_run_v19), so two values differ only
1240 * when the states they fold differ. This is not a serialized checkpoint.
1241 * Returns 0 when @p s is NULL.
1242 *
1243 * A refused read also returns 0, so a 0 alone does not tell it from an
1244 * answer: once a handle that carries selected-window provenance has sealed
1245 * its run, this query is refused and #strategy_state_query_status_v1 names
1246 * it (#PF_STATE_QUERY_SELECTED_WINDOW_AFTER_SEAL_V1). A caller that needs
1247 * that run's terminal witness enables
1248 * #strategy_set_broker_state_hash_recording BEFORE the run and reads the last
1249 * pf_report_t::broker_state_hash row; no row means no witness. */
1251
1252/** What #strategy_state_query_status_v1 answers: whether the two state queries,
1253 * #strategy_broker_state_hash and #strategy_stream_state_hash, may answer a
1254 * handle now, and whether the report-only terminal-quote writers may still
1255 * store. A fixed-width word rather than an enum, so its width does not
1256 * depend on the consumer's compiler flags. */
1258#define PF_STATE_QUERY_ALLOWED_V1 0 /**< Both queries may answer, and the quote writers store. */
1259#define PF_STATE_QUERY_SELECTED_WINDOW_AFTER_SEAL_V1 1 /**< Both are refused, and so are the quote writers: a selected window's run has sealed. */
1260#define PF_STATE_QUERY_INVALID_HANDLE_V1 2 /**< The handle is NULL. */
1261
1262/** The current eligibility of BOTH #strategy_broker_state_hash and
1263 * #strategy_stream_state_hash on @p s, as a #pf_state_query_status_v1_t:
1264 * #PF_STATE_QUERY_ALLOWED_V1 when either may answer,
1265 * #PF_STATE_QUERY_SELECTED_WINDOW_AFTER_SEAL_V1 when the handle carries
1266 * selected-window provenance and its run has sealed (a refused query returns
1267 * 0, which is also a legal hash value: this tells the two apart), and
1268 * #PF_STATE_QUERY_INVALID_HANDLE_V1 for a NULL @p s. A handle answers
1269 * #PF_STATE_QUERY_ALLOWED_V1 unless it was admitted as a selected-window run,
1270 * which only a separately documented export can do. This is the state of the
1271 * handle at the call, not a last-error slot: it allocates nothing, changes
1272 * nothing, runs no host code and never records, replaces or clears
1273 * #strategy_get_last_error or its code, so a run's first cause stays what the
1274 * run made it. To keep a selected run's terminal witness, enable
1275 * #strategy_set_broker_state_hash_recording BEFORE the run and read the last
1276 * pf_report_t::broker_state_hash row. A broker-state row is not a value of
1277 * the #strategy_stream_state_hash token, and no row means no witness.
1278 *
1279 * The same status is the observable refusal of the report-only terminal-quote
1280 * writers (docs/report-terminal-quote.md), under the same condition: once a
1281 * selected-window run has sealed (completed, failed or aborted), the Pine
1282 * host stores neither reserved syminfo metadata key,
1283 * report_terminal_quote_time_ms and report_terminal_quote_close, whatever the
1284 * value, nor a clear of the quote. In C++ the host throws
1285 * pineforge::SelectedWindowQueryAfterSeal before any store; the C export
1286 * #strategy_set_syminfo_metadata keeps its void signature and swallows every
1287 * exception, so a caller learns of the refusal from this status:
1288 * #PF_STATE_QUERY_SELECTED_WINDOW_AFTER_SEAL_V1 means a quote-key write stored
1289 * nothing. Like the state queries, the refusal records, replaces and clears no
1290 * #strategy_get_last_error or its code, latches no run failure and leaves the
1291 * captured report as it was. A handle without selected-window provenance, and
1292 * a selected handle before its first admitted begin, accept the writes as
1293 * before. Proof: tests/test_selected_quote_seal.cpp. */
1295
1296/** Number of orders resting in the engine's pending-order book after the
1297 * most recent run() (ABI v4 live-runtime surface, task 7, spec 3.6): the
1298 * book in force for the NEXT bar. 0 when @p s is NULL. Read-only; a
1299 * historical run is byte-identical whether or not a caller reads it. */
1301/** Copy the @p index-th resting order (0-based, the engine's own book
1302 * order -- insertion order; broker fill priority is decided at fill time
1303 * from `created_seq`, not from this index) into @p out as a
1304 * pf_pending_order_v1_t value snapshot. Copies
1305 * min(@p size_in, sizeof(pf_pending_order_v1_t)) bytes: an older reader
1306 * with a smaller struct receives a prefix (struct_version and size first),
1307 * a newer reader with a larger one receives the whole v1 and must not
1308 * read past pf_pending_order_v1_t::size. Strings are NUL-terminated
1309 * char[64] copies with a `_truncated` flag and a `_hash64` (FNV-1a 64 of
1310 * the full string); enums are int32 values; NaN sentinels are copied
1311 * verbatim. Returns 0 on success, -1 -- with nothing written -- when
1312 * @p s or @p out is NULL, @p index is out of range, or @p size_in < 8
1313 * (too small to hold even the `struct_version` + `size` header; every
1314 * larger @p size_in is honoured as a prefix copy). The layout is
1315 * self-described by #strategy_pending_order_layout. */
1316PF_API int strategy_pending_order_get(pf_strategy_t s, int index, void* out, size_t size_in);
1317/** The field table of pf_pending_order_v1_t as THIS runtime compiled it --
1318 * one pf_field_desc_t {name, type, offset, size} per field, in struct
1319 * order, starting with `struct_version` and `size`. Static storage: the
1320 * pointer stays valid for the life of the process and needs no handle.
1321 * @p count (may be NULL) receives the row count. An FFI consumer builds
1322 * its struct from this table rather than from a hand-typed copy, so the
1323 * mirror can grow (append-only) without breaking it. */
1325/** Which sizing rule #strategy_pending_order_fill_qty's quantity came from,
1326 * written to its `partition` out-parameter; each value is documented there.
1327 * The value set is the Pine source host's pending-order sizing, the only
1328 * host that answers a probe. */
1336
1337/** Engine-computed fill quantity of the @p index-th resting order (ABI v4
1338 * live-runtime surface, task 8, spec 3.6): the contracts the entry kernel
1339 * would OPEN if that order filled at @p fill_price, sized by the engine's
1340 * own rules so a live runtime never re-implements them. @p fill_price is
1341 * slipped the way the kernel slips it (`native_matching::apply_slippage`
1342 * then the directional grid snap; an entry with a
1343 * limit leg takes the unslipped limit-or-better route). @p partition
1344 * receives which sizing rule produced the value, a #pf_fill_qty_partition_t:
1345 * - `0` EXPLICIT -- a script-supplied qty: for `strategy.entry` the
1346 * lot-floored contracts (`apply_qty_step`) of a fixed qty, or the
1347 * explicit percent/cash budget sized at the fill for a per-call
1348 * qty_type override; a `strategy.order` explicit qty is dispatched
1349 * verbatim (no lot step).
1350 * - `1` FROZEN_PLACEMENT -- a quantity fixed before the fill, never
1351 * re-derived from the fill price: the default percent_of_equity /
1352 * cash MARKET (or strategy.order) size frozen at the signal close
1353 * (`frozen_default_qty`); a MARKET's frozen broker transaction (a
1354 * finalized flat pair's `paired_flat_market_transaction_qty`; a
1355 * same-bar-market member's `sbmt_tx_qty` from flat or as a kept
1356 * over-cap add); or what one of the two MARKET reversal kernels
1357 * opens -- a same-bar-market member against an opposite live position
1358 * opens the remainder `sbmt_tx_qty - min(sbmt_tx_qty, live qty)`
1359 * (the same-bar market reversal path), and the exact SHORT-seed
1360 * collision's final short re-opens the residual
1361 * `pyramid_entries[0].qty - pyramid_entries[1].qty` after closing both
1362 * lots (the short-seed collision path). Both kernels are
1363 * modelled; each reports `close_only` 1 when it opens nothing.
1364 * - `2` DEFAULT_STOP_PLACEMENT -- the DEFAULT percent_of_equity pure
1365 * STOP entry's placement size, at any percentage
1366 * (`default_stop_placement_qty`, round-7 family K), when the
1367 * placement-size rule says the fill consumes it: created flat, filling
1368 * from flat, positive fill.
1369 * - `3` AT_FILL -- default sizing at the slipped fill.
1370 * @p close_only receives 1 when the kernel's close-only predicate fires
1371 * -- the fill closes against the live opposite position and that
1372 * predicate opens no leg of its own: the order's
1373 * `affordability_close_only` (entry leg declined at placement), the
1374 * priced-entry `prior_cycle_close_only` rule (opposite live position whose
1375 * cycle the order was not placed in -- `created_position_side !=` the
1376 * live side -- and not a `reverses_same_bar_market_from_flat`), the
1377 * same-cycle frozen explicit-FIXED transaction the close consumes exactly,
1378 * a finalized flat MARKET pair against an opposite position, or one of the
1379 * two reversal kernels above opening nothing. Where the order was created
1380 * FLAT the engine's close-only branch can open a remainder: a
1381 * transaction larger than the live position still opens the remainder, so
1382 * a consumer compares @p qty with the live position. A replaced
1383 * default-percent short is dispatched
1384 * with its `frozen_default_qty`:
1385 * @p qty is that transaction, @p close_only 0. The probe answers for the
1386 * order filling against the CURRENT book and position; fills that an
1387 * earlier order in the same pass would make first are not simulated.
1388 * NOT folded into @p qty: the deferred-flip
1389 * carry (`tv_carry_qty`, added on a priced entry from FLAT whose
1390 * placement side is the opposite of the
1391 * requested side) -- read `tv_carry_qty` / `created_position_side` from
1392 * the mirror. Returns 0 on success; 1 -- with @p qty NaN, @p close_only 0,
1393 * @p partition -1 (#PF_FILL_QTY_PARTITION_EXIT) -- when the order is an EXIT (its fill quantity is
1394 * decided against the live position at the fill, not by a partition); -1
1395 * with nothing written when @p s is NULL, @p index is out of range, or any
1396 * out-pointer is NULL. Read-only: no historical run changes because a
1397 * caller probed it. */
1398PF_API int strategy_pending_order_fill_qty(pf_strategy_t s, int index, double fill_price,
1399 double* qty, int* close_only, int* partition);
1400/** 1 when the @p index-th resting order's entry-relative offsets
1401 * (`profit_ticks` / `loss_ticks` / `trail_points`) resolve now (ABI v4,
1402 * task 8): entries, plain orders and exits with an empty `from_entry`
1403 * always; an exit bound to a `from_entry` only once that id has filled in
1404 * the CURRENT position cycle -- the same condition the level-resolution
1405 * and eligibility passes use.
1406 * 0 otherwise; -1 when @p s is NULL or
1407 * @p index is out of range. */
1409/** The price levels the @p index-th resting order would fire at, as the
1410 * engine's fill path resolves them (ABI v4, task 8). A leg the order
1411 * carries as a price (`stop_price`, `limit_price`, `trail_price`) is
1412 * reported verbatim -- it is already on the price grid. A leg carried as
1413 * a tick offset is resolved against the live position's average entry
1414 * price only when #strategy_pending_order_level_resolved is 1 AND a
1415 * position is live, with the POSITION side's sign exactly as the fill
1416 * path: `limit = entry + dir * profit_ticks * mintick`, `stop = entry -
1417 * dir * loss_ticks * mintick` (dir = +1 long, -1 short; both
1418 * `level_on_price_grid`), and `trail_activation = entry +/- ceil(
1419 * trail_points - 8e-3 * mintick) * mintick` snapped to the tick grid
1420 * (`trail_points` wins over `trail_price` when both are set, as in
1421 * the trail activation rule). NaN for a leg that is unset or not yet
1422 * resolvable. Returns 0; -1 with nothing written when @p s is NULL,
1423 * @p index is out of range, or any out-pointer is NULL. */
1425 double* limit, double* trail_activation);
1426/** The trail extreme the exit trail legs ride (`trail_best_price_`: the
1427 * running high of a long / low of a short since the position filled,
1428 * bar extremes folded in as the fill path folds them). NaN when @p s is
1429 * NULL and NaN until a position has filled. */
1431/** The live position's volume-weighted average entry price
1432 * (`position_entry_price_`). NaN when @p s is NULL and NaN when the
1433 * position is flat (the engine keeps 0 there; a live reader must not
1434 * mistake it for a price). */
1436/** The live position's cycle id (`position_cycle_seq_`): 0 when flat, a
1437 * fresh nonzero id per open or reversal, unchanged across same-direction
1438 * adds -- the id `created_position_cycle_seq` on a mirrored order refers
1439 * to. -1 when @p s is NULL. */
1441/** Task 9: closed-trade id / exit-comment string accessors, indexing the
1442 * same REPORT row space as #strategy_closed_trade_entry_incarnation
1443 * (`trades_` then the range-end rows, `open_at_end`). The returned pointer
1444 * is valid until the next run() (or stream call) on this handle, like
1445 * #strategy_get_last_error. NULL on NULL @p s, out-of-range @p trade_index
1446 * or policy-erased rows; retained sealed rows are readable. This legacy API
1447 * materializes rows until the next run; full-history reads use growing RAM. */
1449/** See #strategy_closed_trade_entry_id for the row-space/lifetime/NULL
1450 * contract. The returned string is the engine's own INTERNAL exit id, not
1451 * always the script's `strategy.exit`/`strategy.close` id verbatim:
1452 * - a real `strategy.exit` bracket leg -- the user's own id, unchanged.
1453 * - a `strategy.close(id, ...)` close -- `"__close__" + id`.
1454 * - `strategy.close_all()` / a bare-id `strategy.close()` -- the literal
1455 * `"__close__"` (empty target id appended).
1456 * - a margin-call forced liquidation -- the sentinel `"__margin_call__"`.
1457 * - an intraday-cap close (`risk.max_intraday_loss`, or the
1458 * max-filled-orders cap) -- empty (`""`).
1459 * A caller matching exit ids back to its own `strategy.close` calls should
1460 * strip the `"__close__"` prefix rather than compare verbatim. */
1462/** See #strategy_closed_trade_entry_id. */
1464/** Why a closed report row exited — `execution::CloseCause`, the value
1465 * #strategy_closed_trade_close_cause answers for a valid row. The names are
1466 * the kernel's; the descriptive labels in that function's documentation are
1467 * the same numbers. */
1468typedef enum pf_close_cause_e {
1469 PF_CLOSE_CAUSE_UNSPECIFIED = 0, /**< No cause recorded (UNKNOWN). */
1470 PF_CLOSE_CAUSE_SCRIPT = 1, /**< A market close or a reversal. */
1471 PF_CLOSE_CAUSE_BRACKET = 2, /**< A bracket leg. */
1472 PF_CLOSE_CAUSE_LIQUIDATION = 3, /**< A margin-call liquidation slice (MARGIN_CALL). */
1473 PF_CLOSE_CAUSE_RISK_LIMIT = 4, /**< A risk-limit flatten (INTRADAY_LOSS_CAP). */
1474 PF_CLOSE_CAUSE_FILL_CAP = 5, /**< A filled-order cap (INTRADAY_FILL_CAP). */
1475 PF_CLOSE_CAUSE_RANGE_END = 6 /**< The range-end close of a position still open. */
1477
1478/** Task 9: why the @p trade_index-th REPORT-row closed trade exited, a
1479 * #pf_close_cause_t.
1480 * Values:
1481 * - `0` UNKNOWN -- reserved for the documented "no cause" value on a
1482 * VALID trade. Every in-range row currently falls through the
1483 * derivation below to at worst `1` SCRIPT, so no live derivation
1484 * today actually returns `0`; it is not used for a bad index (see
1485 * `-1` below, final review F7).
1486 * - `1` SCRIPT -- a `strategy.close` / `strategy.close_all` market close,
1487 * or a reversal-driven close.
1488 * - `2` BRACKET -- a `strategy.exit` stop/limit/trail/profit/loss leg,
1489 * or a native host's close its owner's fill armed.
1490 * - `3` MARGIN_CALL -- a forced liquidation slice.
1491 * - `4` INTRADAY_LOSS_CAP -- `risk.max_intraday_loss`.
1492 * - `5` INTRADAY_FILL_CAP -- the max-filled-orders intraday cap.
1493 * - `6` RANGE_END -- the still-open position closed at the end of a
1494 * flag-off run (`open_at_end`, ABI v3) -- this always wins over every
1495 * other cause below it.
1496 * Derivation order (see `BacktestEngine::closed_trade_close_cause`,
1497 * engine_trade_accessors.cpp): `open_at_end` -> 6; then the cause the
1498 * CLOSER recorded on the row (`execution::CloseCause`, whose numbers are
1499 * exactly these values) -- a kernel-originated liquidation or risk flatten
1500 * states it through the settling fill, and so does a host request its
1501 * owner's fill armed (a `WaitForApplied` owner relation, as
1502 * `native_toolkit::submit_bracket` builds, with an intent that only closes:
1503 * `Reduce`, `Flatten` or a host-sized close), which records `2` BRACKET; a
1504 * host running its own forced-close policy states it on the row it
1505 * produced, which is where the Pine adapter's margin-call (3),
1506 * max-intraday-loss (4) and filled-order-cap (5) rows get their value (the
1507 * adapter classifies its own bracket rows by order family, so it clears the
1508 * kernel's `2` on them first); then the row's `exit_from_bracket` flag -- true
1509 * only for a REAL `strategy.exit` leg, either an exit-order fill
1510 * whose id does NOT carry the internal `"__close__"` prefix that a deferred
1511 * `strategy.close`/`close_all` order is also given (that path reuses the
1512 * same exit fill machinery), or a whole-position bracket
1513 * revived and fired at the margin-call event price
1514 * (through the margin-call bracket revival) -- -> 2;
1515 * otherwise 1.
1516 * A Pine/source run's values are unchanged. A bare native host that declares
1517 * a kernel margin model now reads `3` for its own liquidation rows (and `4`
1518 * for a kernel risk flatten) where the retired string derivation, which only
1519 * recognised the adapter's sentinels, answered `1`, and it reads `2` for a
1520 * bracket leg its owner armed, where it read `1` before R5 lane F3.
1521 * `-1` when @p s is NULL, or when @p trade_index is out of range (final
1522 * review F7: matches every sibling indexed live accessor's -1-on-bad-index
1523 * convention -- #strategy_pending_order_fill_qty,
1524 * #strategy_pending_order_level_resolved,
1525 * #strategy_pending_order_effective_levels). `0` therefore never
1526 * ambiguously means "bad index"; a caller can tell "row exists but reads
1527 * UNKNOWN" apart from "bad index" without a separate
1528 * #strategy_closed_trade_entry_incarnation `report_trade_count` bounds
1529 * check first, though doing that check is still good practice. */
1531/** Task 9: the script-facing signed position size (`strategy.position_size`;
1532 * Freeze-aware -- while a same-bar `process_orders_on_close` close is
1533 * frozen for the current bar, this reads the PRE-close position, matching
1534 * what the script itself observes). NaN when @p s is NULL. */
1536/** Task 9: initial capital plus realized net profit
1537 * (`strategy.initial_capital + strategy.netprofit`). NOT Pine's
1538 * `strategy.equity`, which adds open profit on top of this (unlike the
1539 * last point of `pf_report_t::equity_curve`, which does). NaN when @p s is
1540 * NULL. */
1542/** ABI v4 live-runtime surface (1.8.0): the configured run's initial capital,
1543 * before realized or open profit. Includes staged native specs before begin,
1544 * and generated defaults or overrides once the source host configures the run.
1545 * NaN when @p s is NULL or the read raises, matching
1546 * #strategy_current_equity's invalid-handle sentinel so a failed query is
1547 * not mistaken for a capital value. */
1549/** Raw whole-lot realized P&L, excluding initial capital and report-only
1550 * terminal rows. Reads the engine accumulator directly, including before
1551 * report retrieval; independent of report sorting and global-exit recuts.
1552 * Nonvirtual, additive accessor; ABI version unchanged. NaN when @p s is NULL. */
1554/** Effective symbol pointvalue used by the engine report. NaN on NULL. */
1556/** Opt in to the separate pf-report-replay/v1 companion before a run.
1557 * Copies @p path and @p run_id (at most 64 KiB each; NULL run ID means empty).
1558 * NULL path disables.
1559 * Does not modify primary sink descriptors or existing C layouts.
1560 * Returns 0 on success, -1 for invalid arguments, -2 during streaming,
1561 * -3 during an active call or after ledger retirement. Check the result. */
1562PF_API int strategy_set_report_replay_v1(pf_strategy_t s, const char* path, const char* run_id);
1563/** Task 9: total SCRIPT bars dispatched by the most recent run() (mirrors
1564 * `pf_report_t::script_bars_processed`, engine_report.cpp) -- includes a
1565 * stream's warmup leg and every realtime tick-driven bar dispatched
1566 * afterward by #strategy_stream_push_tick / #strategy_stream_push_ticks.
1567 * `-1` when @p s is NULL. */
1569/** @} */
1570
1571/** @addtogroup pf_config
1572 * @{
1573 */
1574
1575/** Set the strategy's chart timezone (IANA / POSIX TZ string).
1576 *
1577 * Pine builtins ``hour``, ``minute``, ``second``, ``dayofmonth``,
1578 * ``dayofweek``, ``month``, ``year`` and ``weekofyear`` return the
1579 * wall-clock for the chart's timezone — TV exports trade rows in chart
1580 * TZ too. Engine bars are stored as Unix-ms (UTC), so without this
1581 * override these builtins return UTC and silently diverge from TV by N
1582 * hours when the chart is on a non-UTC zone (Asia/Taipei = UTC+8 is the
1583 * validator default).
1584 *
1585 * Pass `NULL`, `""`, `"UTC"` or `"Etc/UTC"` for the legacy UTC
1586 * behaviour (cheap, mutex-free). Any other value names a TZ resolved by
1587 * the system tzdata; the per-bar decomposition then runs under a
1588 * process-global mutex so multi-threaded harnesses don't corrupt each
1589 * other's wall time.
1590 *
1591 * Should be called before #run_backtest / #run_backtest_full. Persists
1592 * across runs on the same strategy handle until overridden. */
1594
1595/** Plumb the symbol's exchange timezone (IANA string) into syminfo. Feeds
1596 * ``session.ismarket`` / ``time(session)`` predicates. Defaults to "UTC"
1597 * (crypto). Distinct from #strategy_set_chart_timezone — the chart TZ
1598 * drives wall-clock builtins and intraday-cap day rollover; this drives
1599 * session membership. `NULL` is ignored. Call before #run_backtest / #run_backtest_full. */
1601
1602/** Set the symbol's session string (e.g. "0930-1600:23456", default
1603 * "24x7"). Feeds ``session.ismarket`` / ``time(session)``. `NULL`
1604 * ignored. Call before #run_backtest / #run_backtest_full. */
1606
1607/** Set the instrument class (``syminfo.type``: "forex", "stock", "crypto",
1608 * "futures", "index", "fund", "cfd", ...; default "crypto"). Scripts branch
1609 * on it for instrument conventions (e.g. the forex pip size). `NULL` /
1610 * empty ignored. Call before #run_backtest / #run_backtest_full. */
1612
1613/** Set one of the remaining string members of ``syminfo`` by Pine member
1614 * name: "ticker", "tickerid", "currency", "basecurrency", "description",
1615 * "volumetype" (and "type"). Returns 0 when set, -1 for an unknown key,
1616 * empty value or NULL. Call before #run_backtest / #run_backtest_full. */
1618 const char* value);
1619
1620/** Set the instrument tick size (``syminfo.mintick``, default 0.01). Drives the
1621 * directional stop-entry snap and ``slippage = N*mintick`` economics. Set
1622 * per-instrument (e.g. 0.25 for ES, 0.00001 for FX). Non-positive ignored.
1623 * Call before #run_backtest / #run_backtest_full. */
1625
1626/** Set the instrument point value (``syminfo.pointvalue``, default 1.0) — the
1627 * $-per-point-per-contract multiplier applied to every money path: realized
1628 * PnL and MFE/MAE, open profit / mark-to-market equity (and the drawdown /
1629 * runup extremes), percent-of-equity and cash position sizing, percent
1630 * commission notionals, and the margin admission check. Set per-instrument
1631 * (e.g. 50 for ES). Non-positive ignored. Call before #run_backtest / #run_backtest_full. */
1633
1634/** Inject a fundamental/exchange metadata value by Pine member name
1635 * (e.g. "shares_outstanding_total", "target_price_average"). These have
1636 * no OHLCV source; reads of un-injected members return na. Call before
1637 * #run_backtest / #run_backtest_full. */
1639 double value);
1640
1641/** Install a timestamped quote-to-account currency conversion curve.
1642 *
1643 * Each value is account-currency units per one unit of the symbol's quote
1644 * currency and becomes active, inclusively, at the corresponding Unix-ms
1645 * timestamp. The latest active value carries forward; broker events before
1646 * the first point use the scalar `account_currency_fx` metadata fallback.
1647 * Installing a curve also selects the converted account-currency broker
1648 * ledger, including during that pre-first fallback interval; it is not
1649 * equivalent to a same-currency run merely because a rate happens to be 1.
1650 * Arrays are copied. Timestamps must be strictly increasing and rates
1651 * positive and finite. Pass `n == 0` to clear the curve and restore scalar
1652 * behavior. Timestamped curves currently support ordinary historical runs.
1653 * Broker-open rate changes: carried 1× longs and 1× shorts are supported;
1654 * leveraged positions fail closed at the crossing. Streaming,
1655 * calc-on-order-fills, and bar-magnifier runs
1656 * also fail closed.
1657 *
1658 * @return 0 on success, -1 for a null strategy or invalid arrays. */
1660 pf_strategy_t s, const int64_t* effective_from_ms,
1661 const double* account_per_quote, int n);
1662
1663#ifdef PINEFORGE_HAS_AUX_SECURITY_FEED_V1
1664/** Copy a finer feed used exclusively by same-symbol request.security calls.
1665 *
1666 * The next ordinary #run_backtest_full call must receive native chart bars
1667 * with @c input_tf equal to @c script_tf and bar magnifier disabled. Chart
1668 * OHLCV, broker fills, and @c bar_index continue to advance only from that
1669 * native chart feed; @p bars advance only request.security evaluators.
1670 * Every auxiliary bar must map to exactly one native chart bar and every
1671 * native chart bar must have at least one auxiliary bar, otherwise the run
1672 * fails closed via #strategy_get_last_error. Arrays are copied. Pass
1673 * @p n == 0 to clear the auxiliary feed.
1674 *
1675 * @return 0 on success, -1 for a null strategy or invalid input. */
1677 const pf_bar_t* bars,
1678 int n,
1679 const char* input_tf);
1680#endif
1681
1682#ifdef PINEFORGE_HAS_NATIVE_SECURITY_FEED_V1
1683/** Copy the exchange's OWN bars of one higher timeframe for same-symbol
1684 * request.security calls that request exactly that timeframe.
1685 *
1686 * On intraday charts of CME futures and US/Indian equities TradingView's
1687 * request.security(syminfo.tickerid, "D", close) returns the exchange's
1688 * daily bar -- the 15:00 CT settlement on ES1!/NQ1!, the official closing
1689 * print on NASDAQ/NYSE/NSE -- which no aggregation of the intraday feed can
1690 * reproduce. With a native feed installed for @p timeframe ("D", "1D", "W",
1691 * ...), every completed request.security bucket of that timeframe carries
1692 * the native bar's OHLCV (matched by the bucket's own label: the session-day
1693 * stamp for D/W/M, the grid open for intraday) instead of the aggregate;
1694 * bucket TIMING -- when the bar completes relative to the chart -- and every
1695 * other timeframe's request are unchanged, and so are chart OHLCV, broker
1696 * fills and bar_index. A "W" / "M" request with no feed of its own reads
1697 * the daily feed's bars aggregated per week / month (TradingView builds its
1698 * W/M bars from the native daily bars, never from intraday prints), keyed by
1699 * the period's first session-day. A completed bucket with no native bar
1700 * keeps its aggregate (counted, not fatal). Bars must be strictly
1701 * increasing; arrays are copied; pass @p n == 0 to clear the feed for
1702 * @p timeframe. Historical runs only: stream_begin() fails closed while a
1703 * native feed is installed.
1704 *
1705 * @return 0 on success, -1 for a null strategy or invalid input. */
1707 const char* timeframe,
1708 const pf_bar_t* bars,
1709 int n);
1710#endif
1711
1712/** Feature probe for #strategy_set_symbol_feed: the first of four opt-in
1713 * setters of another symbol's data for request.security (lane XSYM-D),
1714 * each behind its own feature probe. They are the Pine source host's
1715 * doors: a strategy that registers a request site for another symbol reads
1716 * its bars, its facts and its recorded request values from what these
1717 * installed before the run; during a run each answers -1, as a host with
1718 * no source layer does. Every array is copied; a later call replaces what
1719 * an earlier one installed under the same key. Historical runs only:
1720 * stream_begin() fails closed while a symbol feed or a recorded series is
1721 * installed. */
1722#define PINEFORGE_HAS_SYMBOL_FEED_V1 1
1723/** Feature probe for #strategy_set_symbol_feed_column. */
1724#define PINEFORGE_HAS_SYMBOL_FEED_COLUMN_V1 1
1725/** Feature probe for #strategy_set_symbol_facts. */
1726#define PINEFORGE_HAS_SYMBOL_FACTS_V1 1
1727/** Feature probe for #strategy_set_recorded_series. */
1728#define PINEFORGE_HAS_RECORDED_SERIES_V1 1
1729
1730#ifdef PINEFORGE_HAS_SYMBOL_FEED_V1
1731/** Copy another symbol's own bars of one timeframe, keyed by the exact
1732 * symbol string a request site passes at run time (@p key) and by
1733 * @p timeframe ("15", "60", "1D", ...; "D" and "1D" are one key).
1734 *
1735 * Each bar carries its own close, @p close_ms[i]: after its open
1736 * (@c bars[i].timestamp) and at or before the next bar's open. No calendar
1737 * is guessed for the symbol. Bars must be strictly increasing with finite
1738 * prices; a volume the symbol does not publish is NaN. The feed reaches
1739 * the kernel as a NativeRunSpec instrument feed: on each chart bar, a site
1740 * with lookahead off reads the last bar that has closed by the chart bar's
1741 * close, one with lookahead on the last bar that opened by its open, and the
1742 * site's expression runs over every bar of the feed, in order. Installing a
1743 * feed drops the columns of the feed it replaces; @p n == 0 installs a feed
1744 * without bars, whose sites read na. Historical runs only: stream_begin()
1745 * fails closed while a symbol feed is installed.
1746 *
1747 * @return 0 on success, -1 for a null strategy, invalid input (the reason in
1748 * #strategy_get_last_error) or a host with no source layer. */
1749PF_API int strategy_set_symbol_feed(pf_strategy_t s, const char* key, const char* timeframe,
1750 const pf_bar_t* bars, const int64_t* close_ms, int n);
1751#endif
1752
1753#ifdef PINEFORGE_HAS_SYMBOL_FEED_COLUMN_V1
1754/** Copy one named column of an installed symbol feed: @p n values, one per
1755 * bar of the feed of (@p key, @p timeframe), in bar order (a footprint
1756 * delta, @c "fp_delta_100_70"). NaN is an absent value. A column of the same
1757 * name is replaced. Historical runs only: stream_begin() fails closed while
1758 * a symbol feed is installed.
1759 *
1760 * @return 0 on success, -1 for a null strategy, a feed that is not
1761 * installed, a length that is not the feed's, a name the kernel
1762 * refuses, or a host with no source layer. */
1764 const char* timeframe, const char* name,
1765 const double* values, int n);
1766#endif
1767
1768#ifdef PINEFORGE_HAS_SYMBOL_FACTS_V1
1769/** Set one fact of the symbol a request site names by @p key: @p field is
1770 * one of @c "canonical", @c "valid" (@c "true" / @c "false"), @c "type",
1771 * @c "timezone", @c "session", @c "currency" or @c "mintick" (a positive
1772 * decimal). Inside that site's expression @c syminfo.* reads these facts
1773 * (@c syminfo.tickerid reads @p key itself). A symbol whose @c valid is
1774 * @c "false" makes a site with @c ignore_invalid_symbol read na and fails
1775 * any other site's run closed. Facts only describe an installed feed, so
1776 * historical runs only: stream_begin() fails closed while a symbol feed is
1777 * installed.
1778 *
1779 * @return 0 on success, -1 for a null strategy, an unknown field, a value
1780 * the field refuses, or a host with no source layer. */
1781PF_API int strategy_set_symbol_facts(pf_strategy_t s, const char* key, const char* field,
1782 const char* value);
1783#endif
1784
1785#ifdef PINEFORGE_HAS_RECORDED_SERIES_V1
1786/** Copy a recorded request series: the values a request returned on the
1787 * chart bars that opened at @p chart_open_ms (strictly increasing), keyed
1788 * by the request key a generated strategy reads it under. On a chart bar
1789 * with no row the series reads na, and on every bar when @p n == 0.
1790 * Historical runs only: stream_begin() fails closed while a recorded series
1791 * is installed.
1792 *
1793 * @return 0 on success, -1 for a null strategy, invalid input or a host
1794 * with no source layer. */
1796 const int64_t* chart_open_ms, const double* values,
1797 int n);
1798#endif
1799
1800/** Returns the error message captured by the most recent #run_backtest /
1801 * #run_backtest_full call on this strategy.
1802 *
1803 * Returns an empty string when the run completed normally, or `NULL`
1804 * only when `s` itself is `NULL`. The pointer is owned by the engine
1805 * and remains valid until the next #run_backtest / #run_backtest_full call (which clears
1806 * the captured error before it begins).
1807 *
1808 * The runtime catches every `std::exception` derivative inside the
1809 * engine's run loop so the C ABI never unwinds a C++ exception across
1810 * the `extern "C"` boundary. Consumers must check this after every
1811 * run to surface engine-rejected configurations such as a script
1812 * timeframe finer than the input timeframe, a `request.security`
1813 * timeframe below the chart timeframe without a supported lower-TF
1814 * emulation, or a missing input timeframe when securities are
1815 * registered. */
1817
1818/** Compile-time probe for the two run-failure code getters below (and the
1819 * generated-code helpers of `<pineforge/run_failure.hpp>`). A runtime library
1820 * without them lacks the symbols: look them up with `dlsym`. */
1821#define PINEFORGE_HAS_RUN_FAILURE_CODES_V1 1
1822
1823/** Stable code of the most recent failure on @p s: lower snake_case ASCII
1824 * matching `^[a-z][a-z0-9_]{2,47}$`, from the closed vocabulary published as
1825 * `run_failure_codes.json` (schema `pineforge-run-failure-catalog/v1`); a
1826 * code is never reused for another meaning.
1827 *
1828 * Returns `""` exactly when the most recent run (or setter) on @p s recorded
1829 * no failure, and `NULL` only when @p s is `NULL`. It is not keyed on the
1830 * text: a script that stops with an empty `runtime.error()` message fails
1831 * with #strategy_get_last_error `""` and the code `strategy_runtime_error`,
1832 * and a non-empty error that no coded site wrote reads
1833 * `engine_unclassified_error`, never `""`. A text a script writes never
1834 * selects a code. The pointer has the lifetime of #strategy_get_last_error's
1835 * (until the next run or setter on @p s). */
1837
1838/** The typed arguments of the failure #strategy_get_last_error_code names, as
1839 * one canonical JSON object: UTF-8, keys sorted, no whitespace; an integer
1840 * is a JSON integer, a number the shortest round-trip double, every other
1841 * kind a JSON string. `"{}"` when the code has no arguments, `""` when there
1842 * is no failure, `NULL` only when @p s is `NULL`. An argument is never a
1843 * value the script computed while it ran. Same lifetime as
1844 * #strategy_get_last_error_code. */
1846
1847/** @} */ /* end of pf_config */
1848
1849/** @defgroup pf_version Version query
1850 * @brief Runtime version metadata.
1851 * @{
1852 */
1853
1854/** Runtime version descriptor returned by #pf_version_get. */
1855typedef struct pf_version_s {
1856 int major; /**< Major version. */
1857 int minor; /**< Minor version. */
1858 int patch; /**< Patch version. */
1859 const char* commit_sha; /**< Short git commit SHA, or `""` if unknown. */
1860} pf_version_t;
1861
1862/** @return Linked runtime version. */
1864
1865/** @return Monotonic ABI version (see #PF_ABI_VERSION). */
1867
1868/** Full version descriptor, the `PINEFORGE_VERSION_FULL` of the generated
1869 * `pineforge/version.h`.
1870 *
1871 * Returns `"MAJOR.MINOR.PATCH[-rc.N][-N-gSHA[-dirty]]"` for a tagged git
1872 * checkout, or exactly the `VERSION` file (`"MAJOR.MINOR.PATCH"`, or
1873 * `"MAJOR.MINOR.PATCH-rc.N"` for a release candidate) for a tarball build
1874 * or one configured with `PINEFORGE_VERSION_SOURCE=FILE`. #pf_version_get
1875 * carries only the numeric MAJOR.MINOR.PATCH. The pointer is to a static
1876 * string with program lifetime; do not free. */
1877PF_API const char* pf_version_string(void);
1878
1879/** @} */ /* end of pf_version */
1880
1881/** @defgroup pf_settings Generated Strategy Settings
1882 * Additive, opt-in validation and effective-settings receipts.
1883 * @{ */
1884
1885/** Current generated-strategy settings extension version. */
1886#define PF_SETTINGS_API_VERSION 1u
1887
1888/** Current compiled-strategy execution-capabilities extension version. */
1889#define PF_CAPABILITIES_API_VERSION 1u
1890
1891/** Status codes for the opt-in generated-strategy settings extension. */
1900
1901/** @} */ /* end of pf_settings */
1902
1903#ifndef PINEFORGE_NO_STRATEGY_DECLS
1904
1905/** Generated-strategy settings extension version (currently 1).
1906 * Older compiled strategies lack this symbol. This is independent of
1907 * #pf_abi_version and does not change the legacy entry points. */
1909
1910/** Compiled execution-capabilities extension version (currently 1).
1911 * Older strategies lack this symbol. Independent of #PF_ABI_VERSION and
1912 * #PF_SETTINGS_API_VERSION; implemented by the generated strategy, not runtime. */
1914
1915/** Read immutable canonical JSON describing compiled execution declarations
1916 * and analyzed request/data requirements, never inferred from runtime state.
1917 * Uses #pf_settings_status_t and the buffer/error protocol of
1918 * #strategy_get_effective_settings: @p required includes the NUL, NULL/0
1919 * queries return #PF_SETTINGS_BUFFER_TOO_SMALL, and no partial JSON is returned.
1920 * @p s and @p required must be non-NULL. Receipt bytes remain identical across
1921 * settings changes, batch runs, stream runs and fresh/reused handles.
1922 * A consumer must separately validate effective settings and staged run data. */
1924 size_t capacity, size_t* required,
1925 char* error, size_t error_capacity);
1926
1927/** Allocate a strategy without allowing any exception across the C boundary.
1928 * @p params_json is reserved: pass NULL or an empty string. Nonempty values
1929 * are refused, unlike the legacy factory which ignores them.
1930 * @p out is required and is set to NULL before allocation.
1931 * All checked calls return #pf_settings_status_t; an optional caller-owned
1932 * error buffer receives a NUL-terminated, possibly truncated message.
1933 * A successful call clears that buffer. No error buffer allocation is needed. */
1934PF_API int strategy_create_checked(const char* params_json, pf_strategy_t* out,
1935 char* error, size_t error_capacity);
1936
1937/** Validate and install a declared input before execution.
1938 * Rejects unknown or ambiguous keys, invalid options/booleans, non-finite
1939 * numbers, trailing bytes, range violations and unsupported input forms.
1940 * Invalid values leave the existing setting unchanged. Pine's numeric step
1941 * is UI metadata, not a restriction on programmatically supplied values.
1942 * Calls after execution starts are refused; use the legacy setter for its
1943 * historical subsequent-run behavior. */
1945 const char* value, char* error,
1946 size_t error_capacity);
1947
1948/** Validate and install a supported strategy override before execution.
1949 * Accepts the legacy documented enum aliases, but never silently defaults
1950 * an unknown key, invalid enum, invalid boolean or invalid numeric value. */
1952 const char* value, char* error,
1953 size_t error_capacity);
1954
1955/** Read a deterministic UTF-8 JSON receipt of effective settings.
1956 * Lists every declared input and supported override, including type,
1957 * options, min/max/step constraints, default and effective value. Values
1958 * use canonical serialized strings. No new execution state is retained.
1959 * @p required is required and receives the byte count INCLUDING the NUL.
1960 * Pass NULL/0 for @p json/capacity to query that count; this returns
1961 * #PF_SETTINGS_BUFFER_TOO_SMALL. No partial JSON is returned.
1962 * The receipt describes source settings, not a native run-spec capability. */
1964 size_t capacity, size_t* required,
1965 char* error, size_t error_capacity);
1966
1967/** Exception-contained counterpart of #run_backtest_full.
1968 * Validates pointers and bar count, then uses the identical batch dispatch.
1969 * Returns #PF_SETTINGS_RUN_FAILED for a runtime-reported failure and
1970 * #PF_SETTINGS_EXCEPTION for an escaping C++ exception. Initialize @p out
1971 * to zero and release any populated arrays with #report_free, including
1972 * after a failed call. */
1974 const char* input_tf, const char* script_tf,
1975 int bar_magnifier, int magnifier_samples,
1976 pf_magnifier_distribution_t magnifier_dist,
1977 pf_report_t* out, char* error,
1978 size_t error_capacity);
1979
1980#endif
1981
1982/** @defgroup pf_outputs Recorded outputs
1983 * What a module records besides trades, read after a run or between the
1984 * inputs of a stream (docs/outputs.md). A module that records declares named
1985 * series slots, outputs and run constants, and per run writes one row per
1986 * bar it publishes: a double per slot (NaN = na), events with an optional
1987 * message, and one value per run constant. Nothing is recorded until
1988 * #strategy_outputs_set_enabled turns recording on, and nothing a run
1989 * computes depends on it: trades, the report, #PF_ABI_VERSION and every
1990 * hash are what they are with recording off. What a slot, an output or a
1991 * constant means is in the manifest the module exports
1992 * (#strategy_outputs_manifest).
1993 *
1994 * Part B, the readers, is implemented by the runtime and present in every
1995 * module built on it; a module that declares nothing answers them as empty.
1996 * Part A is generated per module and present only in one that records:
1997 * detect recording with dlsym("strategy_outputs_api_version").
1998 *
1999 * Threading: every strategy_outputs_* call runs on the thread that drives
2000 * the handle, never during one of its run or stream calls. A NULL handle
2001 * answers -1 (nothing for a void function).
2002 * @{ */
2003
2004/** Present in a pineforge.h that declares the group. */
2005#define PINEFORGE_HAS_OUTPUTS_V1 1
2006/** What #strategy_outputs_api_version answers. */
2007#define PF_OUTPUTS_API_VERSION 1u
2008
2009/** The run phase an event was recorded in. */
2010typedef enum pf_output_phase_e {
2011 PF_OUTPUT_PHASE_BATCH = 0, /**< a batch run */
2012 PF_OUTPUT_PHASE_WARMUP = 1, /**< a stream's warm-up, inside strategy_stream_begin */
2013 PF_OUTPUT_PHASE_REALTIME = 2 /**< a stream after its warm-up */
2016
2017/** One recorded event, copied by #strategy_outputs_event_get. Every 8-byte
2018 * field sits at a multiple of 8, so the 72 bytes before `message` are laid
2019 * out alike on every target. */
2020typedef struct pf_output_event_v1_s {
2021 uint32_t struct_version; /**< 1 */
2022 uint32_t size; /**< sizeof(pf_output_event_v1_t) as the runtime compiled it */
2023 uint64_t sequence; /**< 1-based per run; once cleared, never issued again */
2024 int32_t output_index; /**< the output, as the manifest's `outputs[].index` */
2025 int32_t bar_index; /**< the row, 0-based */
2026 int64_t bar_open_ms; /**< the row's open, Unix ms */
2027 int64_t bar_close_ms; /**< the row's close, Unix ms; INT64_MIN when unknown */
2028 uint32_t ordinal_in_bar; /**< per output, from 0 within the row */
2029 uint32_t phase; /**< a #pf_output_phase_t */
2030 uint32_t confirmed; /**< 0 when recorded on a partially finalized bar */
2031 uint32_t reserved0; /**< 0 */
2032 double value; /**< the event's value; NaN = na */
2033 uint64_t message_hash64; /**< FNV-1a 64 of every byte the module recorded as the
2034 message; 0 without one */
2035 const char* message; /**< NUL-terminated, or NULL without one: a message holding a
2036 NUL byte reads to its first NUL, while message_hash64
2037 covers every recorded byte. Borrowed until the next call
2038 that runs, streams or clears on the handle */
2040
2041/** Turn recording on (@p on nonzero) or off. A change clears the record; a
2042 * call that changes nothing changes nothing, even while a run is in progress.
2043 * Returns 0, or -1 with nothing changed and the refusal recorded as code
2044 * `outputs_rejected`: reason `not_declared`, "outputs: this module declares
2045 * no outputs" (turning on a module that records nothing; turning it off
2046 * answers 0), or reason `run_in_progress`, "outputs: recording cannot
2047 * change during a run" (a change while a batch or a stream, from
2048 * #strategy_stream_begin to #strategy_stream_end, is running). As with
2049 * #strategy_set_trace_enabled, a success leaves #strategy_get_last_error
2050 * and its code as they were; a refusal keeps a failed run's own. */
2052/** Series slots per row; 0 while recording is off. */
2054/** Rows recorded: one per bar the module published this run. A module that
2055 * opens at most one row per calculation, as a generated one does, never
2056 * records more than #strategy_script_bars_processed. */
2058/** Copy the open and close times (Unix ms) of rows from @p from_bar on.
2059 * Requires 0 <= @p from_bar <= rows, @p capacity >= 0 and @p written; either
2060 * array may be NULL. Writes min(capacity, rows - from_bar) items, sets
2061 * *written to that count and returns 0; otherwise -1 and nothing written. */
2063 int64_t* open_ms, int64_t* close_ms,
2064 int64_t capacity, int64_t* written);
2065/** Copy one slot's values (NaN = na) of rows from @p from_bar on, as
2066 * #strategy_outputs_bar_times_copy does; @p out is required when
2067 * @p capacity > 0, and @p series must be below the series count. */
2068PF_API int strategy_outputs_series_copy(pf_strategy_t s, int series, int64_t from_bar,
2069 double* out, int64_t capacity, int64_t* written);
2070/** Events queued since the run began or since the last clear. */
2072/** Copy event @p index into @p out: min(@p size_in, sizeof(pf_output_event_v1_t))
2073 * bytes, so an older or newer caller's struct works. Returns 0, or -1 with
2074 * nothing written for an index out of range, a NULL @p out, or a @p size_in
2075 * below the 8 bytes of struct_version and size. */
2076PF_API int strategy_outputs_event_get(pf_strategy_t s, int index, void* out, size_t size_in);
2077/** Drop the queued events, in every mode; rows and the sequence are kept. A
2078 * streaming caller clears after #strategy_stream_begin and after each input
2079 * it has read; a batch caller need not clear. */
2081/** Copy the run constants (NaN = na, or not written this run) into @p out:
2082 * the first min(@p capacity, n) of them. Returns n, the declared count (0
2083 * while recording is off), or -1 for @p capacity < 0 or a NULL @p out with
2084 * @p capacity > 0. */
2085PF_API int strategy_outputs_constants_copy(pf_strategy_t s, double* out, int capacity);
2086
2087#ifndef PINEFORGE_NO_STRATEGY_DECLS
2088/** Part A: #PF_OUTPUTS_API_VERSION. Present only in a module that records. */
2090/** Part A: the module's outputs manifest, canonical JSON
2091 * (`pineforge-outputs-manifest/v1`): what every output, slot and constant
2092 * means. The buffer and status protocol of #strategy_capabilities_receipt. */
2093PF_API int strategy_outputs_manifest(pf_strategy_t s, char* json, size_t capacity,
2094 size_t* required, char* error,
2095 size_t error_capacity);
2096/** Part A: the module's signal-safety receipt, canonical JSON
2097 * (`pineforge-signal-safety/v1`), per output. The protocol of
2098 * #strategy_capabilities_receipt. */
2099PF_API int strategy_signal_safety_receipt(pf_strategy_t s, char* json, size_t capacity,
2100 size_t* required, char* error,
2101 size_t error_capacity);
2102#endif
2103
2104/** @} */ /* end of pf_outputs */
2105
2106/** Feature probe for the borrowed symbol-feed setter. */
2107#define PINEFORGE_HAS_SYMBOL_FEED_BORROWED_V1 1
2108/** Install another symbol's bars without copying them. The caller keeps both
2109 * arrays alive until the feed is replaced or cleared, or #strategy_free.
2110 * Validation and return values match #strategy_set_symbol_feed. Historical
2111 * runs only: stream_begin() fails closed while a symbol feed is installed. */
2113 pf_strategy_t s, const char* key, const char* timeframe,
2114 const pf_bar_t* bars, const int64_t* close_ms, int n);
2115
2116/** Exact C++ engine layout accepted by the live report accumulator.
2117 * Bump whenever that layout changes, independently of #PF_ABI_VERSION. */
2118#define PF_STREAM_ENGINE_LAYOUT_VERSION 2u
2119
2120/** Optional module declaration that its strategy handles point directly to
2121 * a BacktestEngine with the returned #PF_STREAM_ENGINE_LAYOUT_VERSION.
2122 * The engine runtime exports this with its streaming implementation.
2123 * Modules with opaque or wrapped handles must omit it or return 0.
2124 * Absence or a different version requires using #strategy_stream_fill_report;
2125 * stream ABI and execution-capabilities versions alone do not prove layout.
2126 * This query reads no strategy state and changes no report or hash. */
2128
2129/** Set this module's creation-time recorded-series limit on the calling thread.
2130 * Call before strategy_create/strategy_create_checked (or C++ construction):
2131 * the base engine snapshots it before the derived constructor declares.
2132 * NULL or 0 means unlimited, the default. A non-NULL positive int64 is the
2133 * operator's maximum.
2134 * Returns a pf_settings_status_t; negative values return
2135 * #PF_SETTINGS_INVALID_ARGUMENT without changing the previous setting.
2136 * Errors follow the checked-settings buffer protocol. Success also clears
2137 * the typed declaration refusal read by #pf_output_series_limit_failure.
2138 * The host must restore NULL after its factory call; existing hosts retain
2139 * their snapshot. Different threads have independent defaults and refusals. */
2141 char* error, size_t error_capacity);
2142/** Read an outputs_limit / too_many_series declaration refusal even when a
2143 * factory returned NULL. Returns 1 and writes the positive operator maximum
2144 * from the refused setting (NULL or 0 selects unlimited), 0 when no refusal
2145 * has occurred since the last successful setter (leaving the destination
2146 * unchanged), or -1 for NULL. Query on the creation thread before restoring
2147 * the default. No exception or allocation crosses the ABI. */
2149
2150#ifdef __cplusplus
2151} /* extern "C" */
2152#endif
2153
2154/* The C-level native host API (R5 lane L13). Included last, after the PODs
2155 * it builds on (pf_bar_t, pf_report_t, pf_strategy_t) and outside this
2156 * header's `extern "C"` block, which it opens for itself. Both headers guard
2157 * their own include, so either include order resolves. Its PF_API symbols are
2158 * pinned separately by scripts/check_c_abi_runtime.py, which is why they do
2159 * not move this header's own declaration count. */
2160#include <pineforge/native_c_api.h>
2161
2162/** Feature probe: include <pineforge/report_sink.h> for the additive sink ABI. */
2163#define PINEFORGE_HAS_REPORT_SINK_V1 1
2164
2165#endif /* PINEFORGE_H */
const char * strategy_get_last_error_args(pf_strategy_t s)
The typed arguments of the failure strategy_get_last_error_code names, as one canonical JSON object: ...
void strategy_set_syminfo_timezone(pf_strategy_t s, const char *tz)
Plumb the symbol's exchange timezone (IANA string) into syminfo.
const char * strategy_get_last_error_code(pf_strategy_t s)
Stable code of the most recent failure on s: lower snake_case ASCII matching ^[a-z][a-z0-9_]{2,...
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...
int strategy_set_native_security_feed(pf_strategy_t s, const char *timeframe, const pf_bar_t *bars, int n)
Copy the exchange's OWN bars of one higher timeframe for same-symbol request.security calls that requ...
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).
int strategy_set_symbol_feed_column(pf_strategy_t s, const char *key, const char *timeframe, const char *name, const double *values, int n)
Copy one named column of an installed symbol feed: n values, one per bar of the feed of (key,...
int strategy_set_symbol_facts(pf_strategy_t s, const char *key, const char *field, const char *value)
Set one fact of the symbol a request site names by key: field is one of "canonical",...
int strategy_set_aux_security_feed(pf_strategy_t s, const pf_bar_t *bars, int n, const char *input_tf)
Copy a finer feed used exclusively by same-symbol request.security calls.
void strategy_set_magnifier_volume_weighted(pf_strategy_t s, int on)
Toggle volume-weighted bar-magnifier sampling.
int strategy_set_recorded_series(pf_strategy_t s, const char *key, const int64_t *chart_open_ms, const double *values, int n)
Copy a recorded request series: the values a request returned on the chart bars that opened at chart_...
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.
int strategy_set_symbol_feed(pf_strategy_t s, const char *key, const char *timeframe, const pf_bar_t *bars, const int64_t *close_ms, int n)
Copy another symbol's own bars of one timeframe, keyed by the exact symbol string a request site pass...
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_syminfo_string(pf_strategy_t s, const char *key, const char *value)
Set one of the remaining string members of syminfo by Pine member name: "ticker", "tickerid",...
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.
void strategy_set_syminfo_type(pf_strategy_t s, const char *type)
Set the instrument class (syminfo.type: "forex", "stock", "crypto", "futures", "index",...
int strategy_declares_bar_magnifier(void)
Report the script's strategy(..., use_bar_magnifier = true) declaration.
int pf_run_abort_requested_v1(void)
Read the invocation stop flag: 0 or 1.
int strategy_execution_contract(pf_strategy_t s)
Native execution contract query: a pf_execution_contract_t, or -1 for a NULL handle.
uint64_t strategy_closed_trade_entry_incarnation(pf_strategy_t s, int trade_index)
Return the physical entry incarnation for one closed-trade row.
int strategy_configure_native_fx_curve_v1(pf_strategy_t s, const pf_native_fx_curve_v1 *curve)
Stage an immutable account-currency FX curve on a Ready native handle.
pf_execution_contract_t
What strategy_execution_contract answers for a valid handle.
Definition pineforge.h:456
pf_strategy_t strategy_create(const char *params_json)
Allocate a new strategy instance.
int strategy_require_report_scratch_arena_v1(pf_strategy_t s)
Admit a jailed handle only with an attached scratch arena.
pf_native_fx_curve_error_t
Why a native FX curve was refused — the C twin of pineforge::NativeFxCurveError.
Definition pineforge.h:529
int strategy_configure_native_fx_curve_ext_v1(pf_strategy_t s, const pf_native_fx_curve_v1 *curve, pf_native_fx_curve_error_t *error, uint64_t *index)
Typed additive spelling of strategy_configure_native_fx_curve_v1.
int pf_run_abort_install_sigterm_v1(void)
Install the invocation's cooperative SIGTERM handler.
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.
int strategy_report_sink_diagnostics_v1(pf_strategy_t s, struct pf_report_sink_diagnostics_v1 *out)
Read bounded sink provenance without reading the sink or replaying rows.
int pf_report_scratch_arena_stats(uint64_t *high_water_bytes, uint64_t *arena_bytes)
Read process-wide scratch-arena diagnostics, outside canonical report bytes.
void report_free(pf_report_t *report)
Free heap arrays attached to a filled report.
int strategy_configure_native_v1(pf_strategy_t s, const pf_native_run_spec_v1 *spec)
Apply a native v1 specification.
int pf_report_cache_stats(pf_strategy_t s, uint64_t *resident_bytes, uint64_t *allocation_bypass_count)
Read the handle's admitted-run command-cache and trade-ledger diagnostics.
pf_native_spec_optional_t
The bits of pf_native_run_spec_v1::optional_mask: each names the one optional field it makes meaningf...
Definition pineforge.h:469
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 pf_run_abort_restore_sigterm_v1(void)
Restore the disposition saved by pf_run_abort_install_sigterm_v1.
@ PF_EXECUTION_CONTRACT_LEGACY
The retired legacy execution path.
Definition pineforge.h:457
@ PF_EXECUTION_CONTRACT_NATIVE_MARKET_V1
The native market kernel; every handle this runtime creates.
Definition pineforge.h:458
@ PF_NATIVE_FX_CURVE_ERROR_NOT_FINITE_POSITIVE
Definition pineforge.h:533
@ PF_NATIVE_FX_CURVE_ERROR_NOT_STRICTLY_INCREASING
Definition pineforge.h:532
@ PF_NATIVE_FX_CURVE_ERROR_ALLOCATION_FAILURE
Definition pineforge.h:534
@ PF_NATIVE_FX_CURVE_ERROR_LENGTH_MISMATCH
Definition pineforge.h:531
@ PF_NATIVE_FX_CURVE_ERROR_WRONG_PHASE
Definition pineforge.h:535
@ PF_NATIVE_FX_CURVE_ERROR_NONE
Definition pineforge.h:530
@ PF_NATIVE_SPEC_OPTIONAL_MAX_ABS_UNITS
max_abs_units.
Definition pineforge.h:471
@ PF_NATIVE_SPEC_OPTIONAL_MAX_OPEN_LOTS
max_open_lots.
Definition pineforge.h:473
@ PF_NATIVE_SPEC_OPTIONAL_INITIAL_MARGIN_FRACTION
initial_margin_fraction.
Definition pineforge.h:472
@ PF_NATIVE_SPEC_OPTIONAL_QUANTITY_GRID
quantity_grid.
Definition pineforge.h:470
double strategy_net_profit_sum(pf_strategy_t s)
Raw whole-lot realized P&L, excluding initial capital and report-only terminal rows.
double strategy_current_equity(pf_strategy_t s)
Task 9: initial capital plus realized net profit (strategy.initial_capital + strategy....
pf_scratch_table_backend_t
Storage policy for the nine historical scratch-table regions.
Definition pineforge.h:1001
void strategy_set_probe_suppress_tail_logic(pf_strategy_t s, int on)
Live probe tail suppression (spec §3.2): the LAST script bar of the array fed to every subsequent run...
const char * strategy_closed_trade_exit_id(pf_strategy_t s, int trade_index)
See strategy_closed_trade_entry_id for the row-space/lifetime/NULL contract.
pf_state_query_status_v1_t strategy_state_query_status_v1(pf_strategy_t s)
The current eligibility of BOTH strategy_broker_state_hash and strategy_stream_state_hash on s,...
void strategy_set_path_order(pf_strategy_t s, int mode)
Force this run's intrabar path order (ABI v4 live-runtime surface): the leg order every modeled OHLC ...
int64_t strategy_script_bars_processed(pf_strategy_t s)
Task 9: total SCRIPT bars dispatched by the most recent run() (mirrors pf_report_t::script_bars_proce...
void strategy_request_abort(pf_strategy_t s)
Request cooperative abort of the run in progress (see c_abi.cpp).
int strategy_closed_trade_close_cause(pf_strategy_t s, int trade_index)
Task 9: why the trade_index-th REPORT-row closed trade exited, a pf_close_cause_t.
uint32_t pf_cache_kind_t
Append-only cache-kind vocabulary.
Definition pineforge.h:920
int32_t pf_state_query_status_v1_t
What strategy_state_query_status_v1 answers: whether the two state queries, strategy_broker_state_has...
Definition pineforge.h:1257
int strategy_set_scratch_tables_v1(pf_strategy_t s, const pf_scratch_tables_v1 *config)
Copy scratch-table configuration while idle; NULL restores disk defaults.
double strategy_position_avg_price(pf_strategy_t s)
The live position's volume-weighted average entry price (position_entry_price_).
const char * strategy_closed_trade_entry_id(pf_strategy_t s, int trade_index)
Task 9: closed-trade id / exit-comment string accessors, indexing the same REPORT row space as strate...
const char * strategy_closed_trade_exit_comment(pf_strategy_t s, int trade_index)
See strategy_closed_trade_entry_id.
int strategy_pending_orders_len(pf_strategy_t s)
Number of orders resting in the engine's pending-order book after the most recent run() (ABI v4 live-...
int64_t strategy_position_cycle_seq(pf_strategy_t s)
The live position's cycle id (position_cycle_seq_): 0 when flat, a fresh nonzero id per open or rever...
int strategy_pending_order_effective_levels(pf_strategy_t s, int index, double *stop, double *limit, double *trail_activation)
The price levels the index-th resting order would fire at, as the engine's fill path resolves them (A...
int strategy_pending_order_level_resolved(pf_strategy_t s, int index)
1 when the index-th resting order's entry-relative offsets (profit_ticks / loss_ticks / trail_points)...
pf_fill_qty_partition_t
Which sizing rule strategy_pending_order_fill_qty's quantity came from, written to its partition out-...
Definition pineforge.h:1329
int strategy_pending_order_fill_qty(pf_strategy_t s, int index, double fill_price, double *qty, int *close_only, int *partition)
Engine-computed fill quantity of the index-th resting order (ABI v4 live-runtime surface,...
int strategy_last_bar_dual_entry_path(pf_strategy_t s)
The dual-entry-stop arbitration decided on the LAST bar the most recent run() dispatched: a flat posi...
double strategy_position_size(pf_strategy_t s)
Task 9: the script-facing signed position size (strategy.position_size; Freeze-aware – while a same-b...
int strategy_pending_order_get(pf_strategy_t s, int index, void *out, size_t size_in)
Copy the index-th resting order (0-based, the engine's own book order – insertion order; broker fill ...
double strategy_report_pointvalue(pf_strategy_t s)
Effective symbol pointvalue used by the engine report.
int strategy_set_report_replay_v1(pf_strategy_t s, const char *path, const char *run_id)
Opt in to the separate pf-report-replay/v1 companion before a run.
const pf_field_desc_t * strategy_pending_order_layout(int *count)
The field table of pf_pending_order_v1_t as THIS runtime compiled it – one pf_field_desc_t {name,...
int strategy_set_cache_policy_v1(pf_strategy_t s, const pf_cache_policy_v1 *policy)
Copy cache settings before run admission; NULL restores internal defaults.
int strategy_last_run_status(pf_strategy_t s)
0 = completed, 1 = NOT_COMPLETED (aborted), -1 = s is NULL.
double strategy_trail_best_price(pf_strategy_t s)
The trail extreme the exit trail legs ride (trail_best_price_: the running high of a long / low of a ...
pf_close_cause_t
Why a closed report row exited — execution::CloseCause, the value strategy_closed_trade_close_cause a...
Definition pineforge.h:1468
double strategy_initial_capital(pf_strategy_t s)
ABI v4 live-runtime surface (1.8.0): the configured run's initial capital, before realized or open pr...
void strategy_set_broker_state_hash_recording(pf_strategy_t s, int on)
Toggle per-report-point broker-state hash recording (spec §3.4, ABI v4).
uint64_t strategy_broker_state_hash(pf_strategy_t s)
Return the broker-state hash of the FINAL state after the most recent run() (see strategy_set_broker_...
void strategy_set_realtime_tail(pf_strategy_t s, int on, int horizon_bars)
Live-runtime tail semantics (spec §3.1): the LAST bar of the array fed to every subsequent run() is a...
@ PF_SCRATCH_TABLE_BOUNDED_MEMORY
Definition pineforge.h:1003
@ PF_SCRATCH_TABLE_DISK_SCRATCH
Definition pineforge.h:1002
@ PF_FILL_QTY_PARTITION_EXIT
An exit: no partition.
Definition pineforge.h:1330
@ PF_FILL_QTY_PARTITION_DEFAULT_STOP_PLACEMENT
Definition pineforge.h:1333
@ PF_FILL_QTY_PARTITION_EXPLICIT
Definition pineforge.h:1331
@ PF_FILL_QTY_PARTITION_FROZEN_PLACEMENT
Definition pineforge.h:1332
@ PF_FILL_QTY_PARTITION_AT_FILL
Definition pineforge.h:1334
@ PF_CLOSE_CAUSE_LIQUIDATION
A margin-call liquidation slice (MARGIN_CALL).
Definition pineforge.h:1472
@ PF_CLOSE_CAUSE_RANGE_END
The range-end close of a position still open.
Definition pineforge.h:1475
@ PF_CLOSE_CAUSE_SCRIPT
A market close or a reversal.
Definition pineforge.h:1470
@ PF_CLOSE_CAUSE_RISK_LIMIT
A risk-limit flatten (INTRADAY_LOSS_CAP).
Definition pineforge.h:1473
@ PF_CLOSE_CAUSE_BRACKET
A bracket leg.
Definition pineforge.h:1471
@ PF_CLOSE_CAUSE_UNSPECIFIED
No cause recorded (UNKNOWN).
Definition pineforge.h:1469
@ PF_CLOSE_CAUSE_FILL_CAP
A filled-order cap (INTRADAY_FILL_CAP).
Definition pineforge.h:1474
int strategy_outputs_set_enabled(pf_strategy_t s, int on)
Turn recording on (on nonzero) or off.
int strategy_outputs_manifest(pf_strategy_t s, char *json, size_t capacity, size_t *required, char *error, size_t error_capacity)
Part A: the module's outputs manifest, canonical JSON (pineforge-outputs-manifest/v1): what every out...
int64_t strategy_outputs_bars_len(pf_strategy_t s)
Rows recorded: one per bar the module published this run.
void strategy_outputs_events_clear(pf_strategy_t s)
Drop the queued events, in every mode; rows and the sequence are kept.
pf_output_phase_t
The run phase an event was recorded in.
Definition pineforge.h:2010
int strategy_outputs_series_count(pf_strategy_t s)
Series slots per row; 0 while recording is off.
int strategy_outputs_bar_times_copy(pf_strategy_t s, int64_t from_bar, int64_t *open_ms, int64_t *close_ms, int64_t capacity, int64_t *written)
Copy the open and close times (Unix ms) of rows from from_bar on.
int strategy_signal_safety_receipt(pf_strategy_t s, char *json, size_t capacity, size_t *required, char *error, size_t error_capacity)
Part A: the module's signal-safety receipt, canonical JSON (pineforge-signal-safety/v1),...
int strategy_outputs_series_copy(pf_strategy_t s, int series, int64_t from_bar, double *out, int64_t capacity, int64_t *written)
Copy one slot's values (NaN = na) of rows from from_bar on, as strategy_outputs_bar_times_copy does; ...
uint32_t strategy_outputs_api_version(void)
Part A: PF_OUTPUTS_API_VERSION.
int strategy_outputs_constants_copy(pf_strategy_t s, double *out, int capacity)
Copy the run constants (NaN = na, or not written this run) into out: the first min(capacity,...
int strategy_outputs_events_len(pf_strategy_t s)
Events queued since the run began or since the last clear.
int strategy_outputs_event_get(pf_strategy_t s, int index, void *out, size_t size_in)
Copy event index into out: min(size_in, sizeof(pf_output_event_v1_t)) bytes, so an older or newer cal...
@ PF_OUTPUT_PHASE_REALTIME
a stream after its warm-up
Definition pineforge.h:2013
@ PF_OUTPUT_PHASE_BATCH
a batch run
Definition pineforge.h:2011
@ PF_OUTPUT_PHASE_WARMUP
a stream's warm-up, inside strategy_stream_begin
Definition pineforge.h:2012
pf_settings_status_t
Status codes for the opt-in generated-strategy settings extension.
Definition pineforge.h:1892
@ PF_SETTINGS_UNSUPPORTED
Definition pineforge.h:1895
@ PF_SETTINGS_RUN_FAILED
Definition pineforge.h:1898
@ PF_SETTINGS_EXCEPTION
Definition pineforge.h:1896
@ PF_SETTINGS_BUFFER_TOO_SMALL
Definition pineforge.h:1897
@ PF_SETTINGS_INVALID_ARGUMENT
Definition pineforge.h:1894
@ PF_SETTINGS_OK
Definition pineforge.h:1893
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_order_actions_len(pf_strategy_t s)
Queued physical fills since the last clear, in execution order.
int strategy_stream_push_ticks(pf_strategy_t s, const pf_trade_tick_t *ticks, int n)
Push an ordered batch of realtime trades.
uint64_t strategy_stream_state_hash(pf_strategy_t s)
Versioned deterministic fingerprint of observable broker/stream state.
int strategy_stream_api_version(void)
Native live extension version (1).
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_push_bar(pf_strategy_t s, const pf_bar_t *bar)
Consume one confirmed input-timeframe bar.
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.
int strategy_stream_order_action_get(pf_strategy_t s, int index, pf_stream_order_action_t *out)
Copy one action; return 0 on success, -1 on invalid handle/index/output.
void strategy_stream_order_actions_clear(pf_strategy_t s)
Clear observed events after the caller durably journals them.
pf_magnifier_distribution_t
Bar-magnifier sub-bar sampling distribution.
Definition pineforge.h:128
@ PF_MAGNIFIER_FRONT_LOADED
Sample density biased toward bar open.
Definition pineforge.h:133
@ PF_MAGNIFIER_COSINE
Cosine-tapered density.
Definition pineforge.h:130
@ PF_MAGNIFIER_ENDPOINTS
Default — exact O,H,L,C points plus uniform fill between.
Definition pineforge.h:132
@ PF_MAGNIFIER_BACK_LOADED
Sample density biased toward bar close.
Definition pineforge.h:134
@ PF_MAGNIFIER_TRIANGLE
Triangle-tapered density.
Definition pineforge.h:131
@ PF_MAGNIFIER_UNIFORM
Uniform spacing across the parent bar.
Definition pineforge.h:129
int pf_abi_version(void)
pf_version_t pf_version_get(void)
const char * pf_version_string(void)
Full version descriptor, the PINEFORGE_VERSION_FULL of the generated pineforge/version....
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:441
#define PF_API
Definition pineforge.h:83
int pf_output_series_limit_set_checked(const int64_t *maximum, char *error, size_t error_capacity)
Set this module's creation-time recorded-series limit on the calling thread.
int strategy_set_input_checked(pf_strategy_t s, const char *key, const char *value, char *error, size_t error_capacity)
Validate and install a declared input before execution.
int run_backtest_full_checked(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, char *error, size_t error_capacity)
Exception-contained counterpart of run_backtest_full.
int strategy_get_effective_settings(pf_strategy_t s, char *json, size_t capacity, size_t *required, char *error, size_t error_capacity)
Read a deterministic UTF-8 JSON receipt of effective settings.
uint32_t strategy_capabilities_api_version(void)
Compiled execution-capabilities extension version (currently 1).
int strategy_create_checked(const char *params_json, pf_strategy_t *out, char *error, size_t error_capacity)
Allocate a strategy without allowing any exception across the C boundary.
int strategy_set_symbol_feed_borrowed_v1(pf_strategy_t s, const char *key, const char *timeframe, const pf_bar_t *bars, const int64_t *close_ms, int n)
Install another symbol's bars without copying them.
#define PF_STATIC_ASSERT(condition)
Definition pineforge.h:52
uint32_t strategy_stream_engine_layout_version(void)
Optional module declaration that its strategy handles point directly to a BacktestEngine with the ret...
int strategy_capabilities_receipt(pf_strategy_t s, char *json, size_t capacity, size_t *required, char *error, size_t error_capacity)
Read immutable canonical JSON describing compiled execution declarations and analyzed request/data re...
uint32_t strategy_settings_api_version(void)
Generated-strategy settings extension version (currently 1).
int strategy_set_override_checked(pf_strategy_t s, const char *key, const char *value, char *error, size_t error_capacity)
Validate and install a supported strategy override before execution.
int pf_output_series_limit_failure(int64_t *maximum)
Read an outputs_limit / too_many_series declaration refusal even when a factory returned NULL.
Single OHLCV bar pushed into the engine.
Definition pineforge.h:140
double volume
Bar volume.
Definition pineforge.h:145
double high
High price.
Definition pineforge.h:142
double low
Low price.
Definition pineforge.h:143
double close
Close price.
Definition pineforge.h:144
double open
Open price.
Definition pineforge.h:141
int64_t timestamp
Bar open time, Unix milliseconds.
Definition pineforge.h:146
One optional resident cache size.
Definition pineforge.h:960
uint32_t kind
Definition pineforge.h:961
uint32_t reserved
Definition pineforge.h:962
uint64_t capacity_bytes
Definition pineforge.h:963
Versioned resident cache policy.
Definition pineforge.h:976
const pf_cache_policy_entry_v1 * entries
Definition pineforge.h:980
uint32_t struct_size
Definition pineforge.h:977
uint32_t reserved[2]
Definition pineforge.h:981
uint64_t entry_count
Definition pineforge.h:979
Single per-script-bar equity point.
Definition pineforge.h:326
double open_profit
Mark-to-market open P&L at bar close.
Definition pineforge.h:329
double equity
initial_capital + net_profit + open_profit.
Definition pineforge.h:328
int64_t time_ms
Script-bar OPEN timestamp (Unix ms).
Definition pineforge.h:327
Equity-curve-derived statistics (all-trades only, like TV).
Definition pineforge.h:272
double sharpe_monthly
Month-end-resampled equity simple returns (chart timezone, open-time bucketing), risk-free 2%/yr (2/1...
Definition pineforge.h:290
double time_in_market_pct
PERCENT (0-100) of script bars with an open position at bar close.
Definition pineforge.h:310
double max_equity_drawdown_pct
max_equity_drawdown relative to the peak in effect (PERCENT 0-100).
Definition pineforge.h:274
double max_equity_drawdown
Peak-to-trough equity drop, positive currency magnitude.
Definition pineforge.h:273
double max_equity_runup_pct
max_equity_runup relative to that trough (PERCENT 0-100).
Definition pineforge.h:278
double buy_hold_return
initial_capital * (last_close/first_open - 1), currency.
Definition pineforge.h:279
double open_pl
Mark-to-market open profit at the final bar.
Definition pineforge.h:312
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:276
double cagr
PERCENT per year: 100*((final_equity/initial_capital)^(1/years)-1).
Definition pineforge.h:304
double recovery_factor
net_profit / max_equity_drawdown (currency / currency).
Definition pineforge.h:308
double calmar
cagr / max_equity_drawdown_pct — BOTH IN PERCENT, so the ratio is dimensionless.
Definition pineforge.h:306
double buy_hold_return_pct
buy_hold_return as PERCENT.
Definition pineforge.h:281
double sharpe_bar
Per-script-bar returns, annualized by observed bar density (bars per year = (len-1)/calendar span),...
Definition pineforge.h:297
double sortino_monthly
Same resampling as sharpe_monthly; uses population downside deviation vs the monthly risk-free.
Definition pineforge.h:296
double sortino_bar
Same construction as sharpe_bar over per-bar returns; uses population downside deviation.
Definition pineforge.h:301
Composite metrics container: trade stats (all / long / short) + equity-curve stats.
Definition pineforge.h:317
pf_trade_stats_t all
Definition pineforge.h:318
pf_equity_stats_t equity
Definition pineforge.h:319
pf_trade_stats_t longs
Definition pineforge.h:318
pf_trade_stats_t shorts
Definition pineforge.h:318
Immutable timestamped account-currency FX curve for a native run.
Definition pineforge.h:519
const int64_t * effective_from_ms
Definition pineforge.h:522
const double * account_per_quote
Definition pineforge.h:523
Versioned native run specification.
Definition pineforge.h:500
const char * tickerid
Definition pineforge.h:504
uint32_t allowed_open_directions
Definition pineforge.h:508
const char * volumetype
Definition pineforge.h:504
const char * basecurrency
Definition pineforge.h:504
const char * input_tf
Definition pineforge.h:503
const char * session_key
Definition pineforge.h:502
double initial_margin_fraction
Definition pineforge.h:509
const char * type
Definition pineforge.h:504
const char * script_tf
Definition pineforge.h:503
const char * chart_timezone
Definition pineforge.h:505
const char * timezone
Definition pineforge.h:505
const char * ticker
Definition pineforge.h:504
const char * session
Definition pineforge.h:505
const char * description
Definition pineforge.h:504
const char * currency
Definition pineforge.h:504
One recorded event, copied by strategy_outputs_event_get.
Definition pineforge.h:2020
int64_t bar_close_ms
the row's close, Unix ms; INT64_MIN when unknown
Definition pineforge.h:2027
uint32_t ordinal_in_bar
per output, from 0 within the row
Definition pineforge.h:2028
double value
the event's value; NaN = na
Definition pineforge.h:2032
uint32_t size
sizeof(pf_output_event_v1_t) as the runtime compiled it
Definition pineforge.h:2022
int32_t bar_index
the row, 0-based
Definition pineforge.h:2025
uint64_t sequence
1-based per run; once cleared, never issued again
Definition pineforge.h:2023
uint32_t struct_version
1
Definition pineforge.h:2021
const char * message
NUL-terminated, or NULL without one: a message holding a NUL byte reads to its first NUL,...
Definition pineforge.h:2035
int64_t bar_open_ms
the row's open, Unix ms
Definition pineforge.h:2026
uint32_t phase
a pf_output_phase_t
Definition pineforge.h:2029
int32_t output_index
the output, as the manifest's outputs[].index
Definition pineforge.h:2024
uint32_t confirmed
0 when recorded on a partially finalized bar
Definition pineforge.h:2030
uint64_t message_hash64
FNV-1a 64 of every byte the module recorded as the message; 0 without one.
Definition pineforge.h:2033
Fixed-size annualization provenance from the emitted v1 records.
Backtest report filled by run_backtest / run_backtest_full.
Definition pineforge.h:366
int input_tf_seconds
Detected/configured input timeframe (seconds).
Definition pineforge.h:393
int security_diag_len
Length of security_diag.
Definition pineforge.h:401
int64_t security_feeds_total
Total higher-TF feed bars across all security sites.
Definition pineforge.h:384
int bar_magnifier_enabled
1 if magnifier was active for this run.
Definition pineforge.h:397
pf_trace_entry_t * trace
Per-bar trace records (empty unless tracing enabled).
Definition pineforge.h:404
int trace_names_len
Length of trace_names.
Definition pineforge.h:407
int64_t input_bars_processed
Source-feed bars consumed.
Definition pineforge.h:380
int script_tf_seconds
Script timeframe (seconds).
Definition pineforge.h:394
double net_profit
Sum of all closed-trade PnL.
Definition pineforge.h:377
int64_t equity_curve_len
Definition pineforge.h:424
int trades_len
Resident length of trades, not the logical total.
Definition pineforge.h:376
pf_metrics_t metrics
Definition pineforge.h:413
int trace_len
Length of trace.
Definition pineforge.h:405
int64_t script_bars_processed
Script-timeframe bars evaluated.
Definition pineforge.h:381
int64_t magnifier_sample_ticks_total
Sample ticks visited by the magnifier.
Definition pineforge.h:390
int total_trades
Logical closed-trade count, including the range-end close of a position still open after the final ba...
Definition pineforge.h:368
int64_t security_partial_total
Total partial-bar evals across all security sites.
Definition pineforge.h:386
pf_security_diag_t * security_diag
One entry per request.security site.
Definition pineforge.h:400
int64_t magnifier_sub_bars_total
Sub-bars synthesized by the magnifier.
Definition pineforge.h:389
int64_t broker_state_hash_len
Definition pineforge.h:435
uint64_t * broker_state_hash
Definition pineforge.h:434
const char ** trace_names
Names indexed by pf_trace_entry_t::name_id.
Definition pineforge.h:406
pf_equity_point_t * equity_curve
Definition pineforge.h:423
int64_t security_complete_total
Total complete-bar evals across all security sites.
Definition pineforge.h:385
int script_tf_ratio
script_tf_seconds / input_tf_seconds.
Definition pineforge.h:395
int needs_aggregation
1 if input → script TF aggregation was performed.
Definition pineforge.h:396
pf_trade_t * trades
Heap array of closed trades; script-driven exits first, then the range-end rows (open_at_end=1).
Definition pineforge.h:374
Version 1 scratch-table admission maxima, in keys/rows and key bytes.
Definition pineforge.h:1023
Per-request.security() site diagnostic counters.
Definition pineforge.h:335
int sec_id
Stable id for the request.security site.
Definition pineforge.h:336
int64_t feed_count
Higher-TF feed bars consumed.
Definition pineforge.h:337
int64_t complete_count
Evaluations on completed parent bars.
Definition pineforge.h:338
int64_t partial_count
Evaluations on still-forming parent bars.
Definition pineforge.h:339
One physical emulator lot action.
Definition pineforge.h:831
Single per-bar trace entry.
Definition pineforge.h:347
double value
Traced expression value on this bar.
Definition pineforge.h:351
int64_t timestamp
Bar timestamp (Unix ms).
Definition pineforge.h:348
int32_t name_id
Index into pf_report_t::trace_names.
Definition pineforge.h:350
int32_t bar_index
Zero-based bar index.
Definition pineforge.h:349
Trade-level statistics block — computed once each for all / long / short.
Definition pineforge.h:209
double avg_win_pct
Mean of per-trade pnl_pct over winning trades.
Definition pineforge.h:232
double avg_win
gross_profit / num_wins.
Definition pineforge.h:231
double net_profit_pct
net_profit as a percent of initial capital (0-100 scale).
Definition pineforge.h:219
double largest_loss_pct
Maximum of -pnl_pct over losing trades (positive magnitude) — an INDEPENDENT maximum,...
Definition pineforge.h:249
int32_t num_losses
Trades with pnl < 0.
Definition pineforge.h:212
double gross_profit
Sum of winning pnl.
Definition pineforge.h:221
int32_t max_consecutive_losses
Longest losing run; even trades reset both streaks.
Definition pineforge.h:258
double avg_loss_pct
Mean of the NEGATED pnl_pct of the losing trades.
Definition pineforge.h:236
double commission_paid
Sum of pf_trade_t::commission in the block.
Definition pineforge.h:254
double percent_profitable
100 * num_wins / num_trades, in PERCENT (0-100).
Definition pineforge.h:216
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:243
double gross_loss
Sum of |losing pnl| — POSITIVE magnitude (TV display convention).
Definition pineforge.h:224
double gross_profit_pct
gross_profit as a percent of initial capital (0-100 scale).
Definition pineforge.h:222
double largest_loss
Single largest |pnl| among losing trades (positive magnitude).
Definition pineforge.h:247
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:263
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:266
double profit_factor
gross_profit / gross_loss.
Definition pineforge.h:227
double avg_loss
gross_loss / num_losses (positive magnitude).
Definition pineforge.h:234
double largest_win
Single largest pnl among winning trades.
Definition pineforge.h:241
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:259
double gross_loss_pct
gross_loss as a percent of initial capital (0-100 scale).
Definition pineforge.h:225
int32_t max_consecutive_wins
Longest winning run; even trades reset both streaks.
Definition pineforge.h:257
double expectancy
(num_wins/num_trades)*avg_win - (num_losses/num_trades)*avg_loss, account currency per trade.
Definition pineforge.h:255
double net_profit
Sum of pnl (account currency, net of commission).
Definition pineforge.h:218
int32_t num_trades
Closed trades in this block (all / long-only / short-only).
Definition pineforge.h:210
double avg_trade_pct
Mean of per-trade pnl_pct over all trades.
Definition pineforge.h:229
double ratio_avg_win_avg_loss
avg_win / avg_loss.
Definition pineforge.h:240
double avg_trade
net_profit / num_trades.
Definition pineforge.h:228
int32_t num_even
Trades with pnl == 0.0 exactly; breaks both win and loss streaks; excluded from win/loss averages.
Definition pineforge.h:213
int32_t num_wins
Trades with pnl > 0.
Definition pineforge.h:211
Closed-trade record returned in pf_report_t::trades.
Definition pineforge.h:166
int32_t exit_bar_index
Script-bar index of the exit fill (0-based).
Definition pineforge.h:187
double pnl_pct
Net return-on-cost in percent: pnl (NET of commission) / entry cost (entry_price * qty * pointvalue) ...
Definition pineforge.h:172
double exit_price
Exit fill price (incl.
Definition pineforge.h:170
int32_t open_at_end
1 when this row is the RANGE-END close of a position that was still open after the final bar; 0 for a...
Definition pineforge.h:188
int32_t entry_bar_index
Script-bar index of the entry fill (0-based).
Definition pineforge.h:186
double commission
Entry+exit commission actually deducted from pnl (account currency).
Definition pineforge.h:184
double pnl
Net realized PnL in account currency (commission-inclusive).
Definition pineforge.h:171
int is_long
1 if long, 0 if short.
Definition pineforge.h:180
double max_drawdown
Peak adverse whole-trade excursion, account currency, net of entry fees.
Definition pineforge.h:182
double qty
Filled quantity.
Definition pineforge.h:183
double max_runup
Peak favorable whole-trade excursion, account currency, net of entry fees.
Definition pineforge.h:181
int64_t entry_time
Entry fill time (Unix ms).
Definition pineforge.h:167
double entry_price
Entry fill price (incl.
Definition pineforge.h:169
int64_t exit_time
Exit fill time (Unix ms).
Definition pineforge.h:168
One provider-neutral realtime executed-trade update.
Definition pineforge.h:156
double quantity
Traded quantity in symbol volume units (>= 0).
Definition pineforge.h:160
uint64_t sequence
Normalized per-stream sequence, or 0.
Definition pineforge.h:158
int64_t timestamp
Source event time, Unix milliseconds.
Definition pineforge.h:157
double price
Executed trade price (> 0).
Definition pineforge.h:159
Runtime version descriptor returned by pf_version_get.
Definition pineforge.h:1855
int patch
Patch version.
Definition pineforge.h:1858
int minor
Minor version.
Definition pineforge.h:1857
int major
Major version.
Definition pineforge.h:1856
const char * commit_sha
Short git commit SHA, or "" if unknown.
Definition pineforge.h:1859